3

Is there an easy way to combine two dictionaries of dictionaries in Python? Here's what I need:

dict1 = {'A' : {'B' : 'C'}}
dict2 = {'A' : {'D' : 'E'}}

result = dict_union(dict1, dict2)
# => result = {'A' : {'B' : 'C', 'D' : 'E'}}

I created a brute-force function that does it, but I was looking for a more compact solution:

def dict_union(train, wagon):
    for key, val in wagon.iteritems():
        if not isinstance(val, dict):
            train[key] = val
        else:
            subdict = train.setdefault(key, {})
            dict_union(subdict, val)
Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
Cat
  • 7,042
  • 8
  • 34
  • 36
  • 4
    I'm not clear what you want to happen when the structure of the dicts doesn't match up. For instance, if dict3 = {'A': 'F'}, then using your version here, dict_union(dict3, dict2) throws a TypeError. Is that the desired behavior? – Cosmologicon Jun 06 '11 at 18:25
  • Related (but simpler): http://stackoverflow.com/questions/1031199/adding-dictionaries-in-python – Gilles 'SO- stop being evil' Jan 17 '12 at 13:29

6 Answers6

4

Here is a class, RUDict (for Recursive-Update dict) that implements the behaviour you're looking for:

class RUDict(dict):

    def __init__(self, *args, **kw):
        super(RUDict,self).__init__(*args, **kw)

    def update(self, E=None, **F):
        if E is not None:
            if 'keys' in dir(E) and callable(getattr(E, 'keys')):
                for k in E:
                    if k in self:  # existing ...must recurse into both sides
                        self.r_update(k, E)
                    else: # doesn't currently exist, just update
                        self[k] = E[k]
            else:
                for (k, v) in E:
                    self.r_update(k, {k:v})

        for k in F:
            self.r_update(k, {k:F[k]})

    def r_update(self, key, other_dict):
        if isinstance(self[key], dict) and isinstance(other_dict[key], dict):
            od = RUDict(self[key])
            nd = other_dict[key]
            od.update(nd)
            self[key] = od
        else:
            self[key] = other_dict[key]


def test():
    dict1 = {'A' : {'B' : 'C'}}
    dict2 = {'A' : {'D' : 'E'}}

    dx = RUDict(dict1)
    dx.update(dict2)
    print(dx)


if __name__ == '__main__':
    test()


>>> import RUDict
>>> RUDict.test()
{'A': {'B': 'C', 'D': 'E'}}
>>>
Gerrat
  • 28,863
  • 9
  • 73
  • 101
3

This solution is pretty compact. It's ugly, but you're asking for some rather complicated behavior:

dict_union = lambda d1,d2: dict((x,(dict_union(d1.get(x,{}),d2[x]) if
  isinstance(d2.get(x),dict) else d2.get(x,d1.get(x)))) for x in
  set(d1.keys()+d2.keys()))
Cosmologicon
  • 2,127
  • 1
  • 16
  • 18
1

My solution is designed to combine any number of dictionaries as you had and could probably be cut down to look neater by limiting it to combining only two dictionaries but the logic behind it should be fairly easy to use in your program.

def dictCompressor(*args):
    output = {x:{} for mydict in args for x,_ in mydict.items()}
    for mydict in args:
        for x,y in mydict.items():
            output[x].update(y)
    return output
mroduin44
  • 13
  • 4
0

You could subclass dict and wrap the original dict.update() method with a version which would call update() on the subdicts rather than directly overwriting subdicts. That may end up taking at least as much effort as your existing solution, though.

JAB
  • 20,783
  • 6
  • 71
  • 80
  • Yeah but you'd need to make sure that any dicts within the dicts that are being updated are also instances of your subclass. – Cosmologicon Jun 06 '11 at 18:14
0

Has to be recursive, since dictionaries may nest. Here's my first take on it, you probably want to define your behavior when dictionaries nest at different depths.

def join(A, B):
    if not isinstance(A, dict) or not isinstance(B, dict):
        return A or B
    return dict([(a, join(A.get(a), B.get(a))) for a in set(A.keys()) | set(B.keys())])

def main():
    A = {'A': {'B': 'C'}, 'D': {'X': 'Y'}}
    B = {'A': {'D': 'E'}}
    print join(A, B)
Matei Gruber
  • 189
  • 3
  • That returns a different result from the OP's function for arguments {'A': {'B': 'C'}} and {'A': 'F'}. I'm not sure the OP has thought this example through, though. – Cosmologicon Jun 06 '11 at 18:43
  • 1
    Yeah you're right, it's what I said about the behaviour when dicts have different depths. You have to define your own. I just return the first one which is non None `A or B`. You can do `B or A`, which is what his code does, or any other conflict resolution. – Matei Gruber Jun 06 '11 at 18:53
0

As for me there is not enaugh information but anyway please find my sample code below:

dict1 = {'A' : {'B' : 'C'}}
dict2 = {'A' : {'D' : 'E'}, 'B':{'C':'D'}}
output = {}
for key in (set(dict1) | set(dict2):
    output[key] = {}
    (key in dict1 and output[key].update(dict1.get(key)))
    (key in dict2 and output[key].update(dict2.get(key)))
Artsiom Rudzenka
  • 27,895
  • 4
  • 34
  • 52