web-dev-qa-db-fra.com

Existe-t-il un moyen Pythonic de combiner deux dessins (en ajoutant des valeurs pour les clés qui apparaissent dans les deux)?

Par exemple, j'ai deux dict:

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

J'ai besoin d'un moyen Pythonic de 'combiner' deux dict de telle sorte que le résultat est:

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

C'est-à-dire: si une clé apparaît dans les deux dictés, ajoutez leurs valeurs, si elle apparaît dans un seul dict, conservez sa valeur.

440
Spirit Zhang

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

Les compteurs sont fondamentalement une sous-classe de dict, de sorte que vous pouvez toujours faire tout le reste avec eux, comme faire une itération sur leurs clés et leurs valeurs.

796
Martijn Pieters

Une solution plus générique, qui fonctionne également pour les valeurs non numériques:

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

ou même plus générique:

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

Par exemple:

>>> 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}
112
georg
>>> 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}
63
Ashwini Chaudhary

Intro: Il existe les (probablement) meilleures solutions. Mais vous devez le savoir et vous en souvenir, et vous devez parfois espérer que votre version de Python n’est pas trop ancienne ou quel que soit le problème.

Ensuite, il y a les solutions les plus "hacky". Ils sont grands et courts, mais sont parfois difficiles à comprendre, à lire et à retenir.

Il y a cependant une alternative qui est d'essayer de réinventer la roue .- Pourquoi réinventer la roue? - Généralement parce que c'est un très bon moyen d'apprendre (et parfois simplement parce que l'outil ne faites pas exactement ce que vous voulez et/ou la façon dont vous le souhaitez) et le moyen le plus simple si vous ne connaissez pas ou ne vous souvenez pas de l’outil idéal pour votre problème.

Alors , je propose de réinventer la roue de la classe Counter à partir du module collections (au moins partiellement):

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}

Il y aurait probablement d'autres moyens de le mettre en œuvre et il existe déjà des outils pour le faire, mais il est toujours agréable de visualiser comment les choses fonctionneraient.

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

Celui avec pas d'importations supplémentaires!

Leur est un standard Pythonic appelé EAFP (Plus facile de demander pardon que la permission). Le code ci-dessous est basé sur ce norme 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: merci à jerzyk pour ses suggestions d’amélioration.

12
Devesh Saini

Faire la somme de Counter()s est certainement la méthode la plus pythonique, mais uniquement si elle aboutit à une valeur positive . Voici un exemple et comme vous pouvez le constater, il n'y a pas de c dans le résultat après la négation de la valeur de c dans le dictionnaire 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})

En effet, Counters a été principalement conçu pour fonctionner avec des entiers positifs afin de représenter des comptages en cours (le nombre négatif n'a pas de sens). Mais pour vous aider avec ces cas d'utilisation, python documente les restrictions de type et de plage minimales comme suit:

  • La classe Counter elle-même est un dictionnaire sous-classe sans restrictions sur ses clés et ses valeurs. Les valeurs sont destiné à être des nombres représentant des comptes, mais vous pouvez stocker n'importe quoi dans le champ de valeur.
  • La méthode most_common() requiert uniquement que les valeurs soient ordonnables.
  • Pour les opérations sur place telles que c[key]+= 1, le type de valeur n'a besoin que de prendre en charge l'addition et la soustraction. Ainsi, les fractions, les flottants et les décimales fonctionneraient et les valeurs négatives sont prise en charge. Il en va de même pour update() et subtract() qui autoriser des valeurs négatives et nulles pour les entrées et les sorties.
  • Les méthodes multiset ne sont conçues que pour les cas d’utilisation avec des valeurs positives . Les entrées peuvent être négatives ou nulles, mais uniquement les sorties avec positif les valeurs sont créées. Il n'y a pas de restrictions de type, mais le type de valeur doit prendre en charge l'addition, la soustraction et la comparaison.
  • La méthode elements() nécessite des comptes entiers. Il ignore les comptes zéro et négatifs.

Donc, pour résoudre ce problème après avoir additionné votre compteur, vous pouvez utiliser Counter.update afin d’obtenir le résultat souhaité. Cela fonctionne comme dict.update() mais ajoute des comptes au lieu de les remplacer.

In [24]: A.update(B)

In [25]: A
Out[25]: Counter({'d': 5, 'b': 5, 'a': 1, 'c': -1})
10
Kasrâmvd
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}

OU 

Vous pouvez également utiliser Counter comme @Martijn l’a mentionné plus haut.

10
Adeel

Pour un moyen plus générique et extensible, vérifiez mergedict . Il utilise singledispatch et peut fusionner des valeurs en fonction de ses types.

Exemple:

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

De plus, veuillez noter que a.update( b ) est 2x plus rapide 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
4
shouldsee

À partir de Python 3.5: fusion et sommation

Merci à @tokeinizer_fsj qui m'a dit dans un commentaire que je ne comprenais pas tout le sens de la question (je pensais que cela voulait dire simplement ajouter des clés qui étaient différentes dans les deux dictionnaires et que je voulais plutôt dire que les valeurs des clés communes devrait être résumé). J'ai donc ajouté cette boucle avant la fusion, de sorte que le deuxième dictionnaire contienne la somme des clés communes. Le dernier dictionnaire sera celui dont les valeurs seront conservées dans le nouveau dictionnaire résultant de la fusion des deux. Le problème est donc résolu. La solution est valable à partir de python 3.5 et des versions suivantes.

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}

Code réutilisable

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))
3
Giovanni Gianni

Ceci est une solution simple pour la fusion de deux dictionnaires où += peut être appliqué aux valeurs, il doit itérer sur un dictionnaire une seule fois, je suis surpris que personne ne l'ait suggéré

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

Fusion de trois plans a, b, c dans une seule ligne sans aucun autre module ou bibliothèque

Si nous avons les trois dict

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

Fusionner le tout avec une seule ligne et renvoyer un objet dict en utilisant

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

Retour

{'a': 9, 'b': 2, 'd': 90}
2
user6830669
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)

Vous pouvez facilement généraliser ceci:

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)

Ensuite, il peut prendre un nombre quelconque de dict.

2
Jonas Kölker

Cette solution est facile à utiliser, elle est utilisée comme un dictionnaire normal, mais vous pouvez utiliser la fonction somme.

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}
1
Ignacio Villela

Les solutions ci-dessus conviennent parfaitement au scénario dans lequel vous avez un petit nombre de Counters. Si vous en avez une grande liste, quelque chose comme ceci est bien plus agréable:

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 solution ci-dessus consiste essentiellement à additionner le Counters par:

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

Cela fait la même chose mais je pense que cela aide toujours de voir ce qu’il fait effectivement dessous.

0
Michael Hall

Qu'en est-il de:

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

Sortie:

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