在字典中递增数值

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

这样做合适吗?

60856 次浏览

It is readable and the intent is clear. I think this is fine. No need to invent some smarter or shorter code; if it doesn't keep the intent just as clear as your initial version :-)

That being said, here is a slightly shorter version:

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


someDictionary[id]++;
}

If you have concurrent access to the dictionary, remember to synchronize access to it.

As it turns out it made sense to use the ConcurrentDictionary which has the handy upsert method: AddOrUpdate.

So, I just used:

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

Your code is fine. But here's a way to simplify in a way that doesn't require branching in your code:

int currentCount;


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


someDictionary[id] = currentCount + 1;

This relies on the fact that the TryGetValue method sets value to the default value of its type if the key doesn't exist. In your case, the default value of int is 0, which is exactly what you want.


UPD. Starting from C# 7.0 this snippet can be shortened using out variables:

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

Here's a nice extension method:

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

Usage:

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

Here is a handy unit test for you to play with concerning ConcurrentDictionary and how to keep the values threadsafe:

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

Just some measurements on .NET 4 for integer keys.

It's not quite an answer to your question, but for the sake of completeness I've measured the behavior of various classes useful for incrementing integers based on integer keys: simple Array, Dictionary (@Ani's approach), Dictionary (simple approach), SortedDictionary (@Ani's approach) and ConcurrentDictionary.TryAddOrUpdate.

Here is the results, adjusted by 2.5 ns for wrapping with classes instead of direct usage:

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

And that's the code.

Note that ConcurrentDictionary.TryAddOrUpdate is three times slower than Dictionary's TryGetValue + indexer's setter. And the latter is ten times slower than Array.

So I would use an array if I know the range of keys is small and a combined approach otherwise.