Home > Mobile >  Looping thought dictionary elements and combining (also adding) on similar keys
Looping thought dictionary elements and combining (also adding) on similar keys

Time:10-02

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

  1. 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.
  2. The tuple_union() function takes a group of tuples whose keys match, e.g., [("a", 1), ("a", 5), ("a", 3)] and sums them using tuple_sum(), returning a single tuple e.g., ("a", 9).
  3. 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)]. The itertools.groupby() function returns an iterator which yields those groups, e.g. ("a", 1), ("a", 5), and then ("b", 3), ("b", 7), ("b" 3).
  4. 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.

  • Related