136

I want to have a map with duplicate keys.

I know there are many map implementations (Eclipse shows me about 50), so I bet there must be one that allows this. I know it's easy to write your own map that does this, but I would rather use some existing solution.

Maybe something in commons-collections or google-collections?

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
IAdapter
  • 62,595
  • 73
  • 179
  • 242
  • 4
    How should this work? If you ask for a value associated with a key, and this key exists multiple times in the map, which value should be returned? – Mnementh Jun 30 '09 at 10:37
  • get could throw exception, i need this map only for iteration. – IAdapter Jun 30 '09 at 10:41
  • 6
    If you only need it for iteration, why do you need a map in the first place? Use a list of pairs or something... – Tal Pressman Jun 30 '09 at 10:46
  • 3
    Because my whole program already works with Map and now i discovered that its possible for data to have duplicate keys. If using Map different way would be so wrong we would only have 5 implementations of Map and not 50+. – IAdapter Jun 30 '09 at 11:03

20 Answers20

94

You are searching for a multimap, and indeed both commons-collections and Guava have several implementations for that. Multimaps allow for multiple keys by maintaining a collection of values per key, i.e. you can put a single object into the map, but you retrieve a collection.

If you can use Java 5, I would prefer Guava's Multimap as it is generics-aware.

Grzegorz Rożniecki
  • 27,415
  • 11
  • 90
  • 112
nd.
  • 8,699
  • 2
  • 32
  • 42
  • 3
    Also, this Multimap doesn't pretend to be a Map the way the apache one does. – Kevin Bourrillion Nov 06 '09 at 10:16
  • 7
    Note that Google Collections has been superseded by Guava, so here's the link to the Guava version of MultiMap: https://code.google.com/p/guava-libraries/wiki/NewCollectionTypesExplained#Multimap – Josh Glover Aug 14 '12 at 12:35
  • However, Multimap is not fully serializable, it has transient members which renders a deserialized instance useless – dschulten Jul 24 '14 at 16:05
  • @dschulten Well, Multimap is an interface and you are not specifying which implementation you mean. `com.google.common.collect.HashMultimap` has `readObject`/`writeObject` methods, as do ArrayListMultimap and Immutable{List,Set}Multimap. I would consider a useless deserialized instance a bug worth reporting. – nd. Jul 25 '14 at 06:02
  • 1
    Apache Collections 4.0 supports generics https://commons.apache.org/proper/commons-collections/javadocs/api-release/org/apache/commons/collections4/MultiMap.html – kervin Jun 07 '15 at 15:53
  • HashMultimap does not allow duplicate keys. https://google.github.io/guava/releases/21.0/api/docs/com/google/common/collect/HashMultimap.html – Umut Uzun May 31 '18 at 13:56
  • @umut-uzun HashMultimap does not allow duplicate key-value pairs (i.e. it treats its values as a set), it still does implement the Multimap interface and it allows for multiple keys (provided they have different values). If you want to store the same key-value pair multiple time, then use some other implementation such as ArrayListMultimap. – nd. Jun 04 '18 at 11:45
37

We don't need to depend on the Google Collections external library. You can simply implement the following Map:

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

Please make sure to fine tune the code.

antonpp
  • 2,333
  • 23
  • 28
user668943
  • 535
  • 1
  • 5
  • 8
  • 20
    You don't need to rely on Guava's Multimap, of course. It just eases your life, as you don't have to re-implement them, test them, etc. – PhiLho Jul 30 '13 at 12:51
  • This doesn't allow seamless iteration over all pairs. There are surely more shortcomings. I was about to suggest my solution which also requires one extra class, then saw @Mnementh's answer is just that. – Mark Jeronimus Apr 25 '16 at 18:46
  • 2
    writing basic code is not that clever always. Google is more likely to have better tests – senseiwu Apr 26 '16 at 08:28
29
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));

Output is:

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

Note: we need to import library files.

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

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

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

import org.apache.commons.collections.MultiMap;
import org.apache.commons.collections.map.MultiValueMap;
Hamza Rashid
  • 1,329
  • 15
  • 22
Issac Balaji
  • 1,441
  • 1
  • 13
  • 25
  • 2
    Good suggestion, since I'm using Spring in my project I ended up using the Spring flavor of MultiValueMap as mentioned in the docs [http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/MultiValueMap.html](http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/MultiValueMap.html) – ajup Oct 19 '16 at 20:50
19

You could simply pass an array of values for the value in a regular HashMap, thus simulating duplicate keys, and it would be up to you to decide what data to use.

You may also just use a MultiMap, although I do not like the idea of duplicate keys myself.

AlbertoPL
  • 11,479
  • 5
  • 49
  • 73
11

If you want iterate about a list of key-value-pairs (as you wrote in the comment), then a List or an array should be better. First combine your keys and values:

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

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

}

Replace Class1 and Class2 with the types you want to use for keys and values.

Now you can put them into an array or a list and iterate over them:

Pair[] pairs = new Pair[10];
...
for (Pair pair : pairs)
{
   ...
}
Mnementh
  • 50,487
  • 48
  • 148
  • 202
11

This problem can be solved with a list of map entry List<Map.Entry<K,V>>. We don't need to use neither external libraries nor new implementation of Map. A map entry can be created like this: Map.Entry<String, Integer> entry = new AbstractMap.SimpleEntry<String, Integer>("key", 1);

Thach Van
  • 1,381
  • 16
  • 19
7

[June, 2021]

  1. org.springframework.util.MultiValueMap

  2. commons.apache.org - org.apache.commons.collections4

Ravi Parekh
  • 5,253
  • 9
  • 46
  • 58
  • That class was deprecated. It is now called MultiValueMap. http://commons.apache.org/proper/commons-collections/javadocs/api-3.2.1/org/apache/commons/collections/MultiHashMap.html – Jotschi Aug 29 '13 at 12:58
  • Collections 4.0 supports generics https://commons.apache.org/proper/commons-collections/javadocs/api-release/org/apache/commons/collections4/MultiMap.html – kervin Jun 07 '15 at 15:55
4

Learn from my mistakes...please don't implement this on your own. Guava multimap is the way to go.

A common enhancement required in multimaps is to disallow duplicate keys-value pairs.

Implementing/changing this in a your implementation can be annoying.

In Guava its as simple as:

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

ArrayListMultimap<String, Integer> allow_dupe_key_plus_val = ArrayListMultimap.create();
Alexander Farber
  • 21,519
  • 75
  • 241
  • 416
frostbite
  • 628
  • 1
  • 14
  • 27
4

No fancy libs required. Maps are defined by a unique key, so dont bend them, use a list. Streams are mighty.

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

And thats it. Usage examples:

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
  • 41
  • 1
4

You can use a TreeMap with a custom Comparator in order to treat each key as unequal to the others. It would also preserve the insertion order in your map, just like a LinkedHashMap. So, the net result would be like a LinkedHashMap which allows duplicate keys!

This is a very simple implementation without the need of any third party dependencies or complications of MultiMaps.

import java.util.Map;
import java.util.TreeMap;

...
...

//Define a TreeMap with a custom Comparator
Map<Integer, String> map = new TreeMap<>((a, b) -> 1); // See notes 1 and 2

//Populate the map
map.put(1, "One");
map.put(3, "Three");
map.put(1, "One One");
map.put(7, "Seven");
map.put(2, "Two");
map.put(1, "One One One");
    
//Display the map entries:
map.entrySet().forEach(System.out::println);

//See note number 3 for the following:
Map<Integer, String> sortedTreeMap = map.entrySet().stream()
            .sorted(Map.Entry.comparingByKey())
            .collect(Collectors.toMap(
                Map.Entry::getKey, Map.Entry::getValue,
                (x, y) -> x, () -> new TreeMap<>((a, b) -> 1)
             ));
//Display the entries of this sorted TreeMap: 
sortedTreeMap.entrySet().forEach(System.out::println);

    
...

Notes:

  1. You can also use any positive integer in place of 1 in the comparator's definition here.
  2. If you use any negative integer instead, then it will reverse the insertion order in your map.
  3. If you also want to sort this map based on the keys (which is the default behavior of a TreeMap), then you may do this operation on the current map.
Mayank
  • 79
  • 6
2

I had a slightly different variant of this issue: It was required to associate two different values with same key. Just posting it here in case it helps others, I have introduced a HashMap as the value:

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

In the above code the key frameID is read from a input file's first string in each line, the value for frameTypeHash is constructed by splitting the remaining line and was stored as String object originally, over a period of time the file started having multiple lines (with different values) associated with same frameID key, so frameTypeHash was overwritten with last line as the value. I replaced the String object with another HashMap object as the value field, this helped in maintaining single key to different value mapping.

Suresh Vadali
  • 139
  • 1
  • 3
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;
        }
    }
daspilker
  • 8,154
  • 1
  • 35
  • 49
1
 1, Map<String, List<String>> map = new HashMap<>();

this verbose solution has multiple drawbacks and is prone to errors. It implies that we need to instantiate a Collection for every value, check for its presence before adding or removing a value, delete it manually when no values are left, etcetera.

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

java-map-duplicate-keys

Vikki
  • 1,897
  • 1
  • 17
  • 24
1

what about such a MultiMap impl?

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
  • 19
  • 7
0

Could you also explain the context for which you are trying to implement a map with duplicate keys? I am sure there could be a better solution. Maps are intended to keep unique keys for good reason. Though if you really wanted to do it; you can always extend the class write a simple custom map class which has a collision mitigation function and would enable you to keep multiple entries with same keys.

Note: You must implement collision mitigation function such that, colliding keys are converted to unique set "always". Something simple like, appending key with object hashcode or something?

Priyank
  • 14,231
  • 18
  • 78
  • 107
  • 1
    The context is that i thought that keys will be unique, but it turns out that the might not be. I dont want to refactor everything since it wont be used often. – IAdapter Jun 30 '09 at 11:05
  • i want to convert a small XML file into hashmap like datatype. Only the problem is structure of XML file is not fixed – Amit Kumar Gupta Oct 14 '10 at 13:32
0

just to be complete, Apache Commons Collections also has a MultiMap. The downside of course is that Apache Commons does not use Generics.

newacct
  • 119,665
  • 29
  • 163
  • 224
0

If there are duplicate keys then a key may correspond to more than one value. The obvious solution is to map the key to a list of these values.

For example 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
Alexander Farber
  • 21,519
  • 75
  • 241
  • 416
cyberthanasis
  • 415
  • 2
  • 4
0

With a bit hack you can use HashSet with duplicate keys. WARNING: this is heavily HashSet implementation dependant.

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
  • 964
  • 1
  • 8
  • 17
0

I used this:

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

Alex Arvanitidis
  • 4,403
  • 6
  • 26
  • 36
0

Just use simple Set with Pair. This Set will exclude pairs with the same key-value. Also you can iterate it.

val set = hashSetOf<Pair<String, String>>()
set.add(Pair("1", "a"))
set.add(Pair("1", "b"))
set.add(Pair("1", "b")) // Duplicate
set.add(Pair("2", "a"))
set.add(Pair("2", "b"))
set.forEach { pair -> println(pair) }

result: (1, a),(2, b),(1, b),(2, a)
Prilaga
  • 818
  • 10
  • 18