3

Possible Duplicate:
Dictionary returning a default value if the key does not exist

I have a string that contains only digits. I'm interested in generating a frequency table of the digits. Here's an example string:

var candidate = "424256";

This code works, but it throws a KeyNotFound exception if I look up a digit that's not in the string:

var frequencyTable = candidate
    .GroupBy(x => x)
    .ToDictionary(g => g.Key, g => g.Count());

Which yields:

Key Count
4   2 
2   2 
5   1 
6   1 

So, I used this code, which works:

var frequencyTable = (candidate + "1234567890")
    .GroupBy(x => x)
    .ToDictionary(g => g.Key, g => g.Count() - 1);

However, in other use cases, I don't want to have to specify all the possible key values.

Is there an elegant way of inserting 0-count records into the frequencyTable dictionary without resorting to creating a custom collection with this behavior, such as this?

public class FrequencyTable<K> : Dictionary<K, int>
{
  public FrequencyTable(IDictionary<K, int> dictionary) 
    : base(dictionary)
  { }

  public new int this[K index]
  {
    get
    {
        if (ContainsKey(index))
            return base[index];
        return 0;
    }
  }
}
Community
  • 1
  • 1
neontapir
  • 4,698
  • 3
  • 37
  • 52
  • 2
    Jon Skeet already answered this one: http://stackoverflow.com/questions/2601477/dictionary-returning-a-default-value-if-the-key-does-not-exist – zmbq Jan 16 '13 at 21:32
  • Thank you, his TryGetValueOrDefault method is an interesting possibility – neontapir Jan 16 '13 at 21:33

2 Answers2

3

If you do not somehow specify all possible key values, your dictionary will not contain an entry for such keys.

Rather than storing zero counts, you may wish to use

Dictionary.TryGetValue(...)

to test the existence of the key before trying to access it. If TryGetValue returns false, simply return 0.

You could easily wrap that in an extension method (rather than creating a custom collection).

static public class Extensions
{
    static public int GetFrequencyCount<K>(this Dictionary<K, int> counts, K value)
    {
        int result;
        if (counts.TryGetValue(value, out result))
        {
            return result;
        }
        else return 0;
    }
}

Usage:

Dictionary<char, int> counts = new Dictionary<char, int>();
counts.Add('1', 42);
int count = counts.GetFrequencyCount<char>('1');
Eric J.
  • 147,927
  • 63
  • 340
  • 553
  • This code does work as written. – neontapir Jan 16 '13 at 21:42
  • 1
    @neontapir: I'm in front of VS now and confirmed that the code does work. I extended the code to show how to add it to an extension method, and show how to use the code. – Eric J. Jan 16 '13 at 22:37
0

If there is a pattern for all the possible keys, you can use Enumerable.Range (or a for loop) to generate 0-value keys as a base table, then left join in the frequency data to populate the relevant values:

// test value
var candidate = "424256";

// generate base table of all possible keys
var baseTable = Enumerable.Range('0', '9' - '0' + 1).Select(e => (char)e);

// generate freqTable
var freqTable = candidate.ToCharArray().GroupBy (c => c);

// left join frequency table results to base table
var result =
    from b in baseTable
    join f in freqTable on b equals f.Key into gj
    from subFreq in gj.DefaultIfEmpty()
    select new { Key = b, Value = (subFreq == null) ? 0 : subFreq.Count() };

// convert final result into dictionary
var dict = result.ToDictionary(r => r.Key, r => r.Value);

Sample result:

Key Value
0   0
1   0
2   2 
3   0
4   2
5   1
6   1
7   0
8   0
9   0
mellamokb
  • 56,094
  • 12
  • 110
  • 136
  • 1
    Allocating storage for keys that may have a 0 count is not space efficient (though it may or may not be more time efficient depending on the specific use cases). – Eric J. Jan 16 '13 at 22:38
  • @EricJ.: Agreed. This solution should definitely not be used when the values are sparsely populated among the set of possible keys. – mellamokb Jan 16 '13 at 22:40