Есть ли какой-нибудь питонный способ объединить два слова (добавив значения для ключей, которые появляются в обоих)?

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()).
Мартин Питерс
5
Спасибо. Тем не менее, на этот метод влияет создание промежуточного объекта, поскольку суммирующие строки есть, верно?
Доктор Ян-Филипп Герке
6
@ Jan-PhilipGehrcke: Другой вариант - использовать цикл и +=суммировать на месте. res = counters[0]тогда for c in counters[1:]: res += c.
Мартин Питерс
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}
Georg
источник
27
Вы также можете использовать for k in b.viewkeys() & a.viewkeys()при использовании Python 2.7 и пропустить создание наборов.
Мартин Питерс
Почему set(a)возвращает набор ключей, а не набор кортежей? В чем причина этого?
Sarsaparilla
1
@HaiPhan: потому что диктовки перебирают ключи, а не пары kv. ср list({..})и for k in {...}т. д.
георг
2
@Craicerjack: да, раньше я давал operator.mulпонять, что этот код является общим и не ограничивается добавлением чисел.
Георг
6
Не могли бы вы добавить Python 3-совместимый вариант? {**a, **b, **{k: op(a[k], b[k]) for k in a.keys() & b}}должен работать в Python 3.5+.
17
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}

Возможно, есть другой способ реализовать это, и уже есть инструменты для этого, но всегда приятно представить, как все будет работать в принципе.

jeromej
источник
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

РЕДАКТИРОВАТЬ: спасибо Jerzyk за его предложения по улучшению.

Девеш Сайни
источник
5
Алгоритм n ^ 2 будет значительно медленнее, чем метод Counter
Joop
@DeveshSaini лучше, но все еще неоптимально :) Например: вам действительно нужна сортировка? а потом, почему две петли? у вас уже есть все ключи в новичке, только небольшие подсказки для оптимизации
Ежиц
Алгоритм n ^ 1 был помещен вместо предыдущего алгоритма n ^ 2 @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})
Kasramvd
источник
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.

Adeel
источник
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'}
schettino72
источник
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))
Giovanni G. PY
источник
Этот способ объединения словарей не добавляет значения для общих ключей. В вопросе желаемое значение ключа 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}
ragardner
источник
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}
Lacobus
источник
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})

Вышеупомянутое решение по существу суммирует Counterс помощью:

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 в одну строку без каких-либо других модулей или библиотек

Если у нас есть три дикта

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

Объединить все в одну строку и вернуть объект dict, используя

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

возврате

{'a': 9, 'b': 2, 'd': 90}
user6830669
источник
6
Перечитайте вопрос, это не ожидаемый результат. Это должно было быть с входами: {'a': 9, 'b': 9, 'd': 90}. Вы пропустите требование "сумма".
Патрик Мевзек