How can I merge two dictionaries where their trees have a matching key?

Viewed 94

I'd like to merge these two dicts where they meet, on the "family" key. I'm working on a recursive function to make the merge happen but I wonder if there isn't a better way. I was looking at collections.chainmap and it seemed close that what I'm doing but perhaps overkill.

Input:

xe-3/0/2:
  description:
  unit:
    '0':
      family:
        inet4:
            address: 10.0.0.1
xe-3/0/2:
  description:
  unit:
    '0':
      family:
        inet6:
            address: FE80::

Output:

xe-3/0/2:
  description:
  unit:
    '0':
      family:
        inet4:
            address: 10.0.0.1
        inet6:
            address: FE80::

I'm trying to get better at python by using more tricks and built-in functions but I don't think I have the right approach for discovering what I can do instead of stacking a bunch of for loops.

Thanks,

EDIT:

This function works perfectly for my case, with the following assumptions:

D1 will always be deeper and wider. D2 will always have a single tree.

def join_dicts(d1, d2):
  def recurse_join(d1, d2):
    if not isinstance(d1, dict) and not isinstance(d2, dict):
        return [d1, d2]
    if not isinstance(d1, dict):
        return {**{d1 : ""}, **d2}
    if not isinstance(d2, dict):
        return {**d1, **{d2 : ""}}

    for key in d2:
        if key not in d1:
            return {**d1, **d2}
        else:
            d1[key] = recurse_join(d1[key], d2[key])
    return d1
return recurse_join(d1, d2)
3 Answers

You can use recursion with collections.defaultdict:

import yaml, collections
d1 = yaml.safe_load('''xe-3/0/2:
  description:
  unit:
    '0':
      family:
        inet4:
            address: 10.0.0.1''')
d2 = yaml.safe_load('''xe-3/0/2:
  description:
  unit:
    '0':
      family:
        inet6:
            address: FE80''')

def merge(*args):
   if any(not isinstance(i, dict) for i in args):
       return args[0] if len(args) == 1 else list(args)
   d = collections.defaultdict(list)
   for i in args:
      for a, b in i.items():
         d[a].append(b)
   return {a:merge(*b) for a, b in d.items()}

print(yaml.dump(merge(d1, d2)))

Output:

xe-3/0/2:
  description:
  unit:
    '0':
      family:
        inet4:
          address: 10.0.0.1
        inet6:
          address: FE80

I don't see why recursion wouldn't work in this case - no libraries required.

d1 = {
    "xe-3/0/2": {
        "description": {"unit": {"0": {"family": {"inet4": {"address": "10.0.0.1"}}}}}
    }
}
d2 = {
    "xe-3/0/2": {
        "description": {"unit": {"0": {"family": {"inet6": {"address": "FE80::"}}}}}
    }
}

def join_dicts(d1, d2):
    def recurse_join(d1, d2):
        # d1 must have greater depth than d2
        for key in d1:
            try:
                if not d2.get(key):
                    return {**d1, **d2}
            except AttributeError:
                return {**d1, **{d2: ""}}
            d1[key] = recurse_join(d1[key], d2[key])
            return d1
        
    def recurse_size(d, count=0):
        # Determines depth of dictionary.
        if not isinstance(d, dict):
            return count
        for key in d:
            return recurse_size(d[key], count+1)
    
    if recurse_size(d1) > recurse_size(d2):
        return recurse_join(d1, d2)
    return recurse_join(d2, d1)

print(join_dicts(d1, d2))

Will result in:

{
    "xe-3/0/2": {
        "description": {
            "unit": {
                "0": {
                    "family": {
                        "inet4": {"address": "10.0.0.1"},
                        "inet6": {"address": "FE80::"},
                    }
                }
            }
        }
    }
}

You should be able to do this with just TWO for loops given your example data:

originalDict

dictToMergeIn


for key in dictToMergeIn do:
    var item = dictToMergeIn[key]
    if key in originalDict do:
        for inet in item[unit]['0'][family] do
            originalDict[key][unit]['0'][family][inet] = item[unit]['0'][family][inet] 
    else:
        originalDict[key] = item

I think you are right that you will need more for loops if you have multiple units, for example. Share what you've tried code wise if you want more help.

Related