Gibt es eine pythonische Möglichkeit, zwei Diktate zu kombinieren (Hinzufügen von Werten für Schlüssel, die in beiden vorkommen)?

477

Zum Beispiel habe ich zwei Diktate:

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

Ich brauche eine pythonische Methode, um zwei Diktate so zu kombinieren, dass das Ergebnis lautet:

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

Das heißt: Wenn ein Schlüssel in beiden Diktaten erscheint, fügen Sie deren Werte hinzu. Wenn er nur in einem Diktat erscheint, behalten Sie seinen Wert bei.

Derrick Zhang
quelle

Antworten:

835

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

Zähler sind im Grunde genommen eine Unterklasse von dict, sodass Sie immer noch alles andere mit ihnen tun können, was Sie normalerweise mit diesem Typ tun würden, z. B. über ihre Schlüssel und Werte iterieren.

Martijn Pieters
quelle
4
Was ist mit mehreren Zählern, die so zusammengeführt werden können? sum(counters)funktioniert leider nicht.
Dr. Jan-Philip Gehrcke
27
@ Jan-PhilipGehrcke: Geben Sie sum()einen Startwert an, mit sum(counters, Counter()).
Martijn Pieters
5
Vielen Dank. Diese Methode wird jedoch von der Erstellung von Zwischenobjekten beeinflusst, da das Summieren von Zeichenfolgen richtig ist.
Dr. Jan-Philip Gehrcke
6
@ Jan-PhilipGehrcke: Ihre andere Option ist die Verwendung einer Schleife und die direkte +=Summierung. res = counters[0]dann for c in counters[1:]: res += c.
Martijn Pieters
3
Ich mag diesen Ansatz! Wenn jemand gerne Dinge in der Nähe der Wörterbuchverarbeitung hält, kann man auch update()anstelle von +=: for c in counters[1:]: res.update(c).
Dr. Jan-Philip Gehrcke
119

Eine allgemeinere Lösung, die auch für nicht numerische Werte funktioniert:

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

oder noch allgemeiner:

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

Zum Beispiel:

>>> 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
quelle
27
Sie können auch verwenden for k in b.viewkeys() & a.viewkeys(), wenn Python 2.7 und die Schaffung von Sätzen überspringen.
Martijn Pieters
Warum gibt set(a)der Schlüsselsatz eher den Tupelsatz zurück? Was ist der Grund dafür?
Sarsaparilla
1
@HaiPhan: weil Diktate über Schlüssel iterieren, nicht über kv-Paare. Vgl list({..}). for k in {...}usw.
Georg
2
@Craicerjack: Ja, ich habe immer operator.mulklargestellt, dass dieser Code generisch ist und nicht auf das Hinzufügen von Zahlen beschränkt ist.
Georg
6
Könnten Sie eine Python 3-kompatible Option hinzufügen? {**a, **b, **{k: op(a[k], b[k]) for k in a.keys() & b}}sollte in Python 3.5+ funktionieren.
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
quelle
1
Wäre die Verwendung for x in set(itertools.chain(A, B))nicht logischer? Wie ist die Verwendung von set on dict ein bisschen unsinnig, da Schlüssel bereits eindeutig sind? Ich weiß, es ist nur ein anderer Weg, um einen Satz der Schlüssel zu bekommen, aber ich finde es verwirrender als zu verwenden itertools.chain(was bedeutet, dass Sie wissen, was itertools.chaintut)
jeromej
45

Intro: Es gibt die (wahrscheinlich) besten Lösungen. Aber Sie müssen es wissen und sich daran erinnern, und manchmal müssen Sie hoffen, dass Ihre Python-Version nicht zu alt ist oder was auch immer das Problem sein könnte.

Dann gibt es die "hackigsten" Lösungen. Sie sind großartig und kurz, aber manchmal schwer zu verstehen, zu lesen und sich zu erinnern.

Es gibt jedoch eine Alternative, zu versuchen, das Rad neu zu erfinden. - Warum das Rad neu erfinden? - Im Allgemeinen, weil es eine wirklich gute Art zu lernen ist (und manchmal nur, weil das bereits vorhandene Tool nicht genau das tut, was Sie möchten und / oder wie Sie es möchten) und die einfachste Art, wenn Sie es nicht wissen oder Ich erinnere mich nicht an das perfekte Werkzeug für Ihr Problem.

Also schlage ich vor, das Rad der CounterKlasse aus dem collectionsModul neu zu erfinden (zumindest teilweise):

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}

Es gibt wahrscheinlich andere Möglichkeiten, dies zu implementieren, und es gibt bereits Tools, um dies zu tun, aber es ist immer schön zu visualisieren, wie die Dinge im Grunde funktionieren würden.

jeromej
quelle
3
Schön für diejenigen von uns, die noch am 2.6 sind
Brian B
13
myDict = {}
for k in itertools.chain(A.keys(), B.keys()):
    myDict[k] = A.get(k, 0)+B.get(k, 0)

quelle
13

Der ohne zusätzliche Importe!

Es handelt sich um einen pythonischen Standard namens EAFP (leichter um Vergebung zu bitten als um Erlaubnis). Der folgende Code basiert auf diesem Python-Standard .

# 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: danke an jerzyk für seine verbesserungsvorschläge.

Devesh Saini
quelle
5
n ^ 2 Algorithmus wird deutlich langsamer als die Counter-Methode
Joop
@DeveshSaini besser, aber immer noch nicht optimal :) zB: Brauchen Sie wirklich eine Sortierung? und warum dann zwei Schleifen? Sie haben bereits alle Schlüssel im NewDict, nur kleine Hinweise zur Optimierung
Jerzyk
n ^ 1 Algorithmus wurde anstelle des vorherigen n ^ 2 Algorithmus @Joop
Devesh Saini
11

Counter()In solchen Fällen ist das Summieren des s definitiv der pythonischste Weg, aber nur, wenn es zu einem positiven Wert führt . Hier ist ein Beispiel und wie Sie sehen können, gibt es kein cErgebnis, nachdem der cWert des BWörterbuchs negiert wurde .

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

Dies liegt daran, dass Counters in erster Linie für die Arbeit mit positiven Ganzzahlen entwickelt wurden, um laufende Zählungen darzustellen (negative Zählungen sind bedeutungslos). Um diese Anwendungsfälle zu unterstützen, dokumentiert Python die Mindesteinschränkungen für Bereich und Typ wie folgt:

  • Die Counter-Klasse selbst ist eine Wörterbuch-Unterklasse ohne Einschränkungen für ihre Schlüssel und Werte. Die Werte sollen Zahlen sein, die Zählungen darstellen, aber Sie können alles im Wertefeld speichern.
  • Das most_common() Methode erfordert nur, dass die Werte bestellbar sind.
  • Für In-Place-Operationen wie z. B. c[key] += 1muss der Werttyp nur das Addieren und Subtrahieren unterstützen. Brüche, Gleitkommazahlen und Dezimalstellen würden also funktionieren und negative Werte werden unterstützt. Gleiches gilt auch für update()undsubtract() die negative und Nullwerte für Ein- und Ausgänge zulassen.
  • Die Multiset-Methoden sind nur für Anwendungsfälle mit positiven Werten konzipiert. Die Eingänge können negativ oder null sein, es werden jedoch nur Ausgänge mit positiven Werten erstellt. Es gibt keine Typbeschränkungen, aber der Werttyp muss Addition, Subtraktion und Vergleich unterstützen.
  • Die elements()Methode erfordert Ganzzahlzählungen. Null- und negative Zählwerte werden ignoriert.

Um dieses Problem nach dem Summieren Ihres Zählers zu umgehen, können Sie es verwenden Counter.update, um die gewünschte Ausgabe zu erhalten. Es funktioniert wie dict.update(), fügt aber Zählungen hinzu, anstatt sie zu ersetzen.

In [24]: A.update(B)

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

ODER

Alternativ können Sie Counter verwenden, wie oben von @Martijn erwähnt.

Adeel
quelle
7

Für eine allgemeinere und erweiterbarere Methode überprüfen Sie Mergedict . Es verwendet singledispatchund kann Werte basierend auf seinen Typen zusammenführen.

Beispiel:

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
quelle
5

Ab Python 3.5: Zusammenführen und Summieren

Dank @tokeinizer_fsj, der mir in einem Kommentar sagte, dass ich die Bedeutung der Frage nicht vollständig verstanden habe (ich dachte, dass Hinzufügen nur das Hinzufügen von Schlüsseln bedeutet, die sich letztendlich in den beiden Diktarien unterscheiden, und stattdessen die gemeinsamen Schlüsselwerte sollte summiert werden). Also habe ich diese Schleife vor dem Zusammenführen hinzugefügt, sodass das zweite Wörterbuch die Summe der gemeinsamen Schlüssel enthält. Das letzte Wörterbuch ist dasjenige, dessen Werte im neuen Wörterbuch, das das Ergebnis der Zusammenführung der beiden ist, gültig sind. Ich denke, das Problem ist gelöst. Die Lösung ist ab Python 3.5 und den folgenden Versionen gültig.

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}

Wiederverwendbarer Code

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
quelle
Bei dieser Art des Zusammenführens von Wörterbüchern werden die Werte für allgemeine Schlüssel nicht hinzugefügt. In der Frage, der Sollwert für Schlüssel bist , 5(2 + 3), aber Ihre Methode zurückgibt 3.
Tokenken_fsj
4

Bitte beachten Sie außerdem, dass a.update( b )es 2x schneller ist alsa + 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
sollte sehen
quelle
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)

Sie könnten dies leicht verallgemeinern:

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)

Dann kann es beliebig viele Diktate dauern.

Jonas Kölker
quelle
2

Dies ist eine einfache Lösung zum Zusammenführen von zwei Wörterbüchern +=, die auf die Werte angewendet werden können. Sie müssen nur einmal über ein Wörterbuch iterieren

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
quelle
1

Diese Lösung ist einfach zu verwenden, sie wird als normales Wörterbuch verwendet, aber Sie können die Summenfunktion verwenden.

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
quelle
1

Wie wäre es mit:

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

Ausgabe:

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

Die oben genannten Lösungen eignen sich hervorragend für Szenarien, in denen Sie eine kleine Anzahl von Counters haben. Wenn Sie eine große Liste von ihnen haben, ist so etwas viel schöner:

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

Die obige Lösung summiert im Wesentlichen das Counters durch:

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

Dies macht das Gleiche, aber ich denke, es hilft immer zu sehen, was es effektiv darunter macht.

Michael Hall
quelle
0

Zusammenführen von drei Dikten a, b, c in einer einzigen Zeile ohne andere Module oder Bibliotheken

Wenn wir die drei Diktate haben

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

Führen Sie alle mit einer einzigen Zeile zusammen und geben Sie mit ein Diktatobjekt zurück

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

Rückkehr

{'a': 9, 'b': 2, 'd': 90}
user6830669
quelle
6
Lesen Sie die Frage erneut, dies ist nicht die erwartete Ausgabe. Es sollte mit Ihren Eingaben gewesen sein : {'a': 9, 'b': 9, 'd': 90}. Ihnen fehlt die "Summen" -Anforderung.
Patrick Mevzek