Java có HashMap với tính năng tra cứu ngược không?


98

Tôi có dữ liệu được sắp xếp theo định dạng "khóa-khóa", thay vì "khóa-giá trị". Nó giống như một HashMap, nhưng tôi sẽ cần tra cứu O (1) theo cả hai hướng. Có tên cho kiểu cấu trúc dữ liệu này không và có bất kỳ thứ gì tương tự như thế này được đưa vào các thư viện chuẩn của Java không? (hoặc có thể là Apache Commons?)

Tôi có thể viết lớp học của riêng mình về cơ bản sử dụng hai Bản đồ được nhân đôi, nhưng tôi không muốn phát minh lại bánh xe (nếu điều này đã tồn tại nhưng tôi không tìm kiếm thuật ngữ phù hợp).

Câu trả lời:


106

Không có lớp nào như vậy trong API Java. Lớp Apache Commons mà bạn muốn sẽ là một trong những triển khai của BidiMap .

Là một nhà toán học, tôi gọi loại cấu trúc này là một phép phản chiếu.


82
như một tổ chức phi nhà toán học mà tôi gọi là loại cấu trúc "bản đồ gì cho phép bạn tra cứu các giá trị bằng phím hay cách khác xung quanh"
Dónal

4
Thật tệ là nó không có hỗ trợ cho generic, có vẻ như Guava.
Eran Medan

2
github.com/megamattron/collections-generic có BidiMap với sự hỗ trợ của Generics
Kenston Choi

1
@Don "Bidi" -> "Bi-Directional"
ryvantage

3
@ Dónal Có, nhưng toàn bộ CNTT được dựa trên toán học
Alex

75

Ngoài Apache Commons, Guava cũng có BiMap .


Cảm ơn bạn về thông tin! Tôi đang gắn bó với apache trong thời gian này mặc dù (trừ khi có một số lý do chính đáng không?)
Kip

Tôi không thể đưa ra một so sánh tốt với các bộ sưu tập apache, nhưng các bộ sưu tập của google có rất nhiều thứ hay ho mà tôi nghĩ sẽ khiến nó đáng xem.
ColinD

16
Một lợi thế của Google Collections là nó có các số liệu chung trong khi Commons Collections thì không.
Đánh dấu

3
Để so sánh hai lib, hãy xem phần trích dẫn trong câu trả lời này: stackoverflow.com/questions/787446/… (và cuộc phỏng vấn gốc). Đó là thành kiến ​​đối với Google, vì những lý do rõ ràng, nhưng ngay cả như vậy, tôi nghĩ rằng có thể nói rằng ngày nay bạn đã tốt hơn với Google Collections.
Jonik

1
Liên kết BiMap bị hỏng. Vui lòng sử dụng cái này .
Mahsa2

20

Đây là một lớp đơn giản mà tôi đã sử dụng để thực hiện việc này (tôi không muốn có thêm một phụ thuộc bên thứ ba nào nữa). Nó không cung cấp tất cả các tính năng có sẵn trong Maps nhưng đó là một khởi đầu tốt.

    public class BidirectionalMap<KeyType, ValueType>{
        private Map<KeyType, ValueType> keyToValueMap = new ConcurrentHashMap<KeyType, ValueType>();
        private Map<ValueType, KeyType> valueToKeyMap = new ConcurrentHashMap<ValueType, KeyType>();

        synchronized public void put(KeyType key, ValueType value){
            keyToValueMap.put(key, value);
            valueToKeyMap.put(value, key);
        }

        synchronized public ValueType removeByKey(KeyType key){
            ValueType removedValue = keyToValueMap.remove(key);
            valueToKeyMap.remove(removedValue);
            return removedValue;
        }

        synchronized public KeyType removeByValue(ValueType value){
            KeyType removedKey = valueToKeyMap.remove(value);
            keyToValueMap.remove(removedKey);
            return removedKey;
        }

        public boolean containsKey(KeyType key){
            return keyToValueMap.containsKey(key);
        }

        public boolean containsValue(ValueType value){
            return keyToValueMap.containsValue(value);
        }

        public KeyType getKey(ValueType value){
            return valueToKeyMap.get(value);
        }

        public ValueType get(KeyType key){
            return keyToValueMap.get(key);
        }
    }

5
Bạn sẽ cải thiện đáng kể hiệu suất của containsValue () bằng cách thay đổi nó thành giá trị trả về valueToKeyMap.containsKey (value)
JT.

Tôi sẽ không sử dụng bản đồ này như hiện tại vì tính năng hai chiều bị phá vỡ nếu một khóa (hoặc giá trị) được thêm lại bằng một giá trị (hoặc khóa) khác, đây sẽ là cách sử dụng hợp lệ để cập nhật IMO khóa.
Qw3ry

11

Nếu không có xung đột nào xảy ra, bạn luôn có thể thêm cả hai hướng vào cùng một HashMap :-)


6
@Kip: Tại sao? Trong một số bối cảnh, đây là một giải pháp hoàn toàn hợp pháp. Vì vậy, sẽ có hai bản đồ băm.
Lawrence Dol

7
không, đó là một bản hack xấu xí, mỏng manh. nó yêu cầu duy trì thuộc tính hai hướng trên mọi get () và put (), và nó có thể được chuyển cho các phương thức khác sửa đổi bản đồ mà không cần biết về thuộc tính hai hướng. có thể nó sẽ ổn khi là một biến cục bộ bên trong một phương thức không được truyền vào bất kỳ đâu hoặc nếu nó được làm cho không thể sửa đổi ngay sau khi tạo. nhưng thậm chí sau đó, nó mong manh (một ai đó xuất hiện và chỉnh mà chức năng và nghỉ bidirectionality theo một cách mà không phải lúc nào ngay lập tức hiển thị bản thân là một vấn đề)
Kip

1
@Kip, tôi đồng ý rằng cách sử dụng như vậy nên được giữ trong nội bộ lớp bằng cách sử dụng bản đồ đó, nhưng nhận xét cuối cùng của bạn chỉ đúng nếu các bài kiểm tra JUnit tương ứng là cẩu thả :-)
rsp

Nếu tôi có thể đặt ra một cách sử dụng rất hợp lệ của cách triển khai như vậy, hãy tưởng tượng bạn cần một bản đồ để giải mã / mã hóa các mã lệnh của hợp ngữ ở đây, bạn sẽ không bao giờ thay đổi trạng thái của bản đồ, theo một hướng, các phím là chuỗi lệnh các giá trị nhị phân khác. Vì vậy, bạn đừng bao giờ có xung đột.
MK

Với mục đích tra cứu quy mô nhỏ, vụ hack đó giải quyết được vấn đề của tôi.
milkersarac

5

Đây 2 xu của tôi.

Hoặc bạn có thể sử dụng một phương pháp đơn giản với thuốc generic. Miếng bánh.

public static <K,V> Map<V, K> invertMap(Map<K, V> toInvert) {
    Map<V, K> result = new HashMap<V, K>();
    for(K k: toInvert.keySet()){
        result.put(toInvert.get(k), k);
    }
    return result;
}

Tất nhiên bạn phải có một bản đồ với các giá trị duy nhất. Nếu không, một trong số chúng sẽ được thay thế.


1

Lấy cảm hứng từ câu trả lời của GETah, tôi đã quyết định viết một cái gì đó tương tự cho chính mình với một số cải tiến:

  • Lớp đang triển khai Map<K,V>-Giao diện
  • Tính hai chiều thực sự được đảm bảo bằng cách quan tâm đến nó khi thay đổi giá trị bằng a put(ít nhất tôi hy vọng sẽ đảm bảo điều đó bằng cách này)

Cách sử dụng cũng giống như bản đồ bình thường, để xem ngược lại cuộc gọi ánh xạ getReverseView(). Nội dung không được sao chép, chỉ một lượt xem được trả về.

Tôi không chắc rằng điều này là hoàn toàn chống lại sự đánh lừa (thực ra, có thể là không), vì vậy hãy bình luận nếu bạn nhận thấy bất kỳ sai sót nào và tôi sẽ cập nhật câu trả lời.

public class BidirectionalMap<Key, Value> implements Map<Key, Value> {

    private final Map<Key, Value> map;
    private final Map<Value, Key> revMap;

    public BidirectionalMap() {
        this(16, 0.75f);
    }

    public BidirectionalMap(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

    public BidirectionalMap(int initialCapacity, float loadFactor) {
        this.map = new HashMap<>(initialCapacity, loadFactor);
        this.revMap = new HashMap<>(initialCapacity, loadFactor);
    }

    private BidirectionalMap(Map<Key, Value> map, Map<Value, Key> reverseMap) {
        this.map = map;
        this.revMap = reverseMap;
    }

    @Override
    public void clear() {
        map.clear();
        revMap.clear();
    }

    @Override
    public boolean containsKey(Object key) {
        return map.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        return revMap.containsKey(value);
    }

    @Override
    public Set<java.util.Map.Entry<Key, Value>> entrySet() {
        return Collections.unmodifiableSet(map.entrySet());
    }

    @Override
    public boolean isEmpty() {
        return map.isEmpty();
    }

    @Override
    public Set<Key> keySet() {
        return Collections.unmodifiableSet(map.keySet());
    }

    @Override
    public void putAll(Map<? extends Key, ? extends Value> m) {
        m.entrySet().forEach(e -> put(e.getKey(), e.getValue()));
    }

    @Override
    public int size() {
        return map.size();
    }

    @Override
    public Collection<Value> values() {
        return Collections.unmodifiableCollection(map.values());
    }

    @Override
    public Value get(Object key) {
        return map.get(key);
    }

    @Override
    public Value put(Key key, Value value) {
        Value v = remove(key);
        getReverseView().remove(value);
        map.put(key, value);
        revMap.put(value, key);
        return v;
    }

    public Map<Value, Key> getReverseView() {
        return new BidirectionalMap<>(revMap, map);
    }

    @Override
    public Value remove(Object key) {
        if (containsKey(key)) {
            Value v = map.remove(key);
            revMap.remove(v);
            return v;
        } else {
            return null;
        }
    }

}

lưu ý rằng cũng giống như BiMap và BidiMap, đây là một phân đoạn không cho phép có nhiều khóa có cùng giá trị. (getReverseView (). get (v) sẽ luôn chỉ trả về một khóa).
Donatello

Đúng, nhưng OTOH đó chính xác là những gì OP yêu cầu
Qw3ry

Tôi không chắc anh ấy đã bày tỏ rằng dữ liệu của nó có khớp với ràng buộc này hay không, nhưng dù sao, nó có thể giúp người khác hiểu rõ hơn!
Donatello

0

Một câu hỏi khá cũ ở đây, nhưng nếu ai đó có khối não như tôi vừa làm và tình cờ gặp phải điều này, hy vọng điều này sẽ giúp ích.

Tôi cũng đang tìm kiếm một HashMap hai hướng, đôi khi nó là câu trả lời đơn giản nhất lại hữu ích nhất.

Nếu bạn không muốn phát minh lại bánh xe và không muốn thêm các thư viện hoặc dự án khác vào dự án của mình, thì cách triển khai đơn giản các mảng song song (hoặc ArrayLists nếu thiết kế của bạn yêu cầu).

SomeType[] keys1 = new SomeType[NUM_PAIRS];
OtherType[] keys2 = new OtherType[NUM_PAIRS];

Ngay sau khi bạn biết chỉ số của 1 trong hai khóa, bạn có thể dễ dàng yêu cầu khóa còn lại. Vì vậy, các phương pháp tra cứu của bạn có thể trông giống như sau:

SomeType getKey1(OtherType ot);
SomeType getKey1ByIndex(int key2Idx);
OtherType getKey2(SomeType st); 
OtherType getKey2ByIndex(int key2Idx);

Điều này giả sử bạn đang sử dụng các cấu trúc hướng đối tượng thích hợp, trong đó chỉ có các phương thức sửa đổi các mảng / ArrayLists này, sẽ rất đơn giản nếu giữ chúng song song. Thậm chí còn dễ dàng hơn đối với ArrayList vì bạn sẽ không phải xây dựng lại nếu kích thước của các mảng thay đổi, miễn là bạn thêm / bớt song song.


3
Bạn đang mất một tính năng quan trọng của HashMaps, cụ thể là tra cứu O (1). Việc triển khai như thế này sẽ yêu cầu quét qua một trong các mảng cho đến khi bạn tìm thấy chỉ mục của mặt hàng bạn đang tìm kiếm, đó là O (n)
Kip

Vâng, điều đó rất đúng và là một nhược điểm khá lớn. Tuy nhiên trong tình huống cá nhân của tôi, tôi thực sự đang giải quyết nhu cầu về danh sách khóa ba hướng và tôi luôn biết trước ít nhất 1 trong các khóa, vì vậy đối với cá nhân tôi đó không phải là vấn đề. Cảm ơn bạn đã chỉ ra điều đó mặc dù, tôi dường như đã bỏ qua sự kiện quan trọng đó trong bài đăng ban đầu của tôi.
ThatOneGuy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.