开发者

Incrementing a numerical value in a dictionary

开发者 https://www.devze.com 2023-03-30 01:13 出处:网络
I\'m using the code below to either increment or insert a value in a dictionary.If the key I\'m incrementing doesn\'t exist I\'d like to set its value to 1.

I'm using the code below to either increment or insert a value in a dictionary. If the key I'm incrementing doesn't exist I'd like to set its value to 1.

 public void IncrementCount(Dictionary<int, int> someDictionary, int id)
 {开发者_如何学Python  
     int currentCount;
     if (someDictionary.TryGetValue(id, out currentCount))
     {
         someDictionary[id] = currentCount + 1;
     }
     else
     {
         someDictionary[id] = 1;
     }
 }

Is this an appropriate way of doing so?


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;


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


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


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.


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.


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

精彩评论

暂无评论...
验证码 换一张
取 消