Wie füge ich Wörterbücher von Wörterbüchern zusammen?


129

Ich muss mehrere Wörterbücher zusammenführen. Folgendes habe ich zum Beispiel:

dict1 = {1:{"a":{A}}, 2:{"b":{B}}}

dict2 = {2:{"c":{C}}, 3:{"d":{D}}

Mit A B Cund Dals Blätter des Baumes, wie{"info1":"value", "info2":"value2"}

Es gibt möglicherweise eine unbekannte Ebene (Tiefe) von Wörterbüchern {2:{"c":{"z":{"y":{C}}}}}

In meinem Fall stellt es eine Verzeichnis- / Dateistruktur dar, wobei Knoten Dokumente sind und Blätter Dateien sind.

Ich möchte sie zusammenführen, um Folgendes zu erhalten:

 dict3 = {1:{"a":{A}}, 2:{"b":{B},"c":{C}}, 3:{"d":{D}}}

Ich bin mir nicht sicher, wie ich das mit Python so einfach machen könnte.


Was möchten Sie für Ihre willkürliche Tiefe von Wörterbüchern? Willst du yauf das cLevel abgeflacht oder was? Ihr Beispiel ist unvollständig.
Agf

Überprüfen Sie meine NestedDict-Klasse hier: stackoverflow.com/a/16296144/2334951 Sie verwaltet verschachtelte Wörterbuchstrukturen wie Zusammenführen und mehr.
SzieberthAdam

3
Eine Warnung an alle, die nach Lösungen suchen: Bei dieser Frage geht es nur um verschachtelte Diktate. Die meisten Antworten behandeln den komplizierteren Fall von Listen von Diktaten innerhalb der Struktur nicht richtig. Wenn Sie dies benötigen, versuchen Sie die Antwort von @Osiloke unten: stackoverflow.com/a/25270947/1431660
SHernandez


Siehe auch:
Mehrere

Antworten:


143

Dies ist eigentlich ziemlich schwierig - insbesondere, wenn Sie eine nützliche Fehlermeldung wünschen, wenn die Dinge inkonsistent sind, während Sie doppelte, aber konsistente Einträge korrekt akzeptieren (etwas, das hier keine andere Antwort tut ...).

Vorausgesetzt, Sie haben nicht viele Einträge, ist eine rekursive Funktion am einfachsten:

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

Beachten Sie, dass dies mutiert a- der Inhalt von bwird hinzugefügt a(was auch zurückgegeben wird). Wenn du behalten willst, akannst du es so nennen merge(dict(a), b).

agf wies (unten) darauf hin, dass Sie möglicherweise mehr als zwei Diktate haben. In diesem Fall können Sie Folgendes verwenden:

reduce(merge, [dict1, dict2, dict3...])

wo alles zu dict1 hinzugefügt wird.

[Anmerkung - Ich habe meine erste Antwort bearbeitet, um das erste Argument zu mutieren. das macht das "Reduzieren" leichter zu erklären]

ps in Python 3 benötigen Sie auch from functools import reduce


1
Sie können dies dann in eine reduceoder die entsprechende Schleife stecken , um mit einer beliebigen Anzahl von dicts anstelle von zwei zu arbeiten. Ich bin mir jedoch nicht sicher, ob dies das tut, was er will (er war sich nicht sicher). Sie landen bei 2: {'c': {'z': {'y': {'info1': 'value', 'info2': 'value2'}}}, 'b': {'info1': 'value', 'info2': 'value2'}}seinem zweiten Beispiel. Ich bin mir nicht sicher, ob er das will zund yabgeflacht ist oder nicht.
Agf

1
Sie sind Verzeichnisstrukturen, also glaube ich nicht, dass er / sie etwas Abflachtes will? Oh, tut mir leid, ich habe "mehrere Wörterbücher" verpasst. ja, reduzieren wäre gut. werde das hinzufügen.
Andrew Cooke

Das macht genau das, was ich wollte! Es tut mir leid, dass ich nicht klar genug war ... Ich dachte, ich wäre mit Python einverstanden, scheint nicht: - / Ich brauchte eine rekursive Funktion wegen der verschachtelten Diktate, diese funktioniert und ich kann sie verstehen :) Ich nicht scheinen in der Lage zu sein, es mit Reduzieren zum
Laufen zu bringen

2
Für alle Personen mit Listen als letzte verschachtelte Ebene unter den Diktaten können Sie dies tun, anstatt den Fehler zum Verketten der beiden Listen auszulösen : a[key] = a[key] + b[key]. Danke für die hilfreiche Antwort.
Kevinmicke

1
> Wenn Sie a behalten möchten, können Sie es als Zusammenführen bezeichnen (Diktat (a), b). Beachten Sie, dass verschachtelte Diktate weiterhin mutiert sind. Um dies zu vermeiden, verwenden Sie copy.deepcopy.
Rcorre

30

Hier ist eine einfache Möglichkeit, dies mit Generatoren zu tun:

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

Dies druckt:

{1: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}}

Wenn Sie das Generator-Thema beibehalten möchten, können Sie verketten (dict1.keys (), dict2.keys ())
Andrew Cooke

Würde das nicht doppelte Schlüssel bekommen?
Jterrace

Dieser scheint die Arbeit zu erledigen, zumindest an meinem Datensatz, aber da ich Ertrag und Generatoren nie gut verstanden habe, bin ich ziemlich verloren, warum, aber ich werde mich ein bisschen mehr anstrengen, könnte nützlich sein!
Fdhex

Ah, ja, es würde doppelte Schlüssel bekommen. Sie müssten es immer noch in ein Set einwickeln, sorry.
Andrew Cooke

2
Ich fand das besonders hilfreich. Am schönsten wäre es jedoch, die Funktion zur Lösung der Konflikte als Parameter zuzulassen.
Mentatkgs

25

Ein Problem bei dieser Frage ist, dass die Werte des Diktats beliebig komplexe Daten sein können. Basierend auf diesen und anderen Antworten habe ich diesen Code gefunden:

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

Mein Anwendungsfall ist das Zusammenführen von YAML-Dateien, bei denen ich mich nur mit einer Teilmenge möglicher Datentypen befassen muss. Daher kann ich Tupel und andere Objekte ignorieren. Für mich bedeutet eine vernünftige Zusammenführungslogik

  • Skalare ersetzen
  • Listen anhängen
  • Führen Sie Diktate zusammen, indem Sie fehlende Schlüssel hinzufügen und vorhandene Schlüssel aktualisieren

Alles andere und das Unvorhergesehene führt zu einem Fehler.


1
Fantastisch. Funktioniert auch gut auf JSON-Dumps. Einfach die Fehlerbehandlung entfernt. (
Faul sein

3
Die "isinstance" -Sequenz kann ersetzt werden, isinstance(a, (str, unicode, int, long, float))wenn nicht?
Simahawk

12

Wörterbücher von Wörterbüchern werden zusammengeführt

Da dies die kanonische Frage ist (trotz bestimmter Nicht-Allgemeinheiten), biete ich den kanonischen pythonischen Ansatz zur Lösung dieses Problems an.

Einfachster Fall: "Blätter sind verschachtelte Diktate, die in leeren Diktaten enden":

d1 = {'a': {1: {'foo': {}}, 2: {}}}
d2 = {'a': {1: {}, 2: {'bar': {}}}}
d3 = {'b': {3: {'baz': {}}}}
d4 = {'a': {1: {'quux': {}}}}

Dies ist der einfachste Fall für eine Rekursion, und ich würde zwei naive Ansätze empfehlen:

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

Ich glaube, ich würde den zweiten dem ersten vorziehen, aber denke daran, dass der ursprüngliche Zustand des ersten von Anfang an wiederhergestellt werden müsste. Hier ist die Verwendung:

>>> 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': {}}}}

Komplexer Fall: "Blätter sind von jeder anderen Art:"

Wenn sie also in Diktaten enden, ist es ein einfacher Fall, die leeren Enddiktate zusammenzuführen. Wenn nicht, ist es nicht so trivial. Wenn Strings, wie fügst du sie zusammen? Sets können auf ähnliche Weise aktualisiert werden, sodass wir diese Behandlung durchführen können, aber wir verlieren die Reihenfolge, in der sie zusammengeführt wurden. Also ist Ordnung wichtig?

Anstelle von mehr Informationen besteht der einfachste Ansatz darin, ihnen die Standardaktualisierungsbehandlung zu geben, wenn beide Werte keine Diktate sind: dh der Wert des zweiten Diktats überschreibt den ersten, selbst wenn der Wert des zweiten Diktats Keine ist und der Wert des ersten a ist diktieren mit vielen Infos.

d1 = {'a': {1: 'foo', 2: None}}
d2 = {'a': {1: None, 2: 'bar'}}
d3 = {'b': {3: 'baz'}}
d4 = {'a': {1: 'quux'}}

from collections 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(): # in Python 2, use .iteritems()!
        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

Und nun

from functools import reduce
reduce(rec_merge, (d1, d2, d3, d4))

kehrt zurück

{'a': {1: 'quux', 2: 'bar'}, 'b': {3: 'baz'}}

Anwendung auf die ursprüngliche Frage:

Ich musste die geschweiften Klammern um die Buchstaben entfernen und sie in einfache Anführungszeichen setzen, damit dies legitimes Python ist (andernfalls würden sie in Python 2.7+ Literale setzen) sowie eine fehlende Klammer anhängen:

dict1 = {1:{"a":'A'}, 2:{"b":'B'}}
dict2 = {2:{"c":'C'}, 3:{"d":'D'}}

und rec_merge(dict1, dict2)kehrt jetzt zurück:

{1: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}}

Welches passt zum gewünschten Ergebnis der ursprünglichen Frage (nach dem Ändern, zB das {A}zu 'A'.)


10

Basierend auf @andrew cooke. Diese Version verarbeitet verschachtelte Listen von Dikten und bietet auch die Möglichkeit, die Werte zu aktualisieren

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

1
Danke, das ist so hilfreich. Ich habe ständig Listen mit Diktaten in meinen Strukturen, die anderen Lösungen können dies nicht richtig zusammenführen.
SHernandez

7

Diese einfache rekursive Prozedur führt ein Wörterbuch in ein anderes zusammen und überschreibt dabei widersprüchliche Schlüssel:

#!/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"}}))

Ausgabe:

{1: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}}
{1: {'a': 'A'}, 2: {'b': 'C'}}

7

Basierend auf Antworten von @andrew cooke. Es kümmert sich besser um verschachtelte Listen.

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]

intuitiv und symmetrisch. +1 für Listenbehandlung :)
vdwees

6

Wenn Sie eine unbekannte Ebene von Wörterbüchern haben, würde ich eine rekursive Funktion vorschlagen:

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

5

Überblick

Der folgende Ansatz unterteilt das Problem einer tiefen Verschmelzung von Diktaten in:

  1. Eine parametrisierte flache Zusammenführungsfunktion merge(f)(a,b), die eine Funktion fzum Zusammenführen von zwei Dikten aund verwendetb

  2. Eine rekursive Zusammenführungsfunktion f, die zusammen mit verwendet werden kannmerge


Implementierung

Eine Funktion zum Zusammenführen von zwei (nicht verschachtelten) Diktaten kann auf viele Arten geschrieben werden. Ich persönlich mag

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

Eine gute Möglichkeit, eine geeignete rekursive Zusammenführungsfunktion zu definieren, fist die Verwendung von Multipledispatch , mit dem Funktionen definiert werden können, die je nach Art ihrer Argumente auf verschiedenen Pfaden ausgewertet werden.

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)

Beispiel

Um zwei verschachtelte Dikte zusammenzuführen, verwenden Sie einfach merge(f)z.

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

Anmerkungen:

Die Vorteile dieses Ansatzes sind:

  • Die Funktion besteht aus kleineren Funktionen, die jeweils eine einzelne Funktion ausführen, wodurch der Code einfacher zu überlegen und zu testen ist

  • Das Verhalten ist nicht fest codiert, kann jedoch nach Bedarf geändert und erweitert werden, wodurch die Wiederverwendung von Code verbessert wird (siehe Beispiel unten).


Anpassung

Einige Antworten berücksichtigten auch Diktate, die Listen enthalten, z. B. anderer (möglicherweise verschachtelter) Diktate. In diesem Fall möchten Sie möglicherweise die Listen zuordnen und sie basierend auf der Position zusammenführen. Dies kann durch Hinzufügen einer weiteren Definition zur Fusionsfunktion erfolgen f:

import itertools
@dispatch(list, list)
def f(a,b):
    return [merge(f)(*arg) for arg in itertools.zip_longest(a, b)]

4

Sie könnten versuchen, zusammen zu verschmelzen .


Installation

$ pip3 install mergedeep

Verwendung

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

Eine vollständige Liste der Optionen finden Sie in den Dokumenten !


3

Es gibt ein kleines Problem mit der Antwort von Andrew Cookes: In einigen Fällen wird das zweite Argument geändert, bwenn Sie das zurückgegebene Diktat ändern. Insbesondere liegt es an dieser Zeile:

if key in a:
    ...
else:
    a[key] = b[key]

Wenn dies der Fall b[key]ist dict, wird es einfach zugewiesen a, was bedeutet, dass alle nachfolgenden Änderungen daran dictsowohl aals auch betreffen b.

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)

Um dies zu beheben, müsste die Zeile durch folgende ersetzt werden:

if isinstance(b[key], dict):
    a[key] = clone_dict(b[key])
else:
    a[key] = b[key]

Wo clone_dictist:

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

Immer noch. Es geht dabei natürlich nicht für list, setund andere Sachen, aber ich hoffe , dass es die Fallen zeigt , wenn zu verschmelzen versucht dicts.

Und der Vollständigkeit halber hier meine Version, in der Sie sie mehrfach übergeben können 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, {})

Warum nicht deepcopystatt clone_dict?
Armando Pérez Marqués

1
Weil die Python-Stdlib verdammt groß und großartig ist! Ich hatte keine Ahnung, dass dies existiert - und es war eine lustige Kleinigkeit zu codieren :-)
andsens

3

Für den Fall, dass jemand noch einen anderen Ansatz für dieses Problem wünscht , hier ist meine Lösung.

Tugenden : kurz, deklarativ und funktional im Stil (rekursiv, macht keine Mutation).

Möglicher Nachteil : Dies ist möglicherweise nicht die Zusammenführung, nach der Sie suchen. Informationen zur Semantik finden Sie in der Dokumentzeichenfolge.

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
        }

Sehr interessante Antwort, danke, dass Sie es geteilt haben. Welche Syntax haben Sie nach der return-Anweisung verwendet? Ich bin damit nicht vertraut.
dev_does_software

2

Diese Version der Funktion berücksichtigt N Wörterbücher und nur Wörterbücher - es können keine falschen Parameter übergeben werden, oder es wird ein TypeError ausgelöst. Die Zusammenführung selbst berücksichtigt Schlüsselkonflikte. Anstatt Daten aus einem Wörterbuch weiter unten in der Zusammenführungskette zu überschreiben, werden eine Reihe von Werten erstellt und an diese angehängt. Es gehen keine Daten verloren.

Es ist vielleicht nicht das effizienteste auf der Seite, aber es ist das gründlichste und Sie werden keine Informationen verlieren, wenn Sie Ihre 2 zu N Diktate zusammenführen.

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

Ausgabe: {1: [1, 2], 2: {1: 2, 3: 1}, 4: 4}


2

Da dictviews Set-Operationen unterstützen, konnte ich die Antwort von jterrace erheblich vereinfachen.

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

Jeder Versuch, ein Diktat mit einem Nicht-Diktat zu kombinieren (technisch gesehen ein Objekt mit einer Schlüsselmethode und ein Objekt ohne Schlüsselmethode), löst einen AttributeError aus. Dies umfasst sowohl den ersten Aufruf der Funktion als auch rekursive Aufrufe. Genau das wollte ich, also habe ich es verlassen. Sie können leicht einen AttributeErrors abfangen, der durch den rekursiven Aufruf ausgelöst wird, und dann einen beliebigen Wert liefern.


2

Short-n-Sweet:

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]

Dies funktioniert wie die Python- dict.updateMethode (und baut darauf auf) . Es wird zurückgegeben None(Sie können es jederzeit hinzufügen, return dwenn Sie dies bevorzugen) , wenn das Diktat direkt aktualisiert wird d. Mit den Schlüsseln vwerden alle vorhandenen Schlüssel überschriebend (es wird nicht versucht, den Inhalt des Diktats zu interpretieren).

Es funktioniert auch für andere ("diktartige") Zuordnungen.


1

Der Code hängt natürlich von Ihren Regeln zur Lösung von Zusammenführungskonflikten ab. Hier ist eine Version, die eine beliebige Anzahl von Argumenten aufnehmen und diese rekursiv zu einer beliebigen Tiefe zusammenführen kann, ohne eine Objektmutation zu verwenden. Es werden die folgenden Regeln verwendet, um Zusammenführungskonflikte zu lösen:

  • Wörterbücher haben Vorrang vor nicht diktierten Werten (haben {"foo": {...}}Vorrang vor {"foo": "bar"})
  • später Argumente haben Vorrang vor den früheren Argumenten (wenn Sie verschmelzen {"a": 1}, {"a", 2}und {"a": 3}in Ordnung, wird das Ergebnis {"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  

1

Ich hatte zwei Wörterbücher ( aund b), die jeweils eine beliebige Anzahl verschachtelter Wörterbücher enthalten konnten. Ich wollte sie rekursiv zusammenführen bund Vorrang vor ihnen haben a.

Ich wollte die verschachtelten Wörterbücher als Bäume betrachten und wollte:

  • Aktualisieren, adamit jeder Pfad zu jedem Blatt in bdargestellt wirda
  • Um Teilbäume zu überschreiben, awenn ein Blatt im entsprechenden Pfad in gefunden wirdb
    • Behalten Sie die Invariante bei, dass alle bBlattknoten Blätter bleiben.

Die vorhandenen Antworten waren für meinen Geschmack etwas kompliziert und ließen einige Details im Regal. Ich habe Folgendes zusammen gehackt, das Unit-Tests für meinen Datensatz besteht.

  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

Beispiel (aus Gründen der Übersichtlichkeit formatiert):

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

Die Pfade b, die beibehalten werden mussten, waren:

  • 1 -> 'b' -> 'white'
  • 2 -> 'd' -> 'black'
  • 3 -> 'e'.

a hatte die einzigartigen und widersprüchlichen Wege von:

  • 1 -> 'a' -> 'red'
  • 1 -> 'c' -> 'orange' -> 'dog'

Daher werden sie weiterhin in der zusammengeführten Karte dargestellt.


1

Ich habe eine iterative Lösung - funktioniert viel besser mit großen Diktaten und vielen von ihnen (zum Beispiel jsons usw.):

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

Beachten Sie, dass dies den Wert in d2 verwendet, um d1 zu überschreiben, falls es sich nicht um beide Dikte handelt. (wie bei Pythondict.update() )

Einige Tests:

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

Ich habe mit ungefähr 1200 Diktaten getestet - diese Methode dauerte 0,4 Sekunden, während die rekursive Lösung ~ 2,5 Sekunden dauerte.


0

Dies sollte beim Zusammenführen aller Elemente von dict2in helfen dict1:

for item in dict2:
    if item in dict1:
        for leaf in dict2[item]:
            dict1[item][leaf] = dict2[item][leaf]
    else:
        dict1[item] = dict2[item]

Bitte testen Sie es und sagen Sie uns, ob dies das ist, was Sie wollten.

BEARBEITEN:

Die oben erwähnte Lösung führt nur eine Ebene zusammen, löst jedoch das von OP gegebene Beispiel korrekt. Um mehrere Ebenen zusammenzuführen, sollte die Rekursion verwendet werden.


1
Er hat eine beliebige Tiefe der Verschachtelung bekommt
agf

Das kann einfach so umgeschrieben werden for k,v in dict2.iteritems(): dict1.setdefault(k,{}).update(v). Aber wie @agf betonte, werden die verschachtelten Dikte nicht zusammengeführt.
Shawn Chin

@agf: Richtig, es scheint also, dass OP eine Lösung mit Wiederholung benötigt. Dank der Tatsache, dass Wörterbücher veränderlich sind, sollte dies recht einfach sein. Aber ich denke, die Frage ist nicht spezifisch genug, um zu sagen, was passieren soll, wenn wir Orte mit unterschiedlichen Tiefen finden (z. B. versuchen, sich {'a':'b'}mit ihnen zu verbinden {'a':{'c':'d'}).
Tadeck

0

Ich habe Ihre Lösungen getestet und beschlossen, diese in meinem Projekt zu verwenden:

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

Das Übergeben von Funktionen als Parameter ist der Schlüssel, um die jterrace-Lösung so zu erweitern, dass sie sich wie alle anderen rekursiven Lösungen verhält.


0

Der einfachste Weg, den ich mir vorstellen kann, ist:

#!/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)

Ausgabe:

{1: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}}

0

Ich habe hier eine andere, etwas andere Lösung:

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

Standardmäßig werden Konflikte zugunsten von Werten aus dem zweiten Diktat gelöst, aber Sie können dies leicht überschreiben. Mit etwas Hexerei können Sie möglicherweise sogar Ausnahmen daraus werfen. :).


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

0

Hey, da hatte ich auch das gleiche Problem, aber ich dachte über eine Lösung nach und werde es hier posten, falls es auch für andere nützlich ist, im Grunde verschachtelte Wörterbücher zusammenzuführen und auch die Werte hinzuzufügen. Für mich musste ich einige Wahrscheinlichkeiten berechnen, damit dies man hat super gearbeitet:

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

Mit der obigen Methode können wir zusammenführen:

Ziel = {'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} }}

und dies wird: {'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 }}

Beachten Sie auch die Änderungen hier:

Ziel = {'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} }

Vergessen Sie nicht, auch den Import zum Kopieren hinzuzufügen:

import copy

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

Ausgabe:

{'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}}

Während dieser Code die Frage möglicherweise beantwortet, verbessert die Bereitstellung eines zusätzlichen Kontexts darüber, warum und / oder wie dieser Code die Frage beantwortet, ihren langfristigen Wert.
Xiawi

Ich denke, dies ist eine generische Implementierung des Zusammenführens eines oder mehrerer verschachtelter Wörterbücher unter Berücksichtigung der Art der Objekte, die beschädigt werden sollen
Dorcioman

0

Schauen Sie sich das toolzPaket an

import toolz
dict1={1:{"a":"A"},2:{"b":"B"}}
dict2={2:{"c":"C"},3:{"d":"D"}}
toolz.merge_with(toolz.merge,dict1,dict2)

gibt

{1: {'a': 'A'}, 2: {'b': 'B', 'c': 'C'}, 3: {'d': 'D'}}

0

Die folgende Funktion führt b zu a zusammen.

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

0

Und noch eine kleine Variation:

Hier ist eine reine Python3-Set-basierte Deep-Update-Funktion. Es aktualisiert verschachtelte Wörterbücher, indem es jeweils eine Ebene durchläuft, und ruft sich selbst auf, um jede nächste Ebene von Wörterbuchwerten zu aktualisieren:

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

Ein einfaches Beispiel:

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}

0

Wie wäre es mit einer anderen Antwort?!? Dieser vermeidet auch Mutationen / Nebenwirkungen:

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)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.