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?


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.


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.


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;

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.


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

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



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

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.


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()...

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

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


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() .....

}

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?


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.


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

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

0

J'ai utilisé ceci:

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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.