Apakah ada cara pythonic untuk menggabungkan dua dicts (menambahkan nilai untuk kunci yang muncul di keduanya)?

477

Misalnya saya punya dua dicts:

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

Saya membutuhkan cara pythonic untuk 'menggabungkan' dua dicts sehingga hasilnya adalah:

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

Dengan kata lain: jika sebuah kunci muncul di kedua dicts, tambahkan nilainya, jika hanya muncul dalam satu dict, pertahankan nilainya.

Derrick Zhang
sumber

Jawaban:

835

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

Penghitung pada dasarnya adalah subkelas dari dict, jadi Anda masih bisa melakukan segala hal lain dengan mereka yang biasanya Anda lakukan dengan jenis itu, seperti iterate atas kunci dan nilainya.

Martijn Pieters
sumber
4
Bagaimana dengan ada beberapa Counter untuk digabungkan seperti ini? sum(counters)sayangnya tidak bekerja.
Dr. Jan-Philip Gehrcke
27
@ Jan-PhilipGehrcke: Berikan sum()nilai awal, dengan sum(counters, Counter()).
Martijn Pieters
5
Terima kasih. Namun, metode ini dipengaruhi oleh pembuatan objek-menengah seperti string penjumlahan, kan?
Dr. Jan-Philip Gehrcke
6
@ Jan-PhilipGehrcke: Pilihan Anda yang lain adalah menggunakan loop dan +=melakukan penjumlahan di tempat. res = counters[0], Kemudian for c in counters[1:]: res += c.
Martijn Pieters
3
Saya suka pendekatan itu! Jika seseorang seperti menjaga hal-hal dekat dengan pengolahan kamus, kita juga bisa menggunakan update()bukan +=: for c in counters[1:]: res.update(c).
Dr. Jan-Philip Gehrcke
119

Solusi yang lebih umum, yang juga berfungsi untuk nilai-nilai non-numerik:

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

atau bahkan lebih umum:

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

Sebagai contoh:

>>> 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
sumber
27
Anda juga bisa menggunakan for k in b.viewkeys() & a.viewkeys(), ketika menggunakan python 2.7 , dan melewati pembuatan set.
Martijn Pieters
Mengapa set(a)mengembalikan set kunci daripada set tupel? Apa alasannya?
Sarsaparilla
1
@ HaiPhan: karena dicts beralih pada kunci, bukan atas kv-pasangan. cf list({..}), for k in {...}dll
georg
2
@Craicerjack: ya, saya dulu operator.mulmenjelaskan bahwa kode ini generik dan tidak terbatas pada penambahan angka.
georg
6
Bisakah Anda menambahkan opsi yang kompatibel dengan Python 3? {**a, **b, **{k: op(a[k], b[k]) for k in a.keys() & b}}harus bekerja dengan 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
sumber
1
Bukankah menggunakan for x in set(itertools.chain(A, B))lebih logis? Seperti menggunakan set pada dikt adalah sedikit omong kosong karena kunci sudah unik? Saya tahu ini hanyalah cara lain untuk mendapatkan satu set kunci tetapi saya merasa lebih membingungkan daripada menggunakan itertools.chain(menyiratkan Anda tahu apa yang harus itertools.chaindilakukan)
jeromej
45

Intro: Ada (mungkin) solusi terbaik. Tetapi Anda harus mengetahuinya dan mengingatnya dan kadang-kadang Anda harus berharap bahwa versi Python Anda tidak terlalu tua atau apa pun masalahnya.

Lalu ada solusi yang paling 'retas'. Mereka hebat dan pendek tetapi kadang-kadang sulit dimengerti, dibaca dan diingat.

Namun, ada alternatif untuk mencoba menemukan kembali roda. - Mengapa menciptakan kembali roda? - Umumnya karena ini adalah cara yang sangat baik untuk belajar (dan kadang-kadang hanya karena alat yang sudah ada tidak melakukan apa yang Anda inginkan dan / atau cara Anda suka) dan cara termudah jika Anda tidak tahu atau jangan ingat alat yang sempurna untuk masalah Anda.

Jadi , saya mengusulkan untuk menemukan kembali roda Counterkelas dari collectionsmodul (setidaknya sebagian):

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}

Mungkin akan ada cara lain untuk mengimplementasikannya dan sudah ada alat untuk melakukan itu tetapi selalu menyenangkan untuk memvisualisasikan bagaimana hal-hal pada dasarnya akan bekerja.

jeromej
sumber
3
Bagus untuk kita yang masih di 2,6 juga
Brian B
13
myDict = {}
for k in itertools.chain(A.keys(), B.keys()):
    myDict[k] = A.get(k, 0)+B.get(k, 0)

sumber
13

Yang tanpa impor tambahan!

Mereka adalah standar pythonic yang disebut EAFP (Lebih Mudah Meminta Pengampunan daripada Izin). Kode di bawah ini didasarkan pada standar python itu .

# 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: terima kasih kepada jerzyk untuk saran peningkatannya.

Devesh Saini
sumber
5
Algoritma n ^ 2 akan secara signifikan lebih lambat daripada metode Counter
Joop
@ EveshSaini lebih baik, tapi masih kurang optimal :) misalnya: apakah Anda benar-benar perlu menyortir? dan kemudian, mengapa dua loop? Anda sudah memiliki semua kunci dalam dekrit baru, hanya petunjuk kecil untuk dioptimalkan
Jerzyk
Algoritma n ^ 1 telah ditempatkan sebagai pengganti algoritma n ^ 2 sebelumnya @Joop
Devesh Saini
11

Menjumlahkan Counter()s adalah cara yang paling pythonic dalam kasus-kasus seperti itu tetapi hanya jika menghasilkan nilai positif . Berikut ini adalah contoh dan seperti yang Anda lihat tidak ada chasil setelah meniadakan cnilai dalam Bkamus.

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

Itu karena Counters terutama dirancang untuk bekerja dengan bilangan bulat positif untuk mewakili jumlah berjalan (jumlah negatif tidak ada artinya). Tetapi untuk membantu kasus penggunaan tersebut, python mendokumentasikan kisaran minimum dan jenis pembatasan sebagai berikut:

  • Kelas Counter itu sendiri adalah subclass kamus tanpa batasan pada kunci dan nilainya. Nilai-nilai dimaksudkan sebagai angka yang mewakili jumlah, tetapi Anda bisa menyimpan apa pun di bidang nilai.
  • Itu most_common() Metode hanya membutuhkan bahwa nilai-nilai menjadi orderable.
  • Untuk operasi di tempat seperti c[key] += 1, tipe nilai hanya perlu mendukung penambahan dan pengurangan. Jadi pecahan, float, dan desimal akan bekerja dan nilai-nilai negatif didukung. Hal yang sama juga berlaku untuk update()dansubtract() yang memungkinkan nilai negatif dan nol untuk input dan output.
  • Metode multiset dirancang hanya untuk kasus penggunaan dengan nilai positif. Input mungkin negatif atau nol, tetapi hanya output dengan nilai positif yang dibuat. Tidak ada batasan tipe, tetapi tipe nilai perlu mendukung penambahan, pengurangan, dan perbandingan.
  • The elements()Metode membutuhkan jumlah integer. Mengabaikan jumlah nol dan negatif.

Jadi untuk mengatasi masalah itu setelah menjumlahkan Counter Anda, Anda dapat menggunakan Counter.updateuntuk mendapatkan output keinginan. Ini berfungsi seperti dict.update()tetapi menambahkan jumlah alih-alih menggantinya.

In [24]: A.update(B)

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

ATAU

Alternatif Anda dapat menggunakan Penghitung seperti @Martijn telah disebutkan di atas.

Adeel
sumber
7

Untuk cara yang lebih umum dan dapat diperluas, periksa mergedict . Ini menggunakan singledispatchdan dapat menggabungkan nilai berdasarkan jenisnya.

Contoh:

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

Dari python 3.5: penggabungan dan penjumlahan

Terima kasih kepada @tokeinizer_fsj yang memberi tahu saya dalam komentar bahwa saya tidak sepenuhnya memahami arti dari pertanyaan tersebut (saya pikir bahwa menambahkan berarti hanya menambahkan kunci yang pada akhirnya berbeda di kedua dictinaries dan, sebaliknya, saya maksudkan bahwa nilai-nilai kunci umum harus dijumlahkan). Jadi saya menambahkan loop itu sebelum penggabungan, sehingga kamus kedua berisi jumlah dari tombol-tombol umum. Kamus terakhir adalah kamus yang nilainya akan bertahan di kamus baru yang merupakan hasil penggabungan keduanya, jadi saya pikir masalahnya sudah terpecahkan. Solusinya valid dari python 3.5 dan versi berikut.

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}

Kode yang dapat digunakan kembali

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
sumber
Cara penggabungan kamus ini bukan menambahkan nilai untuk kunci umum. Dalam pertanyaan, nilai yang diinginkan untuk kunci badalah 5(2 + 3), tetapi metode Anda kembali 3.
tokenizer_fsj
4

Selain itu, harap dicatat a.update( b )2x lebih cepat daria + 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
seharusnya lihat
sumber
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)

Anda dapat dengan mudah menggeneralisasi ini:

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)

Kemudian dapat mengambil sejumlah dikte.

Jonas Kölker
sumber
2

Ini adalah solusi sederhana untuk menggabungkan dua kamus di mana +=dapat diterapkan ke nilai-nilai, itu harus beralih ke kamus hanya sekali

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

Solusi ini mudah digunakan, digunakan sebagai kamus normal, tetapi Anda dapat menggunakan fungsi penjumlahan.

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

Bagaimana dengan:

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

Keluaran:

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

Solusi di atas sangat bagus untuk skenario di mana Anda memiliki sejumlah kecil Counter. Jika Anda memiliki daftar besar mereka, sesuatu seperti ini jauh lebih baik:

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

Solusi di atas pada dasarnya menjumlahkan Counters dengan:

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

Ini melakukan hal yang sama tetapi saya pikir itu selalu membantu untuk melihat apa yang dilakukannya secara efektif di bawahnya.

Michael Hall
sumber
0

Menggabungkan tiga dict a, b, c dalam satu baris tanpa modul atau lib lainnya

Jika kita memiliki tiga dicts

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

Gabungkan semua dengan satu baris dan kembalikan objek dict menggunakan

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

Kembali

{'a': 9, 'b': 2, 'd': 90}
pengguna6830669
sumber
6
Baca kembali pertanyaannya, ini bukan hasil yang diharapkan. Seharusnya dengan input Anda: {'a': 9, 'b': 9, 'd': 90}. Anda kehilangan persyaratan "jumlah".
Patrick Mevzek