17

Is there a .NET analogue of Python's defaultdict? I find it useful to write short code, eg. counting frequencies:

>>> words = "to be or not to be".split()
>>> print words
['to', 'be', 'or', 'not', 'to', 'be']
>>> from collections import defaultdict
>>> frequencies = defaultdict(int)
>>> for word in words:
...     frequencies[word] += 1
... 
>>> print frequencies
defaultdict(<type 'int'>, {'not': 1, 'to': 2, 'or': 1, 'be': 2})

So ideally in C# I could write:

var frequencies = new DefaultDictionary<string,int>(() => 0);
foreach(string word in words)
{
    frequencies[word] += 1
}
Colonel Panic
  • 132,665
  • 89
  • 401
  • 465
  • 2
    What does the `defaultdict` do? I'm not familiar with python. Edit: It's just a dictionary that has a default value for the keys. You can subclass `Dictionary` and implement the functionality. – Dustin Kingen Mar 25 '13 at 19:02
  • 1
    Jon Skeet's answer may help: http://stackoverflow.com/a/2601501/1786606. – Vladimir Mar 25 '13 at 19:19

4 Answers4

14

Here's a simple implementation:

public class DefaultDictionary<TKey, TValue> : Dictionary<TKey, TValue> where TValue : new()
{
    public new TValue this[TKey key]
    {
        get
        {
            TValue val;
            if (!TryGetValue(key, out val))
            {
                val = new TValue();
                Add(key, val);
            }
            return val;
        }
        set { base[key] = value; }
    }
}

And how you would use it:

var dict = new DefaultDictionary<string, int>();
Debug.WriteLine(dict["foo"]);  // prints "0"
dict["bar"] = 5;
Debug.WriteLine(dict["bar"]);  // prints "5"

Or like this:

var dict = new DefaultDictionary<string, List<int>>();
dict["foo"].Add(1);
dict["foo"].Add(2);
dict["foo"].Add(3);
Jacob Krall
  • 28,341
  • 6
  • 66
  • 76
Daniel Jonsson
  • 3,261
  • 5
  • 45
  • 66
  • How do you make this work polymorphically. In other words, I do `IDictionary mydict = new DefaultDict()`. The `this[]` operator does not get invoked. – void.pointer Mar 18 '21 at 22:28
  • Suggested improvement to add null constrain on the generic key, such that it aligns with Dictionary. `public class DefaultDict : Dictionary where TValue : new() ` – schwartz Aug 04 '22 at 19:18
  • Any way of get this to work with a `string` value type? I get `'T' must be a non-abstract type with a public parameterless constructor in order to use it as 'TValue'`. – alelom Dec 10 '22 at 09:52
6

Something to get you started. I basically just changed the this indexer. Since I don't know the complete functionality of python's defaultdict I cannot improve it further. Your given example will work.

public class DefaultDictionary<TKey, TValue> : IDictionary<TKey,TValue>
{
    private readonly Func<TValue> _defaultSelector;
    private readonly Dictionary<TKey, TValue> _values = new Dictionary<TKey, TValue>();

    public DefaultDictionary()
        : this(() => default(TValue))
    {
    }

    public DefaultDictionary(Func<TValue> defaultSelector)
    {
        _defaultSelector = defaultSelector;
    }

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

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

    public void Add(KeyValuePair<TKey, TValue> item)
    {
        ((IDictionary<TKey,TValue>)_values).Add(item);
    }

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

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

    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
    {
        ((IDictionary<TKey, TValue>)_values).CopyTo(array, arrayIndex);
    }

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

    public int Count { get { return _values.Count; } }
    public bool IsReadOnly { get { return ((IDictionary<TKey, TValue>) _values).IsReadOnly; } }
    public bool ContainsKey(TKey key)
    {
        return _values.ContainsKey(key);
    }

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

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

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

    public TValue this[TKey key]
    {
        get
        {
            if (!_values.ContainsKey(key))
            {
                _values.Add(key, _defaultSelector());
            }
            return _values[key];
        }
        set
        {
            if(!_values.ContainsKey(key))
            {
                _values.Add(key, _defaultSelector());
            }
            _values[key] = value;
        }
    }

    public ICollection<TKey> Keys { get { return _values.Keys; } }
    public ICollection<TValue> Values { get { return _values.Values; } }

    public Dictionary<TKey, TValue> ToDictionary()
    {
        return new Dictionary<TKey, TValue>(_values);
    }
}
Dustin Kingen
  • 20,677
  • 7
  • 52
  • 92
4

I don't think there is an equivalent, but given your example you could do this with LINQ:

var words = new List<string>{ "One", "Two", "Three", "One" };
var frequencies = words.GroupBy (w => w).ToDictionary (w => w.Key, w => w.Count());
Phil
  • 42,255
  • 9
  • 100
  • 100
3

The ConcurrentDictionary (in System.Collections.Generic) behaves very similar (although designed for concurrent use)

  • To retrieve the value: The GetOrAdd method returns the value for a key, and if it doesn't exist, creates one using a value factory.

  • To set the value: The AddOrUpdate method updates a value, or sets it if it doesn't exist yet

Advantages:

  • Thread safe
  • Full control over the default value and the update step

Disadvantages:

  • Slightly more verbose syntax
trykyn
  • 451
  • 2
  • 9