2つのdictを組み合わせるためのpythonicの方法はありますか(両方に現れるキーの値を追加します)?


477

たとえば、私は2つの辞書を持っています:

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

結果が次のようになるように2つのdictを「組み合わせる」ためのpython的な方法が必要です:

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

つまり、キーが両方の辞書に表示される場合は、それらの値を追加します。一方の辞書にのみ表示される場合は、その値を保持します。

回答:


835

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

カウンタは基本的にのサブクラスでdictあるため、キーと値を反復するなど、通常はその型を使用して行う他のすべての操作を実行できます。


4
このようにマージする複数のカウンターは何ですか?sum(counters)残念ながら機能しません。
Jan-Philip Gehrcke博士、2015年

27
@ Jan-PhilipGehrcke:sum()で開始値を指定しsum(counters, Counter())ます。
Martijn Pieters

5
ありがとう。ただし、この方法は中間オブジェクトの作成の影響を受けます。
Jan-Philip Gehrcke博士、2015年

6
@ Jan-PhilipGehrcke:もう1つのオプションは、ループを使用+=してインプレース加算を行うことです。res = counters[0]、その後for c in counters[1:]: res += c
Martijn Pieters

3
私はそのアプローチが好きです!誰かが辞書の処理に近いものを保持したい場合update()は、+=:の代わりに使用することもできますfor c in counters[1:]: res.update(c)
Jan-Philip Gehrcke博士、2015年

119

数値以外の値でも機能する、より一般的なソリューション:

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

またはさらに一般的:

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

例えば:

>>> 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
Python 2.7を使用する場合for k in b.viewkeys() & a.viewkeys()は、使用して、セットの作成をスキップすることもできます。
Martijn Pieters

なぜset(a)タプルのセットではなくキーのセットを返すのですか?これの理由は何ですか?
サルサパリラ2017

1
@HaiPhan:dictsはkvペアではなくキーを反復するためです。CF list({..})for k in {...}など
ゲオルク

2
@Craicerjack:うん、私operator.mulはこのコードが一般的であり、数字の追加に限定されないことを明確にしていた。
georg

6
Python 3互換のオプションを追加できますか?{**a, **b, **{k: op(a[k], b[k]) for k in a.keys() & b}}Python 3.5以降で動作するはずです。
金庫室2017年

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
for x in set(itertools.chain(A, B))より論理的ではないでしょうか?キーがすでに一意であるため、set on dictを使用するのは少しナンセンスです。それはキーのセットを取得するための別の方法であるitertools.chainことは知ってitertools.chainいますが、使用するよりも混乱します(何をしているのかを知っていることを意味します)
jeromej

45

はじめに:( おそらく)最良の解決策があります。しかし、それを知って覚えておく必要があります。また、Pythonのバージョンが古すぎないか、問題が何であろうと期待しないといけない場合もあります。

次に、最も「ハッキー」なソリューションがあります。それらは素晴らしくて短いですが、時々、理解したり、読んだり、覚えたりするのが難しいです。

ただし、ホイールの再発明を試みるという選択肢もあります。-なぜ車輪を再発明するのですか?-一般に、これは本当に学ぶための優れた方法であり(既存のツールが希望どおりに機能しない、または希望どおりの方法を実行しない場合があるため)、または知らない場合や最も簡単な方法あなたの問題に最適なツールを覚えていない。

したがって、モジュールCounterからクラスのホイールを再発明することを提案しcollectionsます(少なくとも部分的に):

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}

それを実装する他の方法がおそらくあり、それを行うためのツールはすでにありますが、基本的にどのように機能するかを視覚化することは常に素晴らしいことです。


3
まだ2.6にいる私たちにとっても良いです
ブライアンB

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

13

余分な輸入ないもの!

彼らはEAFP(許可よりも許しを求めるのが簡単)と呼ばれるPython標準です。以下のコードは、その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

編集:彼の改善提案のためのjerzykに感謝します。


5
n ^ 2アルゴリズムはCounterメソッドよりも大幅に遅くなります
Joop

@DeveshSainiの方が良いですが、それでも最適ではありません:)例:本当にソートが必要ですか?では、なぜ2つのループが必要なのでしょうか。newdictにはすべてのキーがあり、最適化するための小さなヒントがあります
Jerzyk

以前のn ^ 2アルゴリズムの代わりにn ^ 1アルゴリズムが配置されました@Joop
Devesh Saini

11

Counter()sを明確に合計することは、そのような場合に行くための最もパイソン的な方法ですが、それ正の値になる場合に限られます。これは例であり、ご覧のcとおり、辞書cのの値を否定した後の結果はありません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})

これは、Countersが正の整数を使用して現在のカウントを表すように設計されているためです(負のカウントは無意味です)。しかし、それらのユースケースを支援するために、pythonは次のように最小範囲とタイプ制限を文書化します:

  • Counterクラス自体は、キーと値に制限のないディクショナリサブクラスです。値はカウントを表す数値を意図していますが、値フィールドには何でも格納できます。
  • このmost_common()メソッドは、値が順序付け可能であることのみを必要とします。
  • などのインプレース演算c[key] += 1の場合、値タイプは加算と減算のみをサポートする必要があります。したがって、分数、浮動小数点数、および小数が機能し、負の値がサポートされます。同じことはupdate()subtract()その負と、入力と出力の両方のためのゼロ値を可能にします。
  • マルチセットメソッドは、正の値を使用する場合にのみ設計されています。入力は負またはゼロですが、正の値を持つ出力のみが作成されます。タイプの制限はありませんが、値のタイプは加算、減算、比較をサポートする必要があります。
  • このelements()メソッドには整数カウントが必要です。ゼロおよび負のカウントは無視されます。

したがって、カウンターを合計した後にその問題を回避Counter.updateするには、欲望の出力を取得するために使用できます。のように動作dict.update()しますが、それらを置き換える代わりにカウントを追加します。

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}

または

@Martijnが前述したように、代わりにCounterを使用できます。


7

より一般的で拡張可能な方法については、mergedictを確認してください。これはsingledispatch、そのタイプに基づいて値を使用およびマージできます。

例:

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から:マージと合計

@tokeinizer_fsjのおかげでコメントで私は質問の意味を完全には理解できなかったと言った合計する必要があります)。そこで、マージの前にそのループを追加して、2番目の辞書に共通キーの合計が含まれるようにしました。最後のディクショナリは、値が2つのディクショナリのマージの結果である新しいディクショナリで存続するディクショナリになるため、問題が解決されると思います。このソリューションは、Python 3.5以降のバージョンから有効です。

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}

再利用可能なコード

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

辞書をマージするこの方法は、一般的なキーの値を追加するものではありません。質問では、キーの望ましい値b5(2 + 3)ですが、メソッドはを返し3ます。
tokenizer_fsj 2018

4

さらに、a.update( b )2倍の速さであることに注意してください。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

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)

これは簡単に一般化できます。

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)

次に、いくつもの口述を取ることができます。


2

これは+=、値に適用できる2つのディクショナリをマージするための簡単なソリューションです。ディクショナリを1回だけ繰り返す必要があります。

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

このソリューションは使いやすく、通常の辞書として使用されますが、合計関数を使用できます。

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

何について:

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

出力:

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

0

上記のソリューションは、Countersの数が少ないシナリオに最適です。ただし、それらの大きなリストがある場合は、次のようなものがより適切です。

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

上記のソリューションは、基本的にCountersを合計します。

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

これは同じことを行いますが、その下で効果的に何が行われているかを確認することは常に役立つと思います。


0

他のモジュールやライブラリなしで、3つのディクショナリa、b、cを1行にマージする

3つの辞書がある場合

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

すべてを1行でマージし、次を使用してdictオブジェクトを返します

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

戻る

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

6
質問をもう一度読んでください。これは予想される出力ではありません。それはあなたの入力にあったはず{'a': 9, 'b': 9, 'd': 90}です:。「合計」要件がありません。
Patrick Mevzek 2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.