Fusión y sum de dos diccionarios.

Tengo un diccionario a continuación, y quiero agregar a otro diccionario con elementos no necesariamente distintos y fusionar sus resultados. ¿Hay alguna función incorporada para esto, o necesitaré hacer la mía?

{ '6d6e7bf221ae24e07ab90bba4452267b05db7824cd3fd1ea94b2c9a8': 6, '7c4a462a6ed4a3070b6d78d97c90ac230330603d24a58cafa79caf42': 7, '9c37bdc9f4750dd7ee2b558d6c06400c921f4d74aabd02ed5b4ddb38': 9, 'd3abb28d5776aef6b728920b5d7ff86fa3a71521a06538d2ad59375a': 15, '2ca9e1f9cbcd76a5ce1772f9b59995fd32cbcffa8a3b01b5c9c8afc2': 11 } 

El número de elementos en el diccionario también es desconocido.

Cuando la combinación considera dos claves idénticas, los valores de estas claves deben sumrse en lugar de sobrescribirse.

No dijiste exactamente cómo quieres fusionar, así que elige:

 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) } 

Resultados:

 {'both2': 22, 'only_x': 100, 'both1': 11} {'both2': 22, 'both1': 11} {'only_y': 200, 'both2': 22, 'both1': 11, 'only_x': 100} 

Puede realizar + , - , & , y | (intersección y unión) sobre collections.Counter() . collections.Counter() .

Entonces podemos hacer lo siguiente (Nota: solo los valores de conteo positivos permanecerán en el diccionario):

 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} 

Para abordar la adición de valores donde el resultado puede ser cero o negativo, utilice Counter.update() para la sum y Counter.subtract() para la resta:

 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} 

Podrías usar defaultdict para esto:

 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)) 

Esto produce

 {'both1': 11, 'both2': 22, 'only_x': 100, 'only_y': 200} 

Notas adicionales basadas en las respuestas de georg , NPE y Scott .

Estaba intentando realizar esta acción en colecciones de 2 o más diccionarios y estaba interesado en ver el tiempo que tomó para cada uno. Como quería hacer esto en cualquier número de diccionarios, tuve que cambiar algunas de las respuestas un poco. Si alguien tiene mejores sugerencias para ellos, siéntase libre de editar.

Aquí está mi método de prueba. Lo he actualizado recientemente para incluir pruebas con diccionarios MUCHO más grandes:

En primer lugar utilicé los siguientes datos:

 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 

Ahora cada uno de los métodos:

 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} 

También escribí una función rápida para encontrar las diferencias entre las listas. Desafortunadamente, fue entonces cuando encontré el problema en el método de Scott, es decir, si tiene diccionarios que sumn un total de 0, el diccionario no se incluirá en absoluto debido a cómo se comporta Counter() al agregar.

Finalmente, los resultados:

Resultados: Pruebas pequeñas

 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 

Resultados: Pruebas grandes

Naturalmente, no podía correr en ninguna parte cerca de tantos bucles

 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 

Conclusión

 ╔═══════════════════════════╦═══════╦═════════════════════════════╗ ║ ║ ║ 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 ║ ╚═══════════════════════════╩═══════╩══════════════╩══════════════╝ 

Importante. YMMV.

Otras opciones utilizando una función de reducción. Esto permite sumr y fusionar una colección arbitraria de diccionarios:

 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) 

Ejecución:

 {'a': 45, 'b': 45, 'c': 3} 

Ventajas:

  • Sencillo, claro, pythonico.
  • Sin esquema, siempre que todas las claves sean “sumbles”.
  • O (n) complejidad temporal y O (1) complejidad de memoria.

Sospecho que estás buscando el método de update dict :

 >>> d1 = {1:2,3:4} >>> d2 = {5:6,7:8} >>> d1.update(d2) >>> d1 {1: 2, 3: 4, 5: 6, 7: 8} 
 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(), ) 

También hay un reemplazo bastante simple de dict.update() :

 merged = dict(d1, **d2) 
 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} 

Esa es la sobrecarga del operador. Usando __add__ , hemos definido cómo usar el operador + para nuestro dict_merge que hereda del dict_merge de Python incorporado. Puede seguir adelante y hacerlo más flexible utilizando una forma similar de definir otros operadores en esta misma clase, por ejemplo, * con __mul__ para multiplicar, o / con __div__ para dividir, o incluso % con __mod__ para el módulo, y reemplazando el + in self[key] + other[key] con el operador correspondiente, si alguna vez se encuentra necesitando tal fusión. Solo he probado esto sin otros operadores, pero no preveo un problema con otros operadores. Solo aprende intentando.

Si quieres crear un nuevo dict como | utilizar:

 >>> dict({'a': 1,'c': 2}, **{'c': 1}) {'a': 1, 'c': 1}