Чи є якийсь пітонічний спосіб поєднати два дикти (додавання значень для ключів, які відображаються в обох)?


477

Наприклад, у мене є два дикти:

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

Мені потрібен пітонічний спосіб "поєднання" двох диктів таким чином, що результат:

{'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)не працює, на жаль.
д-р Ян-Філіп Геркк

27
@ Jan-PhilipGehrcke: Дайте sum()початкове значення, с sum(counters, Counter()).
Martijn Pieters

5
Дякую. Однак на цей метод впливає створення проміжних об'єктів, оскільки підсумовування рядків є, правда?
Доктор Ян-Філіп Геркк

6
@ Jan-PhilipGehrcke: Ваш інший варіант - використовувати цикл і +=робити підсумки на місці. res = counters[0], то for c in counters[1:]: res += c.
Martijn Pieters

3
Мені подобається такий підхід! Якщо хто - то любить тримати речі близько до обробці словників, можна також використовувати update()замість +=: for c in counters[1:]: res.update(c).
Доктор Ян-Філіп Герк

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
Крім того, можна використовувати for k in b.viewkeys() & a.viewkeys(), при використанні Python 2.7 і пропустити створення наборів.
Martijn Pieters

Чому set(a)повертає набір ключів, а не набір кортежів? Що обґрунтовує це?
Сарсапарілья

1
@HaiPhan: бо диктує ітерацію над клавішами, а не над kv-парами. cf list({..}), for k in {...}etc.
georg

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+.
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
Не вдалося for x in set(itertools.chain(A, B))б бути більш логічним? Оскільки використання встановлених на дікт дещо дурниць, оскільки ключі вже унікальні? Я знаю, що це просто інший спосіб отримати набір ключів, але я вважаю це більш заплутаним, ніж використання 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 також
Брайан Б

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

13

Той, що не має додаткового імпорту!

Вони є пітонічним стандартом під назвою EAFP (простіше просити прощення, ніж дозволу). Нижче код заснований на цьому стандарті 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: завдяки джизику за його пропозиції щодо вдосконалення.


5
n ^ 2 алгоритм буде значно повільнішим, ніж метод Counter
Joop

@DeveshSaini кращий, але все ще недооптимальний :) Наприклад: вам справді потрібне сортування? і тоді, чому дві петлі? у вас уже є всі ключі у новому вердикті, лише невеликі підказки для оптимізації
Єжик

n ^ 1 алгоритм розміщено замість попереднього алгоритму n ^ 2 @Joop
Devesh Saini

11

Безумовно, підсумовування Counter()s є найбільш пітонічним шляхом у таких випадках, але лише якщо це призводить до позитивного значення . Наведемо приклад, і як ви бачите, cпісля заперечення значення c's у 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}

АБО

Альтернативно, ви можете використовувати Counter, як @Martijn вже згадував вище.


7

Для більш загального і розширюваного способу перевірити об'єднаний . Він використовує 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, який сказав мені в коментарі, що я не зрозумів повністю сенсу питання (я вважав, що додавання означає просто додавання ключів, які в кінцевому підсумку там, де різні в двох довідниках, і, натомість, я мав на увазі, що загальні ключові значення слід підсумувати). Тому я додав цю петлю перед об'єднанням, щоб другий словник містив суму загальних ключів. Останнім словником буде той, чиї значення триватимуть у новому словнику, який є результатом злиття двох, тож я вирішую, що проблема вирішена. Рішення діє з 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))

Цей спосіб об’єднання словників - це не додавання значень для загальних ключів. У питанні потрібне значення для ключа b- 5(2 + 3), але ваш метод повертається 3.
tokenizer_fsj

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

Це просте рішення для об'єднання двох словників, де +=можна застосувати значення, воно має повторювати словник лише один раз

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

Об'єднання трьох диктовок a, b, c в одному рядку без будь-яких інших модулів або libs

Якщо у нас є три дикти

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

Об'єднайте всі одним рядком і поверніть діктакт, використовуючи

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

Повернення

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

6
Перечитайте питання, це не очікуваний результат. Це повинно було бути з входами: {'a': 9, 'b': 9, 'd': 90}. Ви не вистачаєте вимоги "сума".
Патрік Мевзек
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.