4

Trying to translate some methods written in Python over into C#. The line looks like this:

d[p] = d.setdefault(p, 0) + 1

What exactly does setdefault do? And is there anything similar I can use in a C# dictionary? Or rather, how can I translate that line into C#?

Community
  • 1
  • 1
Svish
  • 138,188
  • 158
  • 423
  • 589

5 Answers5

13

From the Python docs:

setdefault(key[, default])

If key is in the dictionary, return its value. If not, insert key with a value of default and return default. default defaults to None.

There is no direct implementation of this in the .NET framework, but you can define an extension method:

public static V SetDefault<K,V>(this IDictionary<K,V> dict, K key, V @default)
{
    V value;
    if (!dict.TryGetValue(key, out value))
    {
        dict.Add(key, @default);
        return @default;
    }
    else
    {
        return value;
    }
}

Usage:

string key;
Dictionary<string, int> dict;

dict[key] = dict.SetDefault(key, 0) + 1;
Svish
  • 138,188
  • 158
  • 423
  • 589
dtb
  • 198,715
  • 31
  • 379
  • 417
  • 1
    Watch out for concurrency issues when adding to the dictionary. The call to `Add` will throw an exception if two threads get to that point. – Nader Shirazie Oct 03 '09 at 18:27
  • 6
    @Nader: Concurrency is a general concern when working with collections. There is nothing wrong with this code in particular; in fact, I'd hate to see a lock statement in this method. – Ben M Oct 03 '09 at 18:37
  • 1
    If I am not mistaken that last parameter should be `@default`, since `default` is a keyword. – Svish Oct 03 '09 at 20:20
  • somehow I don't like the name `SetDefault()` - I wrote a similar extension method a while back and called it `GetOrAddDefault()` (which I don't particularly like either) – Adam Ralph Oct 29 '10 at 13:36
6

Edit -- warning: the following works only for this specific case, not for the general case -- see below.

int value = 0;
d.TryGetValue(p, out value);
d[p] = value + 1;

this is equivalent to the following Python snippet (which is better than the one you show):

d[p] = d.get(p, 0) + 1

setdefault is like get (fetch if present, else use some other value) plus the side effect of injecting the key / other value pair in the dict if the key wasn't present there; but here this side effect is useless, since you're about to assign d[p] anyway, so using setdefault in this case is just goofy (complicates things and slow you down to no good purpose).

In C#, TryGetValue, as the name suggests, tries to get the value corresponding to the key into its out parameter, but, if the key's not present, then it (warning: the following phrase is not correct:) just leaves said value alone (edit:) What it actually does if the key's not present is not to "leave the value alone" (it can't, since it's an out value; see the comments), but rather to set it to the default value for the type -- here, since 0 (the default value) is what we want, we're fine, but this doesn't make TryGetValue a general-purpose substitute for Python's dict.get.

TryGetValue also returns a boolean result, telling you whether it did manage to get the value or not, but you don't need it in this case (just because the default behavior happens to suit us). To build the general equivalent of Python's dict.get, you need another idiom:

if (!TryGetValue(d, k)) {
  k = whatyouwant;
}

Now this idiom is indeed the general-purpose equivalent to Python's k = d.get(k, whatyouwant).

Alex Martelli
  • 762,786
  • 156
  • 1,160
  • 1,345
  • 2
    Alex, I think I got burned by this, I had made the same assumption about TryGetValue in the event of the value's absence, that the out value would be unchanged. BUT, the docs say that the out value get set with the default value of the value's type. Try initializing value in your example to 100, and in the absence of p in d, value will have the value of 0 after calling TryGetValue. I tripped over this by trying to look for override settings in a dictionary, and found that I was resetting all of my values to 0 if no override existed. – PaulMcG Oct 03 '09 at 18:56
  • To expand on Paul's comment, in C#, the callee (in this case TryGetValue) MUST write to the out parameter before returning -- and is not allowed to read from the out-parameter before writing to it. So TryGetValue couldn't leave the value alone even if it wanted to: it has to write to it, and it can't write the passed-in value because it can't read the passed-in value. (If TryGetValue took a ref parameter, that would be a different matter.) – itowlson Oct 03 '09 at 19:09
  • Thanks -- this is an important error on my part so I'm going to edit the answer to clarify it! – Alex Martelli Oct 03 '09 at 22:14
1

If you want to have the item default to the default instance of object, you might want to consider this (from here)

public static TValue SetDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key) 
{ 
  TValue result; 
  if (!dictionary.TryGetValue(key, out result)) 
  { 
    return dictionary[key] = (TValue)Activator.CreateInstance(typeof(TValue)); 
  } 
  return result; 
} 

This leads to the rather nice syntax of:

var children = new Dictionary<string, List<Node>>();
d.SetDefault(“left”).Add(childNode);
Shaun McCarthy
  • 1,610
  • 11
  • 9
0

I know I'm 3 years late to the party, but a variation that works and is useful:

public static TV SetDefault<TK, TV>(this IDictionary<TK, TV> dict, TK key) where TV: new() {
    TV value;
    if (!dict.TryGetValue(key, out value)) dict.Add(key, value = new TV());
    return value;
}

That is, for value types that can be initialized without a parameter, do so.

var lookup = new Dictionary<string, HashSet<SomeType>>();
lookup.SetDefault("kittens").Add(mySomeType);
AKX
  • 93,995
  • 11
  • 81
  • 98
0

d.setdefault(p, 0) will return the value of the entry with key p if it exists and if it does not then it will set the value for the key p to 0.

Mike Two
  • 41,318
  • 7
  • 77
  • 93