Como implementar um mapa com várias chaves?

Eu preciso de uma estrutura de dados que se comporte como um mapa, mas usa várias chaves (de tipos diferentes) para acessar seus valores.
(Não vamos ser muito gerais, digamos duas chaves)

As chaves são garantidas como únicas.

Algo como:

MyMap ... 

Com methods como:

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

Você tem alguma sugestão?

A única coisa em que consigo pensar é:
Escreva uma turma que use dois mapas internamente.

EDIT Algumas pessoas sugerem que eu use uma tupla , um par ou similar como chave para o Mapa do Java, mas isso não funcionaria para mim:
Eu tenho que ser capaz, como escrito acima, de pesquisar valores por apenas uma das duas chaves especificadas.
Mapas usam códigos hash de chaves e checam sua igualdade.

Dois mapas. Um Map e um Map . Se você precisa ter uma única interface, escreva uma class wrapper que implemente os methods mencionados.

As collections comuns fornecem exatamente o que você está procurando: http://commons.apache.org/proper/commons-collections/javadocs/api-release/index.html

Parece que agora as collections comuns são digitadas.

Uma versão digitada pode ser encontrada em: https://github.com/megamattron/collections-generic

Isso suportará exatamente seu caso de uso:

  MultiKeyMap multiMap = ?? 

Eu ainda vou sugerir a solução de 2 mapas, mas com um tweest

 Map m2; Map m1; 

Este esquema permite que você tenha um número arbitrário de “aliases” de chave.

Também permite atualizar o valor por meio de qualquer tecla sem que os mapas fiquem fora de sincronia.

Outra solução é usar goiabas do Google

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

O uso é muito simples:

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

O método HashBasedTable.create() basicamente faz algo assim:

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

Se você está tentando criar alguns mapas personalizados, você deve ir para a segunda opção (como sugere o @Karatheodory), caso contrário você deve estar bem com o primeiro.

E você declara a seguinte class “Key”:

 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 o Mapa

 Map map = new HashMap(); 

Declarando o object chave

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

Preenchendo o mapa

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

Obtendo o object do mapa

 Double result = map.get(key); 

Proposta, como sugerido por alguns respondentes:

 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 que não abandonar o requisito de que a chave tenha que ser um tipo específico, isto é, use Map .

Às vezes, os genéricos não valem o trabalho extra.

Eu criei isso para resolver um problema semelhante.

Estrutura de dados

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

Recupere um valor:

(Nota * Cast o object de volta para o tipo inserido. No meu caso, era meu object de 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(); } } 

Inserindo

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

Eu posso ver as seguintes abordagens:

a) Use dois mapas diferentes. Você pode envolvê-los em uma aula como sugere, mas mesmo isso pode ser um exagero. Apenas use os mapas diretamente: key1Map.getValue (k1), key2Map.getValue (k2)

b) Você pode criar uma class de chave com reconhecimento de tipos e usá-la (não testada).

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

By the way, em muitos casos comuns, o espaço de key1 eo espaço de key2 não se cruzam. Nesse caso, você não precisa fazer nada de especial. Basta definir um mapa que tenha inputs key1=>v , assim como key2=>v

sol: cancela ambas as chaves e faz uma chave final, use isso como chave.

para valores-chave,

concatenate ket-1, e key-2 com um vir “,” em beetween, use isso como chave original.

chave = chave-1 + “,” + chave-2;

myMap.put (chave, valor);

da mesma forma enquanto recupera valores.

Soa como uma tupla Python. Seguindo esse espírito, você pode criar uma class imutável de sua própria concepção que implemente Comparable e você terá.

todas as chaves multy provavelmente falharam, porque o put ([key1, key2], val) e get ([null, key2]) acabam usando os iguais de [key1, key2] e [null, key2]. Se o mapa de apoio não contiver hash buckets por chave, as pesquisas serão lentas.

Eu acho que o caminho a percorrer é usando um decorador de índice (consulte os exemplos key1, key2 acima) e se as chaves de índice extras são propriedades do valor armazenado, você pode usar o nome da propriedade e reflection para construir os mapas secondairy quando você coloca , val) e adicione um método extra get (propertyname, propertyvalue) para usar esse índice.

o tipo de retorno do get (propertyname, propertyvalue) pode ser uma coleção, portanto, nenhuma chave exclusiva é indexada …

Eu usei essa implementação para vários objects-chave. Isso me permite usar um número incontável de chaves para o mapa. É escalável e bastante simples. Mas tem limitações: as chaves são ordenadas de acordo com a ordem dos argumentos no construtor e não funcionariam com arrays 2D, devido ao uso de Arrays.equals (). Para corrigi-lo – você poderia usar Arrays.deepEquals ();

Espero que isso ajude você. Se você sabe alguma razão pela qual não pode ser usado como solução para tais problemas – por favor, me avise!

 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 uma class que tenha uma instância de K1 e K2. Em seguida, use isso como class como seu tipo de chave.

Veja Coleções do Google . Ou, como você sugere, use um mapa internamente e use esse mapa para usar um par. Você terá que escrever ou encontrar Pair <>; é muito fácil, mas não faz parte das coleções padrão.

Parece que a sua solução é bastante plausível para essa necessidade, eu honestamente não vejo um problema se os dois tipos de chaves forem realmente distintos. Apenas faz você escrever sua própria implementação para isso e lidar com problemas de synchronization, se necessário.

Se você pretende usar a combinação de várias chaves como um, então talvez o apache commkys MultiKey é seu amigo. Eu não acho que funcionaria um por um embora ..

Dependendo de como será usado, você pode fazer isso com dois mapas Map e Map ou com dois mapas Map e Map . Se uma das chaves é mais permanente que a outra, a segunda opção pode fazer mais sentido.

Parece-me que os methods que você deseja em sua pergunta são suportados diretamente pelo Mapa. O (s) que você parece querer

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

Note que no mapa, get() e containsKey() etc todos pegam os argumentos do Object . Não há nada que impeça você de usar o método get() para delegar a todos os mapas compostos que você combina (como observado em sua pergunta e em outras respostas). Talvez você precise de um registro de tipo para não ter problemas de transmissão de class (se eles forem especiais + implementados ingenuamente).

Um registro baseado em digitação também permitiria que você recuperasse o mapa “correto” a ser usado:

 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 //classs that implement multiple interfaces... Map specificTypeMap = (Map 

Apenas algumas ideias ...

Eu sugeriria a estrutura

 Map> 

embora a pesquisa pela segunda chave possa não ser eficiente

Eu recomendo algo como isto:

  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 } 

Então você pode usá-lo como:
myMap.put(k1,value) ou myMap.put(k2,value)

Vantagens : É simples, reforça a segurança de tipos e não armazena dados repetidos (como fazem as duas soluções de mapas, embora ainda armazenem valores duplicados).
Desvantagens : Não é genérico.

Outra solução possível que oferece a possibilidade de chaves mais complicadas pode ser encontrada aqui: http://insidecoffe.blogspot.de/2013/04/indexable-hashmap-implementation.html

Que tal usar uma estrutura de dados trie?

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

A raiz do trie será em branco. Os irmãos de primeiro nível serão suas chaves primárias do mapa, os irmãos de segundo nível serão suas chaves secundárias e o terceiro nível será os nós terminais que terão o valor alongado null para indicar o término daquele ramo. Você também pode adicionar mais de duas chaves usando o mesmo esquema.

A pesquisa é simples DFS.

Que tal algo como isso:

Sua declaração diz que as chaves são Únicas, então salvar os mesmos objects de valor contra chaves diferentes é bem possível e quando você envia qualquer chave que corresponda ao valor mencionado, nós poderíamos voltar ao object de valor.

Veja o código abaixo:

Um valor de class de object,

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

O resultado é:

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

Se as chaves forem exclusivas, não há necessidade de 2 mapas, mapa de mapas, mapOfWhateverThereIs. É necessário que haja apenas um único mapa e apenas um método de wrapper simples que coloque suas chaves e valores nesse mapa. Exemplo:

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

Em seguida, use seu mapa como faria normalmente. Você nem precisa desses extravagantes getByKeyN e containsKeyN.

Tanto o MultiMap quanto o MultiKeyMap do Commons ou do Guava funcionarão.

No entanto, uma solução rápida e simples poderia ser a de estender a manipulação de uma class composta pela manipulação da class Map, considerando que as chaves são do tipo primitivo.

Uma solução simples e suja, se você usar os mapas apenas para classificar, digamos, é adicionar um valor muito pequeno a uma chave até que o valor não exista, mas não adicione o mínimo (por exemplo, Double.MIN_VALUE) porque irá causar um bug. Como eu disse, esta é uma solução muito suja, mas torna o código mais simples.