pythonsetdefault

Using setdefault, but not wanting to reassign back to map/dict


I use setdefault to count instances like this (this is the stripdown version):

user_to_count_map = {}
for username in list_of_usernames:
    x = user_to_count_map.setdefault(username, 0)
    x += 1
    user_to_count_map[username] = x + 1
for username in sorted(usernmae_to_count_map):
    print username, user_to_count_map[username]

I don't like the assigning back to the map, because the real code is more complex with multiple count increas. But I do seem to need to do that. Is there an easy way around that?


Solution

  • You could make the counter a list with one element, effectively making it a mutable:

    user_to_count_map = {}
    for username in list_of_usernames:
        x = user_to_count_map.setdefault(username, [0])
        x[0] += 1
    for username, counter in sorted(user_to_count_map.items()):
        print username, counter[0]
    

    I am not sure if that makes your code any more readable, as explicit is better than implicit.

    Or if use python 2.7 or newer (or use a convenient backport), you could use a Counter object:

    from collections import Counter
    user_to_count_map = Counter()
    for username in list_of_usernames:
        user_to_count_map[username] += 1
    for username, counter in sorted(user_to_count_map.items()):
        print username, counter[0]        
    

    Note that by using a Counter you have a dictionary that automatically gives you a default value of 0. It otherwise acts like a dictionary holding integer values, so you can increment and decrement these values any way you like (including adding more than 1).

    The same effect can be had with defaultdict, also in the collections module, but note that the Counter class offers functionality. defaultdict is present in python 2.5 and up; example:

    from collections import defaultdict
    user_to_count_map = defaultdict(lambda: 0)
    for username in list_of_usernames:
        user_to_count_map[username] += 1
    

    Alternatively, you could just dispense with the setdefault altogether, as you are always assigning back to the mapping anyway:

    user_to_count_map = {}
    for username in list_of_usernames:
        x = user_to_count_map.get(username, 0)
        x += 1
        user_to_count_map[x] = x
    for username, counter in sorted(user_to_count_map.items()):
        print username, counter[0]