Reputation: 618
I've been struggling to Google this question as I can't get the wording quite right (hence the title).
The gist is why do one of the below work, is there a shorthand for test3:
var test1 = new Dictionary<string, int>();
test1["Derp"] = 10; // Success
var test2 = new Dictionary<string, List<int>>();
test2["Derp"].Add(10); // Fail
var test3 = new Dictionary<string, List<int>>();
test3["Derp"] = new List<int>();
test3["Derp"].Add(10); // Success
A scenario I'm coming across often is similar to the below (this is a very basic example):
var names = new List<string>() { "Jim", "Fred", "Fred", "Dave", "Jim", "Jim", "Jim" };
var nameCounts = new Dictionary<string, int>();
foreach(var name in names)
{
if (!nameCounts.ContainsKey(name))
nameCounts.Add(name, 0);
nameCounts[name]++;
}
In other words - is there a way to skip the "ContainsKey" check, and go straight to adding to my list (and key automatically)?
Edit: to be clear, I hadn't used the below as in my real-life situation, it isn't quite as simple (unfortunately!)
var nameCounts = names.GroupBy(x => x)
.ToDictionary(x => x.Key, x => x.Count());
Upvotes: 3
Views: 1034
Reputation: 81493
Another way you can do this (among many), is a little extension method (cutesy of Jon Skeet here)
public static TValue GetOrCreate<TKey, TValue>(this IDictionary<TKey, TValue> dictionary,TKey key) where TValue : new()
{
TValue ret;
if (!dictionary.TryGetValue(key, out ret))
{
ret = new TValue();
dictionary[key] = ret;
}
return ret;
}
Usage
strong textvar test2 = new Dictionary<string, List<int>>();
var myNewList = test2.GetOrCreate("Derp");
myNewList.Add(10);
// or
var test2 = new Dictionary<string, List<int>>();
test2.GetOrCreate("Derp").Add(10); // winning!
Note : In all my early morning pep, i actually didn't look at this question, Eric Lippert is on the money in the comments, this can be simply done via a GroupBy
and a projection to a dictionary with ToDictionary
without all the extra fluff of extension methods and classes
Cutesy of Eric Lippert
// Count occurrences of names in a list
var nameCounts = names.GroupBy(x => x)
.ToDictionary(x => x.Key, x => x.Count());
Additional Resources
Groups the elements of a sequence.
Enumerable.ToDictionary Method
Creates a
Dictionary<TKey,TValue>
from anIEnumerable<T>
.
Upvotes: 2
Reputation: 22876
Alternative with C# 7 out variable :
foreach(var name in names)
{
nameCounts[name] = nameCounts.TryGetValue(name, out var count) ? count + 1 : 1;
}
Upvotes: 0
Reputation: 111
I usually do something like this:
TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key)
where TValue : new()
=> dict.TryGetValue(key, out TValue val) ? val : dict[key] = new TValue();
Edit: Another way is:
TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key)
where TValue : new()
=> dict.ContainsKey(key) ? dict[key] : dict[key] = new TValue();
I'm not sure if this is as performant, but it works on older C# versions, where my first example doesn't.
Upvotes: 0
Reputation: 26917
Perl calls this auto-vivification, and I use some extensions to Dictionary
to implement various forms, you would need the one that uses a lambda to generate the initial values:
//***
// Enhanced Dictionary that auto-creates missing values with seed lambda
// ala auto-vivification in Perl
//***
public class SeedDictionary<TKey, TValue> : Dictionary<TKey, TValue> {
Func<TValue> seedFn;
public SeedDictionary(Func<TValue> pSeedFn) : base() {
seedFn = pSeedFn;
}
public SeedDictionary(Func<TValue> pSeedFn, IDictionary<TKey, TValue> d) : base() {
seedFn = pSeedFn;
foreach (var kvp in d)
Add(kvp.Key, kvp.Value);
}
public new TValue this[TKey key]
{
get
{
if (!TryGetValue(key, out var val))
base[key] = (val = seedFn());
return val;
}
set => base[key] = value;
}
}
So then you could do test2 like so:
var test2 = new SeedDictionary<string, List<int>>(() => new List<int>());
test2["Derp"].Add(10); // works
For your name counts example, you could use the version that auto-creates the default value for the value type:
//***
// Enhanced Dictionary that auto-creates missing values as default
// ala auto-vivification in Perl
//***
public class AutoDictionary<TKey, TValue> : Dictionary<TKey, TValue> {
public AutoDictionary() : base() { }
public AutoDictionary(IDictionary<TKey, TValue> d) : base() {
foreach (var kvp in d)
Add(kvp.Key, kvp.Value);
}
public new TValue this[TKey key]
{
get
{
if (!TryGetValue(key, out var val))
base[key] = val;
return val;
}
set => base[key] = value;
}
}
Upvotes: 3