Чи має Java HashMap із зворотним пошуком?


98

У мене є дані, які організовані у формі "ключ-ключ", а не "ключ-значення". Це як HashMap, але мені знадобиться пошук O (1) в обох напрямках. Чи існує назва цього типу структури даних і чи є щось подібне до стандартних бібліотек Java? (а може, Apache Commons?)

Я міг би написати власний клас, який в основному використовує дві дзеркальні карти, але я краще не винаходити колесо (якщо таке вже існує, але я просто не шукаю потрібного терміна).

Відповіді:


106

Такого класу в Java API немає. Клас Apache Commons, який ви хочете, стане однією з реалізацій BidiMap .

Як математик я би назвав цю структуру бієкцією.


83
як не-математик я б назвав подібну структуру "картою, що дозволяє шукати значення за ключем або навпаки"
Dónal

4
Дуже шкода, що він не підтримує дженерики, схоже, це робить Гуава.
Еран Медан

2
github.com/megamattron/collections-generic має BidiMap з підтримкою
Generics

1
@Don "Bidi" -> "Двонаправлений"
ryvantage

3
@ Dónal Так, але весь ІТ базується на математиці
Алекс

75

Окрім Apache Commons, Guava також має BiMap .


дякую за інформацію! Я поки що дотримуюся апаша (хоча хіба є якісь вагомі причини цього не робити)
Kip

Я не можу запропонувати хорошого порівняння з колекціями apache, але в колекціях Google є багато приємних речей, які, на мою думку, заслуговують на це, щоб заглянути.
ColinD

16
Однією з переваг Google Collections є те, що у них є загальні відомості, тоді як у Commons Collection немає.
Марк

3
Для порівняння двох лайфів див. Цитати у цій відповіді: stackoverflow.com/questions/787446/… (та оригінальне інтерв'ю). Це з упередженим ставленням до Google, з очевидних причин, але навіть тому я думаю, що можна впевнено сказати, що вам сьогодні краще з колекціями Google.
Jonik

1
Посилання BiMap розірвано. Будь ласка, використовуйте цей .
Mahsa2

20

Ось простий клас, який я використовував для цього (я не хотів мати ще одну залежність від третьої сторони). Він не пропонує всі функції, доступні на Картах, але це хороший початок.

    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
Ви значно поліпшите продуктивність contentValue (), змінивши його на повернене значенняToKeyMap.containsKey (значення)
JT.

Я б не використовував цю карту як зараз, оскільки двонаправленість порушується, якщо ключ (або значення) знову додається з іншим значенням (або ключем), що було б дійсним використанням для оновлення ключового ІМО.
Qw3ry

11

Якщо не трапляються зіткнення, ви завжди можете додати обидва напрямки до одного HashMap :-)


6
@Kip: Чому? У деяких контекстах це цілком законне рішення. Так було б мати дві хеш-карти.
Лоуренс Дол

7
ні, це некрасивий, тендітний злом. воно вимагає підтримання властивості двонаправленого на кожному get () та put (), і його можна передати іншим методам, що змінюють карту, навіть не знаючи про властивість двонаправленого. можливо, це було б нормально як локальна змінна всередині методу, який ніде не передається, або якби його було неможливо змінити відразу після створення. але навіть тоді він крихкий (хтось підходить і налаштовує цю функцію і порушує двонаправленість таким чином, що не завжди одразу виявиться проблемою)
Kip

1
@Kip, я погоджуюся, що таке використання повинно зберігатись у класі, використовуючи цю карту, але ваше останнє зауваження справедливе лише у тому випадку, якщо відповідні тести JUnit неохайні :-)
rsp

Якщо я можу представляти дуже обґрунтованим використання такої реалізації, уявіть, що тут потрібна карта для декодування / кодування опкодів інструкцій з мови монтажу, ви також ніколи не змінюватимете стан карти, в одному напрямку клавіші - це інструкції, що містять рядки інших двійкових значень. Тому у вас ніколи не повинно виникати конфліктів.
МК

З метою пошуку в невеликих масштабах цей хак вирішує мою проблему.
доярка

5

Тут мої 2 копійки.

Або ви можете скористатися простим методом з дженериками. Шматок торту.

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;
}

Звичайно, ви повинні мати карту з унікальними значеннями. Інакше один із них буде замінений.


1

Натхненний відповіддю GETah, я вирішив написати щось подібне сам із деякими вдосконаленнями:

  • Клас реалізує Map<K,V>інтерфейс -Interface
  • Двонаправленість дійсно гарантується, дбаючи про нього при зміні значення на put(принаймні, я сподіваюся гарантувати це)

Використання - це як звичайна карта, щоб отримати зворотний вигляд на дзвінку на карті getReverseView(). Вміст не копіюється, повертається лише перегляд.

Я не впевнений, що це абсолютно нерозумно (насправді, напевно, це не так), тому сміливо коментуйте, якщо помітите якісь недоліки, і я оновлю відповідь.

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;
        }
    }

}

зауважте, що так само, як BiMap і BidiMap, це бієкція, яка не дозволяє мати кілька клавіш з однаковим значенням. (getReverseView (). get (v) завжди поверне лише один ключ).
Донателло

Щоправда, але OTOH саме те, про що просила ОП
Qw3ry

Я не впевнений, що він висловив, що його дані відповідають цьому обмеженням, але все одно, це може допомогти іншому краще зрозуміти!
Донателло

0

Тут досить старе питання, але якщо у когось іншого є мозковий блок, як я щойно, і натрапляє на це, сподіваємось, це допоможе.

Я теж шукав двонаправлену HashMap, іноді найпростіші відповіді є найбільш корисними.

Якщо ви не бажаєте заново вигадувати колесо і вважаєте за краще не додавати до свого проекту інші бібліотеки чи проекти, як щодо простої реалізації паралельних масивів (або ArrayLists, якщо цього вимагає ваш дизайн).

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

Як тільки ви дізнаєтесь індекс 1 з двох клавіш, ви можете запросити інший. Тож ваші методи пошуку можуть виглядати приблизно так:

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

Це припущення, що ви використовуєте належні об'єктно-орієнтовані структури, де лише методи модифікують ці масиви / ArrayLists, було б дуже просто тримати їх паралельно. Ще простіше для ArrayList, оскільки вам не доведеться перебудовувати, якщо розмір масивів змінюється, доки ви додаєте / видаляєте в тандемі.


3
Ви втрачаєте важливу особливість HashMaps, а саме пошук O (1). Така реалізація потребує сканування через один з масивів, поки ви не знайдете індекс потрібного елемента, який є O (n)
Kip

Так, це дуже правда і є одним з досить великих недоліків. Однак в моїй особистої ситуації я насправді мав справу з необхідністю перелічення двонаправлених ключів, і я завжди знав принаймні 1 ключ достроково, тому для мене особисто це не було проблемою. Дякую, що наголосили на цьому, але, здається, я пропустив цей важливий факт у своєму первісному пості.
ThatOneGuy
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.