Muszę scalić wiele słowników, oto co mam na przykład:
dict1 = {1:{"a":{A}}, 2:{"b":{B}}}
dict2 = {2:{"c":{C}}, 3:{"d":{D}}
Z A
B
C
i D
będąc liśćmi drzewa, jak{"info1":"value", "info2":"value2"}
Możliwe, że istnieje nieznany poziom (głębokość) słowników {2:{"c":{"z":{"y":{C}}}}}
W moim przypadku reprezentuje strukturę katalogów / plików z węzłami będącymi dokumentami i pozostawiając jako pliki.
Chcę je połączyć, aby uzyskać:
dict3 = {1:{"a":{A}}, 2:{"b":{B},"c":{C}}, 3:{"d":{D}}}
Nie jestem pewien, jak mógłbym to łatwo zrobić w Pythonie.
python
dictionary
merge
array-merge
fdhex
źródło
źródło
y
spłaszczyć doc
poziomu czy co? Twój przykład jest niekompletny.Odpowiedzi:
jest to w rzeczywistości dość trudne - szczególnie jeśli chcesz otrzymać przydatny komunikat o błędzie, gdy rzeczy są niespójne, jednocześnie poprawnie akceptując zduplikowane, ale spójne wpisy (coś, czego nie robi żadna inna odpowiedź ...)
zakładając, że nie masz dużej liczby wpisów, funkcja rekurencyjna jest najłatwiejsza:
def merge(a, b, path=None): "merges b into a" if path is None: path = [] for key in b: if key in a: if isinstance(a[key], dict) and isinstance(b[key], dict): merge(a[key], b[key], path + [str(key)]) elif a[key] == b[key]: pass # same leaf value else: raise Exception('Conflict at %s' % '.'.join(path + [str(key)])) else: a[key] = b[key] return a # works print(merge({1:{"a":"A"},2:{"b":"B"}}, {2:{"c":"C"},3:{"d":"D"}})) # has conflict merge({1:{"a":"A"},2:{"b":"B"}}, {1:{"a":"A"},2:{"b":"C"}})
zauważ, że to mutuje
a
- zawartośćb
jest dodawana doa
(która również jest zwracana). jeśli chcesz zatrzymać,a
możesz to nazwać takmerge(dict(a), b)
.agf wskazał (poniżej), że możesz mieć więcej niż dwie dykty, w takim przypadku możesz użyć:
gdzie wszystko zostanie dodane do dict1.
[uwaga - zredagowałem moją początkową odpowiedź, aby zmienić pierwszy argument; to sprawia, że „redukcja” jest łatwiejsza do wyjaśnienia]
ps w pythonie 3, będziesz również potrzebować
from functools import reduce
źródło
reduce
pętli lub równoważnej, aby pracować z dowolną liczbądict
s zamiast dwóch. Jednak nie jestem pewien, czy to robi to, czego chce (nie był jasny), kończysz z2: {'c': {'z': {'y': {'info1': 'value', 'info2': 'value2'}}}, 'b': {'info1': 'value', 'info2': 'value2'}}
jego drugim przykładem, nie jestem pewien, czy chcez
iy
spłaszczony, czy nie?a[key] = a[key] + b[key]
. Dzięki za pomocną odpowiedź.copy.deepcopy
.Oto prosty sposób na zrobienie tego za pomocą generatorów:
def mergedicts(dict1, dict2): for k in set(dict1.keys()).union(dict2.keys()): if k in dict1 and k in dict2: if isinstance(dict1[k], dict) and isinstance(dict2[k], dict): yield (k, dict(mergedicts(dict1[k], dict2[k]))) else: # If one of the values is not a dict, you can't continue merging it. # Value from second dict overrides one in first and we move on. yield (k, dict2[k]) # Alternatively, replace this with exception raiser to alert you of value conflicts elif k in dict1: yield (k, dict1[k]) else: yield (k, dict2[k]) dict1 = {1:{"a":"A"},2:{"b":"B"}} dict2 = {2:{"c":"C"},3:{"d":"D"}} print dict(mergedicts(dict1,dict2))
To drukuje:
{1: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}}
źródło
Jednym z problemów związanych z tym pytaniem jest to, że wartościami dyktowania mogą być dowolnie złożone fragmenty danych. Na podstawie tych i innych odpowiedzi wymyśliłem ten kod:
class YamlReaderError(Exception): pass def data_merge(a, b): """merges b into a and return merged result NOTE: tuples and arbitrary objects are not handled as it is totally ambiguous what should happen""" key = None # ## debug output # sys.stderr.write("DEBUG: %s to %s\n" %(b,a)) try: if a is None or isinstance(a, str) or isinstance(a, unicode) or isinstance(a, int) or isinstance(a, long) or isinstance(a, float): # border case for first run or if a is a primitive a = b elif isinstance(a, list): # lists can be only appended if isinstance(b, list): # merge lists a.extend(b) else: # append to list a.append(b) elif isinstance(a, dict): # dicts must be merged if isinstance(b, dict): for key in b: if key in a: a[key] = data_merge(a[key], b[key]) else: a[key] = b[key] else: raise YamlReaderError('Cannot merge non-dict "%s" into dict "%s"' % (b, a)) else: raise YamlReaderError('NOT IMPLEMENTED "%s" into "%s"' % (b, a)) except TypeError, e: raise YamlReaderError('TypeError "%s" in key "%s" when merging "%s" into "%s"' % (e, key, b, a)) return a
Mój przypadek użycia to scalanie plików YAML, gdzie mam do czynienia tylko z podzbiorem możliwych typów danych. Dlatego mogę ignorować krotki i inne obiekty. Dla mnie rozsądna logika scalania oznacza
Wszystko inne i nieprzewidziane prowadzi do błędu.
źródło
isinstance(a, (str, unicode, int, long, float))
niej?Możesz spróbować połączyć się .
Instalacja
Stosowanie
from mergedeep import merge a = {"keyA": 1} b = {"keyB": {"sub1": 10}} c = {"keyB": {"sub2": 20}} merge(a, b, c) print(a) # {"keyA": 1, "keyB": {"sub1": 10, "sub2": 20}}
źródło
Ponieważ jest to kwestia kanoniczna (pomimo pewnych nieogólności), przedstawiam kanoniczne podejście Pythona do rozwiązania tego problemu.
Najprostszy przypadek: „liście to zagnieżdżone dykty, które kończą się pustymi słowami”:
d1 = {'a': {1: {'foo': {}}, 2: {}}} d2 = {'a': {1: {}, 2: {'bar': {}}}} d3 = {'b': {3: {'baz': {}}}} d4 = {'a': {1: {'quux': {}}}}
To najprostszy przypadek rekurencji i zalecałbym dwa naiwne podejścia:
def rec_merge1(d1, d2): '''return new merged dict of dicts''' for k, v in d1.items(): # in Python 2, use .iteritems()! if k in d2: d2[k] = rec_merge1(v, d2[k]) d3 = d1.copy() d3.update(d2) return d3 def rec_merge2(d1, d2): '''update first dict with second recursively''' for k, v in d1.items(): # in Python 2, use .iteritems()! if k in d2: d2[k] = rec_merge2(v, d2[k]) d1.update(d2) return d1
Wydaje mi się, że wolałbym drugi od pierwszego, ale pamiętaj, że pierwotny stan pierwszego musiałby zostać odbudowany od jego pochodzenia. Oto zastosowanie:
>>> from functools import reduce # only required for Python 3. >>> reduce(rec_merge1, (d1, d2, d3, d4)) {'a': {1: {'quux': {}, 'foo': {}}, 2: {'bar': {}}}, 'b': {3: {'baz': {}}}} >>> reduce(rec_merge2, (d1, d2, d3, d4)) {'a': {1: {'quux': {}, 'foo': {}}, 2: {'bar': {}}}, 'b': {3: {'baz': {}}}}
Przypadek złożony: „liście są dowolnego innego typu:”
Więc jeśli kończą się dyktami, jest to prosty przypadek połączenia pustych dykt na końcu. Jeśli nie, to nie jest takie trywialne. Jeśli ciągi, jak je scalić? Zestawy mogą być aktualizowane w podobny sposób, więc możemy poddać to obróbce, ale tracimy kolejność, w jakiej zostały połączone. Więc czy porządek ma znaczenie?
Zatem zamiast większej ilości informacji, najprostszym podejściem będzie zapewnienie im standardowego traktowania aktualizacji, jeśli obie wartości nie są dyktami: tj. Wartość drugiego dyktu zastąpi pierwszą, nawet jeśli wartość drugiej dyktu to None, a pierwsza to a dyktować z dużą ilością informacji.
d1 = {'a': {1: 'foo', 2: None}} d2 = {'a': {1: None, 2: 'bar'}} d3 = {'b': {3: 'baz'}} d4 = {'a': {1: 'quux'}} from collections.abc import MutableMapping def rec_merge(d1, d2): ''' Update two dicts of dicts recursively, if either mapping has leaves that are non-dicts, the second's leaf overwrites the first's. ''' for k, v in d1.items(): if k in d2: # this next check is the only difference! if all(isinstance(e, MutableMapping) for e in (v, d2[k])): d2[k] = rec_merge(v, d2[k]) # we could further check types and merge as appropriate here. d3 = d1.copy() d3.update(d2) return d3
I teraz
from functools import reduce reduce(rec_merge, (d1, d2, d3, d4))
zwroty
{'a': {1: 'quux', 2: 'bar'}, 'b': {3: 'baz'}}
Zastosowanie do pierwotnego pytania:
Musiałem usunąć nawiasy klamrowe wokół liter i umieścić je w pojedynczych cudzysłowach, aby był to legalny Python (w przeciwnym razie byłyby ustawionymi literałami w Pythonie 2.7+), a także dodać brakujący nawias:
dict1 = {1:{"a":'A'}, 2:{"b":'B'}} dict2 = {2:{"c":'C'}, 3:{"d":'D'}}
a
rec_merge(dict1, dict2)
teraz zwraca:{1: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}}
Który pasuje do pożądanego wyniku pierwotnego pytania (po zmianie, np
{A}
do'A'
.)źródło
Na podstawie @andrew cooke. Ta wersja obsługuje zagnieżdżone listy dykt, a także umożliwia aktualizację wartości
def merge(a, b, path=None, update=True): "http://stackoverflow.com/questions/7204805/python-dictionaries-of-dictionaries-merge" "merges b into a" if path is None: path = [] for key in b: if key in a: if isinstance(a[key], dict) and isinstance(b[key], dict): merge(a[key], b[key], path + [str(key)]) elif a[key] == b[key]: pass # same leaf value elif isinstance(a[key], list) and isinstance(b[key], list): for idx, val in enumerate(b[key]): a[key][idx] = merge(a[key][idx], b[key][idx], path + [str(key), str(idx)], update=update) elif update: a[key] = b[key] else: raise Exception('Conflict at %s' % '.'.join(path + [str(key)])) else: a[key] = b[key] return a
źródło
Ta prosta procedura rekurencyjna połączy jeden słownik w inny, nadpisując sprzeczne klucze:
#!/usr/bin/env python2.7 def merge_dicts(dict1, dict2): """ Recursively merges dict2 into dict1 """ if not isinstance(dict1, dict) or not isinstance(dict2, dict): return dict2 for k in dict2: if k in dict1: dict1[k] = merge_dicts(dict1[k], dict2[k]) else: dict1[k] = dict2[k] return dict1 print (merge_dicts({1:{"a":"A"}, 2:{"b":"B"}}, {2:{"c":"C"}, 3:{"d":"D"}})) print (merge_dicts({1:{"a":"A"}, 2:{"b":"B"}}, {1:{"a":"A"}, 2:{"b":"C"}}))
Wynik:
{1: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}} {1: {'a': 'A'}, 2: {'b': 'C'}}
źródło
Na podstawie odpowiedzi udzielonych przez @andrew cooke. Lepiej dba o listy zagnieżdżone.
def deep_merge_lists(original, incoming): """ Deep merge two lists. Modifies original. Recursively call deep merge on each correlated element of list. If item type in both elements are a. dict: Call deep_merge_dicts on both values. b. list: Recursively call deep_merge_lists on both values. c. any other type: Value is overridden. d. conflicting types: Value is overridden. If length of incoming list is more that of original then extra values are appended. """ common_length = min(len(original), len(incoming)) for idx in range(common_length): if isinstance(original[idx], dict) and isinstance(incoming[idx], dict): deep_merge_dicts(original[idx], incoming[idx]) elif isinstance(original[idx], list) and isinstance(incoming[idx], list): deep_merge_lists(original[idx], incoming[idx]) else: original[idx] = incoming[idx] for idx in range(common_length, len(incoming)): original.append(incoming[idx]) def deep_merge_dicts(original, incoming): """ Deep merge two dictionaries. Modifies original. For key conflicts if both values are: a. dict: Recursively call deep_merge_dicts on both values. b. list: Call deep_merge_lists on both values. c. any other type: Value is overridden. d. conflicting types: Value is overridden. """ for key in incoming: if key in original: if isinstance(original[key], dict) and isinstance(incoming[key], dict): deep_merge_dicts(original[key], incoming[key]) elif isinstance(original[key], list) and isinstance(incoming[key], list): deep_merge_lists(original[key], incoming[key]) else: original[key] = incoming[key] else: original[key] = incoming[key]
źródło
Jeśli masz nieznany poziom słowników, proponuję funkcję rekurencyjną:
def combineDicts(dictionary1, dictionary2): output = {} for item, value in dictionary1.iteritems(): if dictionary2.has_key(item): if isinstance(dictionary2[item], dict): output[item] = combineDicts(value, dictionary2.pop(item)) else: output[item] = value for item, value in dictionary2.iteritems(): output[item] = value return output
źródło
Przegląd
Poniższe podejście dzieli problem głębokiego połączenia dykt na:
Sparametryzowana funkcja płytkiego scalania,
merge(f)(a,b)
która używa funkcjif
do scalenia dwóch dykta
ib
Rekursywna funkcja scalająca,
f
która ma być używana razem zmerge
Realizacja
Funkcję do łączenia dwóch (nie zagnieżdżonych) dykt można zapisać na wiele sposobów. Osobiście lubię
def merge(f): def merge(a,b): keys = a.keys() | b.keys() return {key:f(a.get(key), b.get(key)) for key in keys} return merge
Dobrym sposobem na zdefiniowanie odpowiedniej funkcji scalającej rekurencyjnej
f
jest użycie multipledispatch, która pozwala zdefiniować funkcje obliczające według różnych ścieżek w zależności od typu ich argumentów.from multipledispatch import dispatch #for anything that is not a dict return @dispatch(object, object) def f(a, b): return b if b is not None else a #for dicts recurse @dispatch(dict, dict) def f(a,b): return merge(f)(a,b)
Przykład
Aby połączyć dwa zagnieżdżone polecenia, po prostu użyj
merge(f)
np .:dict1 = {1:{"a":"A"},2:{"b":"B"}} dict2 = {2:{"c":"C"},3:{"d":"D"}} merge(f)(dict1, dict2) #returns {1: {'a': 'A'}, 2: {'b': 'B', 'c': 'C'}, 3: {'d': 'D'}}
Uwagi:
Zalety tego podejścia to:
Funkcja jest zbudowana z mniejszych funkcji, z których każda wykonuje jedną rzecz, co ułatwia wnioskowanie i testowanie kodu
To zachowanie nie jest zakodowane na stałe, ale można je zmienić i rozszerzyć w razie potrzeby, co poprawia ponowne wykorzystanie kodu (patrz przykład poniżej).
Dostosowywanie
W niektórych odpowiedziach uwzględniono również dykty zawierające listy, np. Inne (potencjalnie zagnieżdżone) dykty. W takim przypadku można zmapować listy i połączyć je na podstawie pozycji. Można to zrobić, dodając kolejną definicję do funkcji łączenia
f
:import itertools @dispatch(list, list) def f(a,b): return [merge(f)(*arg) for arg in itertools.zip_longest(a, b)]
źródło
Na wypadek, gdyby ktoś chciał innego podejścia do tego problemu, oto moje rozwiązanie.
Cnoty : krótkie, deklaratywne i funkcjonalne w stylu (rekurencyjne, bez mutacji).
Potencjalna wada : może to nie być połączenie, którego szukasz. Sprawdź dokumentację dotyczącą semantyki.
def deep_merge(a, b): """ Merge two values, with `b` taking precedence over `a`. Semantics: - If either `a` or `b` is not a dictionary, `a` will be returned only if `b` is `None`. Otherwise `b` will be returned. - If both values are dictionaries, they are merged as follows: * Each key that is found only in `a` or only in `b` will be included in the output collection with its value intact. * For any key in common between `a` and `b`, the corresponding values will be merged with the same semantics. """ if not isinstance(a, dict) or not isinstance(b, dict): return a if b is None else b else: # If we're here, both a and b must be dictionaries or subtypes thereof. # Compute set of all keys in both dictionaries. keys = set(a.keys()) | set(b.keys()) # Build output dictionary, merging recursively values with common keys, # where `None` is used to mean the absence of a value. return { key: deep_merge(a.get(key), b.get(key)) for key in keys }
źródło
Jest mały problem z odpowiedzią Andrew cookes: W niektórych przypadkach modyfikuje drugi argument,
b
gdy modyfikujesz zwrócony dykt. W szczególności to z powodu tej linii:if key in a: ... else: a[key] = b[key]
Jeśli
b[key]
jest adict
, zostanie po prostu przypisany doa
, co oznacza, że wszelkie późniejsze modyfikacjedict
będą miały wpływ zarówno na, jaka
ib
.a={} b={'1':{'2':'b'}} c={'1':{'3':'c'}} merge(merge(a,b), c) # {'1': {'3': 'c', '2': 'b'}} a # {'1': {'3': 'c', '2': 'b'}} (as expected) b # {'1': {'3': 'c', '2': 'b'}} <---- c # {'1': {'3': 'c'}} (unmodified)
Aby to naprawić, wiersz musiałby zostać zastąpiony tym:
if isinstance(b[key], dict): a[key] = clone_dict(b[key]) else: a[key] = b[key]
Gdzie
clone_dict
jest:def clone_dict(obj): clone = {} for key, value in obj.iteritems(): if isinstance(value, dict): clone[key] = clone_dict(value) else: clone[key] = value return
Nadal. To oczywiście nie bierze pod uwagę
list
,set
i inne rzeczy, ale mam nadzieję, że ilustruje pułapek podczas próby scaleniadicts
.A dla kompletności, oto moja wersja, w której możesz podać ją wielokrotnie
dicts
:def merge_dicts(*args): def clone_dict(obj): clone = {} for key, value in obj.iteritems(): if isinstance(value, dict): clone[key] = clone_dict(value) else: clone[key] = value return def merge(a, b, path=[]): for key in b: if key in a: if isinstance(a[key], dict) and isinstance(b[key], dict): merge(a[key], b[key], path + [str(key)]) elif a[key] == b[key]: pass else: raise Exception('Conflict at `{path}\''.format(path='.'.join(path + [str(key)]))) else: if isinstance(b[key], dict): a[key] = clone_dict(b[key]) else: a[key] = b[key] return a return reduce(merge, args, {})
źródło
deepcopy
zamiastclone_dict
?Ta wersja funkcji będzie uwzględniać liczbę N słowników i tylko słowniki - nie można przekazać żadnych niewłaściwych parametrów lub spowoduje to zgłoszenie błędu TypeError. Samo scalanie uwzględnia konflikty kluczy i zamiast nadpisywać dane ze słownika w dalszej części łańcucha scalania, tworzy zestaw wartości i dołącza do niego; żadne dane nie zostaną utracone.
To może nie być najbardziej wydajne na stronie, ale jest najdokładniejsze i nie stracisz żadnych informacji, gdy połączysz swoje 2 z N.
def merge_dicts(*dicts): if not reduce(lambda x, y: isinstance(y, dict) and x, dicts, True): raise TypeError, "Object in *dicts not of type dict" if len(dicts) < 2: raise ValueError, "Requires 2 or more dict objects" def merge(a, b): for d in set(a.keys()).union(b.keys()): if d in a and d in b: if type(a[d]) == type(b[d]): if not isinstance(a[d], dict): ret = list({a[d], b[d]}) if len(ret) == 1: ret = ret[0] yield (d, sorted(ret)) else: yield (d, dict(merge(a[d], b[d]))) else: raise TypeError, "Conflicting key:value type assignment" elif d in a: yield (d, a[d]) elif d in b: yield (d, b[d]) else: raise KeyError return reduce(lambda x, y: dict(merge(x, y)), dicts[1:], dicts[0]) print merge_dicts({1:1,2:{1:2}},{1:2,2:{3:1}},{4:4})
wyjście: {1: [1, 2], 2: {1: 2, 3: 1}, 4: 4}
źródło
Ponieważ dictviews obsługuje operacje na zbiorach, mogłem znacznie uprościć odpowiedź jterrace.
def merge(dict1, dict2): for k in dict1.keys() - dict2.keys(): yield (k, dict1[k]) for k in dict2.keys() - dict1.keys(): yield (k, dict2[k]) for k in dict1.keys() & dict2.keys(): yield (k, dict(merge(dict1[k], dict2[k])))
Każda próba połączenia dict z non dict (technicznie rzecz biorąc, obiekt z metodą „keys” i obiekt bez metody „keys”) spowoduje zgłoszenie błędu AttributeError. Obejmuje to zarówno początkowe wywołanie funkcji, jak i wywołania rekurencyjne. To jest dokładnie to, czego chciałem, więc zostawiłem to. Możesz łatwo złapać AttributeErrors rzucone przez wywołanie rekurencyjne, a następnie uzyskać dowolną wartość.
źródło
Krótkie i słodkie:
from collections.abc import MutableMapping as Map def nested_update(d, v): """ Nested update of dict-like 'd' with dict-like 'v'. """ for key in v: if key in d and isinstance(d[key], Map) and isinstance(v[key], Map): nested_update(d[key], v[key]) else: d[key] = v[key]
Działa to podobnie do
dict.update
metody Pythona (i jest na niej zbudowane) . PowracaNone
(zawsze możesz dodać,return d
jeśli wolisz), ponieważ aktualizujed
na miejscu. Keys inv
nadpisze wszystkie istniejące klucze wd
(nie próbuje zinterpretować zawartości dyktu).Będzie również działać z innymi mapowaniami („podobnymi do dyktowania”).
źródło
Kod będzie oczywiście zależał od twoich reguł rozwiązywania konfliktów scalania. Oto wersja, która może przyjąć dowolną liczbę argumentów i scala je rekurencyjnie na dowolną głębokość, bez użycia jakiejkolwiek mutacji obiektu. Używa następujących reguł do rozwiązywania konfliktów scalania:
{"foo": {...}}
ma pierwszeństwo przed{"foo": "bar"}
){"a": 1}
,{"a", 2}
oraz{"a": 3}
w porządku, wynik będzie{"a": 3}
)try: from collections import Mapping except ImportError: Mapping = dict def merge_dicts(*dicts): """ Return a new dictionary that is the result of merging the arguments together. In case of conflicts, later arguments take precedence over earlier arguments. """ updated = {} # grab all keys keys = set() for d in dicts: keys = keys.union(set(d)) for key in keys: values = [d[key] for d in dicts if key in d] # which ones are mapping types? (aka dict) maps = [value for value in values if isinstance(value, Mapping)] if maps: # if we have any mapping types, call recursively to merge them updated[key] = merge_dicts(*maps) else: # otherwise, just grab the last value we have, since later arguments # take precedence over earlier arguments updated[key] = values[-1] return updated
źródło
Miałem dwa słowniki (
a
ib
), z których każdy mógł zawierać dowolną liczbę zagnieżdżonych słowników. Chciałem je rekurencyjnie scalić,b
mając pierwszeństwo przeda
.Biorąc pod uwagę zagnieżdżone słowniki jako drzewa, chciałem:
a
, aby każda ścieżka do każdego liściab
była reprezentowana wa
a
jeśli liść znajduje się w odpowiedniej ścieżce wb
b
węzły liści pozostają liśćmi.Istniejące odpowiedzi były trochę skomplikowane jak na mój gust i pozostawiły kilka szczegółów na półce. Zhakowałem razem następujące elementy, które pomyślnie przeszły testy jednostkowe dla mojego zestawu danych.
def merge_map(a, b): if not isinstance(a, dict) or not isinstance(b, dict): return b for key in b.keys(): a[key] = merge_map(a[key], b[key]) if key in a else b[key] return a
Przykład (sformatowany dla przejrzystości):
a = { 1 : {'a': 'red', 'b': {'blue': 'fish', 'yellow': 'bear' }, 'c': { 'orange': 'dog'}, }, 2 : {'d': 'green'}, 3: 'e' } b = { 1 : {'b': 'white'}, 2 : {'d': 'black'}, 3: 'e' } >>> merge_map(a, b) {1: {'a': 'red', 'b': 'white', 'c': {'orange': 'dog'},}, 2: {'d': 'black'}, 3: 'e'}
Ścieżki,
b
które należało utrzymać, to:1 -> 'b' -> 'white'
2 -> 'd' -> 'black'
3 -> 'e'
.a
miał unikalne i niesprzeczne ścieżki:1 -> 'a' -> 'red'
1 -> 'c' -> 'orange' -> 'dog'
więc są nadal reprezentowane na scalonej mapie.
źródło
spójrz na
toolz
pakietimport toolz dict1={1:{"a":"A"},2:{"b":"B"}} dict2={2:{"c":"C"},3:{"d":"D"}} toolz.merge_with(toolz.merge,dict1,dict2)
daje
{1: {'a': 'A'}, 2: {'b': 'B', 'c': 'C'}, 3: {'d': 'D'}}
źródło
Mam rozwiązanie iteracyjne - działa znacznie lepiej z dużymi dyktami i wieloma z nich (na przykład jsons itp.):
import collections def merge_dict_with_subdicts(dict1: dict, dict2: dict) -> dict: """ similar behaviour to builtin dict.update - but knows how to handle nested dicts """ q = collections.deque([(dict1, dict2)]) while len(q) > 0: d1, d2 = q.pop() for k, v in d2.items(): if k in d1 and isinstance(d1[k], dict) and isinstance(v, dict): q.append((d1[k], v)) else: d1[k] = v return dict1
zwróć uwagę, że to użyje wartości z d2 do zastąpienia d1, na wypadek gdyby obie nie były dyktami. (tak samo jak Pythona
dict.update()
)kilka testów:
def test_deep_update(): d = dict() merge_dict_with_subdicts(d, {"a": 4}) assert d == {"a": 4} new_dict = { "b": { "c": { "d": 6 } } } merge_dict_with_subdicts(d, new_dict) assert d == { "a": 4, "b": { "c": { "d": 6 } } } new_dict = { "a": 3, "b": { "f": 7 } } merge_dict_with_subdicts(d, new_dict) assert d == { "a": 3, "b": { "c": { "d": 6 }, "f": 7 } } # test a case where one of the dicts has dict as value and the other has something else new_dict = { 'a': { 'b': 4 } } merge_dict_with_subdicts(d, new_dict) assert d['a']['b'] == 4
Testowałem z około 1200 dyktami - ta metoda zajęła 0,4 sekundy, a rozwiązanie rekurencyjne ~ 2,5 sekundy.
źródło
Powinno to pomóc w łącząc wszystkie elementy od
dict2
dodict1
:for item in dict2: if item in dict1: for leaf in dict2[item]: dict1[item][leaf] = dict2[item][leaf] else: dict1[item] = dict2[item]
Przetestuj go i powiedz nam, czy tego właśnie chciałeś.
EDYTOWAĆ:
Powyższe rozwiązanie łączy tylko jeden poziom, ale poprawnie rozwiązuje przykład podany przez OP. Aby scalić wiele poziomów, należy użyć rekursji.
źródło
for k,v in dict2.iteritems(): dict1.setdefault(k,{}).update(v)
. Ale jak zauważył @agf, nie powoduje to scalenia zagnieżdżonych poleceń.{'a':'b'}
z nimi połączyć{'a':{'c':'d'}
).Testowałem Twoje rozwiązania i zdecydowałem się użyć tego w moim projekcie:
def mergedicts(dict1, dict2, conflict, no_conflict): for k in set(dict1.keys()).union(dict2.keys()): if k in dict1 and k in dict2: yield (k, conflict(dict1[k], dict2[k])) elif k in dict1: yield (k, no_conflict(dict1[k])) else: yield (k, no_conflict(dict2[k])) dict1 = {1:{"a":"A"}, 2:{"b":"B"}} dict2 = {2:{"c":"C"}, 3:{"d":"D"}} #this helper function allows for recursion and the use of reduce def f2(x, y): return dict(mergedicts(x, y, f2, lambda x: x)) print dict(mergedicts(dict1, dict2, f2, lambda x: x)) print dict(reduce(f2, [dict1, dict2]))
Przekazywanie funkcji jako parametrów jest kluczem do rozszerzenia rozwiązania jterrace, aby zachowywało się jak wszystkie inne rozwiązania rekurencyjne.
źródło
Najłatwiej przychodzi mi do głowy:
#!/usr/bin/python from copy import deepcopy def dict_merge(a, b): if not isinstance(b, dict): return b result = deepcopy(a) for k, v in b.iteritems(): if k in result and isinstance(result[k], dict): result[k] = dict_merge(result[k], v) else: result[k] = deepcopy(v) return result a = {1:{"a":'A'}, 2:{"b":'B'}} b = {2:{"c":'C'}, 3:{"d":'D'}} print dict_merge(a,b)
Wynik:
{1: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}}
źródło
Mam tutaj inne nieco inne rozwiązanie:
def deepMerge(d1, d2, inconflict = lambda v1,v2 : v2) : ''' merge d2 into d1. using inconflict function to resolve the leaf conflicts ''' for k in d2: if k in d1 : if isinstance(d1[k], dict) and isinstance(d2[k], dict) : deepMerge(d1[k], d2[k], inconflict) elif d1[k] != d2[k] : d1[k] = inconflict(d1[k], d2[k]) else : d1[k] = d2[k] return d1
Domyślnie rozwiązuje konflikty na korzyść wartości z drugiego dyktu, ale możesz to łatwo zmienić, z niektórymi czarami możesz nawet wyrzucić z niego wyjątki. :).
źródło
class Utils(object): """ >>> a = { 'first' : { 'all_rows' : { 'pass' : 'dog', 'number' : '1' } } } >>> b = { 'first' : { 'all_rows' : { 'fail' : 'cat', 'number' : '5' } } } >>> Utils.merge_dict(b, a) == { 'first' : { 'all_rows' : { 'pass' : 'dog', 'fail' : 'cat', 'number' : '5' } } } True >>> main = {'a': {'b': {'test': 'bug'}, 'c': 'C'}} >>> suply = {'a': {'b': 2, 'd': 'D', 'c': {'test': 'bug2'}}} >>> Utils.merge_dict(main, suply) == {'a': {'b': {'test': 'bug'}, 'c': 'C', 'd': 'D'}} True """ @staticmethod def merge_dict(main, suply): """ 获取融合的字典,以main为主,suply补充,冲突时以main为准 :return: """ for key, value in suply.items(): if key in main: if isinstance(main[key], dict): if isinstance(value, dict): Utils.merge_dict(main[key], value) else: pass else: pass else: main[key] = value return main if __name__ == '__main__': import doctest doctest.testmod()
źródło
hej tam też miałem ten sam problem ale przyszło mi do głowy rozwiązanie i napiszę je tutaj na wypadek, gdyby przydało się to również innym, w zasadzie łącząc zagnieżdżone słowniki i dodając wartości, dla mnie musiałem obliczyć pewne prawdopodobieństwa, więc to jeden działał świetnie:
#used to copy a nested dict to a nested dict def deepupdate(target, src): for k, v in src.items(): if k in target: for k2, v2 in src[k].items(): if k2 in target[k]: target[k][k2]+=v2 else: target[k][k2] = v2 else: target[k] = copy.deepcopy(v)
korzystając z powyższej metody możemy scalić:
cel = {'6,6': {'6,63': 1}, '63, 4 ': {' 4,4 ': 1},' 4,4 ': {' 4,3 ': 1} , '6,63': {'63, 4 ': 1}}
src = {'5,4': {'4,4': 1}, '5,5': {'5,4': 1}, '4,4': {'4,3': 1} }
a to stanie się: {'5,5': {'5,4': 1}, '5,4': {'4,4': 1}, '6,6': {'6,63' : 1}, '63, 4 ': {' 4,4 ': 1},' 4,4 ': {' 4,3 ': 2},' 6,63 ': {'63, 4': 1 }}
zwróć też uwagę na zmiany tutaj:
cel = {'6,6': {'6,63': 1}, '6,63': {'63, 4 ': 1}, ' 4,4 ': {' 4,3 ': 1} , '63, 4 ': {' 4,4 ': 1}}
src = {'5,4': {'4,4': 1}, '4,3': {'3,4': 1}, '4,4': {'4,9': 1} , '3,4': {'4,4': 1}, '5,5': {'5,4': 1}}
merge = {'5,4': {'4,4': 1}, '4,3': {'3,4': 1}, '6,63': {'63, 4 ': 1} , '5,5': {'5,4': 1}, '6,6': {'6,63': 1}, '3,4': {'4,4': 1}, ' 63,4 ': {' 4,4 ': 1}, ' 4,4 ': {' 4,3 ': 1,' 4,9 ': 1} }
nie zapomnij również dodać importu do kopiowania:
import copy
źródło
from collections import defaultdict from itertools import chain class DictHelper: @staticmethod def merge_dictionaries(*dictionaries, override=True): merged_dict = defaultdict(set) all_unique_keys = set(chain(*[list(dictionary.keys()) for dictionary in dictionaries])) # Build a set using all dict keys for key in all_unique_keys: keys_value_type = list(set(filter(lambda obj_type: obj_type != type(None), [type(dictionary.get(key, None)) for dictionary in dictionaries]))) # Establish the object type for each key, return None if key is not present in dict and remove None from final result if len(keys_value_type) != 1: raise Exception("Different objects type for same key: {keys_value_type}".format(keys_value_type=keys_value_type)) if keys_value_type[0] == list: values = list(chain(*[dictionary.get(key, []) for dictionary in dictionaries])) # Extract the value for each key merged_dict[key].update(values) elif keys_value_type[0] == dict: # Extract all dictionaries by key and enter in recursion dicts_to_merge = list(filter(lambda obj: obj != None, [dictionary.get(key, None) for dictionary in dictionaries])) merged_dict[key] = DictHelper.merge_dictionaries(*dicts_to_merge) else: # if override => get value from last dictionary else make a list of all values values = list(filter(lambda obj: obj != None, [dictionary.get(key, None) for dictionary in dictionaries])) merged_dict[key] = values[-1] if override else values return dict(merged_dict) if __name__ == '__main__': d1 = {'aaaaaaaaa': ['to short', 'to long'], 'bbbbb': ['to short', 'to long'], "cccccc": ["the is a test"]} d2 = {'aaaaaaaaa': ['field is not a bool'], 'bbbbb': ['field is not a bool']} d3 = {'aaaaaaaaa': ['filed is not a string', "to short"], 'bbbbb': ['field is not an integer']} print(DictHelper.merge_dictionaries(d1, d2, d3)) d4 = {"a": {"x": 1, "y": 2, "z": 3, "d": {"x1": 10}}} d5 = {"a": {"x": 10, "y": 20, "d": {"x2": 20}}} print(DictHelper.merge_dictionaries(d4, d5))
Wynik:
{'bbbbb': {'to long', 'field is not an integer', 'to short', 'field is not a bool'}, 'aaaaaaaaa': {'to long', 'to short', 'filed is not a string', 'field is not a bool'}, 'cccccc': {'the is a test'}} {'a': {'y': 20, 'd': {'x1': 10, 'x2': 20}, 'z': 3, 'x': 10}}
źródło
Następująca funkcja łączy b w a.
def mergedicts(a, b): for key in b: if isinstance(a.get(key), dict) or isinstance(b.get(key), dict): mergedicts(a[key], b[key]) else: a[key] = b[key] return a
źródło
I jeszcze jedna niewielka zmiana:
Oto funkcja głębokiej aktualizacji oparta na czystym zestawie Python3. Aktualizuje zagnieżdżone słowniki, przechodząc przez jeden poziom na raz i woła o aktualizację każdego kolejnego poziomu wartości słownika:
def deep_update(dict_original, dict_update): if isinstance(dict_original, dict) and isinstance(dict_update, dict): output=dict(dict_original) keys_original=set(dict_original.keys()) keys_update=set(dict_update.keys()) similar_keys=keys_original.intersection(keys_update) similar_dict={key:deep_update(dict_original[key], dict_update[key]) for key in similar_keys} new_keys=keys_update.difference(keys_original) new_dict={key:dict_update[key] for key in new_keys} output.update(similar_dict) output.update(new_dict) return output else: return dict_update
Prosty przykład:
x={'a':{'b':{'c':1, 'd':1}}} y={'a':{'b':{'d':2, 'e':2}}, 'f':2} print(deep_update(x, y)) >>> {'a': {'b': {'c': 1, 'd': 2, 'e': 2}}, 'f': 2}
źródło
A może inna odpowiedź?!? Ten również pozwala uniknąć mutacji / skutków ubocznych:
def merge(dict1, dict2): output = {} # adds keys from `dict1` if they do not exist in `dict2` and vice-versa intersection = {**dict2, **dict1} for k_intersect, v_intersect in intersection.items(): if k_intersect not in dict1: v_dict2 = dict2[k_intersect] output[k_intersect] = v_dict2 elif k_intersect not in dict2: output[k_intersect] = v_intersect elif isinstance(v_intersect, dict): v_dict2 = dict2[k_intersect] output[k_intersect] = merge(v_intersect, v_dict2) else: output[k_intersect] = v_intersect return output
dict1 = {1:{"a":{"A"}}, 2:{"b":{"B"}}} dict2 = {2:{"c":{"C"}}, 3:{"d":{"D"}}} dict3 = {1:{"a":{"A"}}, 2:{"b":{"B"},"c":{"C"}}, 3:{"d":{"D"}}} assert dict3 == merge(dict1, dict2)
źródło