IT

사전 병합 방법

itgroup 2022. 12. 7. 22:25
반응형

사전 병합 방법

여러 사전을 병합해야 합니다. 예를 들어 다음과 같습니다.

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

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

★★★★★★★★★★★★★★★★ A B C ★★★★★★★★★★★★★★★★★」D인 모양{"info1":"value", "info2":"value2"}

수준을 알 수 없는 수준()이 . 음음음같 뭇매하다{2:{"c":{"z":{"y":{C}}}}}

이 경우, 노드는 문서이고 파일은 파일인 채로 있는 디렉토리/파일 구조를 나타냅니다.

다음 정보를 얻기 위해 병합합니다.

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

Python으로 어떻게 하면 쉽게 할 수 있을지 모르겠어요.

이것은 실제로 매우 까다롭습니다.특히 일관성이 없는 중복된 엔트리를 올바르게 받아들이면서 유용한 에러 메시지를 필요로 하는 경우(여기에서는 다른 답변은 하지 않습니다).

엔트리의 수가 많지 않다고 가정하면, 재귀 함수가 가장 간단합니다.

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개 이상의 dict를 가질 수 있으며, 이 경우 다음을 사용할 수 있다고 지적했습니다.

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

서 모든 이 「」에 됩니다.dict1.

주의: 첫 번째 인수를 변환하기 위해 첫 번째 답변을 편집했습니다.그러면 "reduce"를 쉽게 설명할 수 있습니다.

추신: python 3에서는,from functools import reduce

발전기를 사용하여 이를 수행하는 간단한 방법은 다음과 같습니다.

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

합병을 시도해 볼 수도 있어.


인스톨

$ pip3 install mergedeep

사용.

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

모든 옵션에 대한 자세한 내용은 문서를 참조하십시오.

이 질문의 한 가지 문제는 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 파일을 병합하는 것입니다. 여기서 가능한 데이터 유형의 하위 집합만 처리하면 됩니다.따라서 튜플이나 다른 오브젝트를 무시할 수 있습니다.나에게 합리적인 병합 논리는

  • 스칼라를 교환하다
  • 리스트를 추가하다
  • 누락된 키를 추가하고 기존 키를 업데이트하여 dits 병합

다른 모든 것과 예기치 못한 일들은 오류를 낳는다.

사전이 병합되다

이것은 (특정 비일반성에도 불구하고) 정례적인 질문이기 때문에저는 이 문제를 해결하기 위한 표준적인 피톤적 접근법을 제공하고 있습니다.

가장 간단한 경우: "leave는 빈 dict로 끝나는 중첩된 dict입니다.

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

나는 첫 번째보다 두 번째가 더 좋다고 생각하지만, 첫 번째의 원형을 원래대로 다시 만들어야 한다는 것을 명심해라.사용방법은 다음과 같습니다.

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

복잡한 케이스: "리브는 다른 타입입니다."

따라서 dicts로 끝나는 경우 빈 dicts를 결합하는 간단한 경우입니다.그렇지 않다면, 그렇게 사소한 일이 아닙니다.스트링의 경우 어떻게 결합합니까?세트도 마찬가지로 갱신할 수 있기 때문에 그 처리를 할 수 있습니다만, 병합 순서가 없어집니다.순서가 중요합니까?

따라서 더 많은 정보 대신 가장 간단한 방법은 두 값이 모두 dict가 아닌 경우 표준 업데이트 처리를 제공하는 것입니다. 즉, 두 번째 dict의 값이 None이고 첫 번째 dict의 값이 많은 정보를 가진 dict인 경우에도 두 번째 dict의 값이 첫 번째 dict의 값을 덮어씁니다.

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

그리고 지금

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

돌아온다

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

첫 번째 질문에 대한 응용 프로그램:

글자 주위에 있는 물결 괄호를 제거하고 작은 따옴표로 묶어야 했습니다(단, 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'

@andrew cooke를 기반으로 합니다.이 버전은 중첩된 딕트 목록을 처리하고 옵션을 통해 값을 업데이트할 수 있습니다.

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

이 간단한 재귀 절차에서는 충돌하는 키를 재정의하면서 한 사전을 다른 사전으로 병합합니다.

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

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

사전 수준을 알 수 없는 경우 재귀 함수를 권장합니다.

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

만약 누군가가 이 문제에 대해 또 다른 접근방식을 원할 경우를 대비해서, 여기 제 해결책이 있습니다.

장점: 짧고 선언적이며 기능적인 스타일(재귀적, 돌연변이가 없음).

잠재적인 결점:이것은 당신이 찾고 있는 결합이 아닐 수 있습니다.의미론에 대해서는 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
        }

개요

다음 접근법은 dicts의 완전 병합 문제를 다음과 같이 세분화합니다.

  1. 된 함수 " " " "merge(f)(a,b)합니다.f a ★★★★★★★★★★★★★★★★★」b

  2. 병합 함수 " " "fmerge


실행

두 개의 (비네스트) 딕트를 병합하는 함수는 여러 가지 방법으로 쓸 수 있습니다.저는 개인적으로

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는 인수 유형에 따라 다른 경로를 따라 평가하는 함수를 정의할 수 있는 멀티플리디스패치를 사용하고 있습니다.

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)

, 「」를 합니다.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)]

앤드류 쿡스의 답변에는 약간의 문제가 있습니다. 두 인수를 합니다.b줄에 설명하겠습니다.특히 다음과 같은 이유로 인해 발생합니다.

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

ifb[key]는 입니다.dict에 할당됩니다.a합니다).dict에 을 주다a ★★★★★★★★★★★★★★★★★」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)

이 문제를 해결하려면 회선을 다음과 같이 치환해야 합니다.

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

이건 이 안 이건 분명히 이 사건에서list,set, 합병을 이 잘 .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, {})

이 버전의 함수에서는 N개의 사전과 사전만 고려되며 잘못된 매개 변수를 전달할 수 없습니다. 그렇지 않으면 TypeError가 발생합니다.머지 자체는 주요 경합을 설명하며, 머지 체인 하부에 있는 사전의 데이터를 덮어쓰는 대신 일련의 값을 생성하여 추가합니다.데이터는 손실되지 않습니다.

이 페이지에서 가장 효율적이지는 않지만, 가장 철저하고 2와 Ndits를 결합해도 정보가 손실되지 않습니다.

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}

dictviews는 set 조작을 지원하므로 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를 non dict(기술적으로는 keys 메서드를 가진 개체와 keys 메서드를 사용하지 않는 개체)와 결합하려고 하면 AttributeError가 발생합니다.여기에는 함수에 대한 초기 호출과 재귀 호출이 모두 포함됩니다.이게 바로 제가 원하던 거라서 두고 왔어요.재귀 호출에 의해 던져진 AttributeErrors를 쉽게 포착하여 원하는 값을 얻을 수 있습니다.

짧은 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]

의 Python과 같이 그 위에).dict.update되돌아오다None 추가할 수 )return d "dict "dict" "dict" "dict" "dict" "dict" "dict" "" "dict" "dictd「」의v는, 를 덮어씁니다.d(어느쪽이든)

다른("딕트 유사") 매핑에도 사용할 수 있습니다.

패키지를 보다

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

주다

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

반복적인 솔루션이 있습니다.큰 딕트나 많은 딕트(jsons 등)를 사용하면 훨씬 더 잘 작동합니다.

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

둘 다 dict가 아닌 경우 d2의 값을 사용하여 d1을 덮어씁니다.(뱀과 동일)dict.update())

일부 테스트:

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

약 1200dicts로 테스트했습니다.이 방법은 0.4초, 재귀 솔루션은 2.5초 정도 소요되었습니다.

물론 코드는 병합 충돌을 해결하기 위한 규칙에 따라 달라집니다.이것은 임의의 수의 인수를 사용하여 오브젝트 변환을 사용하지 않고 임의의 깊이로 재귀적으로 병합할 수 있는 버전입니다.다음 규칙을 사용하여 병합 충돌을 해결합니다.

  • 값보다 {"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  

는 사전을두 권 .a ★★★★★★★★★★★★★★★★★」b각각 임의의 수의 중첩된 사전을 포함할 수 있습니다. them으 them와 으로 병합하고 .b하는 것보다 a.

네스트된 사전을 나무라고 생각하면, 내가 원하는 것은 다음과 같다.

  • 「」를 a 모든 가 so so so so so in so so so so so so so so so so so so so so so so sob 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'

그래서 그것들은 여전히 병합된 지도에 표시됩니다.

그리고 또 다른 작은 변화입니다.

Pure python3 세트 기반의 딥 업데이트 기능을 소개합니다.한 번에 한 수준씩 루프하여 중첩된 사전을 업데이트하고 사전 값의 다음 수준 각각을 업데이트하도록 호출합니다.

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

간단한 예:

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}

다른 대답은 어때?이것은 또한 돌연변이/부작용을 회피한다.

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)

이것은 사전을 무한히 심도 있게 재귀적으로 병합하는 제가 만든 해결책입니다.함수에 전달된 첫 번째 사전은 마스터 사전입니다. 이 사전의 값이 두 번째 사전의 동일한 키의 값을 덮어씁니다.

def merge(dict1: dict, dict2: dict) -> dict:
    merged = dict1

    for key in dict2:
        if type(dict2[key]) == dict:
            merged[key] = merge(dict1[key] if key in dict1 else {}, dict2[key])
        else:
            if key not in dict1.keys():
                merged[key] = dict2[key]

    return merged

하면 모든 수 .dict2dict1:

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

테스트해보고 이것이 당신이 원하는 것인지 알려주세요.

편집:

위의 솔루션은 하나의 수준만 병합하지만 OP에서 제공하는 예를 올바르게 해결합니다.여러 레벨을 Marge하려면 재귀를 사용해야 합니다.

귀사의 솔루션을 테스트하고 있으며, 이 솔루션을 프로젝트에 사용하기로 결정했습니다.

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 솔루션을 확장하여 다른 모든 재귀 솔루션과 동일하게 동작하도록 하는 데 중요합니다.

내가 생각할 수 있는 가장 쉬운 방법은:

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

여기에도 약간 다른 솔루션이 있습니다.

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

디폴트로는 두 번째 dict의 값에 유리한 경합이 해결되지만, 이 경합을 쉽게 덮어쓸 수 있습니다.또한 약간의 위치를 사용하여 예외를 삭제할 수도 있습니다. : ).

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

저도 같은 문제가 있었습니다만, 해결 방법을 생각하고 있었습니다.다른 사람에게도 도움이 될 수 있는 경우에 대비해 여기에 투고하겠습니다.기본적으로 중첩된 사전을 병합하고 값을 추가합니다.저에게는 몇 가지 확률을 계산해야 이 방법이 잘 작동했습니다.

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

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': {'4,6': {'6,63': 1, '63,4': {'4,4': 1, '4',4': {'4,4': {4', '63,63':1,

다음 변경 사항도 유의하십시오.

대상 = {'6,6': {'6,63': 1, '63,63': {'63,4': 1, '4,4': {'4,3': 1, '63,4': {'4,4': {'4,4'}: 1}

src = {'5,4': {'4,4': 1, ''3,3': {'3,4': {'4,9': 1, '3,4': {'4,4': 1, '5',5': {'5,4': 1}

병합 = {'5,4': {'4,4': 1, '4,3': {'3,4': 1, '63,63': {'63,4': 1, '5,5': {5,4': 1, '6,6': {6,63': 1, '3',4':1, '4'

카피용으로 Import도 추가하는 것을 잊지 말아 주세요.

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

출력:

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

다음 함수는 b를 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

언급URL : https://stackoverflow.com/questions/7204805/how-to-merge-dictionaries-of-dictionaries

반응형