【问题标题】:Intersecting two dictionaries交叉两个字典
【发布时间】:2013-09-04 10:17:49
【问题描述】:

我正在开发一个基于倒排索引的搜索程序。索引本身是一个字典,其键是术语,其值本身就是短文档的字典,ID 号作为键,它们的文本内容作为值。

要对两个术语执行“AND”搜索,因此我需要交叉它们的发布列表(字典)。在 Python 中执行此操作的一种清晰(不一定过于聪明)的方法是什么?我开始尝试iter

p1 = index[term1]  
p2 = index[term2]
i1 = iter(p1)
i2 = iter(p2)
while ...  # not sure of the 'iter != end 'syntax in this case
...

【问题讨论】:

  • {i:dict(p1[i],**p2[i]) for i in p1 if i in p2}
  • 我上面的评论将与您的术语词典相交,但联合合并您的发布列表....如果您还想在您的文档 ID 号上与您的发布列表相交,您可以使用 {term:{doc_id:p1[term][doc_id] for doc_id in p1[term] if doc_id in p2[term]} for term in p1 if term in p2}
  • 非常不清楚您想要的输出是什么(甚至您的输入是什么)。从你对问题的描述来看,听起来你有嵌套的字典,你想......“相交”,不管那是什么意思。但是您接受的答案不仅不会与嵌套字典相交,甚至不会与字典相交。它所做的只是与两个字典的 keys 相交。请澄清您的问题并使用minimal reproducible example 进行更新。

标签: python dictionary iteration intersection


【解决方案1】:

一个鲜为人知的事实是,您不需要构造 sets 来执行此操作:

在 Python 2 中:

In [78]: d1 = {'a': 1, 'b': 2}

In [79]: d2 = {'b': 2, 'c': 3}

In [80]: d1.viewkeys() & d2.viewkeys()
Out[80]: {'b'}

在 Python 3 中,将 viewkeys 替换为 keys;这同样适用于viewvaluesviewitems

来自viewitems的文档:

In [113]: d1.viewitems??
Type:       builtin_function_or_method
String Form:<built-in method viewitems of dict object at 0x64a61b0>
Docstring:  D.viewitems() -> a set-like object providing a view on D's items

对于较大的dicts,这也比构造sets 然后与它们相交要快一些:

In [122]: d1 = {i: rand() for i in range(10000)}

In [123]: d2 = {i: rand() for i in range(10000)}

In [124]: timeit d1.viewkeys() & d2.viewkeys()
1000 loops, best of 3: 714 µs per loop

In [125]: %%timeit
s1 = set(d1)
s2 = set(d2)
res = s1 & s2

1000 loops, best of 3: 805 µs per loop

For smaller `dict`s `set` construction is faster:

In [126]: d1 = {'a': 1, 'b': 2}

In [127]: d2 = {'b': 2, 'c': 3}

In [128]: timeit d1.viewkeys() & d2.viewkeys()
1000000 loops, best of 3: 591 ns per loop

In [129]: %%timeit
s1 = set(d1)
s2 = set(d2)
res = s1 & s2

1000000 loops, best of 3: 477 ns per loop

我们在这里比较的是纳秒,这对您来说可能很重要,也可能无关紧要。无论如何,你会得到一个set,所以使用viewkeys/keys 可以消除一些混乱。

【讨论】:

  • viewkeys() 是“2.7 版中的新功能”
  • 不知何故,这最终会比set(d1.keys()) &amp; set(d2.keys()) 方法的计算速度稍微 (~12%) 慢。但是,我不明白为什么会这样。
  • @Dan:即使它(可能)速度较慢,但​​在我看来它更像 Pythonic
  • 使用未来 (pip install future) 包,以下适用于 python 2 和 3:from future.utils import viewkeys; viewkeys(d1) &amp; viewkeys(d2)
【解决方案2】:

一般来说,要在Python中构造字典的交集,可以先使用&amp; operator计算字典键集合的交集(Python 3中的dictionary keys are set-like objects):

dict_a = {"a": 1, "b": 2}
dict_b = {"a": 2, "c": 3} 

intersection = dict_a.keys() & dict_b.keys()  # {'a'}

在 Python 2 上,您必须自己将字典键转换为集合:

keys_a = set(dict_a.keys())
keys_b = set(dict_b.keys())
intersection = keys_a & keys_b

然后给定键的交集,然后您可以构建您的值的交集,但是需要。您必须在此处做出选择,因为集合交集的概念不会告诉您如果相关值不同时该怎么做。 (这大概就是为什么在 Python 中没有直接为字典定义 &amp; 交集运算符的原因)。

在这种情况下,听起来您对同一个键的值是相等的,因此您可以从其中一个字典中选择值:

dict_of_dicts_a = {"a": {"x":1}, "b": {"y":3}}
dict_of_dicts_b = {"a": {"x":1}, "c": {"z":4}} 

shared_keys = dict_of_dicts_a.keys() & dict_of_dicts_b.keys()

# values equal so choose values from a:
dict_intersection = {k: dict_of_dicts_a[k] for k in shared_keys }  # {"a":{"x":1}}

其他合理的值组合方法取决于字典中值的类型以及它们所代表的内容。例如,您可能还需要字典的字典共享键的值的联合。由于字典的并集不依赖于值,因此定义明确,在 python 中您可以使用| 运算符获取它:

# union of values for each key in the intersection:
dict_intersection_2 = { k: dict_of_dicts_a[k] | dict_of_dicts_b[k] for k in shared_keys }

在这种情况下,如果两者中键 "a" 的字典值相同,则结果相同。

【讨论】:

  • 正如@Aran-Fey 在问题下方的评论中指出的那样,这找到了两个字典的键的交集,这可以说与两个字典的交集不同,因为它完全忽略与键关联的值。
  • @martineau 相交字典值的概念没有很好地定义 - 如果值不同,您可能希望对相交键的值做不同的事情。我添加了一些示例来解释这一点。
  • 您的更新是 IMO 的一项改进。 FWIW 确定将值考虑在内的交叉点的最直观(和简洁)的方法之一是用户@schwobaseggl 的answer 对类似问题的回答。
【解决方案3】:
In [1]: d1 = {'a':1, 'b':4, 'f':3}

In [2]: d2 = {'a':1, 'b':4, 'd':2}

In [3]: d = {x:d1[x] for x in d1 if x in d2}

In [4]: d
Out[4]: {'a': 1, 'b': 4}

【讨论】:

  • 这应该是答案,因为这是唯一一个以简单的方式显示如何获取交集字典而不是键列表的答案。
【解决方案4】:

在 Python 3 中,您可以使用

intersection = dict(dict1.items() & dict2.items())
union = dict(dict1.items() | dict2.items())
difference = dict(dict1.items() ^ dict2.items())

【讨论】:

  • 对于dict1 = {1: 1, 2:2}dict2 = {2:4, 3:3} intersection == set()。可能这不是 OP 想要的。
  • @JCode 我不确定我是否理解。 intersection = dict(...) 将其转换回 dict。此外,我刚刚用dict1 = {1: 1, 2: 2}dict2 = {2: 4, 3: 3}(上面的字典)对其进行了测试,intersection == set()False。这是一个空字典。
  • @DCPY 对不起,我错过了dict(...)。关键是,在我的例子中,结果是空的。考虑到 OP 接受的内容和.items() 的更常见用例交集没有什么意义,除非您正在寻找文字重复。
  • @JCode 在这种情况下,可以安全地假设所有值都是唯一的。然后,使用{v: k for k, v in dict1.items()} 代替dict1,使用{v: k for k, v in dict2.items()} 代替dict2。而且,如果您想要它是键 字典的情况,则使用答案中给出的交集和此评论中给出的交集的并集。
  • 这不适用于 dict().values() 中的任何类型的值
【解决方案5】:

好的,这是上面 Python3 中代码的通用版本。 它经过优化,可以使用足够快的理解和类似集合的 dict 视图。

函数与任意多个字典相交,并返回一个带有公共键的字典和每个公共键的一组公共值:

def dict_intersect(*dicts):
    comm_keys = dicts[0].keys()
    for d in dicts[1:]:
        # intersect keys first
        comm_keys &= d.keys()
    # then build a result dict with nested comprehension
    result = {key:{d[key] for d in dicts} for key in comm_keys}
    return result

使用示例:

a = {1: 'ba', 2: 'boon', 3: 'spam', 4:'eggs'}
b = {1: 'ham', 2:'baboon', 3: 'sausages'}
c = {1: 'more eggs', 3: 'cabbage'}

res = dict_intersect(a, b, c)
# Here is res (the order of values may vary) :
# {1: {'ham', 'more eggs', 'ba'}, 3: {'spam', 'sausages', 'cabbage'}}

这里的 dict 值必须是可散列的,如果不是,您可以简单地将设置括号 { } 更改为 list [ ]:

result = {key:[d[key] for d in dicts] for key in comm_keys}

【讨论】:

  • 我正在将 dict 列表传递给函数,但它给出了错误。我如何编辑上面的函数,以便传递 dict 的列表,并获得具有公共键和值的键:值对?
  • @learnningprogramming,希望你已经知道如何解决这个问题,但对于其他好奇的人:*dicts 作为函数参数意味着你需要传递大量参数,而不是它们的列表。如果您有lst = [dict1, dict2, dict3, ...],请使用dict_intersect(dict1, dict2, dict3, ...) 或解压列表dict_intersect(*lst)
【解决方案6】:

通过键和值查找完整的交集

d1 = {'a':1}
d2 = {'b':2, 'a':1}
{x:d1[x] for x in d1 if x in d2 and d1[x] == d2[x]}

>> {'a':1}

【讨论】:

    【解决方案7】:

    到目前为止,没有一个解决方案可以解决相交 N 个字典的一般情况。

    所以,如果要处理N任意字典的交集:

    from functools import reduce
    
    def dict_intersection(*dict_list):
        return reduce(lambda a,b: dict(a.items() & b.items()), dict_list)
    
    a = {k:k for k in range(0,5)} # {0: 0, 1: 1, 2: 2, 3: 3, 4: 4}
    b = {k:k for k in range(2,7)} # {2: 2, 3: 3, 4: 4, 5: 5, 6: 6}
    c = {k:k for k in range(3,8)} # {3: 3, 4: 4, 5: 5, 6: 6, 7: 7}
    
    dict_intersection(a,b,c)  # {3:3, 4:4}
    # or if you have a list of dicts
    dicts = [{k:k for k in range(0+n,5+n)} for n in (0,2,3)] # == [a,b,c]
    dict_intersection(*dicts) # {3:3, 4:4}
    

    使用functools.reduce 允许在字典列表的单次迭代中完成操作,而不是某些解决方案中的多个循环。它也不执行任何额外的条件语句。

    权衡

    dict_intersection_v1 更改为dict_intersection_v2 我们可以看到它对于更大的字典和/或字典列表执行得更快(设计适当的实验来测试哪个是更大的因素超出了此解决方案的范围)。这种性能提升是由于减少了字典实例化的数量。

    def dict_intersection_v1(*dict_list):
        return reduce(lambda a,b: dict(a.items() & b.items()),  dict_list)
    
    def dict_intersection_v2(*dict_list):
        return dict(reduce(lambda a,b: a & b, (d.items() for d in dict_list)))
    
    dict_lst1 = [{k:k for k in range(0+n,5+n)} for n in (0,2,3)] # = [a,b,c]
    dict_lst2 = [{k:k for k in range(0,50,n)} for n in range(1,5)]]
    dict_lst3 = [{k:k for k in range(0,500,n)} for n in range(40)]
    dict_lst4 = [{k:k for k in range(0+n,500+n)} for n in range(400)]
    
    dict list kv pair count dict_intersection_v1 dict_intersection_v2 relative difference
    1 15 808 ns ± 4.31 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) 821 ns ± 0.785 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) + 1.6%
    2 105 3.14 µs ± 11.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) 2.38 µs ± 5.76 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) -24.2%
    3 2155 36.9 µs ± 61.9 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each) 25.1 µs ± 131 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each) -32.0%
    4 200_000 9.08 ms ± 22 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 4.88 ms ± 5.31 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) -46.3%

    结果dict_lst1 的回归主要是由于在每个交集后创建字典之间的开销与生成器内dict.items() 调用造成的开销不同(以及python 的一般函数调用开销)。

    注意:我确实使用预先计算的 dict.items() 列表作为字典而不是 v2 即时构建生成器。

    我测试了在计时之外和计时内传入预先计算的列表,虽然它具有统计学意义,但分别小于 30 μs 和 10 μs。如果您想获得这些收益,请查看不同的语言或 Cython 可能会更好。

    【讨论】:

      【解决方案8】:

      只需使用一个简单的类来包装字典实例,该类可以获取您想要的两个值

      class DictionaryIntersection(object):
          def __init__(self,dictA,dictB):
              self.dictA = dictA
              self.dictB = dictB
      
          def __getitem__(self,attr):
              if attr not in self.dictA or attr not in self.dictB:
                  raise KeyError('Not in both dictionaries,key: %s' % attr)
      
              return self.dictA[attr],self.dictB[attr]
      
      x = {'foo' : 5, 'bar' :6}
      y = {'bar' : 'meow' , 'qux' : 8}
      
      z = DictionaryIntersection(x,y)
      
      print z['bar']
      

      【讨论】:

      • 我为什么要编写所有代码?如果我这样做了,我就不会在 python 中编码,而是在 java 中编码! :)
      【解决方案9】:

      您的问题不够精确,无法给出单一答案。

      1。关键路口

      如果您想与帖子 (credits to James) 中的 IDs 相交,请执行以下操作:

      common_ids = p1.keys() & p2.keys()
      

      但是,如果您想迭代文档,则必须考虑哪个帖子具有优先级,我假设它是 p1。要为common_ids 迭代文档,collections.ChainMap 将是最有用的:

      from collections import ChainMap
      intersection = {id: document
                      for id, document in ChainMap(p1, p2)
                      if id in common_ids}
      for id, document in intersection:
          ...
      

      或者如果您不想创建单独的intersection 字典:

      from collections import ChainMap
      posts = ChainMap(p1, p2)
      for id in common_ids:
          document = posts[id]
      

      2。项目交叉口

      如果您想将两个帖子的 items 相交,这意味着匹配 IDs 和文档,请使用下面的代码 (credits to DCPY)。但是,这仅在您查找重复项时才有用。

      duplicates = dict(p1.items() & p2.items())
      for id, document in duplicates:
          ...
      

      3。遍历 p1 'AND' p2

      如果通过 "'AND' 搜索" 并使用 iter 你打算搜索 both 帖子然后再次 collections.ChainMap 是最好的迭代(几乎)多个帖子中的所有项目:

      from collections import ChainMap
      for id, document in ChainMap(p1, p2):
          ...
      

      【讨论】:

        【解决方案10】:
        def two_keys(term_a, term_b, index):
            doc_ids = set(index[term_a].keys()) & set(index[term_b].keys())
            doc_store = index[term_a] # index[term_b] would work also
            return {doc_id: doc_store[doc_id] for doc_id in doc_ids}
        
        def n_keys(terms, index):
            doc_ids = set.intersection(*[set(index[term].keys()) for term in terms])
            doc_store = index[term[0]]
            return {doc_id: doc_store[doc_id] for doc_id in doc_ids}
        
        In [0]: index = {'a': {1: 'a b'}, 
                         'b': {1: 'a b'}}
        
        In [1]: two_keys('a','b', index)
        Out[1]: {1: 'a b'}
        
        In [2]: n_keys(['a','b'], index)
        Out[2]: {1: 'a b'}
        

        我建议将您的索引从

        index = {term: {doc_id: doc}}
        

        到两个索引,一个是术语,然后是一个单独的索引来保存值

        term_index = {term: set([doc_id])}
        doc_store = {doc_id: doc}
        

        这样您就不会存储相同数据的多个副本

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-06-23
          • 2018-05-02
          • 2018-06-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-11-27
          • 2018-01-21
          相关资源
          最近更新 更多