有什么pythonic方式可以合并两个字典(为同时出现在两个字典中的键添加值)?


477

例如,我有两个字典:

Dict A: {'a': 1, 'b': 2, 'c': 3}
Dict B: {'b': 3, 'c': 4, 'd': 5}

我需要一种“结合”两个字典的pythonic方式,使得结果是:

{'a': 1, 'b': 5, 'c': 7, 'd': 5}

也就是说:如果一个键同时出现在两个字典中,则将其值相加;如果仅出现在一个字典中,则保留其值。

Answers:


835

用途collections.Counter

>>> from collections import Counter
>>> A = Counter({'a':1, 'b':2, 'c':3})
>>> B = Counter({'b':3, 'c':4, 'd':5})
>>> A + B
Counter({'c': 7, 'b': 5, 'd': 5, 'a': 1})

计数器基本上是的子类dict,因此您仍然可以使用该类型对它们执行其他所有操作,例如遍历其键和值。


3
有多少个计数器可以像这样合并?sum(counters)不幸的是,它不起作用。
Jan-Philip Gehrcke博士2015年

27
@ Jan-PhilipGehrcke:使用给出sum()一个初始值sum(counters, Counter())
马丁·皮特斯

5
谢谢。但是,此方法受中间对象创建的影响,因为求和字符串是对的,对吧?
Jan-Philip Gehrcke博士,2015年

6
@ Jan-PhilipGehrcke:您的另一个选择是使用循环并+=进行就地求和。res = counters[0]然后for c in counters[1:]: res += c
马丁·彼得斯

3
我喜欢这种方法!如果有人喜欢让事情接近处理字典,那么也可以使用update()代替+=for c in counters[1:]: res.update(c)
Jan-Philip Gehrcke博士,2015年

119

一个更通用的解决方案,也适用于非数字值:

a = {'a': 'foo', 'b':'bar', 'c': 'baz'}
b = {'a': 'spam', 'c':'ham', 'x': 'blah'}

r = dict(a.items() + b.items() +
    [(k, a[k] + b[k]) for k in set(b) & set(a)])

或更通用的:

def combine_dicts(a, b, op=operator.add):
    return dict(a.items() + b.items() +
        [(k, op(a[k], b[k])) for k in set(b) & set(a)])

例如:

>>> a = {'a': 2, 'b':3, 'c':4}
>>> b = {'a': 5, 'c':6, 'x':7}

>>> import operator
>>> print combine_dicts(a, b, operator.mul)
{'a': 10, 'x': 7, 'c': 24, 'b': 3}

27
for k in b.viewkeys() & a.viewkeys()使用python 2.7时,也可以使用,并跳过集合的创建。
马丁·彼得斯

为什么set(a)返回键集而不是元组集?这有什么理由?
Sarsaparilla

1
@HaiPhan:因为dict遍历密钥,而不是遍历kv对。CF list({..})for k in {...}等等
格奥尔格·

2
@Craicerjack:是的,我曾经operator.mul明确指出这段代码是通用的,不限于加数字。
乔治

6
您可以添加Python 3兼容选项吗?{**a, **b, **{k: op(a[k], b[k]) for k in a.keys() & b}}应该可以在Python 3.5以上版本中使用。
vaultah

66
>>> A = {'a':1, 'b':2, 'c':3}
>>> B = {'b':3, 'c':4, 'd':5}
>>> c = {x: A.get(x, 0) + B.get(x, 0) for x in set(A).union(B)}
>>> print(c)

{'a': 1, 'c': 7, 'b': 5, 'd': 5}

1
使用for x in set(itertools.chain(A, B))会更合乎逻辑吗?由于使用set on dict有点废话,因为键已经是唯一的了?我知道这只是获取一组密钥的另一种方法,但是我发现它比使用更令人困惑itertools.chain(这意味着您知道该怎么itertools.chain做)
jeromej

45

介绍: 有(可能)最好的解决方案。但是您必须了解它并记住它,有时您必须希望您的Python版本不是太旧或任何问题。

然后是最“ hacky”的解决方案。它们虽然长短,但有时难以理解,阅读和记忆。

但是,还有另一种方法可以尝试重新发明轮子。-为什么要重新发明轮子?-通常,因为这是一种非常好的学习方法(有时只是因为现有工具无法完全实现您想要的和/或您想要的方式),而如果您不知道或不了解,则是最简单的方法忘记了解决问题的理想工具。

因此,我建议Countercollections模块(至少部分地)重塑类的方向:

class MyDict(dict):
    def __add__(self, oth):
        r = self.copy()

        try:
            for key, val in oth.items():
                if key in r:
                    r[key] += val  # You can custom it here
                else:
                    r[key] = val
        except AttributeError:  # In case oth isn't a dict
            return NotImplemented  # The convention when a case isn't handled

        return r

a = MyDict({'a':1, 'b':2, 'c':3})
b = MyDict({'b':3, 'c':4, 'd':5})

print(a+b)  # Output {'a':1, 'b': 5, 'c': 7, 'd': 5}

可能会有其他方式来实现它,并且已经有工具可以做到这一点,但是可视化事物的基本工作原理总是很高兴的。


3
对于仍然在2.6上的我们这些人来说也不错
Brian B

13
myDict = {}
for k in itertools.chain(A.keys(), B.keys()):
    myDict[k] = A.get(k, 0)+B.get(k, 0)

13

一个没有额外的进口!

他们是一个称为EAFP的pythonic 标准(要求宽恕比许可容易)。下面的代码基于该python标准

# The A and B dictionaries
A = {'a': 1, 'b': 2, 'c': 3}
B = {'b': 3, 'c': 4, 'd': 5}

# The final dictionary. Will contain the final outputs.
newdict = {}

# Make sure every key of A and B get into the final dictionary 'newdict'.
newdict.update(A)
newdict.update(B)

# Iterate through each key of A.
for i in A.keys():

    # If same key exist on B, its values from A and B will add together and
    # get included in the final dictionary 'newdict'.
    try:
        addition = A[i] + B[i]
        newdict[i] = addition

    # If current key does not exist in dictionary B, it will give a KeyError,
    # catch it and continue looping.
    except KeyError:
        continue

编辑:感谢jerzyk的改进建议。


5
N ^ 2 algorith将显著慢于计数器方法
乔普

@DeveshSaini更好,但仍然不是最佳的:)例如:您真的需要排序吗?然后,为什么要两个循环?您已经拥有了newdict中的所有键,只有一些小小的优化提示
Jerzyk

已放置n ^ 1算法而不是以前的n ^ 2算法@Joop
Devesh Saini

11

Counter()在这种情况下,将s 绝对相加是最有效的方法,但前提是它会导致正值。这是一个示例,如您所见,在字典中c取反c的值后没有结果B

In [1]: from collections import Counter

In [2]: A = Counter({'a':1, 'b':2, 'c':3})

In [3]: B = Counter({'b':3, 'c':-4, 'd':5})

In [4]: A + B
Out[4]: Counter({'d': 5, 'b': 5, 'a': 1})

这是因为Counters最初主要用于与正整数一起表示运行计数(负计数是没有意义的)。但是为了帮助解决这些用例,python记录了最小范围和类型限制,如下所示:

  • Counter类本身是一个字典子类,对其键和值没有限制。这些值应为代表计数的数字,但您可以在值字段中存储任何内容。
  • most_common()方法仅要求值是可排序的。
  • 对于诸如的就地操作c[key] += 1,值类型仅需要支持加法和减法。因此,分数,浮点数和小数将起作用,并且支持负值。对于update()和也是如此subtract()其允许输入和输出的负序和零值。
  • 多重集方法仅设计用于具有正值的用例。输入可以为负或零,但仅创建具有正值的输出。没有类型限制,但是值类型需要支持加,减和比较。
  • elements()方法需要整数计数。它忽略零和负计数。

因此,为解决计数器加总后的问题,可以使用Counter.update以获得所需的输出。它的工作原理类似,dict.update()但增加了计数而不是取代它们。

In [24]: A.update(B)

In [25]: A
Out[25]: Counter({'d': 5, 'b': 5, 'a': 1, 'c': -1})

10
import itertools
import collections

dictA = {'a':1, 'b':2, 'c':3}
dictB = {'b':3, 'c':4, 'd':5}

new_dict = collections.defaultdict(int)
# use dict.items() instead of dict.iteritems() for Python3
for k, v in itertools.chain(dictA.iteritems(), dictB.iteritems()):
    new_dict[k] += v

print dict(new_dict)

# OUTPUT
{'a': 1, 'c': 7, 'b': 5, 'd': 5}

要么

您也可以使用@Martijn上面提到的Counter。


7

有关更通用和可扩展的方法,请检查mergedict。它用singledispatch并可以根据其类型合并值。

例:

from mergedict import MergeDict

class SumDict(MergeDict):
    @MergeDict.dispatch(int)
    def merge_int(this, other):
        return this + other

d2 = SumDict({'a': 1, 'b': 'one'})
d2.merge({'a':2, 'b': 'two'})

assert d2 == {'a': 3, 'b': 'two'}

5

从python 3.5开始:合并和求和

感谢@tokeinizer_fsj在评论中告诉我,我并没有完全理解问题的含义(我认为添加意味着仅添加最终在两个字典中有所不同的键,相反,我的意思是公用键值应该加起来)。因此,我在合并之前添加了该循环,以便第二个字典包含公用键的总和。最后一个字典将是其值将在新字典中持续存在的字典,这是两者合并的结果,所以我认为问题已解决。该解决方案从python 3.5及以下版本开始有效。

a = {
    "a": 1,
    "b": 2,
    "c": 3
}

b = {
    "a": 2,
    "b": 3,
    "d": 5
}

# Python 3.5

for key in b:
    if key in a:
        b[key] = b[key] + a[key]

c = {**a, **b}
print(c)

>>> c
{'a': 3, 'b': 5, 'c': 3, 'd': 5}

可重用代码

a = {'a': 1, 'b': 2, 'c': 3}
b = {'b': 3, 'c': 4, 'd': 5}


def mergsum(a, b):
    for k in b:
        if k in a:
            b[k] = b[k] + a[k]
    c = {**a, **b}
    return c


print(mergsum(a, b))

这种合并字典的方式是不添加通用键的值。在问题中,键的期望值b5(2 + 3),但是您的方法正在返回3
tokenizer_fsj

4

此外,请注意a.update( b ),速度是2倍a + b

from collections import Counter
a = Counter({'menu': 20, 'good': 15, 'happy': 10, 'bar': 5})
b = Counter({'menu': 1, 'good': 1, 'bar': 3})

%timeit a + b;
## 100000 loops, best of 3: 8.62 µs per loop
## The slowest run took 4.04 times longer than the fastest. This could mean that an intermediate result is being cached.

%timeit a.update(b)
## 100000 loops, best of 3: 4.51 µs per loop

2
def merge_with(f, xs, ys):
    xs = a_copy_of(xs) # dict(xs), maybe generalizable?
    for (y, v) in ys.iteritems():
        xs[y] = v if y not in xs else f(xs[x], v)

merge_with((lambda x, y: x + y), A, B)

您可以轻松地对此进行概括:

def merge_dicts(f, *dicts):
    result = {}
    for d in dicts:
        for (k, v) in d.iteritems():
            result[k] = v if k not in result else f(result[k], v)

然后它可以采用任意数量的字典。


2

这是合并两个+=可应用于值的字典的简单解决方案,它只需要对字典进行一次迭代

a = {'a':1, 'b':2, 'c':3}

dicts = [{'b':3, 'c':4, 'd':5},
         {'c':9, 'a':9, 'd':9}]

def merge_dicts(merged,mergedfrom):
    for k,v in mergedfrom.items():
        if k in merged:
            merged[k] += v
        else:
            merged[k] = v
    return merged

for dct in dicts:
    a = merge_dicts(a,dct)
print (a)
#{'c': 16, 'b': 5, 'd': 14, 'a': 10}

1

此解决方案易于使用,它用作普通词典,但是您可以使用sum函数。

class SumDict(dict):
    def __add__(self, y):
        return {x: self.get(x, 0) + y.get(x, 0) for x in set(self).union(y)}

A = SumDict({'a': 1, 'c': 2})
B = SumDict({'b': 3, 'c': 4})  # Also works: B = {'b': 3, 'c': 4}
print(A + B)  # OUTPUT {'a': 1, 'b': 3, 'c': 6}

1

关于什么:

def dict_merge_and_sum( d1, d2 ):
    ret = d1
    ret.update({ k:v + d2[k] for k,v in d1.items() if k in d2 })
    ret.update({ k:v for k,v in d2.items() if k not in d1 })
    return ret

A = {'a': 1, 'b': 2, 'c': 3}
B = {'b': 3, 'c': 4, 'd': 5}

print( dict_merge_and_sum( A, B ) )

输出:

{'d': 5, 'a': 1, 'c': 7, 'b': 5}

0

上述解决方案非常适合Counters 较少的情况。如果您有很多清单,那么这样会更好:

from collections import Counter

A = Counter({'a':1, 'b':2, 'c':3})
B = Counter({'b':3, 'c':4, 'd':5}) 
C = Counter({'a': 5, 'e':3})
list_of_counts = [A, B, C]

total = sum(list_of_counts, Counter())

print(total)
# Counter({'c': 7, 'a': 6, 'b': 5, 'd': 5, 'e': 3})

上面的解决方案本质上Counter是通过以下方式将s 相加:

total = Counter()
for count in list_of_counts:
    total += count
print(total)
# Counter({'c': 7, 'a': 6, 'b': 5, 'd': 5, 'e': 3})

这做同样的事情,但我认为它始终有助于了解其在下面的有效工作。


0

在没有任何其他模块或库的情况下,在一行中合并三个字典a,b,c

如果我们有三个决定

a = {"a":9}
b = {"b":7}
c = {'b': 2, 'd': 90}

用一行合并所有内容,并使用返回一个dict对象

c = dict(a.items() + b.items() + c.items())

归来

{'a': 9, 'b': 2, 'd': 90}

6
重新阅读问题,这不是预期的输出。您应该输入以下内容:{'a': 9, 'b': 9, 'd': 90}。您缺少“求和”要求。
Patrick Mevzek '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.