Java: klucz kompozytowy w hashmaps

Chciałbym zapisać grupę obiektów w hashmapie, gdzie klucz powinien być złożony z dwóch wartości łańcuchowych. czy jest jakiś sposób, aby to osiągnąć?

Mogę po prostu połączyć dwa ciągi, ale jestem pewien, że jest lepszy sposób, aby to zrobić.

Author: user1203861, 2012-07-28

8 answers

Możesz mieć własny obiekt zawierający dwa ciągi znaków:

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

Problem polega na tym, że musisz określić test równości i kod skrótu dla dwóch takich obiektów.

Równość może być dopasowaniem dla obu łańcuchów, a hashcode może być hashcode konkatenowanych członków (jest to dyskusyjne):

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
Author: Tudor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-19 11:18:20
public int hashCode() {
    return (str1 + str2).hashCode();
}

To wydaje się być okropny sposób generowania hashCode: Tworzenie nowej instancji ciągu za każdym razem, gdy kod hash jest obliczany jest straszne! (Nawet generowanie instancji string raz i buforowanie wyniku jest słabą praktyką.)

Jest tu wiele sugestii:

Jak obliczyć dobry kod hash dla listy ciągów?

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

Dla pary strun, która staje się:

return string1.hashCode() * 31 + string2.hashCode();
To bardzo podstawowa implementacja. Dużo rad poprzez link do sugerowania lepiej dostrojone strategie.
 10
Author: Thomas Bitonti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:09:58

Dlaczego nie utworzyć (powiedzmy) Pair obiektu, który zawiera dwa ciągi znaków jako członków, a następnie użyć tego jako klucza ?

Np.

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

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

Nie zapomnij o equals () i hashCode () . Zobacz ten wpis na blogu aby dowiedzieć się więcej o Hashmapach i kluczach, w tym o wymaganiach niezmienności. Jeśli twój klucz nie jest niezmienny, możesz zmienić jego składniki, a kolejne wyszukiwanie go nie zlokalizuje (dlatego obiekty niezmienne, takie jak String są dobrzy kandydaci na klucz)

Masz rację, że konkatenacja nie jest idealna. W pewnych okolicznościach zadziała, ale często jest to niepewne i kruche rozwiązanie (np. czy AB/C jest innym kluczem od A/BC ?).
 7
Author: Brian Agnew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-28 11:54:25

Mam podobną sprawę. Wszystko, co robię, to łączenie dwóch ciągów oddzielonych tyldą ( ~ ).

Więc gdy klient wywołuje funkcję service, aby pobrać obiekt z mapy, wygląda to tak:

MyObject getMyObject(String key1, String key2) {
    String cacheKey = key1 + "~" + key2;
    return map.get(cachekey);
}
To proste, ale działa.
 4
Author: EdgeCase,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-28 12:06:25

Widzę, że wiele osób używa zagnieżdżonych map. Oznacza to, że aby zmapować Key1 -> Key2 -> Value (używam notacji Haskell curring dla (Key1 x Key2) -> Value mapowania, które ma dwa argumenty i generuje wartość), najpierw podajesz pierwszy klucz - to zwraca (częściową) mapę Key2 -> Value, które rozwijasz w następnym kroku.

Na przykład,

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

Nie jestem pewien, czy jest lepszy, czy nie, niż zwykła konstrukcja klucza kompozytowego. Możesz to skomentować.

 2
Author: Val,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-03 17:51:27

Czytając o stosie spaguetti/cactus wymyśliłem wariant, który może służyć do tego celu, w tym możliwość mapowania kluczy w dowolnej kolejności tak, aby Mapa.lookup ("a"," b") i map.lookup ("b"," a") zwraca ten sam element. Działa również z dowolną liczbą klawiszy, a nie tylko dwoma.

Używam go jako stos do eksperymentowania z programowaniem dataflow, ale tutaj jest szybka i brudna wersja, która działa jako mapa wielu klawiszy( należy ją poprawić: Zestawy zamiast tablic powinny być używane, aby uniknąć wyszukiwania zduplikowanych ocurrences klucza)

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
Author: Andrés Tremols,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-04 17:25:25

Nie musisz odkrywać koła na nowo. Po prostu użyj Guava's HashBasedTable<R,C,V> implementacji Table<R,C,V> interfejsu, do swoich potrzeb. Oto przykład

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
Szczęśliwego kodowania!
 2
Author: TMtech,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-07 10:24:30
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);}
public static string FAKE_KEY_SEPARATOR = "~";

INPUT: fakeMapKey("keyPart1","keyPart2","keyPart3");
OUTPUT: keyPart1~keyPart2~keyPart3
 1
Author: Nelson Azevedo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-15 15:20:46