FlyingFoX
FlyingFoX

Reputation: 3509

Add Element to Dictionary of Lists

I have

Dictionary<string, List<int>> myDict = new Dictionary<string, List<int>>();

and at some points I want to add numbers to myDict for a specific Dictionary key.

I am currently doing

if (!myDict.ContainsKey(newKey)){
    myDict[newKey] = new List<int>();
}
myDict[newKey].Add(myNumber);

but that seems to be error prone to forgetting the ContainsKey check at some point. I have searched for a way to make Dictionaries return a new List in case myDict["entry"] doesn't exist yet, but I couldn't find anything.

Upvotes: 2

Views: 766

Answers (6)

aiapatag
aiapatag

Reputation: 3430

You can actually use the others' suggestions. By encapsulating the access in a method or even using ConcurrentDictionary.

But for me, I would have a custom dictionary so you can actually implement what myDict["entry"] does if it did not see an element.

Good thing with this is you have full control on how you would like this dictionary to behave.

class MyCustomDictionary<TKey, TValue> : IDictionary<TKey, TValue>
    where TValue : class, new()
{
    private Dictionary<TKey, TValue> _dictionary;

    public MyCustomDictionary()
    {
        _dictionary = new Dictionary<TKey, TValue>();
    }

    public TValue this[TKey key] // this is what's important
    {
        get
        {
            TValue val;
            if (!_dictionary.TryGetValue(key, out val)) // if there is no element for that key, add a new element and return it
            {
                _dictionary.Add(key, new TValue());
                return _dictionary[key];
            }
            else // else return the found element
            {
                return val;
            }
        }
        set
        {
            _dictionary[key] = value;
        }
    }

    public void Add(TKey key, TValue value)
    {
        _dictionary.Add(key, value);
    }

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

    public ICollection<TKey> Keys
    {
        get { return _dictionary.Keys; }
    }

    public bool Remove(TKey key)
    {
        return _dictionary.Remove(key);
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        return _dictionary.TryGetValue(key, out value);
    }

    public ICollection<TValue> Values
    {
        get { return _dictionary.Values; }
    }

    public void Add(KeyValuePair<TKey, TValue> item)
    {
        _dictionary.Add(item.Key, item.Value);
    }

    public void Clear()
    {
        _dictionary.Clear();
    }

    public bool Contains(KeyValuePair<TKey, TValue> item)
    {
        return _dictionary.Contains(item);
    }

    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
    {
        _dictionary.ToList().CopyTo(array, arrayIndex); // do you need this? you can leave this :)
    }

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

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool Remove(KeyValuePair<TKey, TValue> item)
    {
        return _dictionary.Remove(item.Key);
    }

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        return _dictionary.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return _dictionary.GetEnumerator();
    }
}

Then you use it like:

MyCustomDictionary<string, List<int>> myCustomDict = new MyCustomDictionary<int, List<int>>();
// return a new List of int
var someElementThatIsNotFound = myCustomDict["keyThatIsNonExistent"];

Upvotes: 1

Moo-Juice
Moo-Juice

Reputation: 38825

Lots of good answers already. I implemented an extension method for this exact reason:

    public static TVALUE GetOrSet<TKEY, TVALUE>(this IDictionary<TKEY, TVALUE> self,
                                                TKEY key,
                                                Func<TVALUE> defaultValue)
    {
        TVALUE value;
        if (!self.TryGetValue(key, out value))
        {
            value = defaultValue();
            self[key] = value;
        }
        return value;
    }   // eo GetOrSet

Note that it takes a function to assign the value if it is not present. Either way, the value will be returned. Usage:

var dict = new Dictionary<string, List<int>>();

List<int> ints = dict.GetOrSet("list1", () => return new List<int>());
ints.Add(1);

If you're not referencing it again, you could potentially be less verbose:

dict.GetOrSet("list1", () => return new List<int>()).Add(1);

Upvotes: 0

Chris Sinclair
Chris Sinclair

Reputation: 23208

Here's a relatively simple implementation of the LazyLookup example I mentioned. It only implements IEnumerable out of brevity/simplicity to answer the question.

Essentially, upon accessing an index, it will make sure it has already been initialized to a new instance of the List<T> class.

public class LazyLookup<TKey, TValue> : IEnumerable<List<TValue>>
{
   private readonly Dictionary<TKey, List<TValue>> CachedEntries;
   private readonly Func<List<TValue>> LazyListCreator;

    public LazyLookup()
        : this(() => new List<TValue>())
    {

    }
    public LazyLookup(Func<List<TValue>> lazyListCreator)
    {
        this.LazyListCreator = lazyListCreator;
        this.CachedEntries = new Dictionary<TKey, List<TValue>>();
    }

    public List<TValue> this[TKey key]
    {
        get
        {
            return GetOrCreateValue(key);
        }
    }

    private List<TValue> GetOrCreateValue(TKey key)
    {
        List<TValue> returnValue;
        if (!CachedEntries.TryGetValue(key, out returnValue))
        {
            returnValue = LazyListCreator();
            CachedEntries[key] = returnValue;
        }
        return returnValue;
    }

    public IEnumerator<List<TValue>> GetEnumerator()
    {
        return CachedEntries.Values.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

With some usage:

var lazyLookup = new LazyLookup<string, int>();

lazyLookup["nocheck"].Add(9001);

//outputs 9001
Console.WriteLine(lazyLookup["nocheck"][0]);

//outputs 0 as it's a newly initialized list
Console.WriteLine(lazyLookup["someOtherLookup"].Count); 

At this point, you could update it to be threadsafe (as GetOrCreateValue currently is not threadsafe), or generalize it so it doesn't assume it's of List<T> but any type, or extend it to implement the full IDictionary<TKey, TValue> interface. But at minimum, if the above pattern you posted is used often, you may consider swapping direct usage of the dictionaries with some encapsulation which trivializes the task for you and eliminates code duplication.

Upvotes: 2

Dmitrii Bychenko
Dmitrii Bychenko

Reputation: 186688

You can use TryGetValue method: if there's the key in the dictionary you should just add the value into the list; otherwise you should add a list with a value:

List<int> list

if (myDict.TryGetValue(newKey, out list))
  list.Add(myNumber);
else
  myDict.Add(newKey, new List<int>() { myNumber });

Upvotes: 0

Baldrick
Baldrick

Reputation: 11840

If you use ConcurrentDictionary<T>, you can do this:

 myDict.GetOrAdd(newKey, new List<int>()).Add(myNumber);

Upvotes: 1

Tim Schmelter
Tim Schmelter

Reputation: 460108

You can use TryGetValue:

List<int> list;
if(!myDict.TryGetValue(newKey, out list))
{
    list = new List<int>();
    myDict.Add(newKey, list);
}
list.Add(myNumber);

If the Dictionary is a field i would encapsulate the acces in a method:

Dictionary<string, List<int>> myDict = new Dictionary<string, List<int>>();

public void AddNumber(string key, int value)
{
     List<int> list;
     if(!myDict.TryGetValue(key, out list))
     {
         list = new List<int>();
         myDict.Add(key, list);
     }
     list.Add(value);
}

Upvotes: 2

Related Questions