Reputation: 1250
I have a dict that looks like this:
{'Item1': [{'Name1': 3}, {'Name2': 4}, {'Name1':7}],
'Item2': [{'Name7': 44}, {'Name2': 3}, {'Name6':9}, {'Name6':2}]
}
I want to combine dictionaries in the list attached to the key such that if there are multiple dicts with the same key, I can combine them (sum) and leave the others as they are.
The output would look like:
{'Item1': [{'Name1': 10}, {'Name2': 4}],
'Item2': [{'Name7': 44}, {'Name2': 3}, {'Name6': 11}]
}
I can't figure out how to do this in Python elegantly with a list/dict comprehension.
Upvotes: 0
Views: 695
Reputation: 3547
Can also try with defaultdict
from itertools import chain
from collections import defaultdict
d_new = {}
for k, v in d.items():
d_dict = defaultdict(int)
for k1, v1 in chain(*[ i.items() for i in v ]) :
d_dict[k1]+= v1
d_new[k] = dict(d_dict)
print (d_new)
Output:
{'Item1': {'Name1': 10, 'Name2': 4}, 'Item2': {'Name7': 44, 'Name2': 3, 'Name6': 11}}
chain(*[ i.items() for i in v ]) will flatten the list of dicts into list of items
Converts
[{'Name1': 3}, {'Name2': 4}, {'Name1':7}]
to
[('Name1', 3), ('Name2', 4), ('Name1', 7)]
defaultdict(int) is used to add the values of dict with same keys
Upvotes: 0
Reputation: 30258
Assuming you do want to collapse this into a single dict
vs list[dict]
then you can do this without any additional modules with a couple of simple for
loops:
In []:
r = {}
for k, ds in data.items():
s = {}
for d in ds:
for v, c in d.items():
s[v] = s.get(v, 0) + c
r[k] = s
r
Out[]:
{'Item1': {'Name1': 10, 'Name2': 4}, 'Item2': {'Name2': 3, 'Name6': 11, 'Name7': 44}}
As some seem to be going for one liners:
In []:
import itertools as it
from collections import Counter
{k: dict(Counter(v for v, c in it.chain.from_iterable(d.items() for d in ds))
for _ in range(c)) for k, ds in data.items()}
Out[]:
{'Item1': {'Name1': 10, 'Name2': 4}, 'Item2': {'Name2': 3, 'Name6': 11, 'Name7': 44}}
Upvotes: 1
Reputation: 2545
This uses collections.Counter. It is about the most elegant I could come up with, because of the slightly convoluted structure of your input - a list of 1-length dictionaries would indeed be better implemented as a single dictionary, as the comments suggest. This is also what my code transforms it to, although I have provided some more possible tranformations if you really direly need the old data structure. If you do, I would recommend using tuples as your key-value pairs rather than just single-length dicts, as seen in tuple_output
. I recommend you use output
or dict_output
.
from collections import Counter
d = {'Item1': [{'Name1': 3}, {'Name2': 4}, {'Name1':7}], 'Item2': [{'Name7': 44}, {'Name2': 3}, {'Name6':9}, {'Name6':2}] }
output = {}
for k, v in d.items():
c = Counter()
for sub_dict in v:
c.update(sub_dict)
output[k] = c
dict_output = {k: dict(v) for k, v in output.items()}
tuple_output = {k: v.most_common() for k, v in output.items()}
dict_list_output = {k: [{a: b} for a, b in v.most_common()] for k, v in output.items()}
print(output)
#{'Item1': Counter({'Name1': 10, 'Name2': 4}), 'Item2': Counter({'Name7': 44, 'Name6': 11, 'Name2': 3})}
print(dict_output)
#{'Item1': {'Name1': 10, 'Name2': 4}, 'Item2': {'Name7': 44, 'Name2': 3, 'Name6': 11}}
print(tuple_output)
#{'Item1': [('Name1', 10), ('Name2', 4)], 'Item2': [('Name7', 44), ('Name6', 11), ('Name2', 3)]}
print(dict_list_output)
#{'Item1': [{'Name1': 10}, {'Name2': 4}], 'Item2': [{'Name7': 44}, {'Name6': 11}, {'Name2': 3}]}
Of course, if you change the starting data structure altogether, it will become a lot easier to manage. If you use a dictionary from strings to Counters, you can use the Counter interface to easily update it (refer to the link)
Edit:
Just for fun, done in one line:
results = {item: reduce(lambda a, b: [a, a.update(b)][0], names, Counter()) for item, names in d.items()}
It was inspired by yours, except this only builds one Counter instance (given as the initial value for reduce) per list. Also, a little bit of a golfy trick is required to reduce properly, as Counter.update is in place. If you're reading this, you probably shouldn't use it, and instead build a data structure with Counters or dicts from the start, as mentioned earlier.
Upvotes: 1
Reputation: 1250
It occurred to me just a few minutes after posting the question.
This is what I did:
from operator import add
from collections import Counter
results = {}
for item, names in d.items():
result[item] = (reduce(add, (Counter(name) for name in names)))
As the above comments and answers suggest, I am better off using a 1-length dictionary instead of having to combine several later. Still, leaving the answer out here for anyone that needs it.
Upvotes: 0