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

444

Например, у меня есть два слова:

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

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

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

То есть: если ключ появляется в обоих диктовках, добавьте их значения, если он появляется только в одном диктовке, сохраните его значение.

Это было ошибочно помечено как дубликат. Другой вопрос, заданный для слияния, когда конфликты обрабатываются с последними победами (аналогичноdict.update()); этот вопрос принимает целочисленные значения и требует сложения. Это может показаться незначительным отличием, но это означает, что ни одно из главных решений по другому вопросу не относится к этому, поэтому маркировка их как дубликатов друг друга вводит в заблуждение. Carl Meyer

Ваш Ответ

18   ответов
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'}
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})

Это делает то же самое, но я думаю, что это всегда помогает увидеть, что он делает под ним.

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)

Тогда это может занять любое количество диктов.

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.

2

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}
Перечитайте вопрос, это не ожидаемый результат. Это должно было быть с вашими входами:{'a': 9, 'b': 9, 'd': 90}, Вам не хватает "суммы" требование.
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}
798

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Таким образом, вы можете делать с ними все остальное, что вы обычно делаете с этим типом, например перебирать их ключи и значения.

Что из нескольких счетчиков для слияния, как это?sum(counters) не работает, к сожалению.
Благодарю. Тем не менее, на этот метод влияет создание промежуточного объекта, поскольку суммирующие строки есть, верно?
@ Ян-ФилиппГерке: дайsum() начальное значение, сsum(counters, Counter()).
@ Jan-PhilipGehrcke: Другой вариант - использовать цикл и+= делать суммирование на месте.res = counters[0], затемfor c in counters[1:]: res += c.
Мне нравится такой подход! Если кому-то нравится держать вещи близко к обработке словарей, можно также использоватьupdate() вместо+=: for c in counters[1:]: res.update(c).
0

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

Intro: ом, и иногда вы должны надеяться, что ваша версия Python не слишком старая или что-то в этом роде.

Тогда есть самые «хакерские». решения. Они велики и коротки, но иногда их трудно понять, прочитать и запомнить.

Есть, однако, альтернатива, которая состоит в том, чтобы попытаться заново изобрести колесо. - Зачем изобретать велосипед? - Как правило, потому что это действительно хороший способ обучения (а иногда просто потому, что уже существующий инструмент не делает именно то, что вы хотите и / или так, как вы хотели бы), и самый простой способ, если вы не знаете или не помните идеальный инструмент для вашей проблемы.

SoПредлагаю изобрести колесо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}

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

Хорошо для тех из нас, все еще на 2.6 также
112

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}
@HaiPhan: потому что диктовки перебирают ключи, а не пары kv. сравниlist({..}), for k in {...} так далее
Почемуset(a) вернуть набор ключей, а не набор кортежей? В чем причина этого?
Не могли бы вы добавить Python 3-совместимый вариант?{**a, **b, **{k: op(a[k], b[k]) for k in a.keys() & b}} должен работать в Python 3.5+.
@Craicerjack: да, я использовалoperator.mul пояснить, что этот код является общим и не ограничивается добавлением чисел.
Вы также можете использоватьfor k in b.viewkeys() & a.viewkeys(), когдаusing python 2.7и пропустить создание наборов.
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
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}
10

Counter()s является наиболее питоническим способом пойти в таких случаях, ноonly if it results in a positive value, Вот пример, и, как вы можете видеть, нет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})

Это потому, чтоCounterВ основном они были разработаны для работы с положительными целыми числами для представления счетчиков (отрицательный счет не имеет смысла). Но чтобы помочь с этими вариантами использования, python документирует минимальный диапазон и ограничения типа следующим образом:

The Counter class itself is a dictionary subclass with no restrictions on its keys and values. The values are intended to be numbers representing counts, but you could store anything in the value field. The most_common() method requires only that the values be orderable. For in-place operations such as c[key] += 1, the value type need only support addition and subtraction. So fractions, floats, and decimals would work and negative values are supported. The same is also true for update() and subtract() which allow negative and zero values for both inputs and outputs. The multiset methods are designed only for use cases with positive values. The inputs may be negative or zero, but only outputs with positive values are created. There are no type restrictions, but the value type needs to support addition, subtraction, and comparison. The elements() method requires integer counts. It ignores zero and negative counts.

Так что для решения этой проблемы после суммирования вашего счетчика вы можете использоватьCounter.update для того, чтобы получить вывод желания. Работает какdict.update() но добавляет количество вместо замены их.

In [24]: A.update(B)

In [25]: A
Out[25]: Counter({'d': 5, 'b': 5, 'a': 1, 'c': -1})
Гораздо лучше, чем принятый ответ. Спасибо!
13
myDict = {}
for k in itertools.chain(A.keys(), B.keys()):
    myDict[k] = A.get(k, 0)+B.get(k, 0)
-2

A = {'a':1, 'b':2, 'c':3}
B = {'b':3, 'c':4, 'd':5}
Merged = dict(A, **B)
Merged == {'a':1, 'b':3, 'c':3, 'd':5}
это не суммирует значения
12

no extra imports!

Их этоpythonic standard называетсяЭСПЦ(Проще просить прощения, чем разрешения). Ниже код основан на том, чтоpython standard.

# 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

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

+ как этот простой метод, нет необходимости импортировать дополнительные
@DeveshSaini лучше, но все еще неоптимально :) Например: вам действительно нужна сортировка? а потом, почему две петли? у вас уже есть все ключи в новичке, только небольшие подсказки для оптимизации
Только что отредактировал, теперь работает. спасибо за обзор. @Jerzyk
Вы уверены, что B ['d' '] удастся добраться до нового приговора? (переменные p.s. в python должны быть заглавными буквами)
Алгоритм n ^ 2 будет значительно медленнее, чем метод Counter
3
From python 3.5: merging and summing

который сказал мне в комментарии, что я не совсем понял смысл вопроса (я подумал, что добавление означало просто добавление ключей, которые в конечном итоге сильно различаются в двух словарях, и вместо этого я имел в виду, что значения общих ключей следует суммировать). Поэтому я добавил этот цикл перед объединением, чтобы второй словарь содержал сумму общих ключей. Последний словарь будет тем, чьи значения будут длиться в новом словаре, который является результатом слияния двух, поэтому я думаю, что проблема решена. Решение действительно с 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}
Reusable code
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))
@tokenizer_fsj вы правы, я исправлю это сейчас
Этот способ объединения словарей не добавл ет значений общих ключей. В вопросе желаемое значение для ключаb является5 (2 + 3), но ваш метод возвращается3.
63
>>> 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}
Это должен быть главный ответ.
Не использовал быfor x in set(itertools.chain(A, B)) быть более логичным? Поскольку использование set on dict - это чушь, ведь ключи уже уникальны? Я знаю, что это просто еще один способ получить набор ключей, но я нахожу это более запутанным, чем использованиеitertools.chain (подразумевая, что вы знаете, чтоitertools.chain делает)
... и в Python 2.x, делаяset(A) немного быстрее, чем делатьset(A.keys()) потому что вы избегаете создания дополнительной последовательности, созданной вызовомkeys() (с помощьюset(A) просто причиныA вернуть объект итератораset()).
set(A) такой же какset(A.keys())так что вы можете отказаться от вызова.keys().

Похожие вопросы