【问题标题】:Merging two dicts in python with no duplication permitted在python中合并两个字典,不允许重复
【发布时间】:2015-07-09 16:35:27
【问题描述】:

此问题与类似的字典合并问题的不同之处在于,冲突的重复项应该失败,或者返回 False。其他解决方案使用优先规则来决定如何管理一个键何时可能映射到两个不同的变量。

如何在 python 中有效地合并两个字典。例如,考虑:

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

所以,合并字典 1 和 2 的结果是

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

但是 1 和 3 或 2 和 3 的合并应该会失败,因为 z 有冲突。

我的解决办法是:

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

有什么改进吗?

【问题讨论】:

    标签: python dictionary merge


    【解决方案1】:

    在这里使用dictionary views;它们让您将字典键视为集合:

    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)
    

    上面的代码只测试引用不匹配值的共享键;合并本身最好留给dict() 函数。

    我让这个函数在 Python 2 和 Python 3 上都能工作;如果您只需要支持其中一个,请删除try..except 并将intersection 替换为相关表达式。在 Python 3 中,dict.keys() 方法默认返回一个字典视图。此外,在仅 Python 3 的代码中,我会使用 {**d1, **d2} 扩展,它更快、更简洁,并且不仅限于字符串键。

    您可以想象这是一个单线; Python 3 版本:

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

    如果您只需要支持 Python 3.9 或更高版本,您可以使用| dictionary merge operator

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

    演示:

    >>> 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)
    {}
    

    【讨论】:

    • 由于这个问题的目标是找到最 Pythonic 的方式,我很欣赏 Python 2 和 3 的答案。我需要在这里挖掘和学习的是 dict(d1,** d2) 语义。谢谢大家的帮助。
    • 至于dict(d1, **d2),见What does ** (double star) and * (star) do for Python parameters?dict() function documentationdict(d1) 仅创建 d1 的副本。 dict(**d2) 会以一种迂回的方式创建d2 的副本。 dict(d1, **d2) 创建d1 的副本,添加d2 的键值对。
    【解决方案2】:
    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)
    

    【讨论】:

    • 这会改变 d1 并将值限制为仅可散列的对象。
    • 另外,您可以放弃.keys() 调用,因为这些调用完全多余且效率低下。除此之外,字典视图已经设置了支持交叉点的对象而无需太多调用,您可以将其重新设计为更高效、更简洁的版本。
    • 感谢 Martijn 提供有见地的 cmets,我已经发布了编辑
    • 这无疑是最快的方法,这要归功于在 KeysViewItemsView 容器上完全遵循基于 C 的集合操作。 @MartijnPieters 为胜利...又一次。
    • @CecilCurry 是的,这就是我在自己的答案中也使用它们的原因。如果您只使用 Python 3.9 或更新版本,也请使用 | 而不是 dict(d1. **d2) 以获得更高的可读性、更快的速度并且不受仅限字符串键的限制!
    【解决方案3】:

    稍微不同的方法(预检查):

    d1={'x':'a','y':'b','z':'c'}
    d2={'z':'c','w':'r'}
    d3={'z':'d','w':'r'}
    
    def merge(d1, d2):
        for (k1,v1) in d1.items():
            if k1 in d2 and v1 != d2[k1]:
                raise ValueError
        ret = d1.copy()
        ret.update(d2)
        return ret
    
    print(merge(d1,d2))
    print(merge(d1,d3))
    

    【讨论】:

      【解决方案4】:

      为什么不使用 set ?

      #!/usr/bin/python
      
      d1={'x':'a','y':'b','z':'c'}
      d2={'w':'r'}
      d3={'z':'d','w':'r'}
      d4={'x':'a','y':'b','z':'c'}
      
      def merge_dicts(d1, d2):
          dicts = d1.items() + d2.items()
          if len(dicts) != len(set(dicts)):
              raise ValueError
          else:
              return dict(set(dicts))
      print merge_dicts(d1, d2)
      print merge_dicts(d1, d3)
      try:
          print merge_dicts(d1, d4)
      except:
          print "Failed"
      
      $ python foo.py
      {'y': 'b', 'x': 'a', 'z': 'c', 'w': 'r'}
      {'y': 'b', 'x': 'a', 'z': 'd', 'w': 'r'}
      Failed
      

      编辑:

      确实,这不适用于不可散列的值,这个可以:

      #!/usr/bin/python
      # coding: utf-8 
      
      #!/usr/bin/python
      
      d1={'x':'a','y':'b','z':'c'}
      d2={'w':'r'}
      d3={'z':'d','w':'r'}
      d4={'x':'a','y':'b','z':'c'}
      
      def merge_dicts(d1, d2):
          merged= d1.copy()
          for k, v in d2.iteritems():
              if k in merged:
                  raise ValueError
              else:
                  merged[k] = v 
          return merged
      
      for one, two in [(d1, d2), (d1, d3), (d1, d4)]:
          try:
              print merge_dicts(one, two)
          except:
              print "Merge Failed for %s with %s" %(one, two)
      

      【讨论】:

      • 这要求 是可散列的。例如,如果字典包含其他字典或列表或集合,则不能使用它。
      • Indend,第一个值必须是可散列的。
      • 您的第二个版本再次不允许具有相同值的共享密钥。
      • 我没有使用 set 只是因为我不确定 set 操作是否会重复其他管理计算时间的工作。但是,我更喜欢使用集合操作,因为它会使代码更加简洁和富有表现力。
      【解决方案5】:

      这就是你想要的:

      def merge_dicts(d1, d2):
          # Join dicts and get rid of non-conflicting dups
          elems = set(d1.items()) | set(d2.items())
      
          # Construct join dict
          res = {}
          for k, v in elems:
              if k in res.keys():
                  return dict()  # conflicting dup found
              res[k] = v;
      
          return res
      

      【讨论】:

      • 这将您限制为不可变的;您不能将其与任一字典中的列表、集合或字典一起使用。
      • 此外,在 Python 3 中,dict.items()已经设置为。在 Python 2 中,您可以使用 dict.viewitems(),其额外优势在于它像集合一样操作,而值不必是不可变的
      猜你喜欢
      • 1970-01-01
      • 2012-09-12
      • 2019-01-05
      • 2022-12-26
      • 2023-03-28
      • 1970-01-01
      • 2021-11-05
      • 1970-01-01
      • 2012-04-19
      相关资源
      最近更新 更多