在字典中增加数值


74

我正在使用下面的代码在字典中增加或插入一个值。如果我要递增的键不存在,我想将其值设置为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;
     }
 }

这样是否合适?


3
您要达到什么目的?您要解决什么问题?
Oded

Dictionary对象并没有真正的“计数”,并且像减少数组那样“减少”它没有任何意义。话虽如此,但是您提供的代码应该可以运行,尽管我认为可以使用更简洁的方法来处理这种情况:检查Dictionary对象的内置方法。
Pete855217 2011年

1
最好的方法是什么意思?线程安全,更简洁,最快,但是最易读?
tsul

@tsul这个问题刚刚收到较晚的答案。这个问题已有五年以上的历史了,OP已经不在这里两年多了。
CodeCaster

@CodeCaster是的,现在我明白了。但是,这个问题是搜索结果中第一个“增加.net词典中元素的最有效方法”的问题,因此我尝试对其进行改进。
tsul

Answers:


92

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

因此,我只是使用了:

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

3
很好,但是我认为处理线程问题会有些开销。
Goalie7960 2011年

实际上,ConcurrentDictionary已针对具有许多访问集合的线程的环境进行了优化。
Eric J.

84

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

int currentCount;

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

someDictionary[id] = currentCount + 1;

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


UPD。从C#7.0开始,可以使用以下代码来缩短此代码段out variables

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

1
此代码将不起作用。您是正确的,如果字典中不存在该键,currentCount则将为0。但这也意味着someDictinary[id]会抛出一个错误KeyNotFoundException
JBSnorro 2011年

24
@JBSnorro:不,这可以正常工作;我鼓励您尝试一下。请注意,正在调用索引器的setter,而不是getter。来自文档:“设置属性值时,如果键位于Dictionary <TKey,TValue>中,则与该键关联的值将由分配的值替换。如果键不在Dictionary <TKey,TValue中>,则将键和值添加到字典中。
Ani

1
抱歉 我没有意识到这一点,谢谢您指出这一点。
JBSnorro 2011年

9
我意识到这对节目来说有点晚了,但是我想我会帮助其他人的。上面的代码将在真正的多线程环境中失败。在TryGetValue和它下面的递增函数之间,可以在另一个线程上更新Dictionary,这意味着currentCount将不同步,并且您创建了竞争条件。
MarkWalls

18
这里的所有示例都不是线程安全的。这不是问题的一部分,所以假设它是这样将是一个错误。
Cyber​​conte

16

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

    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"]);

5
我意识到这对节目来说有点晚了,但是我想我会帮助其他人的。上面的代码将在真正的多线程环境中失败。在TryGetValue和它下面的递增函数之间,可以在另一个线程上更新Dictionary,这意味着计数将不同步,并且您创建了竞争条件。
MarkWalls

1
喜欢这个。我用它并将返回值更改为增量值return(dictionary [key] = ++ count);
Rhyous

14

可读性强,意图明确。我认为这很好。无需发明一些更聪明或更短的代码;如果它的意图与最初的版本不一样:-)

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

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

    someDictionary[id]++;
}

如果您可以同时访问字典,请记住要同步对其进行访问。


4
@inflagranti-这也是如此。(它在返回之前递增零-这是在方法中获取较少条件的一种示例)。
driis 2011年

当id不存在时,这是否不需要额外查找?
Goalie7960 2011年

您说得对,对不起。因此,我想说的是原始代码更具可读性;)
Janick Bernet

2
是的,当ID不存在时,确实需要额外的查找。我将“最佳方式”读为“简短/简单”。如果“最佳方式”表示“性能最佳”,则原始版本的效率应稍高一些。但是,我怀疑这是可以测量的,除非将其用于紧密循环中。
driis 2011年

ContainsKey如果我需要“修改”它们,我也更喜欢使用值类型的方法。但是,如果不包含ID,则您的方法需要进行3次查找。您应该使用if...else...。如果使用,它甚至更具可读性if(someDictionary.ContainsKey(id)) someDictionary[id]++; else someDictionary.Add(id, 1);
蒂姆·施密特

7

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

这并不是您问题的答案,但是为了完整起见,我已测量了各种类的行为,这些行为可用于基于整数键递增整数:simple ArrayDictionary(@Ani方法),Dictionary(simple方法),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

就是代码

请注意,ConcurrentDictionary.TryAddOrUpdate它比Dictionary“ s TryGetValue+索引器”的设置器慢三倍。后者比Array慢十倍。

因此,如果我知道键的范围很小,那么我将使用数组,否则将使用组合方法。


1

这是一个方便的单元测试,可让您使用有关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);
     }

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.