¿Hay alguna forma en Pythonic de combinar dos dictados (agregar valores para las claves que aparecen en ambos)?

Por ejemplo tengo dos dicts:

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

Necesito una forma pythonica de “combinar” dos dictados de modo que el resultado sea:

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

Es decir: si una clave aparece en ambos dictados, agregue sus valores, si aparece en un solo dictado, mantenga su valor.

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

Los contadores son básicamente una subclase de dict , por lo que aún puede hacer todo lo demás con ellos que normalmente haría con ese tipo, como iterar sobre sus claves y valores.

Una solución más genérica, que también funciona con valores no numéricos:

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

o incluso más genérico:

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

Por ejemplo:

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

Introducción: Existen las (probablemente) mejores soluciones. Pero tienes que saberlo y recordarlo, ya veces debes esperar que tu versión de Python no sea demasiado antigua o que sea el problema.

Luego están las soluciones más ‘hacky’. Son grandes y cortos, pero a veces son difíciles de entender, de leer y de recordar.

Hay, sin embargo, una alternativa que es intentar reinventar la rueda. – ¿Por qué reinventar la rueda? – Generalmente porque es una buena manera de aprender (y algunas veces solo porque la herramienta ya existente no hace exactamente lo que le gustaría y / o como le gustaría) y la forma más fácil si no sabe o no No recuerdas la herramienta perfecta para tu problema.

Por lo tanto , propongo reinventar la rueda de la clase Counter desde el módulo de collections (al menos parcialmente):

 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} 

Probablemente haya otras formas de implementar eso y ya hay herramientas para hacerlo, pero siempre es bueno visualizar cómo funcionan las cosas básicamente.

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

El que no tiene importaciones extra!

Su es un estándar pythonico llamado EAFP (Más fácil de pedir perdón que el permiso). El código siguiente se basa en ese estándar de 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 

EDIT: gracias a jerzyk por sus sugerencias de mejora.

Definitivamente sumr el Counter() s es la forma más pythonica de ir en estos casos, pero solo si resulta en un valor positivo . Aquí hay un ejemplo y, como puede ver, no hay una c en el resultado después de negar el valor de la c en el diccionario 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}) 

Esto se debe a que los Counter s fueron diseñados principalmente para trabajar con enteros positivos para representar los conteos en ejecución (el conteo negativo no tiene sentido). Pero para ayudar con esos casos de uso, Python documenta el rango mínimo y las restricciones de tipo de la siguiente manera:

  • La clase Counter es una subclase de diccionario sin restricciones en sus claves y valores. Los valores pretenden ser números que representan recuentos, pero puede almacenar cualquier cosa en el campo de valor.
  • El método most_common() solo requiere que los valores sean ordenados.
  • Para operaciones en el lugar, como c[key] += 1 , el tipo de valor solo necesita admitir la sum y la resta. Por lo tanto, las fracciones, los flotantes y los decimales funcionarían y se admiten los valores negativos. Lo mismo es cierto para update() y subtract() que permiten valores negativos y cero tanto para entradas como para salidas.
  • Los métodos multiset están diseñados solo para casos de uso con valores positivos. Las entradas pueden ser negativas o cero, pero solo se crean salidas con valores positivos. No hay restricciones de tipo, pero el tipo de valor debe admitir la sum, la resta y la comparación.
  • El método elements() requiere conteos de enteros. Ignora los conteos cero y negativos.

Así que para solucionar ese problema después de sumr su contador, puede usar Counter.update para obtener la salida deseada. Funciona como dict.update() pero agrega conteos en lugar de reemplazarlos.

 In [24]: A.update(B) In [25]: A Out[25]: Counter({'d': 5, 'b': 5, 'a': 1, 'c': -1}) 
 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} 

O

Como alternativa, puede usar Counter como @Martijn ha mencionado anteriormente.

Para una forma más genérica y extensible verifica mergedict . Utiliza singledispatch y puede combinar valores en función de sus tipos.

Ejemplo:

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

Además, tenga en cuenta que a.update( b ) es 2 a.update( b ) más rápido que 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 

De Python 3.5: fusionar y sumr

Gracias a @tokeinizer_fsj que me dijo en un comentario que no entendí completamente el significado de la pregunta (pensé que agregar solo significaba agregar claves que eventualmente eran diferentes en los dos dictámenes y, en cambio, quería decir que los valores clave comunes debe ser sumdo). Así que agregué ese bucle antes de la fusión, de modo que el segundo diccionario contenga la sum de las claves comunes. El último diccionario será aquel cuyos valores durarán en el nuevo diccionario que es el resultado de la fusión de los dos, por lo que creo que el problema está resuelto. La solución es válida desde Python 3.5 y las siguientes versiones.

 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} 

Código reutilizable

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

Fácilmente podrías generalizar esto:

 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) 

Entonces puede tomar cualquier número de dictados.

Esta es una solución simple para fusionar dos diccionarios donde += se puede aplicar a los valores, tiene que iterar sobre un diccionario una sola vez, me sorprende que nadie haya sugerido esto.

 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} 

Fusionar tres dicts a, b, c en una sola línea sin ningún otro módulo o libs

Si tenemos los tres dados.

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

Combinar todo con una sola línea y devolver un objeto dict utilizando

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

Regresando

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

Esta solución es fácil de usar, se usa como un diccionario normal, pero puede usar la función de 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} 

Las soluciones anteriores son excelentes para el escenario en el que tiene un pequeño número de Counter . Sin embargo, si tienes una gran lista de ellos, algo como esto es mucho mejor:

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

La solución anterior es esencialmente sumr el Counter s por:

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

Esto hace lo mismo, pero creo que siempre ayuda ver qué es lo que está haciendo debajo.

Qué pasa:

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

Salida:

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

Lo mejor de usar es 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}