词典中的“ .Add”,列表作为值

时间:2019-06-27 22:42:09

标签: c# list dictionary

我一直在为这个问题而苦苦挣扎,因为我的措词不太正确(因此标题)。

要点是为什么要执行以下其中一项工作, 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

我经常遇到的情况类似于以下内容(这是一个非常基本的示例):

  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]++;
  }

换句话说-是否有一种方法可以跳过“ ContainsKey”检查,而直接添加到我的列表中(并自动键入密钥)?

编辑:要清楚,我没有像现实生活中那样使用以下内容,它并不是那么简单(不幸的是!)

var nameCounts = names.GroupBy(x => x)
                      .ToDictionary(x => x.Key, x => x.Count());

4 个答案:

答案 0 :(得分:3)

Perl称之为自动生存,我使用Dictionary的一些扩展来实现各种形式,您将需要使用lambda的形式来生成初始值:

//***
// 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;
    }
}

因此,您可以像这样进行test2:

var test2 = new SeedDictionary<string, List<int>>(() => new List<int>());
test2["Derp"].Add(10); // works

对于您的姓名计数示例,您可以使用自动为值类型创建默认值的版本:

//***
// 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;
    }
}

答案 1 :(得分:2)

另一种方法(其中之一)是一种扩展方法(由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; 
 }

用法

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!

注意 :在我整个早上的鼓舞人心的时候,我实际上没有看过这个问题,Eric Lippert在只需用GroupBy和带有ToDictionary的字典投影即可完成注释,而无需扩展方法和类的所有额外装饰

Eric Lippert的好奇

// Count occurrences of names in a list 
var nameCounts = names.GroupBy(x => x)
                      .ToDictionary(x => x.Key, x => x.Count());

其他资源

Enumerable.GroupBy Method

  

对序列的元素进行分组。

Enumerable.ToDictionary Method

  

Dictionary<TKey,TValue>创建一个IEnumerable<T>

答案 2 :(得分:0)

我通常这样做:

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();

编辑:另一种方法是:

TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key)
        where TValue : new()
        => dict.ContainsKey(key) ? dict[key] : dict[key] = new TValue();

我不确定这是否可以执行,但是它可以在较旧的C#版本上运行,而我的第一个示例则不行。

答案 3 :(得分:0)

C# 7 out variable替代:

foreach(var name in names)
{
    nameCounts[name] = nameCounts.TryGetValue(name, out var count) ? count + 1 : 1;
}