Reputation: 563
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
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
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
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
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