268

Are there any dictionary classes in the .NET base class library which allow duplicate keys to be used? The only solution I've found is to create, for example, a class like:

Dictionary<string, List<object>>

But this is quite irritating to actually use. In Java, I believe a MultiMap accomplishes this, but cannot find an analog in .NET.

Mechanical snail
  • 26,499
  • 14
  • 83
  • 107
  • 4
    How is this duplicate key, it's duplicate values(the List), right? – Shamim Hafiz Dec 18 '10 at 05:36
  • 3
    @ShamimHafiz, no, the values need not be duplicates. If you have to store duplicates `{ a, 1 }` and `{ a, 2 }` in a hash table where `a` being the key, one alternative is to have `{ a, [1, 2] }`. – nawfal Jun 19 '14 at 08:27
  • 3
    Actually, I believe what is really wanted here is a collection where each key can map to one or more values. I think that the expression "duplicate keys" doesn't really convey this. – DavidRR Aug 06 '14 at 01:25
  • 1
    For future reference, you should consider keeping 1 key just adding the values to it instead of adding same keys over and over. – Ya Wang Aug 28 '15 at 15:38
  • If both keys and values are strings there is [NameValueCollection](https://msdn.microsoft.com/en-us/library/system.collections.specialized.namevaluecollection.aspx) (which can associate multiple string values with each string key). – AnorZaken Apr 03 '16 at 16:54
  • Arriving at this question ~13 years in the future, I think @YaWang has a good point. If you're facing a problem where you need to store multiple values that may have the same key, then you should consider an implementation where you only store that single key, but map it to an array of sorts of the values. For example, I needed to map duplicate keys to individual arrays of strings. Instead, I'm going to use a Dictionary of strings (keys) mapped to a List of string[] arrays as values. When I have a duplicate key, I'll simply add that value to the list of pre-existing values. – TGP1994 Feb 21 '21 at 22:03

24 Answers24

232

If you're using .NET 3.5, use the Lookup class.

EDIT: You generally create a Lookup using Enumerable.ToLookup. This does assume that you don't need to change it afterwards - but I typically find that's good enough.

If that doesn't work for you, I don't think there's anything in the framework which will help - and using the dictionary is as good as it gets :(

Jon Skeet
  • 1,261,211
  • 792
  • 8,724
  • 8,929
180

The List class actually works quite well for key/value collections containing duplicates where you would like to iterate over the collection. Example:

List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>();

// add some values to the collection here

for (int i = 0;  i < list.Count;  i++)
{
    Print(list[i].Key, list[i].Value);
}
John Saunders
  • 157,405
  • 24
  • 229
  • 388
  • 34
    This solution works functionally, but the implementation of List has no knowledge of the key or value and cant optimize searching for keys at all – Spencer Rose Feb 28 '13 at 03:02
43

Here is one way of doing this with List< KeyValuePair< string, string > >

public class ListWithDuplicates : List<KeyValuePair<string, string>>
{
    public void Add(string key, string value)
    {
        var element = new KeyValuePair<string, string>(key, value);
        this.Add(element);
    }
}

var list = new ListWithDuplicates();
list.Add("k1", "v1");
list.Add("k1", "v2");
list.Add("k1", "v3");

foreach(var item in list)
{
    string x = string.format("{0}={1}, ", item.Key, item.Value);
}

Outputs k1=v1, k1=v2, k1=v3

Hector Correa
  • 24,933
  • 7
  • 55
  • 69
23

If you are using strings as both the keys and the values, you can use System.Collections.Specialized.NameValueCollection, which will return an array of string values via the GetValues(string key) method.

Matt
  • 29,607
  • 4
  • 31
  • 33
  • 8
    NameValueCollection does not allow multiple keys. – Krisztián Balla Apr 03 '15 at 10:15
  • @Jenny O'Reilly: Sure, you can add duplicate keys. – isHuman Jan 05 '16 at 12:51
  • Strictly speaking @JennyO'Reilly is correct, as the remarks on the linked MSDN page clearly states: _this class stores multiple string values under a single key_. – Ronald Mar 15 '17 at 15:57
  • It will allow but will return multiple values, I tried using index and key. – user6121177 Oct 06 '17 at 20:51
  • Note that `NameValueCollection` [cannot be deserailized by `System.Text.Json`](https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-supported-collection-types?pivots=dotnet-5-0#systemcollectionsspecialized-namespace). – somethingRandom May 15 '21 at 08:06
19

I just came across the PowerCollections library which includes, among other things, a class called MultiDictionary. This neatly wraps this type of functionality.

14

Very important note regarding use of Lookup:

You can create an instance of a Lookup(TKey, TElement) by calling ToLookup on an object that implements IEnumerable(T)

There is no public constructor to create a new instance of a Lookup(TKey, TElement). Additionally, Lookup(TKey, TElement) objects are immutable, that is, you cannot add or remove elements or keys from a Lookup(TKey, TElement) object after it has been created.

(from MSDN)

I'd think this would be a show stopper for most uses.

Adriano Carneiro
  • 53,285
  • 12
  • 85
  • 120
TheSoftwareJedi
  • 32,535
  • 19
  • 103
  • 147
  • 6
    I can think of very few uses where this would be a show stopper. But then, I think immutable objects are great. – Joel Mueller May 23 '11 at 16:35
  • 2
    @JoelMueller I can think of a lot of cases where this is a show stopper. Having to re-create a dictionary to add an item is not a particularly efficient solution... – reirab Dec 02 '14 at 20:16
12

I think something like List<KeyValuePair<object, object>> would do the Job.

MADMap
  • 3,004
  • 3
  • 23
  • 30
  • How do you look something up in that list by it's key? – Wayne Bloss Sep 28 '08 at 17:00
  • You have to iterate through it: but I was not aware of the LookUp-Class of .NET 3.5: maybe this is more useful for searching in it's content. – MADMap Sep 28 '08 at 17:20
  • 2
    @wizlib: The only way is to loop through the list, which is not nearly as efficient as hashing. -1 – petr k. Sep 28 '08 at 17:21
  • @petrk. That really depends on what your data is. I used this implementation because I had very few unique keys and didn't want to incur the overhead of hash collisions. +1 – Evan M Jun 11 '12 at 16:13
9

If you are using >= .NET 4 then you can use Tuple Class:

// declaration
var list = new List<Tuple<string, List<object>>>();

// to add an item to the list
var item = Tuple<string, List<object>>("key", new List<object>);
list.Add(item);

// to iterate
foreach(var i in list)
{
    Console.WriteLine(i.Item1.ToString());
}
8

It's easy enough to "roll your own" version of a dictionary that allows "duplicate key" entries. Here is a rough simple implementation. You might want to consider adding support for basically most (if not all) on IDictionary<T>.

public class MultiMap<TKey,TValue>
{
    private readonly Dictionary<TKey,IList<TValue>> storage;

    public MultiMap()
    {
        storage = new Dictionary<TKey,IList<TValue>>();
    }

    public void Add(TKey key, TValue value)
    {
        if (!storage.ContainsKey(key)) storage.Add(key, new List<TValue>());
        storage[key].Add(value);
    }

    public IEnumerable<TKey> Keys
    {
        get { return storage.Keys; }
    }

    public bool ContainsKey(TKey key)
    {
        return storage.ContainsKey(key);
    }

    public IList<TValue> this[TKey key]
    {
        get
        {
            if (!storage.ContainsKey(key))
                throw new KeyNotFoundException(
                    string.Format(
                        "The given key {0} was not found in the collection.", key));
            return storage[key];
        }
    }
}

A quick example on how to use it:

const string key = "supported_encodings";
var map = new MultiMap<string,Encoding>();
map.Add(key, Encoding.ASCII);
map.Add(key, Encoding.UTF8);
map.Add(key, Encoding.Unicode);

foreach (var existingKey in map.Keys)
{
    var values = map[existingKey];
    Console.WriteLine(string.Join(",", values));
}
ChristopheD
  • 100,699
  • 26
  • 154
  • 173
5

Since the new C# (I belive it's from 7.0), you can also do something like this:

var duplicatedDictionaryExample = new List<(string Key, string Value)> { ("", "") ... }

and you are using it as a standard List, but with two values named whatever you want

foreach(var entry in duplicatedDictionaryExample)
{ 
    // do something with the values
    entry.Key;
    entry.Value;
}
reniasa
  • 73
  • 1
  • 7
4

In answer to the original question. Something like Dictionary<string, List<object>> is implemented in a class called MultiMap in The Code Project.

You could find more info to the below link : http://www.codeproject.com/KB/cs/MultiKeyDictionary.aspx

radu florescu
  • 4,199
  • 10
  • 56
  • 90
Dan
  • 41
  • 1
3

The NameValueCollection supports multiple string values under one key (which is also a string), but it is the only example I am aware of.

I tend to create constructs similar to the one in your example when I run into situations where I need that sort of functionality.

ckramer
  • 9,285
  • 1
  • 21
  • 38
3

When using the List<KeyValuePair<string, object>> option, you could use LINQ to do the search:

List<KeyValuePair<string, object>> myList = new List<KeyValuePair<string, object>>();
//fill it here
var q = from a in myList Where a.Key.Equals("somevalue") Select a.Value
if(q.Count() > 0){ //you've got your value }
Mahm00d
  • 3,577
  • 7
  • 36
  • 76
Greg
  • 31
  • 1
2

The way I use is just a

Dictionary<string, List<string>>

This way you have a single key holding a list of strings.

Example:

List<string> value = new List<string>();
if (dictionary.Contains(key)) {
     value = dictionary[key];
}
value.Add(newValue);
Soufiane Hassou
  • 15,973
  • 2
  • 36
  • 71
2

Do you mean congruent and not an actual duplicate? Otherwise a hashtable wouldn't be able to work.

Congruent means that two separate keys can hash to the equivalent value, but the keys aren't equal.

For example: say your hashtable's hash function was just hashval = key mod 3. Both 1 and 4 map to 1, but are different values. This is where your idea of a list comes into play.

When you need to lookup 1, that value is hashed to 1, the list is traversed until the Key = 1 is found.

If you allowed for duplicate keys to be inserted, you wouldn't be able to differentiate which keys map to which values.

Nicholas Mancuso
  • 11,065
  • 6
  • 42
  • 46
  • 3
    A hash table already handles keys which happen to hash to the same value (this is called a collision). I am referring to a situation where you want to map multiple values to the same exact key. –  Sep 28 '08 at 19:21
2

You can create your own dictionary wrapper, something like this one, as a bonus it supports null value as a key:

/// <summary>
/// Dictionary which supports duplicates and null entries
/// </summary>
/// <typeparam name="TKey">Type of key</typeparam>
/// <typeparam name="TValue">Type of items</typeparam>
public class OpenDictionary<TKey, TValue>
{
    private readonly Lazy<List<TValue>> _nullStorage = new Lazy<List<TValue>>(
        () => new List<TValue>());

    private readonly Dictionary<TKey, List<TValue>> _innerDictionary =
        new Dictionary<TKey, List<TValue>>();

    /// <summary>
    /// Get all entries
    /// </summary>
    public IEnumerable<TValue> Values =>
        _innerDictionary.Values
            .SelectMany(x => x)
            .Concat(_nullStorage.Value);

    /// <summary>
    /// Add an item
    /// </summary>
    public OpenDictionary<TKey, TValue> Add(TKey key, TValue item)
    {
        if (ReferenceEquals(key, null))
            _nullStorage.Value.Add(item);
        else
        {
            if (!_innerDictionary.ContainsKey(key))
                _innerDictionary.Add(key, new List<TValue>());

            _innerDictionary[key].Add(item);
        }

        return this;
    }

    /// <summary>
    /// Remove an entry by key
    /// </summary>
    public OpenDictionary<TKey, TValue> RemoveEntryByKey(TKey key, TValue entry)
    {
        if (ReferenceEquals(key, null))
        {
            int targetIdx = _nullStorage.Value.FindIndex(x => x.Equals(entry));
            if (targetIdx < 0)
                return this;

            _nullStorage.Value.RemoveAt(targetIdx);
        }
        else
        {
            if (!_innerDictionary.ContainsKey(key))
                return this;

            List<TValue> targetChain = _innerDictionary[key];
            if (targetChain.Count == 0)
                return this;

            int targetIdx = targetChain.FindIndex(x => x.Equals(entry));
            if (targetIdx < 0)
                return this;

            targetChain.RemoveAt(targetIdx);
        }

        return this;
    }

    /// <summary>
    /// Remove all entries by key
    /// </summary>
    public OpenDictionary<TKey, TValue> RemoveAllEntriesByKey(TKey key)
    {
        if (ReferenceEquals(key, null))
        {
            if (_nullStorage.IsValueCreated)
                _nullStorage.Value.Clear();
        }       
        else
        {
            if (_innerDictionary.ContainsKey(key))
                _innerDictionary[key].Clear();
        }

        return this;
    }

    /// <summary>
    /// Try get entries by key
    /// </summary>
    public bool TryGetEntries(TKey key, out IReadOnlyList<TValue> entries)
    {
        entries = null;

        if (ReferenceEquals(key, null))
        {
            if (_nullStorage.IsValueCreated)
            {
                entries = _nullStorage.Value;
                return true;
            }
            else return false;
        }
        else
        {
            if (_innerDictionary.ContainsKey(key))
            {
                entries = _innerDictionary[key];
                return true;
            }
            else return false;
        }
    }
}

The sample of usage:

var dictionary = new OpenDictionary<string, int>();
dictionary.Add("1", 1); 
// The next line won't throw an exception; 
dictionary.Add("1", 2);

dictionary.TryGetEntries("1", out List<int> result); 
// result is { 1, 2 }

dictionary.Add(null, 42);
dictionary.Add(null, 24);
dictionary.TryGetEntries(null, out List<int> result); 
// result is { 42, 24 }
  • Can you throw some explanation around what your code does, how it answers the question, and some example usages? – Enigmativity Oct 26 '19 at 23:56
  • @Enigmativity, it does exactly what was asked, the question was to suggest some dictionary which supports duplicates, so I offered to create a wrapper around .net dictionary which will support this functionality and as an example created such wrapper, as a bonus it can handle null as a key (even if it's a bad practise, for sure) The usage is quite simple: `var dictionary = new OpenDictionary(); dictionary.Add("1", 1); // The next line won't throw an exception; dictionary.Add("1", 2); dictionary.TryGetEntries("1", out List result); // result is { 1, 2 }` – Alexander Tolstikov Oct 27 '19 at 17:12
  • Can you add the detail to your answer? – Enigmativity Oct 27 '19 at 22:54
  • @Enigmativity, if you mean to the original answer, then sure – Alexander Tolstikov Oct 29 '19 at 07:49
1

I changed @Hector Correa 's answer into an extension with generic types and also added a custom TryGetValue to it.

  public static class ListWithDuplicateExtensions
  {
    public static void Add<TKey, TValue>(this List<KeyValuePair<TKey, TValue>> collection, TKey key, TValue value)
    {
      var element = new KeyValuePair<TKey, TValue>(key, value);
      collection.Add(element);
    }

    public static int TryGetValue<TKey, TValue>(this List<KeyValuePair<TKey, TValue>> collection, TKey key, out IEnumerable<TValue> values)
    {
      values = collection.Where(pair => pair.Key.Equals(key)).Select(pair => pair.Value);
      return values.Count();
    }
  }
Slate
  • 1,401
  • 21
  • 27
1

I stumbled across this post in search of the same answer, and found none, so I rigged up a bare-bones example solution using a list of dictionaries, overriding the [] operator to add a new dictionary to the list when all others have a given key(set), and return a list of values (get).
It's ugly and inefficient, it ONLY gets/sets by key, and it always returns a list, but it works:

 class DKD {
        List<Dictionary<string, string>> dictionaries;
        public DKD(){
            dictionaries = new List<Dictionary<string, string>>();}
        public object this[string key]{
             get{
                string temp;
                List<string> valueList = new List<string>();
                for (int i = 0; i < dictionaries.Count; i++){
                    dictionaries[i].TryGetValue(key, out temp);
                    if (temp == key){
                        valueList.Add(temp);}}
                return valueList;}
            set{
                for (int i = 0; i < dictionaries.Count; i++){
                    if (dictionaries[i].ContainsKey(key)){
                        continue;}
                    else{
                        dictionaries[i].Add(key,(string) value);
                        return;}}
                dictionaries.Add(new Dictionary<string, string>());
                dictionaries.Last()[key] =(string)value;
            }
        }
    }
skolima
  • 29,517
  • 26
  • 108
  • 146
Sintrinsic
  • 11
  • 2
0

This is a tow way Concurrent dictionary I think this will help you:

public class HashMapDictionary<T1, T2> : System.Collections.IEnumerable
{
    private System.Collections.Concurrent.ConcurrentDictionary<T1, List<T2>> _keyValue = new System.Collections.Concurrent.ConcurrentDictionary<T1, List<T2>>();
    private System.Collections.Concurrent.ConcurrentDictionary<T2, List<T1>> _valueKey = new System.Collections.Concurrent.ConcurrentDictionary<T2, List<T1>>();

    public ICollection<T1> Keys
    {
        get
        {
            return _keyValue.Keys;
        }
    }

    public ICollection<T2> Values
    {
        get
        {
            return _valueKey.Keys;
        }
    }

    public int Count
    {
        get
        {
            return _keyValue.Count;
        }
    }

    public bool IsReadOnly
    {
        get
        {
            return false;
        }
    }

    public List<T2> this[T1 index]
    {
        get { return _keyValue[index]; }
        set { _keyValue[index] = value; }
    }

    public List<T1> this[T2 index]
    {
        get { return _valueKey[index]; }
        set { _valueKey[index] = value; }
    }

    public void Add(T1 key, T2 value)
    {
        lock (this)
        {
            if (!_keyValue.TryGetValue(key, out List<T2> result))
                _keyValue.TryAdd(key, new List<T2>() { value });
            else if (!result.Contains(value))
                result.Add(value);

            if (!_valueKey.TryGetValue(value, out List<T1> result2))
                _valueKey.TryAdd(value, new List<T1>() { key });
            else if (!result2.Contains(key))
                result2.Add(key);
        }
    }

    public bool TryGetValues(T1 key, out List<T2> value)
    {
        return _keyValue.TryGetValue(key, out value);
    }

    public bool TryGetKeys(T2 value, out List<T1> key)
    {
        return _valueKey.TryGetValue(value, out key);
    }

    public bool ContainsKey(T1 key)
    {
        return _keyValue.ContainsKey(key);
    }

    public bool ContainsValue(T2 value)
    {
        return _valueKey.ContainsKey(value);
    }

    public void Remove(T1 key)
    {
        lock (this)
        {
            if (_keyValue.TryRemove(key, out List<T2> values))
            {
                foreach (var item in values)
                {
                    var remove2 = _valueKey.TryRemove(item, out List<T1> keys);
                }
            }
        }
    }

    public void Remove(T2 value)
    {
        lock (this)
        {
            if (_valueKey.TryRemove(value, out List<T1> keys))
            {
                foreach (var item in keys)
                {
                    var remove2 = _keyValue.TryRemove(item, out List<T2> values);
                }
            }
        }
    }

    public void Clear()
    {
        _keyValue.Clear();
        _valueKey.Clear();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _keyValue.GetEnumerator();
    }
}

examples:

public class TestA
{
    public int MyProperty { get; set; }
}

public class TestB
{
    public int MyProperty { get; set; }
}

            HashMapDictionary<TestA, TestB> hashMapDictionary = new HashMapDictionary<TestA, TestB>();

            var a = new TestA() { MyProperty = 9999 };
            var b = new TestB() { MyProperty = 60 };
            var b2 = new TestB() { MyProperty = 5 };
            hashMapDictionary.Add(a, b);
            hashMapDictionary.Add(a, b2);
            hashMapDictionary.TryGetValues(a, out List<TestB> result);
            foreach (var item in result)
            {
                //do something
            }
Ali Yousefi
  • 2,099
  • 2
  • 26
  • 40
0

i use this simple class:

public class ListMap<T,V> : List<KeyValuePair<T, V>>
{
    public void Add(T key, V value) {
        Add(new KeyValuePair<T, V>(key, value));
    }

    public List<V> Get(T key) {
        return FindAll(p => p.Key.Equals(key)).ConvertAll(p=> p.Value);
    }
}

usage:

var fruits = new ListMap<int, string>();
fruits.Add(1, "apple");
fruits.Add(1, "orange");
var c = fruits.Get(1).Count; //c = 2;
John
  • 1,153
  • 13
  • 10
-1

U can define a method to building a Compound string key every where u want to using dictionary u must using this method to build your key for example:

private string keyBuilder(int key1, int key2)
{
    return string.Format("{0}/{1}", key1, key2);
}

for using:

myDict.ContainsKey(keyBuilder(key1, key2))
-3

Duplicate keys break the entire contract of the Dictionary. In a dictionary each key is unique and mapped to a single value. If you want to link an object to an arbitrary number of additional objects, the best bet might be something akin to a DataSet (in common parlance a table). Put your keys in one column and your values in the other. This is significantly slower than a dictionary, but that's your tradeoff for losing the ability to hash the key objects.

Ryan
  • 9,742
  • 7
  • 40
  • 56
  • 5
    Isn't the whole point of using a Dictionary for the performance gain? Using a DataSet seems no better than a List>. – Doug Feb 24 '11 at 16:51
-4

Also this is possible:

Dictionary<string, string[]> previousAnswers = null;

This way, we can have unique keys. Hope this works for you.

Misa Lazovic
  • 2,765
  • 9
  • 30
  • 38
shan
  • 1
  • 2
-10

You can add same keys with different case like:

key1
Key1
KEY1
KeY1
kEy1
keY1

I know is dummy answer, but worked for me.

  • 5
    No, it did not work for you. Dictionaries allow very fast lookup - also classified as *O(1)* - by key, You lose that when you add your differently cased multiple keys, because how do you retrieve them? Try every upper/lower case combination? No matter how you do it, performance will not be that of normal, single dictionary lookup. That's in addition to other, more obvious shortcomings like the limitation of values per key, depending on the number of upcaseable characters in the key. – Evgeniy Berezovsky Jul 02 '15 at 23:31