按键组合两个大型词典的最快方法是什么?

What is the fastest approach to combine two large dictionaries by key?

我有两本大词典。这是一个演示示例,但您可以想象每个字典都有接近 100k 条记录:

d1 = {
    '0001': [('skiing',0.789),('snow',0.65),('winter',0.56)],
    '0002': [('drama', 0.89),('comedy', 0.678),('action',-0.42),
             ('winter',-0.12),('kids',0.12)]
}

d2 = {
    '0001': [('action', 0.89),('funny', 0.58),('sports',0.12)],
    '0002': [('dark', 0.89),('Mystery', 0.678),('crime',0.12), ('adult',-0.423)]
}

我想要一个字典,其中包含每个字典中按键组合的值:

{
    '0001': [
        ('skiing', 0.789), ('snow', 0.65), ('winter', 0.56),
        [('action', 0.89), ('funny', 0.58), ('sports', 0.12)]
    ],
    '0002': [
        ('drama', 0.89), ('comedy', 0.678), ('action', -0.42),
        ('winter', -0.12), ('kids', 0.12),
        [('dark', 0.89), ('Mystery', 0.678), ('crime', 0.12), ('adult', -0.423)]
    ]
}

我要实现的方法是:

for key, value in d1.iteritems():
    if key in d2:
        d1[key].append(d2[key])

但是看了很多地方之后我发现iteritems()真的很慢而且实际上并没有使用C数据结构来做,而是使用了Python函数。我怎样才能快速有效地完成这个 combine/merge 过程?

我认为你需要合并 dicts

from collections import Counter
res = Counter(d1) + Counter(d2)
>>>res
Counter({'0001': [('skiing', 0.789), ('snow', 0.65), ('winter', 0.56 **...**

例如

from collections import Counter

d1 = {"a":[1,2], "b":[]}
d2 = {"a":[1,3], "b":[5,6]}

res = Counter(d1)+Counter(d2)

>>>res
Counter({'b': [5, 6], 'a': [1, 2, 1, 3]})

即使这种方法也支持 dicts 中不等数量的键,例如

d1 = {"a":[1,2], "b":[]}
d2 = {"a":[1,3], "b":[5,6], "c":["ff"]}

>>>res
Counter({'c': ['ff'], 'b': [5, 6], 'a': [1, 2, 1, 3]})
for k, v in d2.items():
    if k in d1:
        d1[k].extend(v)
    else:
        d1[k] = v  
d1 = {'0001': [('skiing',0.789),('snow',0.65),('winter',0.56)],'0002': [('drama', 0.89),('comedy', 0.678),('action',-0.42), ('winter',-0.12),('kids',0.12)]}
d2 = {'0001': [('action', 0.89),('funny', 0.58),('sports',0.12)],'0002': [('dark', 0.89),('Mystery', 0.678),('crime',0.12), ('adult',-0.423)]}

for x in set(d1).intersection(set(d2)):
    d1[x].extend(d2[x])

也许你可以试试这个程序。起初我得到 d1 和 d2 之间的交集,因此我可以为此任务执行最少的迭代次数。

您可以通过 -

>>> d1 = {'0001': [('skiing',0.789),('snow',0.65),('winter',0.56)],'0002': [('drama', 0.89),('comedy', 0.678),('action',-0.42), ('winter',-0.12),('kids',0.12)]}
>>> d2 = {'0001': [('action', 0.89),('funny', 0.58),('sports',0.12)],'0002': [('dark', 0.89),('Mystery', 0.678),('crime',0.12), ('adult',-0.423)]}
>>> dict( (n, d1.get(n, []) + d2.get(n, [])) for n in set(d1)|set(d2) )
{'0001': [('skiing', 0.789), ('snow', 0.65), ('winter', 0.56), ('action', 0.89), ('funny', 0.58), ('sports', 0.12)], '0002': [('drama', 0.89), ('comedy', 0.678), ('action', -0.42), ('winter', -0.12), ('kids', 0.12), ('dark', 0.89), ('Mystery', 0.678), ('crime', 0.12), ('adult', -0.423)]}

如果您的输入必须是听写,我认为您找不到比 iteritems 更快的东西了。如果一个字典的键明显少于另一个,您应该迭代较小的键以减少迭代次数。

任何涉及将 dict 转换为不同数据类型的解决方案都将花费更多的创建时间,而不是通过高效循环为您节省的时间。您必须迭代三次而不是迭代一次(两次创建两个新集合,一次 运行 您的合并)。

如果您在最初创建集合时确实可以选择使用不同的数据类型,则对列表的迭代(使用索引而不是键)会稍微快一些。当然,这意味着您将失去字典可能为您提供的所有其他优势。

timeit 给出了各种建议方法的以下速度,给定两个 200 个键的字典(两个字典的键相同):

  • 迭代项(给定 n 个问题):23.0725131035
  • : 31.7572350502
  • : 125.085702181
  • 遍历列表:16.3535020351

为了给集合交集另一个机会,只让 d2 的一半键真正匹配 d1 的键:

  • 迭代项:15.5433290005
  • 设置交集:22.1796240807

正如我们所见,创建两个集合的成本仍然超过了任何潜在的好处(至少在 Python 2 中是这样,其中 dict.keys() 给出了一个列表,而不是一个集合操作兼容的查看)。

旁注:追加与扩展

在您当前的代码示例中

for key,value in d1.iteritems():
    if key in d2:
        d1[key].append(d2[key])

您将 d2 的整个列表附加为 d1 列表的单个新项目,而不是合并列表([1,2].append([3,4]) == [1,2,[3,4]],而不是 [1,2,3,4] ).您可以从 d2 遍历列表并多次调用追加,但 extend() 会更快。