我下面有一个字典,我想添加到另一个不一定是不同元素的字典中并合并它的结果。是否有任何内置功能,或者我需要自己制作?
{
'6d6e7bf221ae24e07ab90bba4452267b05db7824cd3fd1ea94b2c9a8': 6,
'7c4a462a6ed4a3070b6d78d97c90ac230330603d24a58cafa79caf42': 7,
'9c37bdc9f4750dd7ee2b558d6c06400c921f4d74aabd02ed5b4ddb38': 9,
'd3abb28d5776aef6b728920b5d7ff86fa3a71521a06538d2ad59375a': 15,
'2ca9e1f9cbcd76a5ce1772f9b59995fd32cbcffa8a3b01b5c9c8afc2': 11
}
字典中的元素数量也是未知的。
如果合并考虑了两个相同的键,则应将这些键的值相加而不是覆盖。
答案 0 :(得分:118)
您没有说明您想要合并的方式,所以请选择:
x = {'both1':1, 'both2':2, 'only_x': 100 }
y = {'both1':10, 'both2': 20, 'only_y':200 }
print { k: x.get(k, 0) + y.get(k, 0) for k in set(x) }
print { k: x.get(k, 0) + y.get(k, 0) for k in set(x) & set(y) }
print { k: x.get(k, 0) + y.get(k, 0) for k in set(x) | set(y) }
结果:
{'both2': 22, 'only_x': 100, 'both1': 11}
{'both2': 22, 'both1': 11}
{'only_y': 200, 'both2': 22, 'both1': 11, 'only_x': 100}
答案 1 :(得分:26)
您可以在collections.Counter()
上执行+
,-
,&
和|
(交叉和联合)。
所以我们可以执行以下操作(注意:只有正计数值将保留在字典中):
from collections import Counter
x = {'both1':1, 'both2':2, 'only_x': 100 }
y = {'both1':10, 'both2': 20, 'only_y':200 }
z = dict(Counter(x)+Counter(y))
print(z) # {'both2': 22, 'only_x': 100, 'both1': 11, 'only_y': 200}
要解决在结果可能为零或负面的情况下添加值,请使用Counter.update()
进行添加,使用Counter.subtract()
进行减法:
x = {'both1':0, 'both2':2, 'only_x': 100 }
y = {'both1':0, 'both2': -20, 'only_y':200 }
xx = Counter(x)
yy = Counter(y)
xx.update(yy)
dict(xx) # {'both2': -18, 'only_x': 100, 'both1': 0, 'only_y': 200}
答案 2 :(得分:17)
您可以使用defaultdict
:
from collections import defaultdict
def dsum(*dicts):
ret = defaultdict(int)
for d in dicts:
for k, v in d.items():
ret[k] += v
return dict(ret)
x = {'both1':1, 'both2':2, 'only_x': 100 }
y = {'both1':10, 'both2': 20, 'only_y':200 }
print(dsum(x, y))
这会产生
{'both1': 11, 'both2': 22, 'only_x': 100, 'only_y': 200}
答案 3 :(得分:10)
我试图对2个或更多词典的集合执行此操作,并且有兴趣查看每个词典花费的时间。因为我想在任意数量的词典上做这个,所以我不得不稍微改变一些答案。如果有人对他们有更好的建议,请随时编辑。
这是我的测试方法。我最近更新了它以包含更多更大词典的测试:
首先,我使用了以下数据:
import random
x = {'xy1': 1, 'xy2': 2, 'xyz': 3, 'only_x': 100}
y = {'xy1': 10, 'xy2': 20, 'xyz': 30, 'only_y': 200}
z = {'xyz': 300, 'only_z': 300}
small_tests = [x, y, z]
# 200,000 random 8 letter keys
keys = [''.join(random.choice("abcdefghijklmnopqrstuvwxyz") for _ in range(8)) for _ in range(200000)]
a, b, c = {}, {}, {}
# 50/50 chance of a value being assigned to each dictionary, some keys will be missed but meh
for key in keys:
if random.getrandbits(1):
a[key] = random.randint(0, 1000)
if random.getrandbits(1):
b[key] = random.randint(0, 1000)
if random.getrandbits(1):
c[key] = random.randint(0, 1000)
large_tests = [a, b, c]
print("a:", len(a), "b:", len(b), "c:", len(c))
#: a: 100069 b: 100385 c: 99989
现在每个方法:
from collections import defaultdict, Counter
def georg_method(tests):
return {k: sum(t.get(k, 0) for t in tests) for k in set.union(*[set(t) for t in tests])}
def georg_method_nosum(tests):
# If you know you will have exactly 3 dicts
return {k: tests[0].get(k, 0) + tests[1].get(k, 0) + tests[2].get(k, 0) for k in set.union(*[set(t) for t in tests])}
def npe_method(tests):
ret = defaultdict(int)
for d in tests:
for k, v in d.items():
ret[k] += v
return dict(ret)
# Note: There is a bug with scott's method. See below for details.
def scott_method(tests):
return dict(sum((Counter(t) for t in tests), Counter()))
def scott_method_nosum(tests):
# If you know you will have exactly 3 dicts
return dict(Counter(tests[0]) + Counter(tests[1]) + Counter(tests[2]))
methods = {"georg_method": georg_method, "georg_method_nosum": georg_method_nosum,
"npe_method": npe_method,
"scott_method": scott_method, "scott_method_nosum": scott_method_nosum}
我还写了一个快速函数,找出列表之间的差异。不幸的是,当我在Scott的方法中发现问题时,也就是说,如果你的字典总数为0,那么由于Counter()
在添加时的行为方式,字典将不会被包括在内。
最后,结果:
for name, method in methods.items():
print("Method:", name)
%timeit -n10000 method(small_tests)
#: Method: npe_method
#: 10000 loops, best of 3: 5.16 µs per loop
#: Method: georg_method_nosum
#: 10000 loops, best of 3: 8.11 µs per loop
#: Method: georg_method
#: 10000 loops, best of 3: 11.8 µs per loop
#: Method: scott_method_nosum
#: 10000 loops, best of 3: 42.4 µs per loop
#: Method: scott_method
#: 10000 loops, best of 3: 65.3 µs per loop
当然,无法在尽可能多的循环附近运行
for name, method in methods.items():
print("Method:", name)
%timeit -n10 method(large_tests)
#: Method: npe_method
#: 10 loops, best of 3: 227 ms per loop
#: Method: georg_method_nosum
#: 10 loops, best of 3: 327 ms per loop
#: Method: georg_method
#: 10 loops, best of 3: 455 ms per loop
#: Method: scott_method_nosum
#: 10 loops, best of 3: 510 ms per loop
#: Method: scott_method
#: 10 loops, best of 3: 600 ms per loop
╔═══════════════════════════╦═══════╦═════════════════════════════╗
║ ║ ║ Best of 3 Time Per Loop ║
║ Algorithm ║ By ╠══════════════╦══════════════╣
║ ║ ║ small_tests ║ large_tests ║
╠═══════════════════════════╬═══════╬══════════════╬══════════════╣
║ defaultdict sum ║ NPE ║ 5.16 µs ║ 227,000 µs ║
║ set unions without sum() ║ georg ║ 8.11 µs ║ 327,000 µs ║
║ set unions with sum() ║ ║ 11.8 µs ║ 455,000 µs ║
║ Counter() without sum() ║ Scott ║ 42.4 µs ║ 510,000 µs ║
║ Counter() with sum() ║ ║ 65.3 µs ║ 600,000 µs ║
╚═══════════════════════════╩═══════╩══════════════╩══════════════╝
重要。 YMMV。
答案 4 :(得分:5)
使用reduce函数的另一个选项。这允许对任意字典集合进行求和:
from functools import reduce
collection = [
{'a': 1, 'b': 1},
{'a': 2, 'b': 2},
{'a': 3, 'b': 3},
{'a': 4, 'b': 4, 'c': 1},
{'a': 5, 'b': 5, 'c': 1},
{'a': 6, 'b': 6, 'c': 1},
{'a': 7, 'b': 7},
{'a': 8, 'b': 8},
{'a': 9, 'b': 9},
]
def reducer(accumulator, element):
for key, value in element.items():
accumulator[key] = accumulator.get(key, 0) + value
return accumulator
total = reduce(reducer, collection, {})
assert total['a'] == sum(d.get('a', 0) for d in collection)
assert total['b'] == sum(d.get('b', 0) for d in collection)
assert total['c'] == sum(d.get('c', 0) for d in collection)
print(total)
执行:
{'a': 45, 'b': 45, 'c': 3}
优点:
答案 5 :(得分:2)
我怀疑您正在寻找dict
的{{3}}方法:
>>> d1 = {1:2,3:4}
>>> d2 = {5:6,7:8}
>>> d1.update(d2)
>>> d1
{1: 2, 3: 4, 5: 6, 7: 8}
答案 6 :(得分:1)
d1 = {'apples': 2, 'banana': 1}
d2 = {'apples': 3, 'banana': 2}
merged = reduce(
lambda d, i: (
d.update(((i[0], d.get(i[0], 0) + i[1]),)) or d
),
d2.iteritems(),
d1.copy(),
)
还可以非常简单地替换dict.update()
:
merged = dict(d1, **d2)
答案 7 :(得分:1)
class dict_merge(dict):
def __add__(self, other):
result = dict_merge({})
for key in self.keys():
if key in other.keys():
result[key] = self[key] + other[key]
else:
result[key] = self[key]
for key in other.keys():
if key in self.keys():
pass
else:
result[key] = other[key]
return result
a = dict_merge({"a":2, "b":3, "d":4})
b = dict_merge({"a":1, "b":2})
c = dict_merge({"a":5, "b":6, "c":5})
d = dict_merge({"a":8, "b":6, "e":5})
print((a + b + c +d))
>>> {'a': 16, 'b': 17, 'd': 4, 'c': 5, 'e': 5}
这是运营商重载。使用__add__
,我们已经定义了如何将+
运算符用于继承自内置python dict_merge
的{{1}}。您可以使用类似的方法在同一个类中定义其他运算符,从而使其更灵活,例如dict
*
用于乘法,__mul__
用/
进行划分,甚至__div__
用%
用于模数,并替换{{}如果您发现自己需要这样的合并,则在__mod__
中使用相应的运算符。
我只测试了它,因为没有其他运营商,但我没有预见到其他运营商的问题。通过尝试来学习。
答案 8 :(得分:0)
如果您要创建新的['argh==0.26.1', 'docutils==0.12', 'gitdb==0.6.4', 'gitpython==1.0.1', 'jedi==0.9.0', 'kivy-garden==0.1.4', 'kivy.deps.glew== 0.1.4', 'kivy.deps.gstreamer==0.1.5', 'kivy.deps.sdl2==0.1.12', 'kivy==1.9.1', 'lxml==3.3.4', 'matplotlib==1.3.1', 'numpy==1.8.1', 'pandas==0.11.0', 'pathtools==0.1.2', 'pil==1.1.7', 'pip==8.0.2', 'py2exe==0.6.9', 'pycairo==1.8.10', 'pygame==1.9.1','pygments==2.1', 'pygobject==2.28.3', 'pygoocanvas==0.14.2', 'pygtk==2.24.0', 'pygtksourceview==2.10.1', 'pyodbc==3.0.7', 'pyparsing==2.0.1', 'pypiwin32==219', 'pyrsvg==2.32.1', 'pyserial==2.7', 'pywin32==218', 'pyyaml==3.11', 'requests==2.9.1', 'scipy==0.13.3', 'setuptools==19.4', 'six==1.10.0', 'smmap==0.9.0', 'watchdog==0.8.3', 'wheel==0.26.0']
Traceback (most recent call last):
File "C:\Sources\Portable_Python_2.7.6.1\kivy-designer-master\main.py", line 2, in <module>
from designer.app import DesignerApp
File "C:\Sources\Portable_Python_2.7.6.1\kivy-designer-master\designer\app.py", line 28, in <module>
from kivy.garden.filebrowser import FileBrowser
ImportError: No module named filebrowser
,请使用:
dict
答案 9 :(得分:0)
Scott使用collections.Counter
的方法很好,但是它的缺点是不能与sum
一起使用;当您只想逐个添加值时,对负值或零值的处理对我来说也有点直觉。
所以我认为,为此编写一个自定义类可能是一个好主意。这也是约翰·穆图马(John Mutuma)的想法。但是,我想添加我的解决方案:
我创建了一个行为非常类似于dict
的类,基本上将所有成员调用都通过 getatrr 方法传递给基础_data
。仅有的两个不同之处是:
DEFAULT_VALUE
(类似于collections.defaultdict
),用作不存在的键的值。__add__()
方法(与__radd__()
方法一起),它负责逐个添加字典。from typing import Union, Any
class AddableDict:
DEFAULT_VALUE = 0
def __init__(self, data: dict) -> None:
self._data = data
def __getattr__(self, attr: str) -> Any:
return getattr(self._data, attr)
def __getitem__(self, item) -> Any:
try:
return self._data[item]
except KeyError:
return self.DEFAULT_VALUE
def __repr__(self):
return self._data.__repr__()
def __add__(self, other) -> "AddableDict":
return AddableDict({
key: self[key] + other[key]
for key in set(self.keys()) | set(other.keys())
})
def __radd__(
self, other: Union[int, "AddableDict"]
) -> "AddableDict":
if other == 0:
return self
这样,我们可以添加两个对象以及这些对象的sum
可迭代对象:
>>> alpha = AddableDict({"a": 1})
>>> beta = AddableDict({"a": 10, "b": 5})
>>> alpha + beta
{'a': 11, 'b': 5}
>>> sum([beta]*10)
{'a': 100, 'b': 50}
在我看来,该解决方案的优点是为开发人员提供了一个简单易懂的界面。当然,您也可以从dict
继承而无需使用合成。