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

477

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'une manière pythonique de «combiner» deux dicts de telle sorte que le résultat soit:

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

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

Derrick Zhang
la source

Réponses:

835

Utilisation 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, donc vous pouvez toujours faire tout ce que vous feriez normalement avec ce type, comme itérer sur leurs clés et leurs valeurs.

Martijn Pieters
la source
4
Qu'en est-il de plusieurs compteurs à fusionner comme ça? sum(counters)ne fonctionne pas, malheureusement.
Dr.Jan-Philip Gehrcke du
27
@ Jan-PhilipGehrcke: Donnez sum()une valeur de départ, avec sum(counters, Counter()).
Martijn Pieters
5
Merci. Cependant, cette méthode est affectée par la création d'objet intermédiaire comme le sont les chaînes de sommation, non?
Dr.Jan-Philip Gehrcke du
6
@ Jan-PhilipGehrcke: Votre autre option consiste à utiliser une boucle et +=à effectuer la sommation sur place. res = counters[0], alors for c in counters[1:]: res += c.
Martijn Pieters
3
J'aime cette approche! Si aime quelqu'un garder les choses près de dictionnaires de traitement, on pourrait aussi utiliser au update()lieu de +=: for c in counters[1:]: res.update(c).
-Philip Gehrcke du
119

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 encore 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}
georg
la source
27
Vous pouvez également utiliser for k in b.viewkeys() & a.viewkeys(), lors de l' utilisation de python 2.7 , et ignorer la création d'ensembles.
Martijn Pieters
Pourquoi set(a)retourne le jeu de clés plutôt que le jeu de tuples? Quelle en est la raison?
Salsepareille du
1
@HaiPhan: parce que les dictés itèrent sur les clés, pas sur les paires kv. cf list({..}), for k in {...}etc
georg
2
@Craicerjack: oui, j'avais l'habitude operator.mulde préciser que ce code est générique et ne se limite pas à ajouter des chiffres.
georg
6
Pourriez-vous ajouter une option compatible Python 3? {**a, **b, **{k: op(a[k], b[k]) for k in a.keys() & b}}devrait fonctionner dans Python 3.5+.
vaultah
66
>>> 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}
Ashwini Chaudhary
la source
1
L'utilisation ne serait-elle pas for x in set(itertools.chain(A, B))plus logique? L'utilisation de set on dict est un peu absurde, car les clés sont déjà uniques? Je sais que c'est juste une autre façon d'obtenir un jeu de clés, mais je trouve cela plus déroutant que d'utiliser itertools.chain(ce qui implique que vous savez ce que itertools.chainça fait)
jeromej
45

Intro: Il existe (probablement) les meilleures solutions. Mais vous devez le savoir et vous en souvenir et parfois vous devez espérer que votre version 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 parfois difficiles à comprendre, à lire et à retenir.

Il existe cependant une alternative qui consiste à 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 déjà existant ne fait pas exactement ce que vous voudriez et / ou comme vous le voudriez) et le plus simple si vous ne savez pas ou ne me souviens pas de l'outil parfait pour votre problème.

Donc , je propose de réinventer la roue de la Counterclasse à partir du collectionsmodule (en partie au moins):

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 façons de l'implémenter et il existe déjà des outils pour le faire, mais il est toujours agréable de visualiser comment les choses fonctionneraient fondamentalement.

jeromej
la source
3
Sympa pour ceux d'entre nous encore sur 2.6 aussi
Brian B
13
myDict = {}
for k in itertools.chain(A.keys(), B.keys()):
    myDict[k] = A.get(k, 0)+B.get(k, 0)

la source
13

Celui sans importations supplémentaires!

Il s'agit d'un standard pythonique appelé EAFP (plus facile à demander pardon que permission). Le code ci-dessous est basé sur cette 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.

Devesh Saini
la source
5
L'algorithme n ^ 2 sera beaucoup plus lent que la méthode Counter
Joop
@DeveshSaini mieux, mais toujours sous-optimal :) par exemple: avez-vous vraiment besoin d'un tri? et puis, pourquoi deux boucles? vous avez déjà toutes les clés dans le newdict, juste de petits conseils à optimiser
Jerzyk
L'algorithme n ^ 1 a été placé à la place de l'algorithme n ^ 2 précédent @Joop
Devesh Saini
11

Counter()Résumer définitivement le s est la façon la plus pythonique de procéder dans de tels cas, mais seulement si cela donne une valeur positive . Voici un exemple et comme vous pouvez le voir, il n'y a pas cde résultat après avoir annulé la cvaleur de 's dans le Bdictionnaire.

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, les Counters ont été principalement conçus pour fonctionner avec des entiers positifs pour représenter les nombres en cours (le nombre négatif n'a pas de sens). Mais pour aider avec ces cas d'utilisation, python documente la plage minimale et les restrictions de type comme suit:

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

Donc, pour contourner ce problème après avoir additionné votre compteur, vous pouvez l'utiliser Counter.updatepour obtenir la sortie désirée. Cela fonctionne comme dict.update()mais ajoute des nombres au lieu de les remplacer.

In [24]: A.update(B)

In [25]: A
Out[25]: Counter({'d': 5, 'b': 5, 'a': 1, 'c': -1})
Kasramvd
la source
10
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é ci-dessus.

Adeel
la source
7

Pour un moyen plus générique et extensible, consultez mergedict . Il utilise singledispatchet 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'}
schettino72
la source
5

Depuis python 3.5: fusion et sommation

Merci à @tokeinizer_fsj qui m'a dit dans un commentaire que je n'avais pas complètement compris le sens de la question (je pensais que ajouter signifiait simplement ajouter des clés qui finalement étaient différentes dans les deux dictatures et, à la place, je voulais dire que les valeurs de clé communes doit être additionnée). J'ai donc ajouté cette boucle avant la fusion, afin que le deuxième dictionnaire contienne la somme des clés communes. Le dernier dictionnaire sera celui dont les valeurs dureront dans le nouveau dictionnaire qui est le résultat de la fusion des deux, donc je pense que le problème est résolu. La solution est valide à 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))
Giovanni G. PY
la source
Cette façon de fusionner les dictionnaires n'ajoute pas les valeurs des clés communes. Dans la question, la valeur souhaitée pour la clé best 5(2 + 3), mais votre méthode revient 3.
tokenizer_fsj
4

De plus, veuillez noter a.update( b )est 2x plus rapide quea + 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
devrait voir
la source
2
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, cela peut prendre n'importe quel nombre de dict.

Jonas Kölker
la source
2

Ceci est une solution simple pour fusionner deux dictionnaires où +=peut être appliqué aux valeurs, il ne doit itérer sur un dictionnaire qu'une seule fois

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}
ragardner
la source
1

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

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

Production:

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

Les solutions ci-dessus sont parfaites pour le scénario où vous avez un petit nombre de Counters. Si vous en avez une grande liste, quelque chose comme ça est beaucoup 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 résume essentiellement 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 en dessous.

Michael Hall
la source
0

Fusionner trois dict a, b, c sur 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 tout avec une seule ligne et renvoyer un objet dict en utilisant

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

De retour

{'a': 9, 'b': 2, 'd': 90}
user6830669
la source
6
Relisez la question, ce n'est pas la sortie attendue. Il aurait dû être avec vos entrées: {'a': 9, 'b': 9, 'd': 90}. Il vous manque la condition "somme".
Patrick Mevzek