Python中嵌套字典两层求和

3

我有两个嵌套字典变量,它们具有相似的键,每个键定义不同的值:

data1 = {
"2010":{
        'A':2,
        'B':3,
        'C':5
    },
"2011":{
        'A':1,
        'B':2,
        'C':3
    },
"2012":{
        'A':1,
        'B':2,
        'C':4
    }
}

data2 = {
"2010":{
        'A':4,
        'B':4,
        'C':5
    },
"2011":{
        'A':1,
        'B':1,
        'C':3
    },
"2012":{
        'A':3,
        'B':2,
        'C':4
    }
}

在我的情况下,我需要根据相同的键值对两个字典的值进行求和,因此答案将如下所示:
data3 = {
"2010":{
        'A':6,
        'B':7,
        'C':10
    },
"2011":{
        'A':2,
        'B':3,
        'C':6
    },
"2012":{
        'A':4,
        'B':4,
        'C':8
    }
}

我该怎么做呢?


这两个字典的结构是否保证相同? - Willem Van Onsem
4个回答

2

考虑到这两个字典的结构相同,你可以使用字典推导式来实现:

data3 = {key:{key2:val1+data2[key][key2] for key2,val1 in subdic.items()} for key,subdic in data1.items()}

在repl中:
>>> {key:{key2:val1+data2[key][key2] for key2,val1 in subdic.items()} for key,subdic in data1.items()}
{'2010': {'B': 7, 'C': 10, 'A': 6}, '2012': {'B': 4, 'C': 8, 'A': 4}, '2011': {'B': 3, 'C': 6, 'A': 2}}

理解如下:在外层循环中,我们遍历data1key,subdic。因此,在你的情况下,key是一年,而subdic是该年的字典(来自data1)。

现在对于每个年份,我们遍历subdic的项目,在这里key2'A''B''C'val1是我们在这些键中找到的data1的值。我们通过查询data2[key][key2]获得其他值。我们将它们加起来,并构建新的字典。


非常感谢您,Willem,这解决了我的问题,即使在更复杂的情况下也是如此。 - Faizalprbw
@Faizalprbw:请注意,正如答案所说,这仅在结构相同的情况下才有效:因此,两个字典都需要包含 2010ABC 等内容。 - Willem Van Onsem
1
是的,我了解,实际上我有两个包含相同结构和类似键的JSON数据,就像我上面的例子一样。 - Faizalprbw
顺便问一下,我怎么能让那个字典像我的示例(data3)一样排序呢?谢谢..@Willem Van Onsem - Faizalprbw
@Faizalprbw:字典是无序的。因此,根本没有任何排序保证。通常,如果您手动添加到字典中,它会保持一段时间的顺序,直到重新散列。但是,没有一个答案可以提供保证。为了解决这个问题,您需要一个OrderedDict - Willem Van Onsem

1

另一个解决方案 :) 您还可以使用 zip 在同一个 for 循环中获取 data1data2,然后使用 collections.Counter 添加每个字典的值。

from collections import Counter

>> {k1: Counter(v1) + Counter(v2) for (k1, v1), (k2, v2) in zip(sorted(data1.items()), sorted(data2.items()))}
{'2011': Counter({'C': 6, 'B': 3, 'A': 2}), '2010': Counter({'C': 10, 'B': 7, 'A': 6}), '2012': Counter({'C': 8, 'A': 4, 'B': 4})}

你最终将得到一个Counter字典, 但由于它是dict的子类, 所以你仍然可以像使用普通的dict一样使用相同的方法。

这种方法并不总是有效,因为k1可能与k2不同。请注意,在字典中键是无序的。但是,如果您在dict2中进行查找,则可以正常工作。 - Willem Van Onsem
你的评论是不正确的,zip函数总是会在字典上迭代相同的键。或者我可能误解了你的意思。能否请你提供一个简单的例子?你的解决方案也不错,但问题出在需要在n个字典之间进行连接的情况下。 - Kruupös
只想补充一点,zip 函数仅在两个字典拥有 全部 共同的 keys 时才会迭代相同的 keys,否则它将不起作用。 - Kruupös
我不知道那是否属实,即使是真的,据我所知也没有记录,因此你不能保证:构建有效的Python解释器的人可以自由地实现这个方面。 - Willem Van Onsem
好的,我完全错了 https://docs.python.org/3/library/stdtypes.html#mapping-types-dict ... 我的错,抱歉。在短字典上发生这种情况的可能性非常小,但在大型字典上可能会发生。我用 sorted() 更新了我的答案,但现在一行代码变得太长了。另一个解决方案是使用 OrderedDict 重新创建列表。 - Kruupös
如果稍后向字典中添加元素,则可能会出现问题,因为在这种情况下,桶通常不会立即增加,也不会执行重新哈希。 - Willem Van Onsem

1
如果您将dict()添加到Max Chrétiens上面的不错的简短解决方案中,您最终将得到常规字典:
data3 = {k1: dict(Counter(v1) + Counter(v2)) for (k1, v1), (k2, v2) in
         zip(data1.items(), data2.items())}

然而,这仅在两个字典共享完全相同的键时才能正常工作,如上所述。如果两个字典之间存在任何不共享的键,Willem Van Onsem的解决方案将无法正常工作(它将导致错误),而Max Chrétiens的解决方案在这种情况下会错误地合并项目。现在你提到你正在使用始终包含类似键结构的JSON数据,因此这不应构成问题,Max Chrétien的解决方案应该可以很好地工作。
如果您确实希望确保仅使用两个字典(及其子字典)共享的键,请使用以下内容。请注意,我将“X”:111111作为键值对添加到了2012子字典中,并添加了“1999”:{'Z':999999}作为整个子字典。
def sum_two_nested_dicts(d1, d2):
    dicts = [d1, d2]
    d_sum = {}
    for topkey in dicts[0]:
        if topkey in dicts[1]:
            d_sum[topkey] = {}
            for key in dicts[0][topkey]:
                if key in dicts[1][topkey]:
                    new_val = sum([d[topkey][key] for d in dicts])
                    d_sum[topkey][key] = new_val
    return d_sum


data1 = {
    "2010": {
        'A': 2,
        'B': 3,
        'C': 5
    },
    "2011": {
        'A': 1,
        'B': 2,
        'C': 3
    },
    "2012": {
        'A': 1,
        'B': 2,
        'C': 4,
        'X': 111111
    },
    "1999": {
        'Z': 999999
    }
}

data2 = {
    "2010": {
        'A': 4,
        'B': 4,
        'C': 5
    },
    "2011": {
        'A': 1,
        'B': 1,
        'C': 3
    },
    "2012": {
        'A': 3,
        'B': 2,
        'C': 4
    }
}

data3 = sum_two_nested_dicts(data1, data2)

print(data3)

# different order of arguments

data4 = sum_two_nested_dicts(data2, data1)

print(data4)

# {'2010': {'C': 10, 'A': 6, 'B': 7}, '2012': {'C': 8, 'A': 4, 'B': 4}, '2011': {'C': 6, 'A': 2, 'B': 3}}
# {'2010': {'C': 10, 'A': 6, 'B': 7}, '2012': {'C': 8, 'A': 4, 'B': 4}, '2011': {'C': 6, 'A': 2, 'B': 3}}

我知道这并不像能够更加简明优雅的代码那样,但是既然我已经写了,我就在这里发布,以便有人想要实现这种特定功能时可以参考。

这是一个冗长而臃肿的版本,保留了未共享的键/值,仅因为我已经写过了...

def sum_nested_dicts(dic1, dic2):
    # create list of both dictionaries
    dicts = [dic1, dic2]
    # create a set of all unique keys from both dictionaries
    topkeys = set(sum([list(dic.keys()) for dic in dicts], []))
    # this is the merged dictionary to be returned
    d_sum = {}
    for topkey in topkeys:
        # if topkey is shared by both dictionaries
        if topkey in dic1 and topkey in dic2:
            d_sum[topkey] = {}
            keys = set(sum([list(dic[topkey].keys()) for dic in
                            dicts], []))
            for key in keys:
                # if key is shared by both subdictionaries
                if key in dic1[topkey] and key in dic2[topkey]:
                    new_val = sum([d[topkey][key] for d in dicts])
                    d_sum[topkey][key] = new_val
                # if key is only contained in one subdictionary
                elif key in dic1[topkey]:
                    d_sum[topkey][key] = dic1[topkey][key]
                elif key in dic2[topkey]:
                    d_sum[topkey][key] = dic2[topkey][key]
        # if topkey is only contained in one dictionary
        elif topkey in dic1:
            d_sum[topkey] = dic1[topkey]
        elif topkey in dic2:
            d_sum[topkey] = dic2[topkey]
    return d_sum

看看Crystal的解决方案,似乎是迄今为止发布的最简洁和功能最强大的解决方案。

谢谢你升级我的解决方案 :D 你的解决方案看起来适应任何情况,但我认为能够创建一个函数来合并n个字典会很好,即使键相同。此外,即使它们不在两个字典中(例如你的 'X': 111111'),也要添加其他值。 - Kruupös
上面添加了我的循环和if条件狂热版本。但是请看Crystal的解决方案,这似乎是迄今为止最优雅的解决方案。 :) - Spherical Cowboy

1
我希望这可以帮助你:

    data1 = { "2010":{ 'A':2, 'B':3, 'C':5 }, "2011":{ 'A':1, 'B':2, 'C':3 }, "2012":{ 'A':1, 'B':2, 'C':4 } } 
    data2 = { "2010":{ 'A':4, 'B':4, 'C':5 }, "2011":{ 'A':1, 'B':1, 'C':3 }, "2012":{ 'A':3, 'B':2, 'C':4 } }

    data3 = {}

    for data in [data1,data2]:
        for year in data.keys():
                for x,y in data[year].items():
                    if not year in data3.keys():
                        data3[year] = {x:y}
                    else:
                        if not x in data3[year].keys():
                            data3[year].update({x:y})
                        else:
                            data3[year].update({x:data3[year][x] + y})
    print data3

这适用于任意长度的内部和外部字典。

太好了!如果一个字典中有键而另一个字典中没有,它也能工作。此外,如果一个子键(即嵌套字典的键,即外部字典的值之一)不被两个字典共享,它也将起作用。似乎覆盖了我测试过的所有情况。 :-) - Spherical Cowboy

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