texto - obtener valor de entry python
¿Hay alguna forma en Pythonic de combinar dos dictados(agregar valores para las claves que aparecen en ambos)? (18)
Por ejemplo tengo dos dicts:
Dict A: {''a'': 1, ''b'': 2, ''c'': 3}
Dict B: {''b'': 3, ''c'': 4, ''d'': 5}
Necesito una forma pitónica 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.
De Python 3.5: fusionar y sumar
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 sumado). Así que agregué ese bucle antes de la fusión, de modo que el segundo diccionario contenga la suma 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))
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
Definitivamente sumar el Counter()
s es la forma más pitónica de ir en tales 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 suma 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 paraupdate()
ysubtract()
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 suma, 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 sumar 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})
El que no tiene importaciones extra!
Su es un estándar pitón llamado EAFP (Más fácil pedir perdón que 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.
Esta es una solución simple para combinar dos diccionarios donde +=
se puede aplicar a los valores, tiene que iterar sobre un diccionario solo una 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}
Esta solución es fácil de usar, se usa como un diccionario normal, pero puede usar la función de suma.
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}
Fusionar tres dicts a, b, c en una sola línea sin ningún otro módulo o libs
Si tenemos los tres dictados.
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())
Volviendo
{''a'': 9, ''b'': 2, ''d'': 90}
Las soluciones anteriores son excelentes para el escenario en el que tiene un pequeño número de Counter
s. 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 sumar 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.
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}
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''}
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}
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}
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.
Introducción: Existen las (probablemente) mejores soluciones. Pero tienes que saberlo y recordarlo, y algunas veces debes esperar que tu versión de Python no sea demasiado antigua o cualquiera 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.
>>> 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}
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.
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.
myDict = {}
for k in itertools.chain(A.keys(), B.keys()):
myDict[k] = A.get(k, 0)+B.get(k, 0)