Frage Wörterbücher von Wörterbüchern verschmelzen


Ich muss mehrere Wörterbücher zusammenführen, hier habe ich zum Beispiel:

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

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

Mit A  B  C und D wie Blätter des Baumes {"info1":"value", "info2":"value2"}

Es gibt eine unbekannte Ebene (Tiefe) von Wörterbüchern, könnte es sein {2:{"c":{"z":{"y":{C}}}}}

In meinem Fall stellt es eine Verzeichnis- / Dateistruktur dar, wobei Knoten Dokumente sind und Dateien belassen werden.

Ich möchte sie zusammenführen, um 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 leicht machen könnte.


76
2017-08-26 12:44


Ursprung


Antworten:


Das ist ziemlich schwierig - vor allem, wenn Sie eine nützliche Fehlermeldung wünschen, wenn Dinge inkonsistent sind, während Sie doppelte, aber konsistente Einträge korrekt akzeptieren (etwas, was keine andere Antwort hier tut ....)

unter der Annahme, dass Sie keine große Anzahl von Einträgen haben, 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 - Die Inhalte von b werden hinzugefügt a (die auch zurückgegeben wird). wenn du es behalten willst a du könntest es so nennen merge(dict(a), b).

Agf wies darauf hin (unten), dass Sie mehr als zwei Diktate haben können. 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" einfacher zu erklären

PS in Python 3, werden Sie auch brauchen from functools import reduce


92
2017-08-26 13:08



Hier ist ein einfacher Weg, es 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'}}

18
2017-08-26 13:50



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

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 Zusammenführen von YAML-Dateien wo ich nur mit einer Teilmenge möglicher Datentypen umgehen muss. Daher kann ich Tupel und andere Objekte ignorieren. Für mich bedeutet eine sinnvolle Merge-Logik

  • Ersetzen Sie Skalare
  • Listen anhängen
  • Zusammenführen von Diktaten durch Hinzufügen fehlender Schlüssel und Aktualisieren vorhandener Schlüssel

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


16
2018-04-05 14:45



Basierend auf @andrew cooke. Diese Version behandelt verschachtelte Dicts-Listen und ermöglicht auch die Aktualisierung der Werte

def merge (a, b, Pfad = Keine, Update = True):
    "http://stackoverflow.com/questions/7204805/python-dictionaries-of-dictionaries-merge"
    "fusioniert b zu einem"
    Wenn der Pfad None lautet: path = []
    für Schlüssel in b:
        wenn Schlüssel a:
            wenn isinstance (a [key], dict) und isinstance (b [key], dict):
                Zusammenführen (a [Schlüssel], b [Schlüssel], Pfad + [str (Schlüssel)])
            elif a [Schlüssel] == b [Schlüssel]:
                pass # gleicher Blattwert
            elif isinstance (a [key], list) und isinstance (b [key], list):
                für idx, val in enumerate (b [key]):
                    a [key] [idx] = merge (a [Schlüssel] [idx], b [Schlüssel] [idx], Pfad + [str (Schlüssel), str (idx)], update = update)
            elif Aktualisierung:
                a [Schlüssel] = b [Schlüssel]
            sonst:
                Ausnahme auslösen ('Conflict at% s'% '.'. join (Pfad + [str (key)]))
        sonst:
            a [Schlüssel] = b [Schlüssel]
    zurückgeben

7
2017-08-12 17:52



Wörterbücher von Wörterbüchern verschmelzen

Da dies die kanonische Frage ist (trotz gewisser Nicht-Allgemeingültigkeiten), stelle ich den kanonischen pythonischen Ansatz zur Lösung dieses Problems zur Verfügung.

Einfachster Fall: "leaves sind geschachtelte dicts, die in leeren dicts 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 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 das zweite dem ersten vorziehen, aber bedenke, dass der ursprüngliche Zustand des ersten von seinem Ursprung wieder aufgebaut 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': {}}}}

Komplizierter Fall: "Blätter sind von jedem anderen Typ:"

Wenn sie also in Dicts enden, ist es ein einfacher Fall, die leeren End-Dicts zu verschmelzen. Wenn nicht, ist es nicht so trivial. Wenn Strings, wie mischt man sie zusammen? Sets können ähnlich aktualisiert werden, sodass wir diese Behandlung geben 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 Standardaktualisierung zu geben, wenn beide Werte nicht dicts sind: dh der Wert des zweiten Diktats überschreibt den ersten, selbst wenn der Wert des zweiten Diktats None ist und der Wert des ersten diktiert a ist dict 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 (ansonsten würden sie in Python 2.7+ Literale setzen) und eine fehlende Klammer hinzufügen:

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

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

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

Dies entspricht dem gewünschten Ergebnis der ursprünglichen Frage (nach der Änderung, z {A} zu 'A'.)


7
2018-06-06 18:32



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

6
2017-08-26 13:18



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.
    Reursively 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: Calls 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:
            orginal[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. Modfies original.
    For key conflicts if both values are:
     a. dict: Recursivley call deep_merge_dicts on both values.
     b. list: Calls 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]

3
2018-06-09 10:23



Diese Version der Funktion berücksichtigt N Wörterbücher und nur Wörterbücher - es können keine unzulässigen Parameter übergeben werden oder es wird ein TypeError ausgelöst. Die Zusammenführung selbst berücksichtigt Schlüsselkonflikte, und anstatt Daten aus einem Wörterbuch weiter unten in der Zusammenführungskette zu überschreiben, erstellt sie eine Reihe von Werten und fügt diese an; keine Daten sind verloren.

Es ist vielleicht nicht die effektivste auf der Seite, aber es ist die gründlichste und Sie werden keine Informationen verlieren, wenn Sie Ihre 2 zu N dicts 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
2017-08-24 23:32



Diese einfache rekursive Prozedur führt ein Wörterbuch in ein anderes zusammen, während in Konflikt stehende Schlüssel überschrieben werden:

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

2
2017-07-19 06:27