比较两个大型字典,并创建它们共有键的值列表。

20

我有两个字典如下:

dict1 = { (1,2) : 2, (2,3): 3, (1,3): 3}
dict2 = { (1,2) : 1, (1,3): 2}

我希望得到的输出是两个值列表,这些列表包含存在于两个字典中的项:

[2,3]
[1,2]

我现在正在做的事情大概是这样的:

list1 = []
list2 = []

for key in dict1.keys():
    if key in dict2.keys():
        list1.append(dict1.get(key))
        list2.append(dict2.get(key))

这段代码运行时间太长了,这不是我想要的。我在想是否有更高效的方法可以实现它?

4个回答

28
commons = set(dict1).intersection(set(dict2))
list1 = [dict1[k] for k in commons]
list2 = [dict2[k] for k in commons]

我正准备回答类似这样的内容。你比我快了! :) - DJGrandpaJ
1
你比我快了一秒钟。但是你不需要调用keys或第二个set。 - Pedro Werneck
3
@BlackBear —— 你实际上可以在不构建额外集合的情况下获取交集。在Python 2.x上,可以使用common = dict1.viewkeys() & dict2 ,在Python 3.x上则是 common = dict1.keys() & dict2 - mgilson
@BlackBear 谢谢,非常有效。你可能想把“common”改名为“commons”,或者反过来。 - ahajib
@mgilson,谢谢你的提示 :) (我不会编辑,因为你的回答已经很全面了) - BlackBear

14

不要使用 dict.keys。在 Python2.x 中,每次调用它都会创建一个新的列表(这是一个 O(N) 操作 -- 而且 list.__contains__ 在平均情况下也是另一个 O(N) 操作)。只要依赖于字典是可迭代容器并且直接支持 O(1) 查找的事实即可:

list1 = []
list2 = []

for key in dict1:
    if key in dict2:
        list1.append(dict1.get(key))
        list2.append(dict2.get(key))
请注意,在Python 2.7上,您可以使用viewkeys直接获取交集:
>>> a = {'foo': 'bar', 'baz': 'qux'}
>>> b = {'foo': 'bar'}
>>> a.viewkeys() & b
set(['foo'])

在 Python3.x 中,你可以在这里使用 keys 而不是 viewkeys

for key in dict1.viewkeys() & dict2:
    list1.append(dict1[key]))
    list2.append(dict2[key]))

我非常喜欢这种方法,因为它避免使用额外的数据结构,并充分利用了字典的对象方法。 - Iron Fist
非常有用,尤其是在比较两个以上的字典时。谢谢。 - ahajib
为什么甚至需要使用 .keys(),而且只使用一个字典就足够了? - Piotr Dobrogost
@PiotrDobrogost -- 在我看来,在python2.x中,几乎没有使用.keys()的好理由。如果你想要一个字典键的列表,那么你可以使用list(d),因为它在python3.x上也适用。如果你想要迭代一个字典的键,你只需要使用for key in d: ...。在python3.x中,d.keys和python2.7中的d.viewkeys做相同的事情。这个方法是有用的,因为它更有效率,并且在很多方面像一个set。(请参见我上面计算两个字典之间共同键的示例)。 - mgilson
@mgilson,我想问一下为什么你在dict1上调用了viewkeys()/keys(),但没有在dict2上调用?此外,&运算符不应该自动适用于每个容器吗?这样就可以只调用dict1 & dict2了。 - Piotr Dobrogost
@PiotrDobrogost dict1&dict2 不起作用。dict1.viewkeys()&dict2 起作用是因为字典实现了一个由 .viewkeys() 返回的 "keys_view" 对象。dict1.viewkeys()&dict2.viewkeys() 也可以工作 - 如果我没记错,keys_view 实例可以与任何可迭代对象 &。我一直觉得这有点不稳定,因为 keys_view 显然是模仿 set 的 - 而 set 只能与另一个 set &(尽管它们有 .intersection 方法,可以与任何可迭代对象一起使用...)我实际上希望看到 keys_view 对象的行为更像 set - mgilson

4
您可以在zip()函数中使用列表推导式:
>>> vals1, vals2 = zip(*[(dict1[k], v) for k, v in dict2.items() if k in dict1])
>>> 
>>> vals1
(2, 3)
>>> vals2
(1, 2)

或者以更加功能化的方式,使用视图对象和operator.itemgetter(),您可以执行以下操作:
>>> from operator import itemgetter
>>> intersect = dict1.viewkeys() & dict2.viewkeys()
>>> itemgetter(*intersect)(dict1)
(2, 3)
>>> itemgetter(*intersect)(dict2)
(1, 2)

参考答案的基准测试:

from timeit import timeit


inp1 = """
commons = set(dict1).intersection(set(dict2))
list1 = [dict1[k] for k in commons]
list2 = [dict2[k] for k in commons]
   """

inp2 = """
zip(*[(dict1[k], v) for k, v in dict2.items() if k in dict1])
   """
inp3 = """
intersect = dict1.viewkeys() & dict2.viewkeys()
itemgetter(*intersect)(dict1)
itemgetter(*intersect)(dict2)
"""
dict1 = {(1, 2): 2, (2, 3): 3, (1, 3): 3}
dict2 = {(1, 2): 1, (1, 3): 2}
print 'inp1 ->', timeit(stmt=inp1,
                        number=1000000,
                        setup="dict1 = {}; dict2 = {}".format(dict1, dict2))
print 'inp2 ->', timeit(stmt=inp2,
                        number=1000000,
                        setup="dict1 = {}; dict2 = {}".format(dict1, dict2))
print 'inp3 ->', timeit(stmt=inp3,
                        number=1000000,
                        setup="dict1 = {}; dict2 = {};from operator import itemgetter".format(dict1, dict2))

输出:

inp1 -> 0.000132083892822
inp2 -> 0.000128984451294
inp3 -> 0.000160932540894

对于长度为10000且具有随机生成项的字典,在100个循环中:

inp1 -> 1.18336105347
inp2 -> 1.00519990921
inp3 -> 1.52266311646

编辑:

正如@Davidmh在评论中提到的,为了拒绝采用第二种方法引发异常,您可以将代码包装在try-except表达式中:

try:
    intersect = dict1.viewkeys() & dict2.viewkeys()
    vals1 = itemgetter(*intersect)(dict1)
    vals2 = itemgetter(*intersect)(dict2)
except TypeError:
    vals1 = vals2 = []

我认为被接受的答案的可读性比大约10%的时间差更重要;-) - mgilson
你从未测试第三个选项,只测试了第二个选项两次。此外,如果没有公共键,第三个选项会崩溃。 - Davidmh
@Davidmh 是的,我刚刚更新了答案。感谢您的评论。 - Mazdak

0

这应该使用Python3中的keys和Python2中的viewkeys来完成。这些是行为类似于集合的视图对象,并且构造它们不需要额外的努力...它们只是底层字典键的“视图”。这样可以节省构造set对象的开销。

common = dict1.viewkeys() & dict2.viewkeys()
list1 = [dict1[k] for k in common]
list2 = [dict2[k] for k in common]

dict_views对象可以直接与字典进行交集操作,因此以下代码也可以正常工作。不过我更喜欢之前的示例。

common = dict1.viewkeys() & dict2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接