web-dev-qa-db-fra.com

Java: clé composite dans les hashmaps

Je voudrais stocker un groupe d'objets dans une table de hachage, où la clé doit être un composite de deux valeurs de chaîne. existe-t-il un moyen d'y parvenir?

je peux simplement concaténer les deux cordes, mais je suis sûr qu'il existe une meilleure façon de le faire.

28
user1203861

Vous pouvez avoir un objet personnalisé contenant les deux chaînes:

class StringKey {
    private String str1;
    private String str2;
}

Le problème est que vous devez déterminer le test d'égalité et le code de hachage pour deux de ces objets.

L'égalité pourrait être la correspondance sur les deux chaînes et le hashcode pourrait être le hashcode des membres concaténés (cela est discutable):

class StringKey {
    private String str1;
    private String str2;

    @Override
    public boolean equals(Object obj) {
        if(obj != null && obj instanceof StringKey) {
            StringKey s = (StringKey)obj;
            return str1.equals(s.str1) && str2.equals(s.str2);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return (str1 + str2).hashCode();
    }
}
40
Tudor
public int hashCode() {
    return (str1 + str2).hashCode();
}

Cela semble être un moyen terrible de générer le hashCode: la création d'une nouvelle instance de chaîne chaque fois que le code de hachage est calculé est terrible! (Même la génération de l'instance de chaîne une fois et la mise en cache du résultat est une mauvaise pratique.)

Il y a beaucoup de suggestions ici:

Comment calculer un bon code de hachage pour une liste de chaînes?

public int hashCode() {
    final int prime = 31;
    int result = 1;
    for ( String s : strings ) {
        result = result * prime + s.hashCode();
    }
    return result;
}

Pour une paire de cordes, cela devient:

return string1.hashCode() * 31 + string2.hashCode();

Il s'agit d'une mise en œuvre très basique. Beaucoup de conseils à travers le lien pour suggérer des stratégies mieux ajustées.

10
Thomas Bitonti

Pourquoi ne pas créer un objet (par exemple) Pair, qui contient les deux chaînes en tant que membres, puis l'utiliser comme clé?

par exemple.

public class Pair {
   private final String str1;
   private final String str2;

   // this object should be immutable to reliably perform subsequent lookups
}

N'oubliez pas equals () et hashCode () . Voir cette entrée de blog pour en savoir plus sur les HashMaps et les clés, y compris un historique des exigences d'immuabilité. Si votre clé n'est pas immuable, vous pouvez modifier ses composants et une recherche ultérieure échouera à la localiser (c'est pourquoi des objets immuables tels que String sont de bons candidats pour une clé)

Vous avez raison, la concaténation n'est pas idéale. Dans certaines circonstances, cela fonctionnera, mais c'est souvent une solution peu fiable et fragile (par exemple, AB/C une clé différente de A/BC ?).

7
Brian Agnew

J'ai un cas similaire. Je ne fais que concaténer les deux chaînes séparées par un tilde (~).

Ainsi, lorsque le client appelle la fonction de service pour obtenir l'objet de la carte, cela ressemble à ceci:

MyObject getMyObject(String key1, String key2) {
    String cacheKey = key1 + "~" + key2;
    return map.get(cachekey);
}

C'est simple, mais ça marche.

5
EdgeCase

Vous n'avez pas besoin de réinventer la roue. Utilisez simplement le Guava 's HashBasedTable<R,C,V> implémentation de Table<R,C,V> interface, pour votre besoin. Voici un exemple

Table<String, String, Integer> table = HashBasedTable.create();

table.put("key-1", "lock-1", 50);
table.put("lock-1", "key-1", 100);

System.out.println(table.get("key-1", "lock-1")); //prints 50
System.out.println(table.get("lock-1", "key-1")); //prints 100

table.put("key-1", "lock-1", 150); //replaces 50 with 150

Bon codage!

4
TMtech

Je vois que beaucoup de gens utilisent des cartes imbriquées. Autrement dit, pour mapper Key1 -> Key2 -> Value (J'utilise la notation informatique/aka haskell pour (Key1 x Key2) -> Value mapping qui a deux arguments et produit une valeur), vous fournissez d'abord la première clé - cela vous renvoie un (partiel) mapKey2 -> Value, que vous dépliez à l'étape suivante.

Par exemple,

Map<File, Map<Integer, String>> table = new HashMap(); // maps (File, Int) -> Distance

add(k1, k2, value) {
  table2 = table1.get(k1);
  if (table2 == null) table2 = table1.add(k1, new HashMap())
  table2.add(k2, value)
}

get(k1, k2) {
  table2 = table1.get(k1);
  return table2.get(k2)
}

Je ne suis pas sûr que ce soit mieux ou non que la construction de clé composite simple. Vous pouvez commenter cela.

3
Val

En lisant la pile de spaguetti/cactus, j'ai trouvé une variante qui peut servir à cet effet, y compris la possibilité de mapper vos clés dans n'importe quel ordre afin que map.lookup ("a", "b") et map.lookup (" b "," a ") renvoie le même élément. Il fonctionne également avec n'importe quel nombre de clés, pas seulement deux.

Je l'utilise comme une pile pour expérimenter la programmation de flux de données mais voici une version rapide et sale qui fonctionne comme une carte multi-clés (elle devrait être améliorée: des ensembles au lieu de tableaux doivent être utilisés pour éviter de rechercher les occurrences dupliquées d'une clé)

public class MultiKeyMap <K,E> {
    class Mapping {
        E element;
        int numKeys;
        public Mapping(E element,int numKeys){
            this.element = element;
            this.numKeys = numKeys;
        }
    }
    class KeySlot{
        Mapping parent;
        public KeySlot(Mapping mapping) {
            parent = mapping;
        }
    }
    class KeySlotList extends LinkedList<KeySlot>{}
    class MultiMap extends HashMap<K,KeySlotList>{}
    class MappingTrackMap extends HashMap<Mapping,Integer>{}

    MultiMap map = new MultiMap();

    public void put(E element, K ...keys){
        Mapping mapping = new Mapping(element,keys.length);
        for(int i=0;i<keys.length;i++){
            KeySlot k = new KeySlot(mapping);
            KeySlotList l = map.get(keys[i]);
            if(l==null){
                l = new KeySlotList();
                map.put(keys[i], l);
            }
            l.add(k);
        }
    }
    public E lookup(K ...keys){
        MappingTrackMap tmp  = new MappingTrackMap();
        for(K key:keys){
            KeySlotList l = map.get(key);
            if(l==null)return null;
            for(KeySlot keySlot:l){
                Mapping parent = keySlot.parent;
                Integer count = tmp.get(parent);
                if(parent.numKeys!=keys.length)continue;
                if(count == null){
                    count = parent.numKeys-1;
                }else{
                    count--;
                }
                if(count == 0){
                    return parent.element;
                }else{
                    tmp.put(parent, count);
                }               
            }
        }
        return null;
    }
    public static void main(String[] args) {
        MultiKeyMap<String,String> m = new MultiKeyMap<String,String>();
        m.put("brazil", "yellow", "green");
        m.put("canada", "red", "white");
        m.put("USA", "red" ,"white" ,"blue");
        m.put("argentina", "white","blue");

        System.out.println(m.lookup("red","white"));  // canada
        System.out.println(m.lookup("white","red"));  // canada
        System.out.println(m.lookup("white","red","blue")); // USA
    }
}
2
Rupert Hide
public static String fakeMapKey(final String... arrayKey) {
    String[] keys = arrayKey;

    if (keys == null || keys.length == 0)
        return null;

    if (keys.length == 1)
        return keys[0];

    String key = "";
    for (int i = 0; i < keys.length; i++)
        key += "{" + i + "}" + (i == keys.length - 1 ? "" : "{" + keys.length + "}");

    keys = Arrays.copyOf(keys, keys.length + 1);

    keys[keys.length - 1] = FAKE_KEY_SEPARATOR;

    return  MessageFormat.format(key, (Object[]) keys);}
chaîne statique publique FAKE_KEY_SEPARATOR = "~"; 

ENTRÉE: FakeMapKey ("keyPart1", "keyPart2", "keyPart3");
SORTIE: keyPart1 ~ keyPart2 ~ keyPart3
1
Nelson Azevedo