Implementazione della mappa con chiavi duplicate


116

Voglio avere una mappa con chiavi duplicate.

So che ci sono molte implementazioni di mappe (Eclipse me ne mostra circa 50), quindi scommetto che ce ne deve essere una che lo consenta. So che è facile scrivere la tua mappa che fa questo, ma preferirei usare una soluzione esistente.

Forse qualcosa nelle raccolte comuni o raccolte Google?


4
Come dovrebbe funzionare? Se chiedi un valore associato a una chiave e questa chiave esiste più volte nella mappa, quale valore deve essere restituito?
Mnementh

ottenere potrebbe generare un'eccezione, ho bisogno di questa mappa solo per l'iterazione.
IAdapter

6
Se ne hai bisogno solo per l'iterazione, perché hai bisogno di una mappa in primo luogo? Usa un elenco di coppie o qualcosa del genere ...
Tal Pressman,

2
Perché tutto il mio programma funziona già con Map e ora ho scoperto che è possibile che i dati abbiano chiavi duplicate. Se usare Map in un modo diverso sarebbe così sbagliato avremmo solo 5 implementazioni di Map e non 50+.
IAdapter

Risposte:


90

Stai cercando una multimappa, e in effetti sia le collezioni comuni che Guava hanno diverse implementazioni per questo. Le mappe multiple consentono più chiavi mantenendo una raccolta di valori per chiave, ovvero è possibile inserire un singolo oggetto nella mappa, ma si recupera una raccolta.

Se puoi usare Java 5, preferirei Guava Multimappoiché è compatibile con i generici.


3
Inoltre, questo Multimap non pretende di essere una mappa come fa l'apache.
Kevin Bourrillion

7
Tieni presente che le raccolte di Google sono state sostituite da Guava, quindi ecco il link alla versione Guava di MultiMap: code.google.com/p/guava-libraries/wiki/…
Josh Glover

Tuttavia, Multimap non è completamente serializzabile, ha membri temporanei che rendono inutile un'istanza deserializzata
dschulten

@dschulten Bene, Multimap è un'interfaccia e non stai specificando quale implementazione intendi. com.google.common.collect.HashMultimapha readObject/ writeObjectmetodi, così come ArrayListMultimap e Immutable {List, Set} Multimap. Considererei un'istanza deserializzata inutile un bug che vale la pena segnalare.
nd.

1
Apache Collections 4.0 supporta generics commons.apache.org/proper/commons-collections/javadocs/…
kervin

35

Non è necessario dipendere dalla libreria esterna di Google Collections. Puoi semplicemente implementare la seguente mappa:

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

Assicurati di mettere a punto il codice.


14
Non è necessario fare affidamento su Multimap di Guava, ovviamente. Ti semplifica la vita, poiché non devi reimplementarli, testarli, ecc.
PhiLho

Ciò non consente l'iterazione senza interruzioni su tutte le coppie. Ci sono sicuramente altri difetti. Stavo per suggerire la mia soluzione che richiede anche una lezione in più, poi ho visto che la risposta di @ Mnementh è proprio questa.
Mark Jeronimus

2
scrivere codice di base non è sempre così intelligente. È più probabile che Google abbia test migliori
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));

L'output è:

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

Nota: dobbiamo importare i file di libreria.

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

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

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

import org.apache.commons.collections.MultiMap;
import org.apache.commons.collections.map.MultiValueMap;

2
Buon suggerimento, dato che sto usando Spring nel mio progetto, ho finito per usare il sapore Spring di MultiValueMap come menzionato nei documenti http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/ springframework / util / MultiValueMap.html
ajup

18

Potresti semplicemente passare un array di valori per il valore in una normale HashMap, simulando così chiavi duplicate, e spetterà a te decidere quali dati utilizzare.

Puoi anche usare solo una MultiMap , anche se non mi piace l'idea di duplicare le chiavi.


Grazie! L'utilizzo ha TreeMap<String, ArrayList<MyClass>>risolto le mie esigenze di chiavi duplicate.
Joe

10

Se vuoi iterare su un elenco di coppie chiave-valore (come hai scritto nel commento), allora un elenco o un array dovrebbe essere migliore. Innanzitutto combina chiavi e valori:

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

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

}

Sostituisci Class1 e Class2 con i tipi che desideri utilizzare per chiavi e valori.

Ora puoi inserirli in un array o in un elenco e scorrere su di essi:

Pair[] pairs = new Pair[10];
...
for (Pair pair : pairs)
{
   ...
}

Come implementerei add () o put (). Non voglio il numero hardcore di dimensioni.
Amit Kumar Gupta

2
In questo caso usa una lista. Il secondo esempio cambia in List <Pair> pair = new List <Pair> (); Il ciclo for rimane lo stesso. Puoi aggiungere una coppia con questo comando: pair.add (pair);
Mnementh

Questa è probabilmente la migliore risposta ad essere onesti.
PaulBGD

6

Questo problema può essere risolto con un elenco di voci della mappa List<Map.Entry<K,V>>. Non è necessario utilizzare né librerie esterne né nuove implementazioni di Map. È possibile creare una voce di mappa in questo modo: Map.Entry<String, Integer> entry = new AbstractMap.SimpleEntry<String, Integer>("key", 1);



3

Impara dai miei errori ... per favore non implementarlo da solo. Guava multimap è la strada da percorrere.

Un miglioramento comune richiesto nelle mappe multiple consiste nel non consentire le coppie chiave-valore duplicate.

L'implementazione / modifica di questo in una tua implementazione può essere fastidioso.

In Guava è semplice come:

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

ArrayListMultimap<String, Integer> allow_dupe_key_plus_val = ArrayListMultimap.create();

2

Avevo una variante leggermente diversa di questo problema: era necessario associare due valori diversi alla stessa chiave. Solo pubblicandolo qui nel caso in cui aiuti gli altri, ho introdotto una HashMap come valore:

/* @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 );
        }
}

Nel codice precedente la chiave frameID viene letta dalla prima stringa di un file di input in ogni riga, il valore per frameTypeHash viene costruito dividendo la riga rimanente ed è stato memorizzato come oggetto String originariamente, per un periodo di tempo il file ha iniziato ad avere più righe ( con valori diversi) associata alla stessa chiave frameID, quindi frameTypeHash è stato sovrascritto con l'ultima riga come valore. Ho sostituito l'oggetto String con un altro oggetto HashMap come campo del valore, questo ha aiutato a mantenere un'unica chiave per la mappatura di valori diversi.


2

Non sono richieste librerie fantasiose. Le mappe sono definite da una chiave univoca, quindi non piegarle, usa un elenco. I flussi sono potenti.

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

E questo è tutto. Esempi di utilizzo:

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

Grazie @daspilker. Solo ora vedo la tua modifica. Sono contento di vedere qualcuno che trova il mio frammento degno se viene modificato.
Gabrial David

1
 1, Map<String, List<String>> map = new HashMap<>();

questa soluzione dettagliata presenta diversi inconvenienti ed è soggetta a errori. Ciò implica che dobbiamo creare un'istanza di una raccolta per ogni valore, verificarne la presenza prima di aggiungere o rimuovere un valore, eliminarlo manualmente quando non sono rimasti valori, eccetera.

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

java-map-duplicate-keys


1

che dire di un simile impianto 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

Potresti anche spiegare il contesto per il quale stai cercando di implementare una mappa con chiavi duplicate? Sono sicuro che potrebbe esserci una soluzione migliore. Le mappe hanno lo scopo di mantenere chiavi univoche per una buona ragione. Anche se davvero volessi farlo; puoi sempre estendere la classe scrivendo una semplice classe di mappa personalizzata che ha una funzione di mitigazione delle collisioni e ti consentirebbe di mantenere più voci con le stesse chiavi.

Nota: è necessario implementare la funzione di mitigazione delle collisioni in modo tale che le chiavi in ​​conflitto vengano convertite in un insieme univoco "sempre". Qualcosa di semplice come aggiungere la chiave con il codice hash dell'oggetto o qualcosa del genere?


1
Il contesto è che pensavo che le chiavi fossero uniche, ma si scopre che potrebbe non esserlo. Non voglio rifattorizzare tutto poiché non verrà utilizzato spesso.
IAdapter

voglio convertire un piccolo file XML in hashmap come il tipo di dati. Solo il problema è che la struttura del file XML non è stata risolta
Amit Kumar Gupta

0

giusto per essere completo, Apache Commons Collections ha anche una MultiMap . Lo svantaggio ovviamente è che Apache Commons non utilizza i generici.


1
Notare che la loro MultiMap implementa Map ma rompe i contratti dei metodi Map. Questo mi infastidisce.
Kevin Bourrillion

0

Con un po 'di hack puoi usare HashSet con chiavi duplicate. ATTENZIONE: questo dipende fortemente dall'implementazione di 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

Se sono presenti chiavi duplicate, una chiave può corrispondere a più di un valore. La soluzione ovvia è mappare la chiave a un elenco di questi valori.

Ad esempio in 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

Ho usato questo:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.