【问题标题】:Incrementing a numerical value in a dictionary增加字典中的数值
【发布时间】:2011-10-31 06:09:32
【问题描述】:

我正在使用下面的代码在字典中增加或插入一个值。如果我要递增的键不存在,我想将其值设置为 1。

 public void IncrementCount(Dictionary<int, int> someDictionary, int id)
 {  
     int currentCount;
     if (someDictionary.TryGetValue(id, out currentCount))
     {
         someDictionary[id] = currentCount + 1;
     }
     else
     {
         someDictionary[id] = 1;
     }
 }

这样做是否合适?

【问题讨论】:

  • 你想用这个实现什么?你在解决什么问题?
  • Dictionary 对象并没有真正的“计数”,并且像数组一样“减少”它也没有任何意义。话虽如此,您提供的代码应该可以工作,尽管我认为有更简洁的方法来处理这种情况:检查 Dictionary 对象的内置方法。
  • 最好的方法是什么意思?线程安全、更简洁、最快、最易读?
  • @tsul 这个问题刚刚收到了late answer。问题超过五年了,OP已经两年多了。
  • @CodeCaster 是的,现在我明白了。但这个问题是“在 .net 字典中增加元素的最有效方法”的搜索结果中的第一个问题,所以我尝试改进它。

标签: c# dictionary


【解决方案1】:

您的代码很好。但这是一种不需要在代码中分支的方式进行简化的方法:

int currentCount;

// currentCount will be zero if the key id doesn't exist..
someDictionary.TryGetValue(id, out currentCount); 

someDictionary[id] = currentCount + 1;

这依赖于 TryGetValue 方法将 value 设置为其类型的默认值(如果键不存在)这一事实。在您的情况下,int 的默认值是 0,这正是您想要的。


UPD。从 C# 7.0 开始,这个 sn-p 可以使用 out variables 缩短:

// declare variable right where it's passed
someDictionary.TryGetValue(id, out var currentCount); 
someDictionary[id] = currentCount + 1;

【讨论】:

  • 此代码不起作用。你是对的,如果字典中不存在密钥,currentCount 将是0。但这也意味着someDictinary[id] 会抛出KeyNotFoundException
  • @JBSnorro:不,这可以正常工作;我鼓励你尝试一下。请注意,正在调用索引器的 setter,而不是 getter。来自documentation:“设置属性值时,如果键在 Dictionary 中,则与该键关联的值将替换为分配的值。如果键不在Dictionary,将key和value添加到字典中。"
  • 抱歉。我不知道,谢谢您指出。
  • 我意识到这有点晚了,但我想我会帮助其他人。上面的代码在真正的多线程环境中会失败。在 TryGetValue 和它下面的递增函数之间,可以在另一个线程上更新 Dictionary,这意味着 currentCount 将不同步并且您创建了一个竞争条件。
  • 这里所有的例子都不是线程安全的。这不是问题的一部分,因此假设它是这样的假设是错误的。
【解决方案2】:

事实证明,使用具有方便的 upsert 方法的 ConcurrentDictionary 是有意义的:AddOrUpdate。

所以,我只是用了:

someDictionary.AddOrUpdate(id, 1, (id, count) => count + 1);  

【讨论】:

  • 这很好,但我想有一些开销来处理线程问题。
  • 确实,ConcurrentDictionary 针对有许多线程访问集合的环境进行了优化。
【解决方案3】:

这是一个不错的扩展方法:

    public static void Increment<T>(this Dictionary<T, int> dictionary, T key)
    {
        int count;
        dictionary.TryGetValue(key, out count);
        dictionary[key] = count + 1;
    }

用法:

var dictionary = new Dictionary<string, int>();
dictionary.Increment("hello");
dictionary.Increment("hello");
dictionary.Increment("world");

Assert.AreEqual(2, dictionary["hello"]);
Assert.AreEqual(1, dictionary["world"]);

【讨论】:

  • 我意识到这有点晚了,但我想我会帮助其他人。上面的代码在真正的多线程环境中会失败。在 TryGetValue 和它下面的递增函数之间,可以在另一个线程上更新 Dictionary,这意味着计数将不同步并且您创建了一个竞争条件。
  • 喜欢这个。我使用它并将返回值更改为增量值返回(字典[key] = ++count);
【解决方案4】:

它是可读的,意图是明确的。我认为这很好。无需发明一些更智能或更短的代码;如果它不像你的初始版本那样保持意图清晰:-)

话虽如此,这里有一个略短的版本:

public void IncrementCount(Dictionary<int, int> someDictionary, int id)
{
    if (!someDictionary.ContainsKey(id))
        someDictionary[id] = 0;

    someDictionary[id]++;
}

如果您可以同时访问字典,请记住同步访问它。

【讨论】:

  • @inflagranti - 这也是。 (它在返回之前递增零 - 这是在方法中获得更少条件的示例)。
  • id 不存在时不需要额外查找吗?
  • 你是对的,对不起。所以我会说原始代码更具可读性;)
  • 是的,当 ID 不存在时,它确实需要额外的查找。我将“最佳方式”读作“更短/更简单”。如果“最佳方式”意味着“最高性能”,那么原始版本的效率应该稍高一些。但是,我怀疑它是否可以测量,除非在紧密循环中使用。
  • 如果我需要“修改”它们,我也更喜欢带有值类型的ContainsKey 方法。但是如果不包含 id,您的方法需要 3 次查找。你应该使用if...else...。如果你使用if(someDictionary.ContainsKey(id)) someDictionary[id]++; else someDictionary.Add(id, 1);,它的可读性会更高。
【解决方案5】:

仅在 .NET 4 上对整数键进行一些测量。

这不是您问题的完全答案,但为了完整起见,我测量了可用于基于整数键递增整数的各种类的行为:简单的ArrayDictionary(@Ani 的方法), Dictionary(简单方法)、SortedDictionary(@Ani 的方法)和ConcurrentDictionary.TryAddOrUpdate

这是结果,调整了 2.5 ns 以使用类包装而不是直接使用:

Array                 2.5 ns/inc
Dictionary (@Ani)    27.5 ns/inc
Dictionary (Simple)  37.4 ns/inc
SortedDictionary    192.5 ns/inc
ConcurrentDictionary 79.7 ns/inc

还有that's the code

请注意,ConcurrentDictionary.TryAddOrUpdateDictionaryTryGetValue + indexer 的 setter 慢三倍。而且后者比Array慢十倍。

所以如果我知道键的范围很小,我会使用数组,否则我会使用组合方法。

【讨论】:

    【解决方案6】:

    这里有一个方便的单元测试供您使用,涉及 ConcurrentDictionary 以及如何保持值线程安全:

         ConcurrentDictionary<string, int> TestDict = new ConcurrentDictionary<string,int>();
         [TestMethod]
         public void WorkingWithConcurrentDictionary()
         {
             //If Test doesn't exist in the dictionary it will be added with a value of 0
             TestDict.AddOrUpdate("Test", 0, (OldKey, OldValue) => OldValue+1);
    
             //This will increment the test key value by 1 
             TestDict.AddOrUpdate("Test", 0, (OldKey, OldValue) => OldValue+1);
             Assert.IsTrue(TestDict["Test"] == 1);
    
             //This will increment it again
             TestDict.AddOrUpdate("Test", 0, (OldKey, OldValue) => OldValue+1);
             Assert.IsTrue(TestDict["Test"] == 2);
    
             //This is a handy way of getting a value from the dictionary in a thread safe manner
             //It would set the Test key to 0 if it didn't already exist in the dictionary
             Assert.IsTrue(TestDict.GetOrAdd("Test", 0) == 2);
    
             //This will decriment the Test Key by one
             TestDict.AddOrUpdate("Test", 0, (OldKey, OldValue) => OldValue-1);
             Assert.IsTrue(TestDict["Test"] == 1);
         }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多