J'ai besoin d'une structure de données qui se comporte comme une carte, mais utilise plusieurs clés (de types différents) pour accéder à ses valeurs.
(Ne soyons pas trop généraux, disons deux clés)
Les clés sont garanties d'être uniques.
Quelque chose comme:
MyMap<K1,K2,V> ...
Avec des méthodes comme:
getByKey1(K1 key)...
getByKey2(K2 key)...
containsKey1(K1 key)...
containsKey2(K2 key)...
Avez-vous des suggestions?
La seule chose à laquelle je peux penser est:
Ecrivez une classe qui utilise deux cartes en interne.
EDIT Certaines personnes me suggèrent d'utiliser un Tuple, un paire, ou une clé similaire pour la carte de Java, mais ceci ne fonctionnerait pas pour moi:
Je dois être capable, comme écrit ci-dessus, de rechercher des valeurs avec une seule des deux clés spécifiées.
Les cartes utilisent des codes de hachage pour les clés et vérifient leur égalité.
Deux cartes. Un Map<K1, V>
et un Map<K2, V>
. Si vous devez avoir une seule interface, écrivez une classe wrapper qui implémente lesdites méthodes.
Commons-collections fournit exactement ce que vous recherchez: https://commons.Apache.org/proper/commons-collections/apidocs/
On dirait que maintenant, commons-collections est dactylographié.
Une version dactylographiée est disponible à l’adresse suivante: https://github.com/megamattron/collections-generic
Ceci soutiendra exactement votre cas d'utilisation:
MultiKeyMap<k1,k2,...,kn,v> multiMap = ??
Je vais toujours suggérer la solution 2 carte, mais avec un tweest
Map<K2, K1> m2;
Map<K1, V> m1;
Ce schéma vous permet d'avoir un nombre arbitraire de clés "alias".
Il vous permet également de mettre à jour la valeur à l'aide de n'importe quelle clé sans que les cartes ne soient désynchronisées.
Une autre solution consiste à utiliser Google's Guava
import com.google.common.collect.Table;
import com.google.common.collect.HashBasedTable;
Table<String, String, Integer> table = HashBasedTable.create();
L'utilisation est vraiment 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));
La méthode HashBasedTable.create()
fait essentiellement quelque chose comme ceci:
Table<String, String, Integer> table = Tables.newCustomTable(
Maps.<String, Map<String, Integer>>newHashMap(),
new Supplier<Map<String, Integer>>() {
public Map<String, Integer> get() {
return Maps.newHashMap();
}
});
si vous essayez de créer des cartes personnalisées, vous devriez opter pour la deuxième option (comme le suggère @Karatheodory), sans quoi la première option vous conviendra.
Et si vous déclariez la classe "Key" suivante:
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();
}
}
Déclarer la carte
Map<Key, Double> map = new HashMap<Key,Double>();
Déclarer l'objet clé
Key key = new Key(key1, key2, ..., keyN)
Remplir la carte
map.put(key, new Double(0))
Obtenir l'objet de la carte
Double result = map.get(key);
Proposition, suggérée par certains répondants:
public interface IDualMap<K1, K2, V> {
/**
* @return Unmodifiable version of underlying map1
*/
Map<K1, V> getMap1();
/**
* @return Unmodifiable version of underlying map2
*/
Map<K2, V> getMap2();
void put(K1 key1, K2 key2, V value);
}
public final class DualMap<K1, K2, V>
implements IDualMap<K1, K2, V> {
private final Map<K1, V> map1 = new HashMap<K1, V>();
private final Map<K2, V> map2 = new HashMap<K2, V>();
@Override
public Map<K1, V> getMap1() {
return Collections.unmodifiableMap(map1);
}
@Override
public Map<K2, V> getMap2() {
return Collections.unmodifiableMap(map2);
}
@Override
public void put(K1 key1, K2 key2, V value) {
map1.put(key1, value);
map2.put(key2, value);
}
}
Pourquoi ne pas simplement supprimer l'exigence selon laquelle la clé doit être d'un type spécifique, c'est-à-dire utiliser uniquement Map <Object, V>.
Parfois, les génériques ne valent tout simplement pas le travail supplémentaire.
J'ai créé cela pour résoudre un problème similaire.
Structure de données
import Java.util.ArrayList;
import Java.util.HashMap;
import Java.util.Iterator;
public class HashBucket {
HashMap<Object, ArrayList<Object>> hmap;
public HashBucket() {
hmap = new HashMap<Object, ArrayList<Object>>();
}
public void add(Object key, Object value) {
if (hmap.containsKey(key)) {
ArrayList al = hmap.get(key);
al.add(value);
} else {
ArrayList al = new ArrayList<Object>();
al.add(value);
hmap.put(key, al);
}
}
public Iterator getIterator(Object key) {
ArrayList al = hmap.get(key);
return hmap.get(key).iterator();
}
}
Récupérer une valeur:
(Remarque * Remettez l'objet dans le type inséré. Dans mon cas, il s'agissait de mon objet événement)
public Iterator getIterator(Object key) {
ArrayList al = hmap.get(key);
if (al != null) {
return hmap.get(key).iterator();
} else {
List<Object> empty = Collections.emptyList();
return empty.iterator();
}
}
Insertion
Event e1 = new Event();
e1.setName("Bob");
e1.setTitle("Test");
map.add("key",e1);
Je peux voir les approches suivantes:
a) Utilise 2 cartes différentes. Vous pouvez les envelopper dans une classe comme vous le suggérez, mais même cela pourrait être exagéré. Utilisez simplement les cartes directement: key1Map.getValue (k1), key2Map.getValue (k2)
b) Vous pouvez créer une classe de clés sensible au type et l'utiliser (non testée).
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();
}
}
Par ailleurs, dans de nombreux cas courants, l'espace de key1
et l'espace de key2
ne se croisent pas. Dans ce cas, vous n'avez pas besoin de faire quoi que ce soit de spécial. Il suffit de définir une carte comportant les entrées key1=>v
ainsi que key2=>v
sol: annule les deux clés et crée une clé finale, utilise-la comme clé.
pour les valeurs clés,
concaténer ket-1, et key-2 avec un come "," dans beetween, utilisez-le comme clé originale.
clé = clé-1 + "," + clé-2;
myMap.put (clé, valeur);
de la même manière en revenant sur les valeurs.
toutes les clés multy échouent probablement car les options put ([key1, key2], val) et get ([null, key2]) finissent par utiliser les équivalents de [key1, key2] et [null, key2]. Si la carte de sauvegarde ne contient pas de compartiments de hachage par clé, les recherches sont très lentes.
je pense que la solution consiste à utiliser un décorateur d'index (voir les exemples key1, key2 ci-dessus) et si les clés d'index supplémentaires sont des propriétés de la valeur stockée, vous pouvez utiliser le nom de la propriété et la réflexion pour créer les cartes secondaires lorsque vous les mettez (clé). , val) et ajoutez une méthode supplémentaire get (propertyname, propertyvalue) pour utiliser cet index.
le type de retour de get (propertyname, propertyvalue) pourrait être une collection, de sorte que même aucune clé unique n'est indexée ....
Cela ressemble à un Python Tuple. En suivant cet esprit, vous pouvez créer une classe immuable de votre propre conception qui implémente Comparable et vous l'aurez.
J'ai utilisé une telle implémentation pour plusieurs objets clés. Cela me permet d'utiliser un nombre incalculable de clés pour la carte. C'est évolutif et assez simple. Mais il a des limites: les clés sont ordonnées en fonction de l'ordre des arguments dans le constructeur et cela ne fonctionnerait pas avec les tableaux 2D, à cause de l'utilisation de Arrays.equals (). Pour résoudre ce problème, vous pouvez utiliser Arrays.deepEquals ();
J'espère que cela vous aidera. Si vous connaissez une raison quelconque pour laquelle il ne pourrait pas être utilisé comme solution à de tels problèmes, merci de me le faire savoir!
public class Test {
private static Map<InnumerableKey, Object> sampleMap = new HashMap<InnumerableKey, Object>();
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)));
}
}
Une solution simple et sale, si vous utilisez les cartes uniquement pour le tri, consiste à ajouter une très petite valeur à une clé jusqu'à ce que la valeur n'existe pas, sans ajouter le minimum (par exemple, Double.MIN_VALUE) car elle va causer un bug. Comme je le disais, c'est une solution très sale mais cela simplifie le code.
Il me semble que les méthodes que vous souhaitez dans votre question sont directement prises en charge par Map. Celui (s) que vous semblez vouloir sont
put(K1 key, K2 key, V value)
put(K1 key, V value)
put(K2 key, V value)
Notez que dans map, get()
et containsKey()
etc acceptent tous les arguments Object
. Rien ne vous empêche d’utiliser la méthode get()
à déléguer à toutes les cartes composites que vous combinez (comme indiqué dans votre question et d’autres réponses). Peut-être aurez-vous besoin d'un enregistrement de type pour ne pas avoir de problèmes de casting de classe (s'ils sont spéciaux + implémentés naïvement.
Une inscription basée sur le type vous permettrait également de récupérer la carte "correcte" à utiliser:
Map<T,V> getMapForKey(Class<T> 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<T,V> specificTypeMap = (Map<T,V) maps.get(keyClass);
if (specificTypeMap == null){
throw new IllegalArgumentException("There is no map keyed by class" + keyClass);
}
return maps.get(keyClass);
}
V put(Object key, V value) {
//This bit requires generic suppression magic - but
//nothing leaves this class and you're testing it right?
//(You can assert that it *is* type-safe)
Map map = getMapForKey(key.getClass());
map.put(object, key);
}
void put(Object[] keys, V value) { //Or put(V value, Object ... keys)
//Might want to catch exceptions for unsupported keys and log instead?
.....
}
Juste quelques idées ...
Je suggérerais la structure
Map<K1, Map<K2, V>>
bien que la recherche de la deuxième clé pourrait ne pas être efficace
Définissez une classe qui a une instance de K1 et K2. Utilisez ensuite cette classe comme type de clé.
Voir Google Collections . Ou, comme vous le suggérez, utilisez une carte en interne et faites-la utiliser une paire. Vous devrez écrire ou trouver Pair <>; c'est assez facile mais cela ne fait pas partie des collections standard.
On dirait que votre solution est tout à fait plausible pour ce besoin. Honnêtement, je ne vois pas de problème si vos deux types de clé sont vraiment distincts. Cela vous oblige simplement à écrire votre propre implémentation et à régler les problèmes de synchronisation si nécessaire.
Je recommande quelque chose comme ça:
public class MyMap {
Map<Object, V> map = new HashMap<Object, V>();
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
}
Ensuite, vous pouvez l'utiliser comme:myMap.put(k1,value)
ou myMap.put(k2,value)
Avantages: Il est simple, applique la sécurité de type et ne stocke pas les données répétées (comme le font les solutions à deux cartes, tout en conservant les valeurs en double).
Inconvénients: Non générique.
Si vous avez l’intention d’utiliser une combinaison de plusieurs touches en une seule, alors peut-être que Apache commons MultiKey est votre ami. Je ne pense pas que cela fonctionnerait un par un cependant ..
En fonction de son utilisation, vous pouvez le faire avec deux cartes Map<K1, V>
et Map<K2, V>
ou avec deux cartes Map<K1, V>
et Map<K2, K1>
. Si l’une des touches est plus permanente que l’autre, la deuxième option est plus logique.
Que diriez-vous quelque chose comme ça:
Sa déclaration indique que les clés sont uniques. Il est donc tout à fait possible de sauvegarder les mêmes objets de valeur contre différentes clés. Lorsque vous envoyez une clé correspondant à cette valeur, nous pourrons revenir à l’objet de valeur.
Voir le code ci-dessous:
Une classe d'objets de valeur,
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<String, Bond> bondsById = new HashMap<>();
bondsById.put(key1, bond);
bondsById.put(key2, bond);
bondsById.put(key3, bond);
bond.setName("Alfred Hitchcock");
for (Map.Entry<String, Bond> entry : bondsById.entrySet()) {
System.out.println(entry.getValue().getName());
}
}
}
Le résultat est:
The Name is Bond... James Bond...
Alfred HitchCock
Alfred HitchCock
Alfred HitchCock
Une autre solution possible offrant la possibilité de clés plus complexes peut être trouvée ici: http://insidecoffe.blogspot.de/2013/04/indexable-hashmap-implementation.html
Les cartes MultiMap ou MultiKeyMap de Commons ou de Guava fonctionneront.
Cependant, une solution simple et rapide pourrait consister à étendre vous-même l’achat d’une carte composite à une classe Map, en considérant que les clés sont de type primitif.
Si les clés sont uniques, vous n'avez pas besoin de 2 cartes, carte de cartes, mapOfW WhateverThereIs. Il ne doit y avoir qu’une seule carte et juste une méthode d’emballage qui mettrait vos clés et votre valeur dans cette carte. Exemple:
Map<String, String> 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");
}
Ensuite, utilisez votre carte comme vous le feriez normalement. Vous n'avez même pas besoin de ces éléments sophistiqués getByKeyN et includesKeyN.
Pourquoi ne pas utiliser une structure de données trie?
http://en.wikipedia.org/wiki/Trie
La racine du tri sera par blanc. Les frères et sœurs de premier niveau seront vos clés principales de la carte, les frères et sœurs de deuxième niveau seront vos clés secondaires et le troisième niveau sera les nœuds terminaux qui auront la valeur null pour indiquer la fin de cette branche. Vous pouvez également ajouter plus de deux clés en utilisant le même schéma.
La recherche est simple DFS.