有没有什么pythonic方式来结合两个字典(增加键出现在两个值)?
比如我有两个字谜:
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}
也就是说:如果一个关键字出现在两个字典中,那么添加它们的值,如果它只出现在一个字典中,则保留它的值。
使用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
一个子类,所以你仍然可以用他们通常用这种types做的所有事情,比如遍历它们的键和值。
更通用的解决scheme,也适用于非数字值:
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}
>>> 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}
简介:有(可能)最好的解决scheme。 但是你必须知道它并且记住它,有时你必须希望你的Python版本不是太旧,或者不pipe什么问题。
那么最“黑客”的解决scheme。 他们是伟大而短暂的,但有时很难理解,阅读和记忆。
不过,还有一种方法是试图重新发明轮子。 – 为什么重新发明轮子? – 通常是因为这是一个非常好的学习方法(有时候只是因为已经存在的工具不能完全按照你想要的和/或你喜欢的方式来做)以及最简单的方法,如果你不知道或不记得你的问题的完美工具。
所以 ,我build议从collections
模块(至less部分)重新创buildCounter
类的轮子:
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}
可能还有其他方法可以实现这一点,并且已经有了一些工具可以实现这一点,但总是很好地展示事物的基本工作原理。
myDict = {} for k in itertools.chain(A.keys(), B.keys()): myDict[k] = A.get(k, 0)+B.get(k, 0)
一个没有额外的import!
他们是一个称为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
编辑:谢谢他的改进build议jerzyk 。
import itertools import collections dictA = {'a':1, 'b':2, 'c':3} dictB = {'b':3, 'c':4, 'd':5} new_dict = collections.defaultdict(int) 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}
要么
另外你可以使用Counter作为@Martijn上面提到的。
对于更通用和可扩展的方式来检查mergedict 。 它使用了singledispatch
并可以根据其types合并值。
例:
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'}
在这种情况下, Counter()
的绝对加和是最好的方法,但是只有当结果为正值时 。 这里是一个例子,你可以看到在B
字典中否定c
的值后,结果中没有c
。
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})
这是因为Counter
主要被devise成用正整数来表示运行计数(负计数是毫无意义的)。 但为了帮助这些用例,pythonlogging了最小范围和types限制,如下所示:
- Counter类本身是一个字典子类,对其键和值没有限制。 这些值旨在表示计数的数字,但您可以在值字段中存储任何内容。
most_common()
方法只要求值是可订购的。- 对于诸如
c[key]
+= 1
的就地操作,值types只需要支持加法和减法。 所以分数,浮点数和小数将起作用,负值被支持。update()
和subtract()
同样适用于input和输出都为负值和零值的情况。- multiset方法仅为具有正值的用例而devise。 input可能是负数或零,但只有正值的输出被创build。 没有types限制,但值types需要支持加,减,比较。
elements()
方法需要整数。 它忽略零和负数。
所以为了解决这个问题,在总结你的计数器之后,你可以使用Counter.update
来获得所需的输出。 它像dict.update()
一样工作,但增加了计数而不是replace它们。
In [24]: A.update(B) In [25]: A Out[25]: Counter({'d': 5, 'b': 5, 'a': 1, 'c': -1})
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)
那么它可以采取任何数量的字典。
这是我的方式:
dic1 = {'one':1,'two':2} dic2 = {'three':3,'four':4} dic1U2 = {} for i in dic1.keys(): dic1U2[i] = dic1[i] for i in dic2.keys(): dic1U2[i] = dic2[i] print(dic1U2)
这个解决scheme很容易使用,它被用作一个正常的字典,但是你可以使用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}
另外,请注意a.update( b )
比a + b
快2倍
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
这是一个简单的解决scheme,用于合并两个字典,其中+=
可以应用于值,它只需要遍历字典一次,我很惊讶没有人提出这个
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}
上面的解决scheme对于你有less量Counter
的情况非常Counter
。 如果你有他们的大名单,这样的事情更好:
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})
上面的解决scheme实质上是通过以下方式将Counter
相加:
total = Counter() for count in list_of_counts: total += count print(total) # Counter({'c': 7, 'a': 6, 'b': 5, 'd': 5, 'e': 3})
这也是同样的事情,但我认为它总是有助于看到它在底下有效地做了什么。
最好使用的是dict():
A = {'a':1, 'b':2, 'c':3} B = {'b':3, 'c':4, 'd':5} Merged = dict(A, **B) Merged == {'a':1, 'b':3, 'c':3, 'd':5}