Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merging two dicts in python with no duplication permitted

This question differs from similar dictionary merge questions in that conflicting duplicates should fail, or return False. Other solutions use a precedence rule to decide how to manage when one key might be mapped to two different variables.

How do I merge two dicts efficiently in python. As an example, consider:

d1 = {'x': 'a', 'y': 'b', 'z': 'c'}
d2 = {'z': 'c', 'w': 'r'}
d3 = {'z': 'd', 'w': 'r'}

so, the result of merging dictionary 1 and 2 would be

{'x': 'a', 'y': 'b', 'z': 'c', 'w': 'r'}

but the merge of 1 and 3 or 2 and 3 should fail because z has a conflict.

My solution is:

def merge_dicts(d1,d2):
   k1=d1.keys()
   k2=d2.keys()
   unified_dict=dict()
   for k in k1:
       # look up in second dictionary
      if k in k2:
         pt=d2[k]  #pt stands for 'plain text'
         # if lookup is a contradiction, return empty dictionary
         #  don't even bother with partial results
         if pt!=d1[k]:
             return dict()
         else:
             unified_dict[k]=d1[k]  # safe: key is consistent
      else:
          unified_dict[k]=d1[k] # safe:  no key in k2

# get the rest
# already resolved intersection issues so just get set difference
   for k in d2.keys():
      if k not in d1.keys():
          unified_dict[k]=d2[k]

   return unified_dict

Any improvements?

like image 879
Michael Tuchman Avatar asked Jul 09 '15 16:07

Michael Tuchman


2 Answers

Use dictionary views here; they let you treat dictionary keys as sets:

def merge_dicts(d1, d2):
    try:
        # Python 2
        intersection = d1.viewkeys() & d2
    except AttributeError:
        intersection = d1.keys() & d2
       
    if any(d1[shared] != d2[shared] for shared in intersection):
        return {}  # empty result if there are conflicts

    # leave the rest to C code, execute a fast merge using dict()
    return dict(d1, **d2)

The above code only tests for shared keys referencing non-matching values; the merge itself is best just left to the dict() function.

I made the function work both on Python 2 and Python 3; if you only need to support one or the other, remove the try..except and replace intersection with the relevant expression. In Python 3 the dict.keys() method returns a dictionary view by default. Also, in Python 3-only code I’d use {**d1, **d2} expansion, which is a little faster, cleaner and is not limited to string keys only.

You could conceivably make this a one-liner; Python 3 version:

def merge_dicts(d1, d2):
    return (
        {} if any(d1[k] != d2[k] for k in d1.keys() & d2)
        else {**d1, **d2}
    )

If all you need to support is Python 3.9 or newer, you can use the | dictionary merge operator:

def merge_dicts(d1, d2):
   return (
       {} if any(d1[k] != d2[k] for k in d1.keys() & d2)
       else d1 | d2
   )

Demo:

>>> d1 = {'x': 'a', 'y': 'b', 'z': 'c'}
>>> d2 = {'z': 'c', 'w': 'r'}
>>> d3 = {'z': 'd', 'w': 'r'}
>>> merge_dicts(d1, d2)
{'y': 'b', 'x': 'a', 'z': 'c', 'w': 'r'}
>>> merge_dicts(d1, d3)
{}
>>> merge_dicts(d2, d3)
{}
like image 65
Martijn Pieters Avatar answered Nov 15 '22 01:11

Martijn Pieters


d1 = {'x': 'a', 'y': 'b', 'z': 'c'}                                                             
d2 = {'z': 'c', 'w': 'r'}
d3 = {'z': 'd', 'w': 'r'}

def dict_merge(d1, d2):
    """docstring for merge"""
    # doesn't work with python 3.x. Use keys(), items() instead
    if len(d1.viewkeys() & d2) != len(d1.viewitems() & d2.viewitems()):
        return {}
    else:
        result = dict(d1, **d2)
        return result

if __name__ == '__main__':
    print dict_merge(d1, d2)
like image 38
vHalaharvi Avatar answered Nov 15 '22 01:11

vHalaharvi