İki dikteyi birleştirmenin herhangi bir pitonik yolu var mı (her ikisinde de görünen tuşlar için değer eklemek)?


477

Mesela iki diktim var:

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

Sonuç olarak iki dikte 'birleştirmek' pythonic bir yol gerekir:

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

Yani, her iki dikte de bir anahtar görünüyorsa, değerlerini ekleyin, yalnızca bir dikte görünüyorsa değerini koruyun.

Yanıtlar:


835

Kullanım 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})

Sayaçlar temelde bir alt sınıftır dict, bu nedenle normalde bu türle yapacağınız anahtarlar ve değerler üzerinde yineleme gibi her şeyi yapabilirsiniz.


4
Bunun gibi birleştirilecek birden fazla Sayaç var mı? sum(counters)maalesef çalışmıyor.
Dr.Jan-Philip Gehrcke

27
@ Jan-PhilipGehrcke: ile sum()bir başlangıç ​​değeri verin sum(counters, Counter()).
Martijn Pieters

5
Teşekkürler. Ancak, bu yöntem toplama dizeleri olarak ara-nesne-oluşturma işleminden etkilenir, değil mi?
Dr.Jan-Philip Gehrcke

6
@ Jan-PhilipGehrcke: Diğer seçeneğiniz bir döngü kullanmak ve +=yerinde toplama yapmaktır. res = counters[0], sonra for c in counters[1:]: res += c.
Martijn Pieters

3
Bu yaklaşımı seviyorum! Birisi seviyor işleme sözlüklere yakın şeyler tutarsanız, bir de kullanabilirsiniz update()yerine +=: for c in counters[1:]: res.update(c).
Dr.Jan-Philip Gehrcke

119

Sayısal olmayan değerler için de çalışan daha genel bir çözüm:

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

hatta daha genel:

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

Örneğin:

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

27
Ayrıca for k in b.viewkeys() & a.viewkeys(), python 2.7'yi kullanırken kullanabilir ve kümelerin oluşturulmasını atlayabilirsiniz.
Martijn Pieters

Neden set(a)anahtar seti yerine tuples seti geri dönüyor? Bunun mantığı nedir?
Sarsaparilla

1
@HaiPhan: çünkü kv-çiftlerini değil, anahtarları yineliyor. cf list({..}), for k in {...}etc
georg

2
@Craicerjack: evet, operator.mulbu kodun genel olduğunu ve sayı eklemeyle sınırlı olmadığını açıkça belirtirdim.
georg

6
Python 3 uyumlu bir seçenek ekleyebilir misiniz? {**a, **b, **{k: op(a[k], b[k]) for k in a.keys() & b}}Python 3.5+ ile çalışmalıdır.
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}

1
Kullanmak for x in set(itertools.chain(A, B))daha mantıklı olmaz mıydı? Set on dict kullanmak, tuşlar zaten benzersiz olduğu için saçma bir şey mi? Anahtarların bir setini almanın başka bir yolu olduğunu biliyorum ama kullanmaktan daha kafa karıştırıcı buluyorum itertools.chain(ne yaptığını biliyorum itertools.chain)
jeromej

45

Giriş: (Muhtemelen) en iyi çözümler var. Ancak bunu bilmeniz ve hatırlamanız gerekir ve bazen Python sürümünüzün çok eski olmadığını veya sorunun ne olabileceğini ummanız gerekir.

Sonra en 'hacky' çözümleri var. Harika ve kısa ama bazen anlamak, okumak ve hatırlamak zordur.

Bununla birlikte, tekerleği yeniden icat etmeye çalışmak için bir alternatif var. - Tekerleği neden yeniden icat ediyorsun? - Genel olarak, öğrenmenin gerçekten iyi bir yolu (ve bazen de zaten var olan araç tam olarak istediğinizi ve / veya istediğiniz şekilde yapmadığı için) ve bilmiyorsanız en kolay yol olduğu için veya probleminiz için mükemmel aracı hatırlamıyorum.

Yani , Countersınıfın tekerleğini collectionsmodülden yeniden keşfetmeyi öneriyorum (en azından kısmen):

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}

Muhtemelen bunu uygulamanın başka bir yolu olurdu ve bunu yapmak için zaten araçlar var, ancak her şeyin temelde nasıl çalışacağını görselleştirmek her zaman güzel.


3
Hala 2.6 üzerinde olanlar için güzel
Brian B

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

13

İle bir ekstra ithalat!

Onların bir pitonik standardı EAFP ( Affetmeden İzin İstemek Daha Kolay) adlı bir pitonik . Aşağıdaki kod bu python standardını temel alır .

# 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: sayesinde iyileştirme önerileri jerzyk .


5
n ^ 2 algoritması Sayaç yönteminden önemli ölçüde daha yavaş olacaktır
Joop

@DeveshSaini daha iyi, ama yine de alt optimal :) örneğin: gerçekten sıralamaya ihtiyacınız var mı? ve sonra neden iki döngü? Zaten newdict tüm anahtarları, optimize sadece küçük ipuçları var
Jerzyk

Önceki n ^ 2 algoritması yerine n ^ 1 algoritması yerleştirildi @Joop
Saini

11

Kesinlikle Counter()s'yi toplamak, bu gibi durumlarda gitmek için en pythonic yoldur, ancak yalnızca pozitif bir değerle sonuçlanırsa . İşte bir örnek ve gördüğünüz gibi , sözlükteki değerini creddettikten sonra sonuç yok .cB

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

Çünkü Counters öncelikle çalışan sayıları temsil etmek için pozitif tamsayılarla çalışacak şekilde tasarlanmıştır (negatif sayı anlamsızdır). Ancak bu kullanım durumlarına yardımcı olmak için, python minimum aralık ve tür kısıtlamalarını aşağıdaki gibi belgeler:

  • Counter sınıfının kendisi, anahtarlarında ve değerlerinde kısıtlama bulunmayan bir sözlük alt sınıfıdır. Değerlerin, sayıları temsil eden sayılar olması amaçlanmıştır, ancak değer alanında her şeyi saklayabilirsiniz.
  • most_common()Yöntem değerleri sipariş edilebilir olması gerekli olan tek şey.
  • c[key] += 1Değer türü gibi yerinde işlemler için yalnızca toplama ve çıkarma desteği gerekir. Böylece kesirler, kayan noktalar ve ondalık sayılar işe yarar ve negatif değerler desteklenir. Aynı şey hem giriş hem de çıkış için negatif ve sıfır değerlerine izin veren update()ve için de geçerlidir subtract().
  • Çoklu ayar yöntemleri yalnızca pozitif değerlere sahip kullanım durumları için tasarlanmıştır. Girişler negatif veya sıfır olabilir, ancak yalnızca pozitif değerlere sahip çıkışlar oluşturulur. Tür kısıtlaması yoktur, ancak değer türünün toplama, çıkarma ve karşılaştırmayı desteklemesi gerekir.
  • elements()Yöntem Integer sayılarını gerektirmektedir. Sıfır ve negatif sayıları yok sayar.

Bu nedenle, Sayacınızı topladıktan sonra bu sorunu çözmek Counter.updateiçin, arzu çıktısını almak için kullanabilirsiniz . Bu gibi çalışır, dict.update()ancak yerine sayıları ekler.

In [24]: A.update(B)

In [25]: A
Out[25]: Counter({'d': 5, 'b': 5, 'a': 1, 'c': -1})

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}

VEYA

Alternatif olarak @Martijn yukarıda belirtildiği gibi Counter kullanabilirsiniz.


7

Daha genel ve genişletilebilir bir yol için birleşmeyi kontrol edin . singledispatchDeğerleri türlerine göre kullanır ve birleştirebilir.

Misal:

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

5

Python 3.5'ten: Birleştirme ve toplama

Bir yorumda bana sorunun anlamını tam olarak alamadığımı söyleyen @tokeinizer_fsj sayesinde (eklemenin sadece iki diktatörlükte farklı yerlerde anahtarlar eklemeyi düşündüm ve bunun yerine ortak anahtar değerlerin toplanmalıdır). Bu döngüyü birleştirmeden önce ekledim, böylece ikinci sözlük ortak anahtarların toplamını içerir. Son sözlük değerleri ikisinin birleştirilmesinin sonucu olan yeni sözlükte devam edecek, bu yüzden problem çözüldü. Çözüm python 3.5 ve sonraki sürümler için geçerlidir.

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}

Yeniden kullanılabilir kod

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

Sözlükleri birleştirmenin bu yolu, ortak anahtarların değerlerini eklemez. Soruda, anahtar için istenen değerb olan 5(2 + 3), ancak yöntem dönüyor 3.
tokenizer_fsj

4

Ayrıca, not edin a.update( b )2x daha hızlı olduğunua + 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

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)

Bunu kolayca genelleştirebilirsiniz:

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)

Sonra herhangi bir sayıda dikte alabilir.


2

Bu, +=değerlere uygulanabilen iki sözlük birleştirmek için basit bir çözümdür , bir sözlük üzerinde yalnızca bir kez yineleme yapmak zorundadı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}

1

Bu çözümün kullanımı kolaydır, normal sözlük olarak kullanılır, ancak sum işlevini kullanabilirsiniz.

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

Ne dersin:

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

Çıktı:

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

0

Yukarıdaki çözümler az sayıda Counters'nin olduğu senaryo için mükemmeldir . Bunların büyük bir listesi varsa, böyle bir şey çok daha hoş:

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

Yukarıdaki çözüm esasen Counters'yi şu şekilde toplamaktadır :

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

Bu da aynı şeyi yapıyor ancak bence bunun altında etkili bir şekilde ne yaptığını görmeye her zaman yardımcı oluyor.


0

Üç dikte a, b, c'yi başka bir modül veya kütük olmadan tek bir satırda birleştirmek

Üç diktemiz varsa

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

Hepsini tek bir satırla birleştir ve kullanarak bir dikte nesnesi döndür

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

Geri dönen

{'a': 9, 'b': 2, 'd': 90}

6
Soruyu yeniden okuyun, bu beklenen çıktı değildir. Bu sizin girdilerle olmalıydı: {'a': 9, 'b': 9, 'd': 90}. "Toplam" gereksinimini kaçırıyorsunuz.
Patrick Mevzek
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.