【问题标题】:Python: Sum the Values of Three Layer DictionariesPython:对三层字典的值求和
【发布时间】:2015-07-31 06:22:29
【问题描述】:

给定一个包含三层键的字典,求和值的最快方法是什么?这是我目前的方法:

from collections import defaultdict

dicts = [ {'a':{'b':{'c':1}}}, {'a':{'b':{'c':4, 'e':3}}} ]

def sum_three_deep_dict_values(dicts):
    '''Read in two dicts and return a dictionary that contains their outer-joined keys and value sums'''
    combined = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
    for d in dicts:
        for w1, val_dict in d.iteritems():          
            for w2 in val_dict.iterkeys():              
                for w3 in val_dict[w2].iterkeys():
                    combined[w1][w2][w3] += d[w1][w2][w3]
    return combined

print sum_three_deep_dict_values(dicts)

这里的预期输出是{'a': {'b': {'c': 5, 'e': 3}}},目标是对两个字典具有相同键的值求和(例如这里的d[a][b][c]),并将任一字典中剩余的键值对包含在输出字典中。

有许多关于 SO 的问题似乎可以回答以下问题:“应该如何对嵌套字典的值求和”?然而,昨晚通读它们,我发现的每一个都涉及一些奇怪的特殊情况或参数,例如“组合/忽略第 n 层键”或“在特殊位置应用 if 条件”。因此,我想提出一个简单的问题:在 Python 中对双嵌套字典的值求和的最佳方法是什么?

【问题讨论】:

  • 第一层和第二层可以有多个键吗?
  • 哦,是的。我的实际密钥大小约为 100,000 ; 1,000,000;第一层、第二层和第三层(分别)为 100,000,000。
  • 并且预期的输出是两层深度的字典,两层的键与原始字典相同,但最后一个值是第三层中值的总和?
  • 预期输出是{'a': {'b': {'c': 5, 'e': 3}}}吗?
  • 好的......另外,你目前的方法有什么问题?

标签: python dictionary


【解决方案1】:

我认为您目前的方法总体上是一个不错的方法。我的建议是尽可能多地消除字典查找。一起迭代键和值应该和只迭代键一样快,所以你不妨将它们组合起来。如果您这样做,则不需要对d[w1][w2][w3] 的最终调用,也不需要临时键查找。所以是这样的:

def sum_three_deep_dict_values(dicts):
    '''Read in two dicts and return a dictionary that contains 
       their outer-joined keys and value sums'''
    combined = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
    for layer0 in dicts:
        for k1, layer1 in layer0.iteritems():
            for k2, layer2 in layer1.iteritems():
                for k3, count in layer2.iteritems():
                    combined[k1][k2][k3] += count
    return combined

我冒昧地稍微更改了您的名称方案。

如果您在测试上述内容后仍然担心速度,您可能需要查看其他数据结构或第三方库。但在你这样做之前,请尝试PyPy——我发现它通常会在原版for 循环上提供至少 4 倍的加速。

另外,请对照您的原始代码对此进行测试。我认为我的上述推理成立,但仍然有点推测。我也很好奇别人的建议。在你工作的规模上,这可能是一个挑战! (出于好奇,您当前的代码需要多长时间?)

更新:我对此进行了测试,它确实更快,尽管只有一根头发:

>>> %timeit sum_three_deep_original(dicts)
1000 loops, best of 3: 1.38 ms per loop
>>> %timeit sum_three_deep_edited(dicts)
1000 loops, best of 3: 1.26 ms per loop

我猜您的应用程序需要更快的速度。我用 PyPy 试过,我也用 cython 编译它(但没有任何修改或类型注释)。 PyPy 以 66% 的加速率获胜。再次是普通的python(这次参数略有不同):

:~ $ python -c 'from tdsum import test; test()'
1.63905096054

用 cython 编译:

:~ $ python -c 'from tdsum import test; test()'
1.224848032

使用 PyPy:

:~ $ pypy -c 'from tdsum import test; test()'
0.427165031433

我希望真正的 cython 版本使用定制的数据结构能够显着优于 PyPy。问题是您不能使用 dicts 并且仍然获得您想要的迭代加速,因为 cython 必须处理 Python 对象开销。所以你必须实现自己的哈希表!

我经常想知道为什么 cython 不提供解决此问题的方法;也许有一个可用的numpy 类型。我会继续寻找!

【讨论】:

  • 不错的解决方案和建议。
【解决方案2】:

这是一个使用扁平化函数和膨胀函数的解决方案,用于任意深度嵌套的问题。适用于您的输入,但没有进行更多测试:

from collections import Counter

def flatten(d, parent=None):
    for k, v in d.items():
        keys = (k,) if parent is None else parent + (k,)
        if isinstance(v, dict):
            yield from flatten(v, keys)
        else:
            yield keys, v

def puffup(c):
    top = {}
    for k, v in c.items():
        current = top # reset walk
        for ki in k[:-1]:
            if ki not in current:
                current[ki] = {}
        current[k[-1]] = v
    return top

dicts = [ {'a':{'b':{'c':1}}}, {'a':{'b':{'c':4, 'e':3}}} ]
c = Counter()
for d in dicts:
    c += dict(flatten(d))
print(puffup(c))
# {'a': {'b': {'c': 5, 'e': 3}}}

我刚刚看到您正在寻找最快的。虽然更灵活,但它比上面的答案慢了约 2.5 倍,而且根本不需要调整输入。

【讨论】:

    猜你喜欢
    • 2016-12-21
    • 1970-01-01
    • 2018-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多