【问题标题】:Find unique (key: value) pair given N dictionaries in python在python中找到给定N个字典的唯一(键:值)对
【发布时间】:2017-03-01 20:08:05
【问题描述】:

我想找到一种简单和/或快速的方法来查找所有 common 对(对:值)给定 Python 中的 N 个字典。 (3.X 最好

问题

给定一组 3 个dicts(但它可以是任何dict,这只是示例)

n1 = {'a': 1, 'b': 2, 'c': 3}
n2 = {'a': 1, 'b': 4, 'c': 3, 'd': 4}
n3 = {'a': 1, 'b': 2, 'c': 3, 'd': 4}

n1n2n3 的公共(键:值)的结果 应该是:

({'a': 1, 'c': 3})

对于n2n3 应该是

({'a': 1, 'c': 3, 'd': 4})

我首先考虑使用 brute force 算法来检查每个字典的每一对(键:值)

这是一个使用递归算法的实现

解决方案 A

list_dict = [n1, n2, n3]

def finding_uniquness(ls):

    def recursion(ls, result):
        if not ls:
            return result
        result = {k: v  for k, v in result.items()  for k1, v1 in ls[0].items() if k == k1 and v == v1}
        return recursion(ls[1:], result)

    return recursion(ls[1:], ls[0])


finding_uniquness(list_dict)
# {'c': 3, 'a': 1}

但不易理解,复杂度高 (我不确定如何计算复杂度;但是由于我们比较了所有dict 上的所有元素,所以复杂度应该是O(N²)?)

然后,我想到了Sets。因为它可以自然地比较所有元素

解决方案 B

import functools

list_dict = [n1, n2, n3]
set_list = [set(n.items()) for n in list_dict]

functools.reduce(lambda x, y: x & y, set_list)
 # {('a', 1), ('c', 3)}

不幸的是,它比以前的解决方案要好得多,当key 之一具有list 作为值时,它会引发错误:

>>> n = {'a': [], 'b': 2, 'c': 3}
>>> set(n.items()) 

TypeError: unhashable type: 'list'

我的问题是双重的:

  • 还有比SOLUTION A更好的算法吗?
  • 或者有没有办法通过解决方案B避免TypeError

当然,欢迎任何其他评论。

【问题讨论】:

  • 您多次说“唯一”,但实际上您似乎在寻找 common 键:值对?换句话说,所有字典共享的那个。
  • 您的字典中有哪些类型?平面列表?列表列表?因为您可以事先转换为tuple,这样哈希问题就消失了。
  • @roganjosh 例如,这将使6"6" 相等。 repr 可能更安全
  • 专业提示:在 python3 中,.items().values().keys() 已经返回 set-like 对象,因此无需执行 set(d.items())!跨度>
  • 顺便说一句,好问题。展示研究,并产生一些不错的答案(这是一个标志:))

标签: python algorithm python-3.x dictionary


【解决方案1】:

更简单高效的方法:

>>> {k: v
     for k, v in list_dict[0].items()
     if all(k in d and d[k] == v
            for d in list_dict[1:])}
{'c': 3, 'a': 1}

list_dict[1:] 使用一个额外的变量可能是有益的,否则all 的短路会有些浪费。或者,如果您之后不需要该列表,您可以直接弹出“主”字典:

>>> {k: v
     for k, v in list_dict.pop().items()
     if all(k in d and d[k] == v
            for d in list_dict)}
{'c': 3, 'a': 1}

或者按照@Jean-FrançoisFabre 的建议,将get 与字典中不能包含的默认值一起使用:

>>> marker = object()
>>> {k: v
         for k, v in list_dict.pop().items()
         if all(d.get(k, marker) == v
                for d in list_dict)}
{'c': 3, 'a': 1}

【讨论】:

  • 我在阅读代码时理解的那个! (即使在这么晚的时候)
  • nitpick: k in d and d[k] == v 可以是d.get(k,marker) == v 其中marker=object(),以处理键可以是None 的情况。那可能会更快。
  • @Jean-FrançoisFabre 啊,是啊,我想过get,但因为None而拒绝了它,忘记了你提到的技巧。
  • 不错。为了清楚起见,编辑了更多。几周前在 SO 上阅读这个标记技巧。
  • @Jean-FrançoisFabre 你还记得提出这个问题的问题吗?我了解它在这种情况下是如何工作的,但我对问题/答案是否详细说明它感兴趣。
【解决方案2】:

如果无法散列的值是一个问题,您始终可以使用 .keys() 预先计算键的交集,然后仅比较与所有字典共有的键关联的值:

import operator as op
from functools import reduce

common_keys = reduce(op.and_, (d.keys() for d in my_dicts))
common_items = {}
for key in common_keys:
    value = my_dicts[0][key]
    if all(d[key] == value for d in my_dicts):
        common_items[key] = value

这应该比解决方案 a 快得多,比解决方案 b 慢,但适用于所有输入。

【讨论】:

    【解决方案3】:

    包含电池的版本。

    为了处理不可散列的类型,我们使用酸洗;用 dill 或 json 或任何其他可预测的序列化替换它。

    import collections
    import itertools
    import pickle  
    
    def findCommonPairs(dicts):
        all_pairs = itertools.chain(*[d.items() for d in dicts])
        cnt = collections.Counter(map(pickle.dumps, all_pairs))
        return [pickle.loads(pickled_pair)
                for pickled_pair, count in cnt.items()
                if count == len(dicts)]
    
    
    >>> findCommonPairs([n1, n2, n3])
    [('a', 1), ('c', 3)]
    
    >>> findCommonPairs([{'a': [1,2], 'b': [2,3]}, {'a': [1,2]}])
    [('a', [1, 2])]
    

    请注意,序列化只到此为止。例如,要正确比较 dicts if dicts,这些 dicts 必须在序列化之前变成(键,值)对并排序。任何相互引用的结构都可能有问题(或没有问题)。如果您关心这些问题,请用自定义的可预测序列化程序替换酸洗。

    【讨论】:

      猜你喜欢
      • 2023-04-06
      • 2022-12-06
      • 2012-05-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-29
      • 2023-04-08
      • 1970-01-01
      相关资源
      最近更新 更多