Mapper l'implémentation avec des clés en double

116

Je veux avoir une carte avec des clés en double.

Je sais qu'il existe de nombreuses implémentations de carte (Eclipse m'en montre environ 50), donc je parie qu'il doit y en avoir une qui le permet. Je sais qu'il est facile d'écrire votre propre carte pour cela, mais je préférerais utiliser une solution existante.

Peut-être quelque chose dans les collections communes ou les collections google?

IAdapter
la source
4
Comment cela devrait-il fonctionner? Si vous demandez une valeur associée à une clé et que cette clé existe plusieurs fois dans la carte, quelle valeur doit être renvoyée?
Mnementh le
get pourrait lancer une exception, j'ai besoin de cette carte uniquement pour l'itération.
IAdapter
6
Si vous n'en avez besoin que pour l'itération, pourquoi avez-vous besoin d'une carte en premier lieu? Utilisez une liste de paires ou quelque chose ...
Tal Pressman
2
Parce que tout mon programme fonctionne déjà avec Map et maintenant j'ai découvert qu'il était possible que les données aient des clés en double. Si l'utilisation de Map de manière différente était si mauvaise, nous n'aurions que 5 implémentations de Map et pas 50+.
IAdapter

Réponses:

90

Vous recherchez un multimap, et en effet, les collections communes et Guava ont plusieurs implémentations pour cela. Les multimaps permettent plusieurs clés en conservant une collection de valeurs par clé, c'est-à-dire que vous pouvez placer un seul objet dans la carte, mais vous en récupérez une collection.

Si vous pouvez utiliser Java 5, je préférerais Guava Multimapcar il est compatible avec les génériques.

nd.
la source
3
De plus, cette Multimap ne prétend pas être une carte comme celle d'Apache.
Kevin Bourrillion
7
Notez que Google Collections a été remplacé par Guava, voici donc le lien vers la version Guava de MultiMap: code.google.com/p/guava-libraries/wiki/…
Josh Glover
Cependant, Multimap n'est pas entièrement sérialisable, il a des membres transitoires qui rendent une instance désérialisée inutile
dschulten
@dschulten Eh bien, Multimap est une interface et vous ne spécifiez pas de quelle implémentation vous parlez. com.google.common.collect.HashMultimapa readObject/ writeObjectmethods, tout comme ArrayListMultimap et Immutable {List, Set} Multimap. Je considérerais une instance désérialisée inutile comme un bogue qui mérite d'être signalé.
nd.
1
Collections Apache 4.0 prend en charge les génériques commons.apache.org/proper/commons-collections/javadocs/...
Kervin
35

Nous n'avons pas besoin de dépendre de la bibliothèque externe Google Collections. Vous pouvez simplement implémenter la carte suivante:

Map<String, ArrayList<String>> hashMap = new HashMap<String, ArrayList>();

public static void main(String... arg) {
   // Add data with duplicate keys
   addValues("A", "a1");
   addValues("A", "a2");
   addValues("B", "b");
   // View data.
   Iterator it = hashMap.keySet().iterator();
   ArrayList tempList = null;

   while (it.hasNext()) {
      String key = it.next().toString();             
      tempList = hashMap.get(key);
      if (tempList != null) {
         for (String value: tempList) {
            System.out.println("Key : "+key+ " , Value : "+value);
         }
      }
   }
}

private void addValues(String key, String value) {
   ArrayList tempList = null;
   if (hashMap.containsKey(key)) {
      tempList = hashMap.get(key);
      if(tempList == null)
         tempList = new ArrayList();
      tempList.add(value);  
   } else {
      tempList = new ArrayList();
      tempList.add(value);               
   }
   hashMap.put(key,tempList);
}

Veuillez vous assurer d'affiner le code.

user668943
la source
14
Vous n'avez pas besoin de compter sur Multimap de Guava, bien sûr. Cela vous facilite la vie, car vous n'avez pas à les
réimplémenter
Cela ne permet pas une itération transparente sur toutes les paires. Il y a sûrement d'autres lacunes. J'étais sur le point de suggérer ma solution qui nécessite également une classe supplémentaire, puis j'ai vu que la réponse de @ Mnementh est juste cela.
Mark Jeronimus
2
écrire du code de base n'est pas toujours aussi intelligent. Google est plus susceptible d'avoir de meilleurs tests
senseiwu
26
Multimap<Integer, String> multimap = ArrayListMultimap.create();

multimap.put(1, "A");
multimap.put(1, "B");
multimap.put(1, "C");
multimap.put(1, "A");

multimap.put(2, "A");
multimap.put(2, "B");
multimap.put(2, "C");

multimap.put(3, "A");

System.out.println(multimap.get(1));
System.out.println(multimap.get(2));       
System.out.println(multimap.get(3));

La sortie est:

[A,B,C,A]
[A,B,C]
[A]

Remarque: nous devons importer des fichiers de bibliothèque.

http://www.java2s.com/Code/Jar/g/Downloadgooglecollectionsjar.htm

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;

ou https://commons.apache.org/proper/commons-collections/download_collections.cgi

import org.apache.commons.collections.MultiMap;
import org.apache.commons.collections.map.MultiValueMap;
Issac Balaji
la source
2
Bonne suggestion, puisque j'utilise Spring dans mon projet, j'ai fini par utiliser la saveur Spring de MultiValueMap comme mentionné dans la documentation http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/ springframework / util / MultiValueMap.html
ajup
18

Vous pourriez simplement passer un tableau de valeurs pour la valeur dans un HashMap normal, simulant ainsi des clés en double, et ce serait à vous de décider quelles données utiliser.

Vous pouvez également utiliser simplement une MultiMap , même si je n'aime pas l'idée de dupliquer les clés moi-même.

AlbertoPL
la source
Je vous remercie! L'utilisation a TreeMap<String, ArrayList<MyClass>>résolu mes besoins en double clé.
Joe
10

Si vous voulez parcourir une liste de paires clé-valeur (comme vous l'avez écrit dans le commentaire), alors une liste ou un tableau devrait être mieux. Combinez d'abord vos clés et vos valeurs:

public class Pair
{
   public Class1 key;
   public Class2 value;

   public Pair(Class1 key, Class2 value)
   {
      this.key = key;
      this.value = value;
   }

}

Remplacez Class1 et Class2 par les types que vous souhaitez utiliser pour les clés et les valeurs.

Vous pouvez maintenant les mettre dans un tableau ou une liste et les parcourir:

Pair[] pairs = new Pair[10];
...
for (Pair pair : pairs)
{
   ...
}
Mnementh
la source
Comment implémenter add () ou put (). Je ne veux pas de nombre inconditionnel de dimensions.
Amit Kumar Gupta
2
Dans ce cas, utilisez une liste. Le deuxième exemple change en List <Pair> pairs = new List <Pair> (); La boucle for reste la même. Vous pouvez ajouter une paire avec cette commande: paires.add (paire);
Mnementh
C'est probablement la meilleure réponse pour être honnête.
PaulBGD
6

Ce problème peut être résolu avec une liste d'entrées de carte List<Map.Entry<K,V>>. Nous n'avons pas besoin d'utiliser ni de bibliothèques externes ni de nouvelle implémentation de Map. Une entrée de carte peut être créée comme ceci: Map.Entry<String, Integer> entry = new AbstractMap.SimpleEntry<String, Integer>("key", 1);

Thach Van
la source
3

Apprenez de mes erreurs ... s'il vous plaît, ne mettez pas cela en œuvre vous-même. Guava multimap est la voie à suivre.

Une amélioration courante requise dans les multi-cartes consiste à interdire les paires clé-valeur en double.

Mettre en œuvre / modifier cela dans votre implémentation peut être ennuyeux.

Dans Guava, c'est aussi simple que:

HashMultimap<String, Integer> no_dupe_key_plus_val = HashMultimap.create();

ArrayListMultimap<String, Integer> allow_dupe_key_plus_val = ArrayListMultimap.create();
gelure
la source
2

J'avais une variante légèrement différente de ce problème: il était nécessaire d'associer deux valeurs différentes à la même clé. Juste en le publiant ici au cas où cela aiderait les autres, j'ai introduit un HashMap comme valeur:

/* @param frameTypeHash: Key -> Integer (frameID), Value -> HashMap (innerMap)
   @param innerMap: Key -> String (extIP), Value -> String
   If the key exists, retrieve the stored HashMap innerMap 
   and put the constructed key, value pair
*/
  if (frameTypeHash.containsKey(frameID)){
            //Key exists, add the key/value to innerHashMap
            HashMap innerMap = (HashMap)frameTypeHash.get(frameID);
            innerMap.put(extIP, connName+":"+frameType+":"+interfaceName);

        } else {
            HashMap<String, String> innerMap = new HashMap<String, String>();
            innerMap.put(extIP, connName+":"+frameType+":"+interfaceName);
            // This means the key doesn't exists, adding it for the first time
            frameTypeHash.put(frameID, innerMap );
        }
}

Dans le code ci-dessus, la clé frameID est lue à partir de la première chaîne d'un fichier d'entrée dans chaque ligne, la valeur de frameTypeHash est construite en divisant la ligne restante et a été stockée en tant qu'objet String à l'origine, sur une période de temps le fichier a commencé à avoir plusieurs lignes ( avec des valeurs différentes) associée à la même clé frameID, donc frameTypeHash a été écrasé par la dernière ligne comme valeur. J'ai remplacé l'objet String par un autre objet HashMap comme champ de valeur, cela a aidé à maintenir une clé unique vers un mappage de valeurs différentes.

Suresh Vadali
la source
2

Aucune bibliothèque sophistiquée requise. Les cartes sont définies par une clé unique, alors ne les pliez pas, utilisez une liste. Les ruisseaux sont puissants.

import java.util.AbstractMap.SimpleImmutableEntry;

List<SimpleImmutableEntry<String, String>> nameToLocationMap = Arrays.asList(
    new SimpleImmutableEntry<>("A", "A1"),
    new SimpleImmutableEntry<>("A", "A2"),
    new SimpleImmutableEntry<>("B", "B1"),
    new SimpleImmutableEntry<>("B", "B1"),
);

Et c'est tout. Exemples d'utilisation:

List<String> allBsLocations = nameToLocationMap.stream()
        .filter(x -> x.getKey().equals("B"))
        .map(x -> x.getValue())
        .collect(Collectors.toList());

nameToLocationMap.stream().forEach(x -> 
do stuff with: x.getKey()...x.getValue()...
BiggDatta
la source
1
class  DuplicateMap<K, V> 
{
    enum MapType
    {
        Hash,LinkedHash
    }

    int HashCode = 0;
    Map<Key<K>,V> map = null;

    DuplicateMap()
    {
        map = new HashMap<Key<K>,V>();
    }

    DuplicateMap( MapType maptype )
    {
        if ( maptype == MapType.Hash ) {
            map = new HashMap<Key<K>,V>();
        }
        else if ( maptype == MapType.LinkedHash ) {
            map = new LinkedHashMap<Key<K>,V>();
        }
        else
            map = new HashMap<Key<K>,V>();
    }

    V put( K key, V value  )
    {

        return map.put( new Key<K>( key , HashCode++ ), value );
    }

    void putAll( Map<K, V> map1 )
    {
        Map<Key<K>,V> map2 = new LinkedHashMap<Key<K>,V>();

        for ( Entry<K, V> entry : map1.entrySet() ) {
            map2.put( new Key<K>( entry.getKey() , HashCode++ ), entry.getValue());
        }
        map.putAll(map2);
    }

    Set<Entry<K, V>> entrySet()
    {
        Set<Entry<K, V>> entry = new LinkedHashSet<Map.Entry<K,V>>();
        for ( final Entry<Key<K>, V> entry1 : map.entrySet() ) {
            entry.add( new Entry<K, V>(){
                private K Key = entry1.getKey().Key();
                private V Value = entry1.getValue();

                @Override
                public K getKey() {
                    return Key;
                }

                @Override
                public V getValue() {
                    return Value;
                }

                @Override
                public V setValue(V value) {
                    return null;
                }});
        }

        return entry;
    }

    @Override
    public String toString() {
        StringBuilder builder = new  StringBuilder();
        builder.append("{");
        boolean FirstIteration = true;
        for ( Entry<K, V> entry : entrySet() ) {
            builder.append( ( (FirstIteration)? "" : "," ) + ((entry.getKey()==null) ? null :entry.getKey().toString() ) + "=" + ((entry.getValue()==null) ? null :entry.getValue().toString() )  );
            FirstIteration = false;
        }
        builder.append("}");
        return builder.toString();
    }

    class Key<K1>
    {
        K1 Key;
        int HashCode;

        public Key(K1 key, int hashCode) {
            super();
            Key = key;
            HashCode = hashCode;
        }

        public K1 Key() {
            return Key;
        }

        @Override
        public String toString() {
            return  Key.toString() ;
        }

        @Override
        public int hashCode() {

            return HashCode;
        }
    }
Gabrial David
la source
Merci @daspilker. Je ne vois que votre modification maintenant. Gud pour voir quelqu'un trouver mon extrait de code est digne s'il est modifié.
Gabrial David
1
 1, Map<String, List<String>> map = new HashMap<>();

cette solution verbeuse présente de multiples inconvénients et est sujette à des erreurs. Cela implique que nous devons instancier une Collection pour chaque valeur, vérifier sa présence avant d'ajouter ou de supprimer une valeur, la supprimer manuellement lorsqu'aucune valeur n'est laissée, etc.

2, org.apache.commons.collections4.MultiMap interface
3, com.google.common.collect.Multimap interface 

java-map-duplicate-keys

Daria Yu
la source
1

qu'en est-il d'un tel impl MultiMap?

public class MultiMap<K, V> extends HashMap<K, Set<V>> {
  private static final long serialVersionUID = 1L;
  private Map<K, Set<V>> innerMap = new HashMap<>();

  public Set<V> put(K key, V value) {
    Set<V> valuesOld = this.innerMap.get(key);
    HashSet<V> valuesNewTotal = new HashSet<>();
    if (valuesOld != null) {
      valuesNewTotal.addAll(valuesOld);
    }
    valuesNewTotal.add(value);
    this.innerMap.put(key, valuesNewTotal);
    return valuesOld;
  }

  public void putAll(K key, Set<V> values) {
    for (V value : values) {
      put(key, value);
    }
  }

  @Override
  public Set<V> put(K key, Set<V> value) {
    Set<V> valuesOld = this.innerMap.get(key);
    putAll(key, value);
    return valuesOld;
  }

  @Override
  public void putAll(Map<? extends K, ? extends Set<V>> mapOfValues) {
    for (Map.Entry<? extends K, ? extends Set<V>> valueEntry : mapOfValues.entrySet()) {
      K key = valueEntry.getKey();
      Set<V> value = valueEntry.getValue();
      putAll(key, value);
    }
  }

  @Override
  public Set<V> putIfAbsent(K key, Set<V> value) {
    Set<V> valueOld = this.innerMap.get(key);
    if (valueOld == null) {
      putAll(key, value);
    }
    return valueOld;
  }

  @Override
  public Set<V> get(Object key) {
    return this.innerMap.get(key);
  }

  @Override
  etc. etc. override all public methods size(), clear() .....

}
George
la source
0

Pourriez-vous également expliquer le contexte pour lequel vous essayez d'implémenter une carte avec des clés en double? Je suis sûr qu'il pourrait y avoir une meilleure solution. Les cartes sont destinées à conserver des clés uniques pour une bonne raison. Mais si vous vouliez vraiment le faire; vous pouvez toujours étendre la classe en écrivant une classe de carte personnalisée simple qui a une fonction d'atténuation des collisions et vous permettrait de conserver plusieurs entrées avec les mêmes clés.

Remarque: vous devez implémenter la fonction d'atténuation des collisions de telle sorte que les clés en collision soient converties en un ensemble unique "toujours". Quelque chose de simple comme, ajouter une clé avec un hashcode d'objet ou quelque chose?

Priyank
la source
1
Le contexte est que je pensais que les clés seraient uniques, mais il s'avère que cela pourrait ne pas l'être. Je ne veux pas tout refactoriser car il ne sera pas souvent utilisé.
IAdapter
Je veux convertir un petit fichier XML en hashmap comme un type de données. Seul le problème est que la structure du fichier XML n'est pas corrigée
Amit Kumar Gupta
0

juste pour être complet, Apache Commons Collections a également une MultiMap . L'inconvénient est bien sûr qu'Apache Commons n'utilise pas les génériques.

newacct
la source
1
Notez que leur MultiMap implémente Map mais rompt les contrats des méthodes Map. Cela me dérange.
Kevin Bourrillion
0

Avec un peu de hack, vous pouvez utiliser HashSet avec des clés en double. AVERTISSEMENT: cela dépend fortement de l'implémentation HashSet.

class MultiKeyPair {
    Object key;
    Object value;

    public MultiKeyPair(Object key, Object value) {
        this.key = key;
        this.value = value;
    }

    @Override
    public int hashCode() {
        return key.hashCode();
    }
}

class MultiKeyList extends MultiKeyPair {
    ArrayList<MultiKeyPair> list = new ArrayList<MultiKeyPair>();

    public MultiKeyList(Object key) {
        super(key, null);
    }

    @Override
    public boolean equals(Object obj) {
        list.add((MultiKeyPair) obj);
        return false;
    }
}

public static void main(String[] args) {
    HashSet<MultiKeyPair> set = new HashSet<MultiKeyPair>();
    set.add(new MultiKeyPair("A","a1"));
    set.add(new MultiKeyPair("A","a2"));
    set.add(new MultiKeyPair("B","b1"));
    set.add(new MultiKeyPair("A","a3"));

    MultiKeyList o = new MultiKeyList("A");
    set.contains(o);

    for (MultiKeyPair pair : o.list) {
        System.out.println(pair.value);
    }
}
Erdem
la source
0

S'il existe des clés en double, une clé peut correspondre à plus d'une valeur. La solution évidente consiste à mapper la clé à une liste de ces valeurs.

Par exemple en Python:

map = dict()
map["driver"] = list()
map["driver"].append("john")
map["driver"].append("mike")
print map["driver"]          # It shows john and mike
print map["driver"][0]       # It shows john
print map["driver"][1]       # It shows mike
cyberthanase
la source
0

J'ai utilisé ceci:

java.util.List<java.util.Map.Entry<String,Integer>> pairList= new java.util.ArrayList<>();

Alex Arvanitidis
la source