Sem
Sem

Reputation: 33

Merging dict of dicts and sum values

I'm looking for a way to merge multiple dicts with each other, which contain nested dicts too. The number of nested dicts is not static but dynamic.

At the end the Final dict should contain all the dicts of dicts and the sum of their values:

COUNTRY1 = {'a': {'X': 10, 'Y': 18, 'Z': 17}, 'b': {'AA':{'AAx':45,'AAy':22},'BB':{'BBx':45,'BBy':22}}, 'c': 100}
COUNTRY2 = {'a': {'U': 12, 'V': 34, 'W': 23}, 'b': {'AA':{'AAz':23,'AAa':26},'BB':{'BBz':11,'BBa':15}}, 'c': 115}
COUNTRY3 = {'a': {'Y': 15, 'Z': 14, 'X': 12}, 'b': {'AA':{'AAx':45,'AAz':22},'BB':{'BBy':45,'BBz':22}}, 'c': 232}

# After merging the dictionaries the result should look like:
ALL
>>> {'a': {'X': 22, 'Y': 33, 'Z': 31, 'U': 12, 'V': 34, 'W': 23}, 'b': {'AA':{'AAx':90,'AAy':22,'AAz':45,'AAa':26},'BB':{'BBx':45,'BBy':67, 'BBz':33,'BBa':15}}, 'c': 447}

I tried the following code which allows nested dicts to a max of 3 nested dicts. Unfortunately the code doesn't do what I expected. Thereby it doesn't look very clean, I feel like this could be done with a recursive function, however I can't find a way to do it.

COUNTRIES = ['COUNTRY1','COUNTRY2', 'COUNTRY3']
ALL = {}
for COUNTRY_CODE in COUNTRIES:

    COUNTRY = pickle.load(open(COUNTRY_CODE+".p", "rb"))
    keys = COUNTRY.keys()
    for key in keys:
        try:
            keys2 = COUNTRY[key].keys()
            print(key, keys2)

            for key2 in keys2:
                try:
                    keys3 = COUNTRY[key][key2].keys()
                    print(key2, keys3)

                    for key3 in keys3:
                        try:
                            keys4 = COUNTRY[key][key2][key3].keys()
                            print(key3, keys4)
                        except:
                            print(key3, "NO KEY3")
                            if not key3 in ALL[key][key2]:
                                ALL[key][key2][key3] = COUNTRY[key][key2][key3]
                            else:
                                ALL[key][key2][key3] =+ COUNTRY[key][key2][key3]

                except:
                    print(key2, "NO KEY2")
                    if not key2 in ALL[key]:
                        ALL[key][key2] = COUNTRY[key][key2]
                    else:
                        ALL[key][key2] =+ COUNTRY[key][key2]

        except:
            print(key, "NO KEY")
            if not key in ALL:
                ALL[key] = COUNTRY[key]
            else:
                ALL[key] =+ COUNTRY[key]

print(ALL)

Upvotes: 3

Views: 795

Answers (2)

Pankaj Singhal
Pankaj Singhal

Reputation: 16053

Make two functions like below:

def cal_sum(lst):
    final_dict = dict()
    for l in lst:
        sum(final_dict,l)
    return final_dict

def sum(final_dict,iter_dict):
    for k, v in iter_dict.items():
        if isinstance(v, dict):
            sum(final_dict.setdefault(k, dict()), v)
        elif isinstance(v, int):
            final_dict[k] = final_dict.get(k, 0) + v

Calling the above code as follows produces the desired output:

>>> print(cal_sum([COUNTRY1, COUNTRY2, COUNTRY3]))
{'a': {'U': 12, 'W': 23, 'V': 34, 'Y': 33, 'X': 22, 'Z': 31}, 'c': 447, 'b': {'AA': {'AAa': 26, 'AAy': 22, 'AAx': 90, 'AAz': 45}, 'BB': {'BBa': 15, 'BBz': 33, 'BBy': 67, 'BBx': 45}}}

Upvotes: 2

Mad Physicist
Mad Physicist

Reputation: 114300

The issue is that you need to determine what to do with a dictionary key based on the type of the value. The basic idea is:

  • Input is a pair of dictionaries, output is the sum dictionary
  • Step along both input dictionaries
  • If a value is a dictionary, recurse
  • If a value is a number, add it to the other number

This is fairly easy to implement with a comprehension:

def add_dicts(d1, d2):
    def sum(v1, v2):
        if v2 is None:
            return v1
        try:
            return v1 + v2
        except TypeError:
            return add_dicts(v1, v2)
    result = d2.copy()
    result.update({k: sum(v, d2.get(k)) for k, v in d1.items()})
    return result

The copy ensures that any keys in d2 that are not also in d1 are simply copied over.

You can now sum as follows:

ALL = add_dicts(add_dicts(COUNTRY1, COUNTRY2), COUNTRY3)

More generally, you can use functools.reduce to do this for an indefinite number of dictionaries:

dicts = [COUNTRY1, COUNTRY2, COUNTRY3]
ALL = reduce(add_dicts, dicts)

Upvotes: 4

Related Questions