views:

221

answers:

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#?

+9  A: 

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;
dtb
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
@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
If I am not mistaken that last parameter should be `@default`, since `default` is a keyword.
Svish
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)
AdamRalph
A: 

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
+5  A: 

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
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.
Paul McGuire
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
Thanks -- this is an important error on my part so I'm going to edit the answer to clarify it!
Alex Martelli
A: 

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