使用Python按键对包含嵌套字典的字典列表求和

10

我有一个字典列表,希望设计一个函数来输出一个新的字典,其中包含该列表中所有字典中每个唯一键的总和。

对于给定的列表:

[
    {
         'apples': 1,
         'oranges': 1,
         'grapes': 2
    },
    {
         'apples': 3,
         'oranges': 5,
         'grapes': 8
    },
    {
         'apples': 13,
         'oranges': 21,
         'grapes': 34
    }
]

到目前为止都很好,这可以用计数器完成:

def sumDicts(listToProcess):
    c = Counter()
    for entry in listToProcess:
        c.update(entry)
    return (dict(c))

哪一个是正确的返回结果:

{'apples': 17, 'grapes': 44, 'oranges': 27}

问题出现在我的列表中的字典开始包含嵌套字典时:

[
    {
        'fruits': {
            'apples': 1,
            'oranges': 1,
            'grapes': 2
            },
        'vegetables': {
            'carrots': 6,
            'beans': 3,
            'peas': 2
        },
        'grains': 4,
        'meats': 1  
    },
    {
        'fruits': {
            'apples': 3,
            'oranges': 5,
            'grapes': 8
            },
        'vegetables': {
            'carrots': 7,
            'beans': 4,
            'peas': 3
        },
        'grains': 3,
        'meats': 2  
    },
    {
        'fruits': {
            'apples': 13,
            'oranges': 21,
            'grapes': 34
            },
        'vegetables': {
            'carrots': 8,
            'beans': 5,
            'peas': 4
        },
        'grains': 2,
        'meats': 3
    },
]

现在同样的函数会因为计数器不能添加两个字典而产生TypeError错误。

期望的结果是:

{
    'fruits': {
        'apples': 17,
        'oranges': 27,
        'grapes': 44
        },
    'vegetables': {
        'carrots': 21,
        'beans': 12,
        'peas': 9
    },
    'grains': 9,
    'meats': 6  
}

有没有什么办法可以以一种相对高效、Pythonic和具有普适性的方式实现这个目标呢?

2个回答

5
我会通过在collections.defaultdict对象上执行递归合并来完成此操作,该对象是递归定义的。
from collections import defaultdict

def merge(d, new_d):
    for k, v in new_d.items():
        if isinstance(v, dict):
            merge(d[k], v)
        else: 
            d[k] = d.setdefault(k, 0) + v

# https://dev59.com/KGIk5IYBdhLWcg3wiumk#19189356    
nested = lambda: defaultdict(nested)
d = nested()

for subd in data:
    merge(d, subd)

使用 default_to_regular 进行转换,我们得到:
default_to_regular(d)
# {
#     "fruits": {
#         "apples": 17,
#         "oranges": 27,
#         "grapes": 44
#     },
#     "vegetables": {
#         "carrots": 21,
#         "beans": 12,
#         "peas": 9
#     },
#     "grains": 9,
#     "meats": 6
# }

当我运行 for subd in data: print(merge(d, subd)) 时,我得到了 3 x None。 - Mykola Zotko
1
@MykolaZotko 你不应该打印它。它会直接修改 d - cs95
1
当然。你的函数没有返回任何值。最终结果在哪里也不清楚。 - Mykola Zotko
谢谢你提醒我在这里使用它们!实际上,我之前已经尝试过使用defaultdict来解决按键求和的问题,但最终选择使用计数器,因为对于非嵌套变量来说,它似乎是一个更简单的解决方案。 - Ben Franske

0

您可以使用递归。此解决方案在输入传递给merge时找到所有字典键,然后如果值为整数,则对每个键的值求和。但是,如果值是字典,则再次调用merge

def merge(c):
  _keys = {i for b in c for i in b}
  return {i:[sum, merge][isinstance(c[0][i], dict)]([h[i] for h in c]) for i in _keys}

d = [{'fruits': {'apples': 1, 'oranges': 1, 'grapes': 2}, 'vegetables': {'carrots': 6, 'beans': 3, 'peas': 2}, 'grains': 4, 'meats': 1}, {'fruits': {'apples': 3, 'oranges': 5, 'grapes': 8}, 'vegetables': {'carrots': 7, 'beans': 4, 'peas': 3}, 'grains': 3, 'meats': 2}, {'fruits': {'apples': 13, 'oranges': 21, 'grapes': 34}, 'vegetables': {'carrots': 8, 'beans': 5, 'peas': 4}, 'grains': 2, 'meats': 3}]

import json
print(json.dumps(merge(d), indent=4))

输出:

{
 "meats": 6,
 "grains": 9,
 "fruits": {
    "grapes": 44,
    "oranges": 27,
    "apples": 17
 },
"vegetables": {
     "beans": 12,
     "peas": 9,
     "carrots": 21
  }
}

1
谁给我的答案点了踩,又给这个点了赞,一定是对可读代码有非常扭曲的信仰。我理解你想用 [sum, merge][isinstance(c[0][i], dict)]([h[i] for h in c]) 做什么,但使用布尔值进行索引是不被鼓励的。一个简单的 if-else 可以做同样的事情,并且在此过程中看起来更加清晰易读。当然,你可能无法将其放在80个字符的单行中,但它的美妙之处在于你不必这样做。 - cs95

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