MarStarck
MarStarck

Reputation: 443

python merge dict iterately and elegantly?

I have 2 dicts like:

x = {2: {'a':1, 'b':1}}
y = {3: {'a':1}, 2: {'a':2, 'c':2}}

The merging result I want should be:

z = {3: {'a':1}, 2: {'a':3, 'b':1, 'c':2}}

I tried dict(x.items() + y.items()) and Counter, but only get

{2: {'a': 2, 'c': 2}, 3: {'a': 1}}

as a result.

How can I merge dict whose value is also a dict itself?


I have explained that the difference between How to merge two Python dictionaries in a single expression? is that my dict's value is also a dict.

Upvotes: 1

Views: 173

Answers (7)

Down the Stream
Down the Stream

Reputation: 697

# What about something like this:

# merge_nested_dicts.py

# tested under Python3.6
# assuming dict_02 overwrites dict_01
# one liner functional style
def deep_merge(dict_01, dict_02):
    return {k: {**dict_01.get(k), **dict_02.get(k)} if k in dict_01 and
                isinstance(dict_01.get(k), dict) and
                isinstance(dict_02.get(k), dict) else v 
                for k, v in {**dict_01, **dict_02}.items()}              

if __name__ == '__main__':
    y = {2: {'a': 1, 'b': 1}, 1: {'a': 1, 'b': 1}}
    x = {3: {'a': 1}, 2: {'a': 2, 'c': 2}}

    print(x)
    print(y)
    print(deep_merge(x, y))

'''
{3: {'a': 1}, 2: {'a': 2, 'c': 2}}
{2: {'a': 1, 'b': 1}, 1: {'a': 1, 'b': 1}}
{3: {'a': 1}, 2: {'a': 1, 'c': 2, 'b': 1}, 1: {'a': 1, 'b': 1}}
'''

Upvotes: 0

Stephen Paulger
Stephen Paulger

Reputation: 5343

I don't know what your definition of 'elegantly' is but assuming you mean readable then perhaps this way will suit you.

from collections import Counter


def counter_dict(in_dict):
    """
    Create a dict of Counters from a dict of dicts.
    """
    return dict((k, Counter(v)) for (k, v) in in_dict.items())


def merge_counter_dicts(a, b):
    """
    Create a dict of Counters from two dicts of Counters.
    Where keys exist in both counts are summed.
    """
    out_dict = a.copy()
    for k in b.keys():
        out_dict[k] = out_dict.setdefault(k, Counter()) + b[k]
    return out_dict

x = {2: {'a': 1, 'b': 1}}
y = {3: {'a': 1}, 2: {'a': 2, 'c': 2}}

xc = counter_dict(x)
xy = counter_dict(y)

print merge_counter_dicts(xc, xy)

Upvotes: 0

TMrtSmith
TMrtSmith

Reputation: 471

This works:

x = {2: {'a':1, 'b':1}}
y = {3: {'a':1}, 2: {'a':2, 'c':2}}


def key_merge (dict1, dict2):   
## function which checks for any matching keys, and merges them   
if len(dict1.keys()) == 0 or len(dict2.keys()) == 0:
    return {}
else:    
    for key in dict1.keys():
        if key in dict2.keys():
          return {key:{ k: dict1[key].get(k, 0) + dict2[key].get(k, 0) for k in set(dict1[key])|set(dict2[key]) }}

z = {**x, **y, **key_merge(x, y)}

Again, up to you if it's elegant!

Upvotes: 0

McGrady
McGrady

Reputation: 11477

What about this:

For Python2.x:

from collections import Counter

y = {2: {'a': 1, 'b': 1}, 1: {'a': 1, 'b': 1}}
x = {3: {'a': 1}, 2: {'a': 2, 'c': 2}}

t=x.copy()
print(dict({k: (dict(Counter(t.pop(k, None)) + Counter(v))) for k, v in y.items()},**t))

Result:

{1: {'a': 1, 'b': 1}, 2: {'a': 3, 'c': 2, 'b': 1}, 3: {'a': 1}}

For Python3.5+:

{**{k: (dict(Counter(t.pop(k, None)) + Counter(v))) for k, v in y.items()},**t}

Upvotes: 1

Jean-François Fabre
Jean-François Fabre

Reputation: 140186

Use collections.Counter to "merge" the items you want to merge (only the common keys), and leave the others intact, using a double ternary expression in a dict comprehension, iterating on the union of the keys:

import collections
x = {2: {'a':1, 'b':1}}
y = {3: {'a':1}, 2: {'a':2, 'c':2}}

# pre-compute common keys, one-liners are not the ultimate goal in life!
common_keys = set(x) & set(y)

# now generate the merged dict
result = {k:collections.Counter(x[k])+collections.Counter(y[k]) if k in common_keys else x[k] if k in x else y[k] for k in set(x)|set(y)}
print(result)

result:

{2: Counter({'a': 3, 'c': 2, 'b': 1}), 3: {'a': 1}}

Note: we could have avoided computing the common keys beforehand by replacing:

if k in common_keys

by

if k in x and k in y

Upvotes: 0

HolaYang
HolaYang

Reputation: 419

Suppose for any same depth for a certain key, in two dicts the values are of same type (both dict or both number, i don't know how to define merging a number into a dict).

def merge(d1, d2):
    for i in d2:
        if i not in d1:
            continue
       if isinstance(d1[i], dict) and isinstance(d2[i], dict):
            merge(d1[i], d2[i])
        else:
            d2[i] += d1[i]
        d1.pop(i)
    for j in d1:
        d2[j] = d1[j]

It's straightforward but maybe not elegant.

Upvotes: 0

Carles Mitjans
Carles Mitjans

Reputation: 4866

Here you have a possible solution. Although it doesn't use any library, it is quite complex.

Also it will work with any two dictionaries, independently on which is the largest.

{key: {ikey: x.get(key, {}).get(ikey, 0) + y.get(key, {}).get(ikey, 0)  
    for ikey in x.get(key, {}).keys()+y.get(key, {}).keys()} 
 for key in x.keys()+y.keys()}

Output:

{2: {'a': 3, 'b': 1, 'c': 2}, 3: {'a': 1}}

Upvotes: 0

Related Questions