Kaow
Kaow

Reputation: 563

Add Counters keeping zero values

I try to sum the value of a key present in other dictionaries with this code:

import functools
import operator
import collections

my_dict = [{'a':0, 'b':1, 'c':5}, {'b':3, 'c':2}, {'b':1, 'c':1}]
sum_key_value = functools.reduce(operator.add, map(collections.Counter, my_dict))

print(sum_key_value)

# Output
# Counter({'c': 8, 'b': 5})

My question is if I want the output to keep all dictionary keys, even if the key does not appear in all the dictionaries like a in my case, what is the best way to do that without using a loop ?

Upvotes: 5

Views: 5290

Answers (4)

CN_Cabbage
CN_Cabbage

Reputation: 445

Subtract(or Update) a zero value Counter to keep zero value items

my_dict = [{'a':0, 'b':1, 'c':5}, {'b':3, 'c':2}, {'b':1, 'c':1}]

sum_my_dict = Counter()
zero_dict = Counter()

my_dict_keys = set()

# get the keys of all dict
for dic in my_dict:
    sum_my_dict += Counter(dic)
    my_dict_keys.update(dic.keys())

# create a dict with all zero values
for key in my_dict_keys:
    zero_dict[key] = 0

# in-place subtract zero dict (alter sum_my_dict)
sum_my_dict.subtract(zero_dict)

# in-plact update is the same with subtract
# sum_my_dict.update(zero_dict)


print(sum_my_dict)

Counter({'c': 8, 'b': 5, 'a': 0})

Upvotes: 0

yatu
yatu

Reputation: 88276

As mentioned in the comments, adding Counter objects will remove non positive keys.

So the issue is not really about not ending up with the union of all keys (as well as adding common values), since that is indeed the behaviour, see if we set a:2:

my_dict = [{'a':2, 'b':1, 'c':5}, {'b':3, 'c':2}, {'b':1, 'c':1}]
functools.reduce(operator.add, map(Counter, my_dict))
# Counter({'a': 2, 'b': 5, 'c': 8})

However, as shown in the question, as per the current implementation when adding Counter objects, non positive values (a:0) get removed.

If you really wanted to use Counter for this, you could tweak a little the current implementation overriding __add__ to get the expected behaviour:

class Counter_tweaked(Counter):
    def __add__(self, other):
            if not isinstance(other, Counter):
                return NotImplemented
            result = Counter_tweaked()
            for elem, count in self.items():
                newcount = count + other[elem]
                result[elem] = newcount
            for elem, count in other.items():
                if elem not in self:
                    result[elem] = count
            return result

functools.reduce(operator.add, map(Counter_tweaked, my_dict))
# Counter_tweaked({'a': 0, 'b': 5, 'c': 8})

Upvotes: 2

Ayush
Ayush

Reputation: 1620

Well there's a lot of nice ways to do it with a for loop, but since you specifially want to avoid a for loop, here's one way:

sum_key_value = dict(functools.reduce(lambda a, b: a.update(b) or a,
                     my_dict, collections.Counter()))

So what happens here is you create a single Counter, and use it to accumulate the values.

Upvotes: 3

Martin Wettstein
Martin Wettstein

Reputation: 2894

The most straightforward approach, here, would be a loop. You might have keys appearing in dictionaries anywhere in the list (e.g.: the third one could have the key "e"), so you would need at least one loop to get the total of keys. And then you can just loop through all dictionaries again to sum up the values. Make an own function of it and you can call it without ever caring about loops again.

def sum_it_up(dictlist):
    outdic = {}
    for d in dictlist:
        for k in d.keys():
            outdic[k] = 0
    for d in dictlist:
        for k in d.keys():
            outdic[k]+=d[k]
    return outdic

my_dict = [{'a':0, 'b':1, 'c':5}, {'b':3, 'c':2}, {'b':1, 'c':1}]
sum_key_value = sum_it_up(my_dict)

Upvotes: 0

Related Questions