¿Cómo implementar un mapa con múltiples claves?

Necesito una estructura de datos que se comporte como un Mapa, pero usa varias claves (de tipo diferente) para acceder a sus valores.
(No seamos demasiado generales, digamos dos claves)

Las llaves tienen la garantía de ser únicas.

Algo como:

MyMap ... 

Con métodos como:

 getByKey1(K1 key)... getByKey2(K2 key)... containsKey1(K1 key)... containsKey2(K2 key)... 

¿Tienes alguna sugerencia?

Lo único que puedo pensar es:
Escribe una clase que use dos mapas internamente.

EDITAR Algunas personas me sugieren que use una tupla , un par o similar como clave para el Mapa de Java, pero esto no funcionaría para mí:
Debo, como está escrito anteriormente, poder buscar valores solo con una de las dos claves especificadas.
Los mapas usan códigos hash de claves y verifican su igualdad.

Dos mapas Un Map y un Map . Si debe tener una sola interfaz, escriba una clase contenedora que implemente dichos métodos.

Commons-collections proporciona justo lo que estás buscando: http://commons.apache.org/proper/commons-collections/javadocs/api-release/index.html

Parece que ahora las colecciones comunes están escritas.

Puede encontrar una versión mecanografiada en: https://github.com/megamattron/collections-generic

Esto soportará exactamente su caso de uso:

  MultiKeyMap multiMap = ?? 

Todavía voy a sugerir la solución de 2 mapas, pero con un tweest

 Map m2; Map m1; 

Este esquema le permite tener un número arbitrario de “alias” clave.

También le permite actualizar el valor a través de cualquier tecla sin que los mapas se desincronicen.

Otra solución es usar la guayaba de Google

 import com.google.common.collect.Table; import com.google.common.collect.HashBasedTable; Table table = HashBasedTable.create(); 

El uso es realmente simple:

 String row = "a"; String column = "b"; int value = 1; if (!table.contains(row, column)) { table.put(row, column, value); } System.out.println("value = " + table.get(row, column)); 

El método HashBasedTable.create() básicamente está haciendo algo como esto:

 Table table = Tables.newCustomTable( Maps.>newHashMap(), new Supplier>() { public Map get() { return Maps.newHashMap(); } }); 

si intenta crear algunos mapas personalizados, debe elegir la segunda opción (como sugiere @Karatheodory), de lo contrario, debería estar bien con la primera opción.

¿Qué tal si declaras la siguiente clase “Clave”?

 public class Key { public Object key1, key2, ..., keyN; public Key(Object key1, Object key2, ..., Object keyN) { this.key1 = key1; this.key2 = key2; ... this.keyN = keyN; } @Override public boolean equals(Object obj) { if (!(obj instanceof Key)) return false; Key ref = (Key) obj; return this.key1.equals(ref.key1) && this.key2.equals(ref.key2) && ... this.keyN.equals(ref.keyN) } @Override public int hashCode() { return key1.hashCode() ^ key2.hashCode() ^ ... ^ keyN.hashCode(); } } 

Declarando el mapa

 Map map = new HashMap(); 

Declarando el objeto clave

 Key key = new Key(key1, key2, ..., keyN) 

Llenar el mapa

 map.put(key, new Double(0)) 

Obtener el objeto del mapa

 Double result = map.get(key); 

Propuesta, según lo sugerido por algunos contestadores:

 public interface IDualMap { /** * @return Unmodifiable version of underlying map1 */ Map getMap1(); /** * @return Unmodifiable version of underlying map2 */ Map getMap2(); void put(K1 key1, K2 key2, V value); } public final class DualMap implements IDualMap { private final Map map1 = new HashMap(); private final Map map2 = new HashMap(); @Override public Map getMap1() { return Collections.unmodifiableMap(map1); } @Override public Map getMap2() { return Collections.unmodifiableMap(map2); } @Override public void put(K1 key1, K2 key2, V value) { map1.put(key1, value); map2.put(key2, value); } } 

¿Por qué no simplemente descartar el requisito de que la clave tenga que ser de un tipo específico, es decir, simplemente utilizar Mapa .

A veces los generics simplemente no vale la pena el trabajo extra.

Creé esto para resolver un problema similar.

Estructura de datos

 import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; public class HashBucket { HashMap> hmap; public HashBucket() { hmap = new HashMap>(); } public void add(Object key, Object value) { if (hmap.containsKey(key)) { ArrayList al = hmap.get(key); al.add(value); } else { ArrayList al = new ArrayList(); al.add(value); hmap.put(key, al); } } public Iterator getIterator(Object key) { ArrayList al = hmap.get(key); return hmap.get(key).iterator(); } } 

Recuperar un valor:

(Nota * Vuelve a colocar el Objeto en el tipo insertado. En mi caso fue mi Objeto Evento)

  public Iterator getIterator(Object key) { ArrayList al = hmap.get(key); if (al != null) { return hmap.get(key).iterator(); } else { List empty = Collections.emptyList(); return empty.iterator(); } } 

Insertar

 Event e1 = new Event(); e1.setName("Bob"); e1.setTitle("Test"); map.add("key",e1); 

Puedo ver los siguientes enfoques:

a) Usa 2 mapas diferentes. Puedes envolverlos en una clase como sugieres, pero incluso eso podría ser un exceso. Simplemente use los mapas directamente: key1Map.getValue (k1), key2Map.getValue (k2)

b) Puede crear una clase de clave sensible al tipo, y usar eso (no probado).

 public class Key { public static enum KeyType { KEY_1, KEY_2 } public final Object k; public final KeyType t; public Key(Object k, KeyType t) { this.k = k; this.t= t; } public boolean equals(Object obj) { KeyType kt = (KeyType)obj; return k.equals(kt.k) && t == kt.t; } public int hashCode() { return k.hashCode() ^ t.hashCode(); } } 

Por cierto, en muchos casos comunes, el espacio de key1 y el espacio de key2 no se cruzan. En ese caso, no es necesario que hagas nada especial. Simplemente defina un mapa que tenga entradas key1=>v así como key2=>v

sol: cancanizar ambas teclas y hacer una clave final, usar esto como clave.

para valores clave,

concatena ket-1 y key-2 con come “,” in beetween, usa esto como clave original.

clave = tecla-1 + “,” + tecla-2;

myMap.put (clave, valor);

de manera similar al recuperar valores.

Suena como una tupla de Python. Siguiendo en ese espíritu, puedes crear una clase inmutable de tu propio diseño que implemente Comparable y lo tendrás.

es probable que todas las teclas multilíneas fallen, porque el put ([key1, key2], val) y el get ([null, key2]) terminan usando las teclas [key1, key2] y [null, key2]. Si el mapa de respaldo no contiene cubos de hash por clave, las búsquedas son realmente lentas.

Creo que el camino a seguir es usar un decorador de índices (vea los ejemplos de key1, key2 arriba) y si las claves de índice extra son propiedades del valor almacenado, puede usar los nombres y reflexiones de la propiedad para construir los mapas secundarios cuando pones (key , val) y agrega un método extra get (propertyname, propertyvalue) para usar ese índice.

el tipo de retorno del get (propertyname, propertyvalue) podría ser una colección, por lo que incluso ninguna clave única está indexada …

Usé dicha implementación para múltiples objetos clave. Me permite usar una cantidad innumerable de claves para el mapa. Es escalable y bastante simple. Pero tiene limitaciones: las claves se ordenan de acuerdo con el orden de los argumentos en el constructor y no funcionaría con las matrices en 2D, debido al uso de Arrays.equals (). Para solucionarlo, puede usar Arrays.deepEquals ();

Espero que te ayude. Si conoce algún motivo por el cual no se puede utilizar como una solución para tales problemas, ¡por favor hágamelo saber!

 public class Test { private static Map sampleMap = new HashMap(); private static class InnumerableKey { private final Object[] keyParts; private InnumerableKey(Object... keyParts) { this.keyParts = keyParts; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof InnumerableKey)) return false; InnumerableKey key = (InnumerableKey) o; if (!Arrays.equals(keyParts, key.keyParts)) return false; return true; } @Override public int hashCode() { return keyParts != null ? Arrays.hashCode(keyParts) : 0; } } public static void main(String... args) { boolean keyBoolean = true; double keyDouble = 1d; Object keyObject = new Object(); InnumerableKey doubleKey = new InnumerableKey(keyBoolean, keyDouble); InnumerableKey tripleKey = new InnumerableKey(keyBoolean, keyDouble, keyObject); sampleMap.put(doubleKey, "DOUBLE KEY"); sampleMap.put(tripleKey, "TRIPLE KEY"); // prints "DOUBLE KEY" System.out.println(sampleMap.get(new InnumerableKey(true, 1d))); // prints "TRIPLE KEY" System.out.println(sampleMap.get(new InnumerableKey(true, 1d, keyObject))); // prints null System.out.println(sampleMap.get(new InnumerableKey(keyObject, 1d, true))); } } 

Defina una clase que tenga una instancia de K1 y K2. Luego usa eso como clase como tu tipo de clave.

Ver colecciones de Google . O, como sugieres, usa un mapa internamente y haz que ese mapa use un par. Tendrás que escribir o encontrar Pair <>; es bastante fácil pero no forma parte de las Colecciones estándar.

Parece que tu solución es bastante plausible para esta necesidad, sinceramente no veo ningún problema si tus dos tipos de teclas son realmente distintos. Solo hace que escribas tu propia implementación para esto y lidies con problemas de sincronización si es necesario.

Si tiene la intención de usar la combinación de varias teclas como una sola, entonces tal vez Commander MultiKey sea ​​su amigo. No creo que funcione uno por uno, aunque ..

Dependiendo de cómo se usará, puede hacer esto con dos mapas: Map y Map o con dos mapas Map y Map . Si una de las teclas es más permanente que la otra, la segunda opción puede tener más sentido.

Me parece que los métodos que desea en su pregunta son compatibles directamente con Map. El que pareciera querer es

 put(K1 key, K2 key, V value) put(K1 key, V value) put(K2 key, V value) 

Tenga en cuenta que en el mapa, get() y containsKey() etc. todos toman argumentos de Object . No hay nada que le impida utilizar el método get() para delegar en todos los mapas compuestos que combine (como se indica en su pregunta y otras respuestas). Tal vez necesites un registro de tipo para que no tengas problemas de conversión de clase (si son especiales + implementados ingenuamente).

Un registro basado en mecanografía también te permitirá recuperar el mapa “correcto” que se utilizará:

 Map getMapForKey(Class keyClass){ //Completely naive implementation - you actually need to //iterate through the keys of the maps, and see if the keyClass argument //is a sub-class of the defined map type. And then ordering matters with //classes that implement multiple interfaces... Map specificTypeMap = (Map 

Solo algunas ideas ...

Sugeriría la estructura

 Map> 

aunque buscar la segunda clave podría no ser eficiente

Recomiendo algo como esto:

  public class MyMap { Map map = new HashMap(); public V put(K1 key,V value){ return map.put(key, value); } public V put(K2 key,V value){ return map.put(key, value); } public V get(K1 key){ return map.get(key); } public V get(K2 key){ return map.get(key); } //Same for conatains } 

Entonces puedes usarlo como:
myMap.put(k1,value) o myMap.put(k2,value)

Ventajas : Es simple, aplica seguridad de tipo y no almacena datos repetidos (como las dos soluciones de mapas, aunque aún almacenan valores duplicados).
Inconvenientes : no genérico.

Otra solución posible que proporciona la posibilidad de claves más complicadas se puede encontrar aquí: http://insidecoffe.blogspot.de/2013/04/indexable-hashmap-implementation.html

¿Qué hay de usar una estructura de datos trie?

http://en.wikipedia.org/wiki/Trie

La raíz del trie will by blank. Los hermanos de primer nivel serán sus claves principales del mapa, los hermanos de segundo nivel serán sus claves secundarias y el tercer nivel serán los nodos terminales que tendrán el valor a lo largo de nulo para indicar la terminación de esa twig. También puede agregar más de dos claves usando el mismo esquema.

Buscar es simple DFS.

Qué tal algo como esto:

Su afirmación dice que las claves son únicas, por lo que es muy posible guardar los mismos objetos de valor en diferentes claves y cuando envíe cualquier clave que coincida con dicho valor, podremos volver al objeto de valor.

Vea el código a continuación:

Un valor de Clase de objeto,

  public class Bond { public Bond() { System.out.println("The Name is Bond... James Bond..."); } private String name; public String getName() { return name;} public void setName(String name) { this.name = name; } } public class HashMapValueTest { public static void main(String[] args) { String key1 = "A"; String key2 = "B"; String key3 = "C"; Bond bond = new Bond(); bond.setName("James Bond Mutual Fund"); Map bondsById = new HashMap<>(); bondsById.put(key1, bond); bondsById.put(key2, bond); bondsById.put(key3, bond); bond.setName("Alfred Hitchcock"); for (Map.Entry entry : bondsById.entrySet()) { System.out.println(entry.getValue().getName()); } } } 

El resultado es:

 The Name is Bond... James Bond... Alfred HitchCock Alfred HitchCock Alfred HitchCock 

Si las claves son únicas, entonces no hay necesidad de 2 mapas, mapas de mapas, mapOfWhateverThereIs. Es necesario que haya solo 1 mapa único y solo un método de envoltura simple que coloque las claves y el valor en ese mapa. Ejemplo:

 Map map = new HashMap<>(); public void addKeysAndValue(String key1, String key2, String value){ map.put(key1, value); map.put(key2, value); } public void testIt(){ addKeysAndValue("behemoth", "hipopotam", "hornless rhino"); } 

Luego usa tu mapa como lo harías normalmente. Ni siquiera necesitas esos elegantes getByKeyN y containsKeyN.

Tanto MultiMap como MultiKeyMap de Commons o Guava funcionarán.

Sin embargo, una solución rápida y simple podría ser extender la compra de la clase Map manejando una clave compuesta usted mismo, considerando que las claves son de tipo primitivo.

Una solución sucia y simple, si usa los mapas solo para ordenar, digamos, es agregar un valor muy pequeño a una clave hasta que el valor no exista, pero no agregue el mínimo (por ejemplo Double.MIN_VALUE) porque causará un error. Como dije, esta es una solución muy sucia pero simplifica el código.