web-dev-qa-db-fra.com

Est-ce que Java a un HashMap avec recherche inversée?

J'ai des données qui sont organisées dans une sorte de format "clé-clé", plutôt que "clé-valeur". C'est comme une carte de hachage, mais j'aurai besoin de O(1) dans les deux sens. Existe-t-il un nom pour ce type de structure de données et un élément similaire est-il inclus dans les bibliothèques standard de Java? (ou peut-être Apache Commons?)

Je pourrais écrire ma propre classe qui utilise essentiellement deux cartes en miroir, mais je préfère ne pas réinventer la roue (si cela existe déjà, mais je ne cherche tout simplement pas le bon terme).

93
Kip

Une telle classe n'existe pas dans l'API Java. La classe Apache Commons de votre choix sera l’une des implémentations de BidiMap .

En tant que mathématicien, j'appellerais ce type de structure une bijection.

103
uckelman

En plus d'Apache Commons, Guava possède également un BiMap .

72
ColinD

Voici une classe simple que j’avais l'habitude de faire (je ne voulais pas avoir encore une autre dépendance à un tiers). Il n'offre pas toutes les fonctionnalités disponibles dans Maps mais c'est un bon début. 

    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);
        }
    }
19
GETah

Si aucune collision ne se produit, vous pouvez toujours ajouter les deux directions au même HashMap :-)

11
rsp

Voici mes 2 cents.

Ou vous pouvez utiliser une méthode simple avec des génériques. Part de gâteau.

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

Bien sûr, vous devez avoir une carte avec des valeurs uniques. Sinon, l'un d'eux sera remplacé.

3
Fulvius

Inspiré par la réponse de GETah j'ai décidé d'écrire quelque chose de similaire par moi-même avec quelques améliorations:

  • La classe implémente l'interface Map<K,V>-
  • La bidirectionnalité est réellement garantie en prenant soin d’en modifier une valeur de put (du moins j’espère pouvoir la garantir par la présente)

L'utilisation est semblable à une carte normale, pour obtenir une vue inversée de l'appel de mappage getReverseView(). Le contenu n'est pas copié, seule une vue est renvoyée.

Je ne suis pas sûr que ce soit totalement infaillible (en fait, ce n'est probablement pas le cas), alors n'hésitez pas à commenter si vous remarquez des défauts et je mettrai à jour la réponse.

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

}
0
Qw3ry

C'est une vieille question, mais si quelqu'un d'autre a un blocage cérébral comme je viens de le faire et trébuche, j'espère que cela aidera.

Moi aussi, je cherchais un HashMap bidirectionnel, parfois ce sont les réponses les plus simples qui sont les plus utiles.

Si vous ne souhaitez pas réinventer la roue et préférez ne pas ajouter d'autres bibliothèques ou projets à votre projet, pourquoi ne pas utiliser une simple implémentation de tableaux parallèles (ou de ArrayLists si votre conception l'exige).

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

Dès que vous connaissez l'index de l'une des deux clés, vous pouvez facilement demander l'autre. Ainsi, vos méthodes de recherche pourraient ressembler à quelque chose comme:

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

En supposant que vous utilisiez des structures orientées objet appropriées, où seules les méthodes modifient ces tableaux/ArrayLists, il serait très simple de les maintenir parallèles. Encore plus facile pour un ArrayList puisque vous n’auriez pas à reconstruire si la taille des tableaux change, tant que vous ajoutez/supprimez en tandem.

0
ThatOneGuy