Có cách pythonic nào để kết hợp hai dicts (thêm giá trị cho các khóa xuất hiện trong cả hai) không?


477

Ví dụ tôi có hai dicts:

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

Tôi cần một cách kết hợp pythonic 'kết hợp' hai câu lệnh sao cho kết quả là:

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

Điều đó có nghĩa là: nếu một khóa xuất hiện trong cả hai lệnh, hãy thêm các giá trị của chúng, nếu nó chỉ xuất hiện trong một lệnh, hãy giữ giá trị của nó.

Câu trả lời:


835

Sử dụng 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})

Bộ đếm về cơ bản là một lớp con dict, vì vậy bạn vẫn có thể làm mọi thứ khác với chúng mà bạn thường làm với loại đó, chẳng hạn như lặp lại các khóa và giá trị của chúng.


4
Cái gì có nhiều Bộ đếm để hợp nhất như thế này? sum(counters)không may làm việc
Tiến sĩ Jan-Philip Gehrcke

27
@ Jan-PhilipGehrcke: Đưa sum()ra giá trị bắt đầu, với sum(counters, Counter()).
Martijn Pieters

5
Cảm ơn. Tuy nhiên, phương pháp này bị ảnh hưởng bởi việc tạo đối tượng trung gian vì các chuỗi tổng là, phải không?
Tiến sĩ Jan-Philip Gehrcke

6
@ Jan-PhilipGehrcke: Tùy chọn khác của bạn là sử dụng vòng lặp và +=thực hiện tổng kết tại chỗ. res = counters[0], Sau đó for c in counters[1:]: res += c.
Martijn Pieters

3
Tôi thích cách tiếp cận đó! Nếu ai đó thích giữ mọi thứ gần với việc xử lý từ điển, người ta cũng có thể sử dụng update()thay vì +=: for c in counters[1:]: res.update(c).
Tiến sĩ Jan-Philip Gehrcke

119

Một giải pháp chung hơn, cũng hoạt động cho các giá trị không phải là số:

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

hoặc thậm chí chung chung hơn:

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

Ví dụ:

>>> 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
Bạn cũng có thể sử dụng for k in b.viewkeys() & a.viewkeys(), khi sử dụng python 2.7 và bỏ qua việc tạo các bộ.
Martijn Pieters

Tại sao set(a)trả về bộ khóa thay vì bộ tuple? Lý do cho việc này là gì?
Sarsaparilla

1
@HaiPhan: bởi vì các lệnh lặp lại trên các khóa, không qua các cặp kv. cf list({..}), for k in {...}vv
georg

2
@Craicerjack: vâng, tôi đã sử dụng operator.mulđể làm rõ rằng mã này là chung chung và không giới hạn trong việc thêm số.
Georgia

6
Bạn có thể thêm tùy chọn tương thích Python 3 không? {**a, **b, **{k: op(a[k], b[k]) for k in a.keys() & b}}nên hoạt động trong 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}

1
Sẽ không sử dụng for x in set(itertools.chain(A, B))hợp lý hơn? Vì sử dụng set trên dict có phải là một chút vô nghĩa vì các khóa đã là duy nhất? Tôi biết đó chỉ là một cách khác để lấy một bộ chìa khóa nhưng tôi thấy nó khó hiểu hơn việc sử dụng itertools.chain(ngụ ý bạn biết cái gì itertools.chain)
jeromej

45

Giới thiệu: Có những giải pháp tốt nhất (có thể). Nhưng bạn phải biết và ghi nhớ nó và đôi khi bạn phải hy vọng rằng phiên bản Python của bạn không quá cũ hoặc bất cứ vấn đề gì có thể xảy ra.

Sau đó, có những giải pháp 'hacky' nhất. Chúng rất hay và ngắn nhưng đôi khi rất khó hiểu, dễ đọc và dễ nhớ.

Tuy nhiên, có một cách khác là cố gắng phát minh lại bánh xe. - Tại sao phải phát minh lại bánh xe? - Nói chung vì đó là cách học thực sự tốt (và đôi khi chỉ vì công cụ đã tồn tại không thực hiện chính xác những gì bạn muốn và / hoặc theo cách bạn muốn) và cách dễ nhất nếu bạn không biết hoặc đừng nhớ công cụ hoàn hảo cho vấn đề của bạn

Vì vậy , tôi đề xuất phát minh lại bánh xe của Counterlớp từ collectionsmô-đun (ít nhất là một phần):

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}

Có lẽ sẽ có cách khác để thực hiện điều đó và đã có công cụ để làm điều đó nhưng thật tuyệt khi hình dung mọi thứ về cơ bản sẽ hoạt động như thế nào.


3
Rất vui cho những người trong chúng ta vẫn còn trên 2.6
Brian B

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

13

Một trong những không có nhập khẩu thêm!

Chúng là một tiêu chuẩn pythonic được gọi là EAFP (Dễ xin tha thứ hơn là Quyền). Mã dưới đây được dựa trên tiêu chuẩn 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: cảm ơn jerhot vì những gợi ý cải tiến của anh ấy.


5
n ^ 2 algorith sẽ chậm hơn đáng kể so với phương pháp Counter
Joop

@DeveshSaini tốt hơn, nhưng vẫn tối ưu phụ :) ví dụ: bạn có thực sự cần sắp xếp không? và sau đó, tại sao hai vòng? bạn đã có tất cả các khóa trong bản tin mới, chỉ là những gợi ý nhỏ để tối ưu hóa
Jerhot

Thuật toán n ^ 1 đã được đặt thay vì thuật toán n ^ 2 trước đó @Joop
Devesh Saini

11

Chắc chắn tóm tắt Counter()s là cách pythonic nhất để đi trong những trường hợp như vậy nhưng chỉ khi nó mang lại một giá trị tích cực . Dưới đây là một ví dụ và như bạn có thể thấy không có ckết quả nào sau khi phủ nhận cgiá trị của Btừ điển trong từ điển.

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

Đó là bởi vì Counters được thiết kế chủ yếu để làm việc với các số nguyên dương để biểu thị số đếm đang chạy (số âm là vô nghĩa). Nhưng để giúp với những trường hợp sử dụng đó, python ghi lại các giới hạn phạm vi và loại tối thiểu như sau:

  • Bản thân lớp Counter là một lớp con từ điển không có giới hạn về các khóa và giá trị của nó. Các giá trị được dự định là số đại diện cho số đếm, nhưng bạn có thể lưu trữ mọi thứ trong trường giá trị.
  • Các most_common()phương pháp đòi hỏi duy nhất mà các giá trị được orderable.
  • Đối với các hoạt động tại chỗ như c[key] += 1, loại giá trị chỉ cần hỗ trợ cộng và trừ. Vì vậy, phân số, số float và số thập phân sẽ hoạt động và các giá trị âm được hỗ trợ. Điều này cũng đúng với update()subtract()cho phép giá trị âm và không cho cả đầu vào và đầu ra.
  • Các phương thức multiset chỉ được thiết kế cho các trường hợp sử dụng có giá trị dương. Các đầu vào có thể âm hoặc bằng 0, nhưng chỉ các đầu ra có giá trị dương được tạo. Không có hạn chế loại, nhưng loại giá trị cần hỗ trợ cộng, trừ và so sánh.
  • Các elements()phương pháp đòi hỏi đếm số nguyên. Nó bỏ qua số không và số âm.

Vì vậy, để giải quyết vấn đề đó sau khi tóm tắt Bộ đếm của bạn, bạn có thể sử dụng Counter.updateđể có được đầu ra mong muốn. Nó hoạt động như thế dict.update()nhưng thêm số lượng thay vì thay thế chúng.

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}

HOẶC LÀ

Thay thế bạn có thể sử dụng Counter như @Martijn đã đề cập ở trên.


7

Đối với một cách tổng quát hơn và mở rộng kiểm tra sáp nhập . Nó sử dụng singledispatchvà có thể hợp nhất các giá trị dựa trên các loại của nó.

Thí dụ:

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

Từ python 3.5: hợp nhất và tổng hợp

Cảm ơn @tokeinizer_fsj đã nói với tôi trong một bình luận rằng tôi không hiểu hoàn toàn ý nghĩa của câu hỏi (tôi nghĩ rằng thêm có nghĩa là chỉ thêm các khóa mà cuối cùng là khác nhau trong hai chế độ độc tài và thay vào đó, tôi có nghĩa là các giá trị khóa chung nên được tóm tắt). Vì vậy, tôi đã thêm vòng lặp đó trước khi hợp nhất, để từ điển thứ hai chứa tổng các khóa chung. Từ điển cuối cùng sẽ là từ điển có giá trị sẽ tồn tại trong từ điển mới, đó là kết quả của sự hợp nhất của hai từ này, vì vậy tôi cho rằng vấn đề đã được giải quyết. Giải pháp có giá trị từ python 3.5 và các phiên bản sau.

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}

Mã tái sử dụng

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

Cách hợp nhất từ ​​điển này không thêm các giá trị cho các khóa chung. Trong câu hỏi, giá trị mong muốn cho khóa b5(2 + 3), nhưng phương thức của bạn đang trả về 3.
tokenizer_fsj

4

Ngoài ra, xin lưu ý a.update( b )là nhanh hơn gấp 2 lầna + 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)

Bạn có thể dễ dàng khái quát điều này:

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)

Sau đó, nó có thể mất bất kỳ số lượng dicts.


2

Đây là một giải pháp đơn giản để hợp nhất hai từ điển +=có thể áp dụng cho các giá trị, nó chỉ phải lặp lại một từ điển một lần

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

Giải pháp này rất dễ sử dụng, nó được sử dụng như một từ điển bình thường, nhưng bạn có thể sử dụng hàm sum.

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

Thế còn:

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

Đầu ra:

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

0

Các giải pháp trên rất phù hợp với kịch bản mà bạn có một số lượng nhỏ Counters. Nếu bạn có một danh sách lớn về chúng, một cái gì đó như thế này đẹp hơn nhiều:

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

Giải pháp trên về cơ bản là tóm tắt Counters bằng cách:

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

Điều này làm điều tương tự nhưng tôi nghĩ nó luôn giúp xem những gì nó đang làm một cách hiệu quả bên dưới.


0

Hợp nhất ba ký tự a, b, c trong một dòng mà không có bất kỳ mô-đun hoặc lib nào khác

Nếu chúng ta có ba dicts

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

Hợp nhất tất cả với một dòng duy nhất và trả về một đối tượng dict bằng cách sử dụng

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

Trở về

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

6
Đọc lại câu hỏi, đây không phải là đầu ra dự kiến. Nó nên có được với đầu vào của bạn : {'a': 9, 'b': 9, 'd': 90}. Bạn đang thiếu yêu cầu "tổng".
Patrick Mevzek
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.