I would have more than "key1" and "key2", I could have 20 or more.
my_dict = {"key1": [{"A": 0}, {"B": 1}, {"C": 0}, {"D": 0},
{"A": 0}, {"B": 0}, {"C": 0}, {"D": 0},
{"A": 0}, {"B": 1}, {"C": 0}, {"D": 0}],
"key2": [{"A": 0}, {"B": 0}, {"C": 0}, {"D": 0},
{"A": 1}, {"B": 0}, {"C": 0}, {"D": 0},
{"A": 0}, {"B": 0}, {"C": 0}, {"D": 0},
{"A": 1}, {"B": 0}, {"C": 0}, {"D": 0},
{"A": 0}, {"B": 0}, {"C": 0}, {"D": 0}],
"key :"..etc..}
My final output would look:
new_dict = {"key1": [{"A": 0}, {"B": 2}, {"C": 0}, {"D": 0}],
"key2": [{"A": 2}, {"B": 0}, {"C": 0}, {"D": 0}]}
I tried:
new_dict = defaultdict(int)
f_dict = {}
for org in my_dict.keys():
f_dict[str(org)] = []
for key, dict_list in my_dict.items():
for d in dict_list:
for k, v in d.items():
new_dict[k] = v
f_dict[str(org)].append(dict(new_dict))
CodePudding user response:
There are several levels to this:
Outer level: dictionary with two unique keys
Each of those dictionaries holds a list (which I called dict_list
) of smaller dictionaries
from collections import defaultdict
# initialize a new default dictionary
# this allows you to initialize each new key with a value of zero
new_dict = defaultdict(int)
for key, dict_list in my_dict.items():
for d in dict_list:
for k, v in d.items():
new_dict[k] = v
Edit 1:
I didn't understand the question. It looks like you want to preserve the outer dictionary's keys.
from collections import defaultdict
new_dict = {}
for key, dict_list in my_dict.items():
new_dict[key] = defaultdict(int)
for d in dict_list:
for k, v in d.items():
new_dict[key][k] = v
This would output new_dict in the format:
new_dict = {'key1': {'A': 0, 'B': 2...} 'key2': {...}}
If you want key1 and key2 to contain a list of dicts, after the previous code, you can add:
for key, d in new_dict.items():
l = []
for k, v in d.items():
l.append({k: v})
new_dict[key] = l
This would output new_dict in the format:
new_dict = {'key1': [{'A': 0}, {'B': 2},...], 'key2': [{...}, ...]}
CodePudding user response:
TL;DR
Use @Cat's solution if you want
f: Dict[str, List[Dict[str, int]]] -> Dict[str, Dict[str, int]]
Use mine if you want
f: Dict[str, List[Dict[str, int]]] -> Dict[str, List[Dict[str, int]]]
Solution
Might be overkill, but I wanted to mess around with some functions I rarely use. To be honest, I'm not totally sure I annotated the types correctly.
import itertools, functools
from typing import Iterator, Iterable, List
def tuple_sum(t1: tuple, t2: tuple) -> tuple:
"""Assumes both `t1` and `t2` are 2-tuples and that they match in the first element."""
(key, x), (_, y) = t1, t2
return (key, x y)
def tuple_union(tuple_group: itertools.groupby) -> tuple:
"""Unions a group of tuples with matching 'keys', with `tuple_sum` as aggregator."""
return functools.reduce(tuple_sum, tuple_group)
def group_tuples(tuples: Iterable[tuple]) -> itertools.groupby:
"""Yields tuples grouped by key. Assumes `tuples` are already sorted by key."""
yield from (g for _, g in itertools.groupby(tuples, key=lambda t: t[0]))
def package_tuples(tuple_iterator: Iterator[tuple]) -> List[dict]:
"""Packages each tuple in `tuple_iterator` as a standalone `dict`"""
return [dict((t,)) for t in tuple_iterator]
def consolidate(d_list: List[dict]) -> List[dict]:
"""Groups dictionaries with matching keys, then sums their values."""
tuples = []
for d in d_list:
tuples.extend(d.items())
sorted_tuples = sorted(tuples, key=lambda t: t[0])
groups = group_tuples(sorted_tuples)
return package_tuples(tuple_union(g) for g in groups)
Usage
>>> {key: consolidate(d_list) for key, d_list in my_dict.items()}
{'key1': [{'A': 0}, {'B': 2}, {'C': 0}, {'D': 0}],
'key2': [{'A': 2}, {'B': 0}, {'C': 0}, {'D': 0}]}
Explanation
- The
tuple_sum()
function is hopefully pretty clear, it takes two 2-tuples and sums their second element. It assumes the two tuples passed have matching "keys" where the "key" is considered the first element. - The
tuple_union()
function takes a group of tuples whose keys match, e.g.,[("a", 1), ("a", 5), ("a", 3)]
and sums them usingtuple_sum()
, returning a single tuple e.g.,("a", 9)
. - The
group_tuples()
function takes a sorted iterable of tuples and returns a generator which yields the groups. An iterable of tuples is considered "sorted" when all tuples with matching keys are adjacent, e.g.:[("a", 1), ("a", 5), ("b", 3), ("b", 7), ("b" 3), ("c", 1)]
. Theitertools.groupby()
function returns an iterator which yields those groups, e.g.("a", 1), ("a", 5)
, and then("b", 3), ("b", 7), ("b" 3)
. - The
package_tuples()
function takes an iterator which yields tuples and packages each tuple into a dictionary, e.g.,dict((("a", 9),)) -> {"a": 9}
.
The way the consolidate()
function works is by taking a list of dictionaries and "melting" them all together as one big list of tuples. Then it sorts those tuples, then passes those sorted tuples to the group_tuples
generator.
Alternative output structure
If you want this as output instead:
{'key1': {'A': 0, 'B': 2, 'C': 0, 'D': 0},
'key2': {'A': 2, 'B': 0, 'C': 0, 'D': 0}}
You can modify the consolidate()
function as follows:
def consolidate(d_list: List[dict]) -> List[dict]:
"""Groups dictionaries with matching keys, then sums their values."""
tuples = []
for d in d_list:
tuples.extend(d.items())
sorted_tuples = sorted(tuples, key=lambda t: t[0])
return dict(tuple_union(g) for g in group_tuples(sorted_tuples))
This also means you can get rid of the package_tuples()
function altogether. At this point though, @Cat's solution is much more concise as this type of consolidation is much simpler.