Java: Clave compuesta en hashmaps


Me gustaría almacenar un grupo de objetos en un hashmap , donde la clave debe ser un compuesto de dos valores de cadena. ¿hay alguna manera de lograrlo?

Simplemente puedo concatenar las dos cadenas , pero estoy seguro de que hay una mejor manera de hacerlo.

Author: user1203861, 2012-07-28

8 answers

Puede tener un objeto personalizado que contenga las dos cadenas:

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

El problema es que debe determinar la prueba de igualdad y el código hash para dos de estos objetos.

La igualdad podría ser la coincidencia en ambas cadenas y el hashcode podría ser el hashcode de los miembros concatenados (esto es discutible):

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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-19 11:18:20
public int hashCode() {
    return (str1 + str2).hashCode();
}

Esta parece ser una forma terrible de generar el hashCode: ¡Crear una nueva instancia de cadena cada vez que se calcula el código hash es terrible! (Incluso generar la instancia de cadena una vez y almacenar el resultado en caché es una práctica deficiente.)

Aquí hay muchas sugerencias:

¿Cómo puedo calcular un buen código hash para una lista de cadenas?

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

Para un par de cuerdas, que se convierte en:

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

Esa es una implementación muy básica. Muchos consejos a través del enlace para sugerir estrategias mejor afinadas.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:09:58

¿Por qué no crear un objeto (digamos) Pair, que contenga las dos cadenas como miembros, y luego usar esto como clave ?

Por ejemplo

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

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

No te olvides de es igual a() y hashCode(). Ver esta entrada del blog para más sobre HashMaps y claves, incluyendo un fondo sobre los requisitos de inmutabilidad. Si su clave no es inmutable, entonces puede cambiar sus componentes y una búsqueda posterior fallará para localizarla (esta es la razón por la que los objetos inmutables como String son buenos candidatos para una clave)

Tienes razón en que la concatenación no es ideal. Para algunas circunstancias va a funcionar, pero a menudo es una solución poco fiable y frágil (por ejemplo, es AB/C una clave diferente de 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-28 11:54:25

Tengo un caso similar. Todo lo que hago es concatenar las dos cadenas separadas por una tilde ( ~ ).

Así que cuando el cliente llama a la función de servicio para obtener el objeto del mapa, se ve así:

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

Es simple, pero funciona.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-28 12:06:25

Veo que mucha gente usa mapas anidados. Es decir, para mapear Key1 -> Key2 -> Value (uso la notación de curring de computer science/ aka haskell para (Key1 x Key2) -> Value mapping que tiene dos argumentos y produce un valor), primero debe proporcionar la primera clave this esto le devuelve un mapa (parcial) Key2 -> Value, que se despliegan en el siguiente paso.

Por ejemplo,

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

No estoy seguro de que sea mejor o no que la construcción de clave compuesta simple. Usted puede comentar sobre eso.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-03 17:51:27

Leyendo sobre la pila de spaguetti/cactus se me ocurrió una variante que puede servir para este propósito, incluida la posibilidad de mapear sus claves en cualquier orden para que el mapa.búsqueda ("a", "b") y mapa.lookup ("b"," a") devuelve el mismo elemento. También funciona con cualquier número de teclas, no solo dos.

Lo uso como una pila para experimentar con la programación de flujo de datos, pero aquí hay una versión rápida y sucia que funciona como un mapa de teclas múltiples( debería mejorarse: Los conjuntos en lugar de los arrays deberían para evitar buscar ocurrencias duplicadas de una clave)

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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-04 17:25:25

No necesitas reinventar la rueda. Simplemente use la implementación de Guava HashBasedTable<R,C,V> de la interfaz Table<R,C,V>, para su necesidad. He aquí un ejemplo

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

Feliz codificación!

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-15 15:20:46