web-dev-qa-db-ja.com

辞書の辞書がマージされます

複数の辞書をマージする必要があります。たとえば、次のようなものがあります。

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

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

ABCおよびDは、{"info1":"value", "info2":"value2"}

辞書のレベル(深さ)が不明です。{2:{"c":{"z":{"y":{C}}}}}

私の場合、これはノードがドキュメントであり、ファイルのままであるディレクトリ/ファイル構造を表します。

私はそれらをマージして取得します:

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

Pythonで簡単にできるかどうかはわかりません。

102
fdhex

これは実際には非常に難しいです-特に、重複しているが一貫性のあるエントリを正しく受け入れながら、物事が一貫していないときに有用なエラーメッセージが必要な場合(他の答えはありません...)

膨大な数のエントリがない場合、再帰関数が最も簡単です。

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

これはaを変異させることに注意してください-bの内容がaに追加されます(これも返されます)。 aを保持したい場合は、merge(dict(a), b)のように呼び出すことができます。

agfは、次のように3つ以上の辞書があるかもしれないことを指摘しました。

_reduce(merge, [dict1, dict2, dict3...])
_

すべてがdict1に追加される場所。

[注-最初の引数を変更するために最初の回答を編集しました。これにより、「削減」の説明が容易になります]

ps in python 3、また_from functools import reduce_が必要です。

117
andrew cooke

ジェネレーターを使用して簡単に実行できます。

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

これは印刷します:

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

この質問の1つの問題は、dictの値が任意の複雑なデータになる可能性があることです。これらと他の答えに基づいて、私はこのコードを思いつきました:

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

私のユースケースは YAMLファイルのマージ です。ここでは、可能なデータ型のサブセットのみを処理する必要があります。したがって、タプルやその他のオブジェクトは無視できます。私にとって賢明なマージロジックとは

  • スカラーを置き換える
  • リストを追加
  • 不足しているキーを追加して既存のキーを更新することにより、辞書をマージします

それ以外のすべておよび予測不能な結果はエラーになります。

18
Schlomo

辞書の辞書がマージされる

これは(特定の非一般性にもかかわらず)標準的な質問なので、この問題を解決するための標準的なPythonのアプローチを提供しています。

最も単純なケース:「葉は空の辞書で終わるネストされた辞書です」:

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

これは再帰の最も単純なケースであり、2つの素朴なアプローチをお勧めします。

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

私は最初よりも2番目の方を好むと思いますが、最初の最初の状態を元から再構築する必要があることに留意してください。使用方法は次のとおりです。

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

複雑なケース:「葉は他のタイプのものです:」

したがって、それらが辞書で終わる場合、それは空の辞書をマージする単純なケースです。そうでない場合、それほど簡単ではありません。文字列の場合、どのようにそれらをマージしますか?セットも同様に更新できるため、その処理を行うことができますが、マージされた順序は失われます。順序は重要ですか?

したがって、より多くの情報の代わりに、最も単純なアプローチは、両方の値が辞書ではない場合に標準の更新処理を与えることです。つまり、2番目の辞書の値がNoneで最初の値がたくさんの情報を持つ辞書。

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

そしていま

_from functools import reduce
reduce(rec_merge, (d1, d2, d3, d4))
_

返却値

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

元の質問への適用:

文字を囲む中括弧を削除し、これを合法にするために単一引用符で囲む必要がありましたPython(そうでなければ、Python = 2.7+)、不足しているブレースを追加:

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

そしてrec_merge(dict1, dict2)は以下を返します:

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

元の質問の望ましい結果に一致するもの(変更後、たとえば_{A}_を_'A'_に変更)

10
Aaron Hall

@andrew cookeに基づいています。このバージョンはネストされた辞書のリストを処理し、値を更新するオプションも許可します

 def merge(a、b、path = None、update = True):
 "http://stackoverflow.com/questions/7204805/python-dictionaries-of-dictionaries-merge" [ .____。 isinstance(a [key]、dict)およびisinstance(b [key]、dict):
 merge(a [key]、b [key]、path + [str(key)])
 Elif a [key] == b [key]:
 pass#同じリーフ値
 Elif isinstance(a [key]、list)および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] 
 
を返します
8
Osiloke

辞書のレベルがわからない場合は、再帰関数をお勧めします。

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
Spencer Rathbun

@andrew cookeからの回答に基づいています。ネストされたリストをより適切に処理します。

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]
5
Vikas Kumar

この単純な再帰的手順は、競合するキーをオーバーライドしながら、ある辞書を別の辞書にマージします。

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

出力:

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

概要

次のアプローチは、辞書の深いマージの問題を細分化します。

  1. 関数fを使用して2つの辞書abをマージする、パラメーター化された浅いマージ関数merge(f)(a,b)

  2. fと一緒に使用される再帰的マージ関数merge


実装

2つの(ネストされていない)dictをマージする関数は、多くの方法で記述できます。私は個人的に好きです

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

適切な再帰的マージ関数fを定義する良い方法は、引数のタイプに応じて異なるパスに沿って評価する関数を定義できる multipledispatch を使用することです。

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

2つのネストされた辞書をマージするには、単にmerge(f)を使用します。例:

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

注:

このアプローチの利点は次のとおりです。

  • 関数は、それぞれが単一のことを実行する小さな関数から構築され、コードの推論とテストを簡単にします

  • 動作はハードコーディングされていませんが、必要に応じて変更および拡張できるため、コードの再利用が向上します(以下の例を参照)。


カスタマイズ

一部の回答では、リストを含む辞書も考慮されています。他の(潜在的にネストされた)辞書の。この場合、リストにマップし、位置に基づいてそれらをマージすることができます。これは、別の定義をマージ関数fに追加することで実行できます。

_import itertools
@dispatch(list, list)
def f(a,b):
    return [merge(f)(*arg) for arg in itertools.Zip_longest(a,b,fillvalue={})]
_
4
Sascha

Dictviewsは集合演算をサポートしているため、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])))

Dictと非dict(技術的には、「keys」メソッドのあるオブジェクトと「keys」メソッドのないオブジェクト)を組み合わせようとすると、AttributeErrorが発生します。これには、関数の初期呼び出しと再帰呼び出しの両方が含まれます。これはまさに私が欲しかったものなので、私はそれを残しました。再帰呼び出しによってスローされたAttributeErrorsを簡単にキャッチして、任意の値を生成できます。

2
Guy Gangemi

このバージョンの関数は、N個のディクショナリと、ディクショナリのみを考慮します。不適切なパラメータを渡すことはできず、TypeErrorが発生します。マージ自体はキーの競合を考慮しており、マージチェーンのさらに下のディクショナリからデータを上書きする代わりに、値のセットを作成してそれに追加します。データは失われません。

これはページ上で最も効率的ではないかもしれませんが、最も徹底的であり、2 to 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})

出力:{1:[1、2]、2:{1:2、3:1}、4:4}

2
blakev

アンドリュークックの回答にはわずかな問題があります。場合によっては、返されたdictを変更するときに2番目の引数bを変更します。具体的には、次の行が原因です。

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

b[key]dictであり、単にaに割り当てられます。つまり、そのdictへの以降の変更はab

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)

これを修正するには、次の行に置き換える必要があります。

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

どこ clone_dictは:

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

まだ。これは明らかにlistsetおよびその他のものを考慮していませんが、dictsをマージしようとするときの落とし穴を説明することを望みます。

完全を期すために、ここに私のバージョンがあります。複数の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, {})
2
andsens

ショートアンドスイート:

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]

これは、Pythonのdict.updateメソッドのように機能します(ビルドされます)。 dict Noneをインプレースで更新するため、d(必要に応じてreturn dをいつでも追加できます)を返します。 vのキーは、dの既存のキーを上書きします(dictの内容を解釈しようとしません)。

他の( "dict-like")マッピングでも機能します。

1

これは、dict2 into 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]

それをテストし、これがあなたが望んでいたものかどうかを教えてください。

編集:

上記のソリューションは1レベルのみをマージしますが、OPで指定された例を正しく解決します。複数のレベルをマージするには、再帰を使用する必要があります。

1
Tadeck

2つの辞書(ab)があり、それぞれに任意の数のネストされた辞書を含めることができます。 baより優先して、それらを再帰的にマージしたかったのです。

入れ子になった辞書をツリーとして考えると、私が欲しかったのは:

  • a内のすべての葉へのすべてのパスがbで表されるようにaを更新するには
  • a の対応するパスに葉が見つかった場合にbのサブツリーを上書きするには
    • すべてのbリーフノードがリーフのままであるという不変条件を維持します。

既存の回答は私の好みにとって少し複雑であり、いくつかの詳細を棚に残しました。私はデータセットの単体テストに合格する次のものを一緒にハックしました。

  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

例(わかりやすくするためにフォーマット):

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

維持する必要があるbのパスは次のとおりです。

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

aには、次の一意の競合しないパスがありました。

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

そのため、それらはマージされたマップにまだ表されています。

1
mateor

誰かがまだこの問題に別のアプローチを望んでいる場合、ここに私の解決策があります。

美徳:短く、宣言的で、機能的(再帰的、突然変異なし)。

潜在的な欠点:これは探しているマージではないかもしれません。セマンティクスについては、docstringを参照してください。

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
        }
1
David Schneider
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 __== '__main__':
    import doctest
    doctest.testmod()
0
wong steve

私はここに別のわずかに異なる解決策があります:

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

デフォルトでは、2番目のdictの値を優先して競合を解決しますが、これを簡単にオーバーライドできます。一部の魔術では、例外をスローすることもできます。 :)。

0
Slava

私はあなたのソリューションをテストしてきましたが、私のプロジェクトでこれを使用することにしました:

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

関数をパラメーターとして渡すことは、jterraceソリューションを拡張して他のすべての再帰的ソリューションとして動作するための鍵です。

0
mentatkgs

私が考えることができる最も簡単な方法は:

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

出力:

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

ちょっとそこにも私は同じ問題を抱えていましたが、ソリューションについてはここに投稿します。他の人にも役立つ場合は、基本的にネストされた辞書をマージし、値を追加しますうまくいきました:

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

上記の方法を使用することで、マージできます:

ターゲット= {'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} }

これは次のようになります:{'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 }}

また、ここの変更点にも注意してください。

ターゲット= {'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}}

コピー用のインポートも追加することを忘れないでください:

import copy
0
SlackSpace

もちろん、コードはマージの競合を解決するためのルールに依存します。これは、オブジェクトの突然変異を使用せずに、任意の数の引数を取り、それらを任意の深さまで再帰的にマージできるバージョンです。次のルールを使用して、マージの競合を解決します。

  • 辞書は非dict値よりも優先されます({"foo": {...}}{"foo": "bar"}よりも優先されます)
  • 後の引数は前の引数よりも優先されます({"a": 1}{"a", 2}、および{"a": 3}を順番にマージすると、結果は{"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  
0
singingwolfboy