Làm thế nào để hợp nhất từ ​​điển của từ điển?


129

Tôi cần hợp nhất nhiều từ điển, đây là những gì tôi có ví dụ:

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

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

Với A B CDlà lá của cây, như{"info1":"value", "info2":"value2"}

Có một mức độ không xác định (độ sâu) của từ điển, nó có thể là {2:{"c":{"z":{"y":{C}}}}}

Trong trường hợp của tôi, nó đại diện cho một cấu trúc thư mục / tệp với các nút là tài liệu và để lại các tệp.

Tôi muốn hợp nhất chúng để có được:

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

Tôi không chắc làm thế nào tôi có thể làm điều đó dễ dàng với Python.


Bạn muốn gì cho độ sâu từ điển tùy ý của bạn? Bạn có muốn ylàm phẳng đến ccấp độ hoặc những gì? Ví dụ của bạn không đầy đủ.
agf

Kiểm tra lớp NestedDict của tôi ở đây: stackoverflow.com/a/16296144/2334951 Nó quản lý các cấu trúc từ điển lồng nhau như hợp nhất và hơn thế nữa.
SzieberthAdam

3
Một cảnh báo cho tất cả mọi người đang tìm giải pháp: Câu hỏi này chỉ nói về các dicts lồng nhau. Hầu hết các câu trả lời không xử lý trường hợp phức tạp hơn của danh sách các ký tự trong cấu trúc đúng. Nếu bạn cần điều này, hãy thử câu trả lời của @Osiloke bên dưới: stackoverflow.com/a/25270947/1431660
SHernandez


Câu trả lời:


143

điều này thực sự khá khó khăn - đặc biệt nếu bạn muốn có một thông báo lỗi hữu ích khi mọi thứ không nhất quán, trong khi chấp nhận chính xác các mục trùng lặp nhưng nhất quán (điều mà không có câu trả lời nào khác ở đây ....)

giả sử bạn không có số lượng lớn các mục nhập, hàm đệ quy là dễ nhất:

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

lưu ý rằng điều này đột biến a- nội dung bđược thêm vào a(cũng được trả lại). nếu bạn muốn giữ abạn có thể gọi nó như thế merge(dict(a), b).

agf đã chỉ ra (bên dưới) rằng bạn có thể có nhiều hơn hai dicts, trong trường hợp đó bạn có thể sử dụng:

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

nơi mọi thứ sẽ được thêm vào dict1.

[lưu ý - tôi đã chỉnh sửa câu trả lời ban đầu của mình để thay đổi đối số đầu tiên; điều đó làm cho "giảm" dễ giải thích hơn]

ps trong python 3, bạn cũng sẽ cần from functools import reduce


1
Sau đó, bạn có thể dán cái này bên trong một reducehoặc vòng lặp tương đương để làm việc với số dicts tùy ý thay vì hai. Tuy nhiên, tôi không chắc điều này sẽ làm những gì anh ấy muốn (anh ấy không rõ ràng), Bạn kết thúc với 2: {'c': {'z': {'y': {'info1': 'value', 'info2': 'value2'}}}, 'b': {'info1': 'value', 'info2': 'value2'}}ví dụ thứ hai của anh ấy, tôi không chắc liệu anh ấy có muốn zylàm phẳng hay không?
agf

1
Chúng là cấu trúc thư mục vì vậy tôi không nghĩ rằng anh ấy muốn bất cứ thứ gì bị san phẳng? oh, xin lỗi, đã bỏ lỡ "nhiều từ điển". vâng, giảm sẽ tốt sẽ thêm điều đó.
rút cooke

Điều này làm chính xác những gì tôi muốn! Tôi xin lỗi tôi đã không đủ rõ ràng ... Tôi nghĩ rằng tôi ổn với Python, dường như không: - / Tôi cần một hàm đệ quy vì các ký tự lồng nhau, cái này hoạt động và tôi có thể hiểu nó :) Tôi không dường như có thể làm cho nó hoạt động với giảm mặc dù ...
fdhex

2
Đối với bất kỳ ai có danh sách là cấp lồng nhau cuối cùng trong các ký tự, bạn có thể thực hiện việc này thay vì đưa ra lỗi để nối hai danh sách : a[key] = a[key] + b[key]. Cảm ơn câu trả lời hữu ích.
kevinmicke

1
> nếu bạn muốn giữ một bạn có thể gọi nó là hợp nhất (dict (a), b) Lưu ý rằng các ký tự lồng nhau sẽ vẫn bị thay đổi. Để tránh điều này, sử dụng copy.deepcopy.
RCorre

30

Đây là một cách dễ dàng để làm điều đó bằng cách sử dụng máy phát điện:

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

Bản in này:

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

nếu bạn muốn giữ chủ đề trình tạo, bạn có thể xâu chuỗi (dict1.keys (), dict2.keys ())
rút cooke

Sẽ không có được các khóa trùng lặp?
jterrace

Công cụ này dường như thực hiện công việc, ít nhất là trên bộ dữ liệu của tôi, nhưng vì tôi chưa bao giờ hiểu rõ về năng suất và máy phát điện nên tôi khá mất phương hướng, nhưng tôi sẽ cố gắng hơn một chút, có thể hữu ích!
fdhex

ah, vâng, nó sẽ nhận được các khóa trùng lặp. bạn vẫn cần phải bọc nó trong một bộ, xin lỗi.
rút cooke

2
Tôi thấy điều này đặc biệt hữu ích. Nhưng tốt nhất sẽ là để cho chức năng giải quyết xung đột như là một tham số.
mentatkgs

25

Một vấn đề với câu hỏi này là các giá trị của dict có thể là các phần dữ liệu phức tạp tùy ý. Dựa trên những điều này và các câu trả lời khác, tôi đã đưa ra mã này:

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

Trường hợp sử dụng của tôi là hợp nhất các tệp YAML trong đó tôi chỉ phải xử lý một tập hợp con các loại dữ liệu có thể. Do đó tôi có thể bỏ qua các bộ dữ liệu và các đối tượng khác. Đối với tôi một logic hợp lý có nghĩa là

  • thay thế vô hướng
  • nối thêm danh sách
  • hợp nhất các ký tự bằng cách thêm các khóa bị thiếu và cập nhật các khóa hiện có

Mọi thứ khác và những điều không lường trước đều dẫn đến một lỗi.


1
Tuyệt diệu. Hoạt động tốt trên bãi rác json, quá. Chỉ cần loại bỏ xử lý lỗi. (Lười biếng, có thể làm những việc phù hợp cho json Tôi chắc chắn)
dgBP 24/2/2016

3
trình tự "isinstance" có thể được thay thế w / isinstance(a, (str, unicode, int, long, float))isnt 'không?
simahawk

12

Từ điển hợp nhất từ ​​điển

Vì đây là câu hỏi kinh điển (mặc dù có một số điểm không chung chung) tôi đang cung cấp cách tiếp cận Pythonic kinh điển để giải quyết vấn đề này.

Trường hợp đơn giản nhất: "những chiếc lá được lồng vào nhau kết thúc bằng những chiếc trống rỗng":

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

Đây là trường hợp đơn giản nhất cho đệ quy và tôi muốn đề xuất hai cách tiếp cận ngây thơ:

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

Tôi tin rằng tôi thích cái thứ hai hơn cái thứ nhất, nhưng hãy nhớ rằng trạng thái ban đầu của cái thứ nhất sẽ phải được xây dựng lại từ nguồn gốc của nó. Đây là cách sử dụng:

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

Trường hợp phức tạp: "lá thuộc bất kỳ loại nào khác:"

Vì vậy, nếu chúng kết thúc bằng các ký tự, thì đó là một trường hợp đơn giản để hợp nhất các ký tự trống cuối. Nếu không, nó không quá tầm thường. Nếu chuỗi, làm thế nào để bạn hợp nhất chúng? Các bộ có thể được cập nhật tương tự, vì vậy chúng tôi có thể đưa ra cách xử lý đó, nhưng chúng tôi mất thứ tự chúng được hợp nhất. Vì vậy, trật tự có vấn đề?

Vì vậy, thay vì có nhiều thông tin hơn, cách tiếp cận đơn giản nhất sẽ là cung cấp cho họ cách xử lý cập nhật tiêu chuẩn nếu cả hai giá trị không phải là giá trị: tức là giá trị của lệnh thứ hai sẽ ghi đè lên giá trị thứ nhất, ngay cả khi giá trị của lệnh thứ hai là Không và giá trị thứ nhất là dict với rất nhiều thông tin.

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

Và bây giờ

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

trả lại

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

Áp dụng cho câu hỏi ban đầu:

Tôi đã phải loại bỏ các dấu ngoặc nhọn xung quanh các chữ cái và đặt chúng trong các dấu ngoặc đơn để đây là Python hợp pháp (nếu không chúng sẽ được đặt thành chữ trong Python 2.7+) cũng như nối thêm một dấu ngoặc nhọn:

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

rec_merge(dict1, dict2)bây giờ trả về:

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

Điều này phù hợp với kết quả mong muốn của câu hỏi ban đầu (sau khi thay đổi, ví dụ: {A}thành 'A'.)


10

Dựa trên @andrew cooke. Phiên bản này xử lý danh sách các dicts lồng nhau và cũng cho phép tùy chọn cập nhật các giá trị

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
Cảm ơn, điều này rất hữu ích. Tôi đang có danh sách các dicts trong cấu trúc của mình mọi lúc, các giải pháp khác không thể hợp nhất chính xác điều này.
SHernandez

7

Quy trình đệ quy đơn giản này sẽ hợp nhất một từ điển này với một từ điển khác trong khi ghi đè các khóa xung đột:

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

Đầu ra:

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

7

Dựa trên câu trả lời từ @andrew cooke. Nó chăm sóc các danh sách lồng nhau một cách tốt hơn.

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]

trực quan và đối xứng. +1 để xử lý danh sách :)
vdwees

6

Nếu bạn có một mức độ từ điển không xác định, thì tôi sẽ đề xuất một hàm đệ quy:

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

Tổng quat

Cách tiếp cận sau đây phân chia vấn đề của sự hợp nhất sâu sắc của các câu lệnh thành:

  1. Hàm hợp nhất nông được tham số hóa merge(f)(a,b)sử dụng hàm fđể hợp nhất hai ký tự ab

  2. Hàm sáp nhập đệ quy fđược sử dụng cùng vớimerge


Thực hiện

Một chức năng để hợp nhất hai dicts (không lồng nhau) có thể được viết theo nhiều cách. Cá nhân tôi thích

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

Một cách hay để xác định hàm sáp nhập đệ quy thích hợp flà sử dụng đa bội cho phép xác định các hàm đánh giá dọc theo các đường dẫn khác nhau tùy thuộc vào loại đối số của chúng.

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)

Thí dụ

Để hợp nhất hai ký tự lồng nhau, chỉ cần sử dụng, merge(f)ví dụ:

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

Ghi chú:

Ưu điểm của phương pháp này là:

  • Hàm được xây dựng từ các hàm nhỏ hơn, mỗi hàm thực hiện một điều duy nhất giúp mã đơn giản hơn để lý giải và kiểm tra

  • Hành vi không được mã hóa cứng nhưng có thể được thay đổi và mở rộng khi cần thiết để cải thiện việc sử dụng lại mã (xem ví dụ bên dưới).


Tùy biến

Một số câu trả lời cũng được coi là các dicts có chứa danh sách, ví dụ như các dicts khác (có khả năng lồng nhau). Trong trường hợp này, người ta có thể muốn ánh xạ qua các danh sách và hợp nhất chúng dựa trên vị trí. Điều này có thể được thực hiện bằng cách thêm một định nghĩa khác cho chức năng sáp nhập f:

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

4

Trong trường hợp ai đó muốn một cách tiếp cận khác cho vấn đề này, đây là giải pháp của tôi.

Virtues : ngắn gọn, khai báo và chức năng trong phong cách (đệ quy, không đột biến).

Hạn chế tiềm năng : Đây có thể không phải là sự hợp nhất mà bạn đang tìm kiếm. Tham khảo các chuỗi tài liệu cho ngữ nghĩa.

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
        }

Câu trả lời rất thú vị, cảm ơn bạn đã chia sẻ nó. Cú pháp nào bạn đã sử dụng sau câu lệnh return? Tôi không quen thuộc với nó.
dev_does_software

4

Bạn có thể thử sáp nhập .


Cài đặt

$ pip3 install mergedeep

Sử dụng

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

Để có danh sách đầy đủ các tùy chọn, hãy xem tài liệu !


3

Có một vấn đề nhỏ với câu trả lời của đầu bếp andrew: Trong một số trường hợp, nó sửa đổi đối số thứ hai bkhi bạn sửa đổi chính tả được trả về. Cụ thể là vì dòng này:

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

Nếu b[key] là một dict, nó chỉ đơn giản sẽ được gán cho a, có nghĩa là bất kỳ sửa đổi tiếp theo nào dictsẽ ảnh hưởng đến cả 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)

Để khắc phục điều này, dòng sẽ phải được thay thế bằng điều này:

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

Ở đâu 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

Vẫn. Điều này rõ ràng không tính đến list, setvà những thứ khác, nhưng tôi hy vọng nó minh họa những cạm bẫy khi cố gắng hợp nhất dicts.

Và để hoàn thiện, đây là phiên bản của tôi, nơi bạn có thể vượt qua nó 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, {})

Tại sao không deepcopythay thế clone_dict?
Armando Pérez Marqués

1
Bởi vì con trăn stdlib rất to lớn và tráng lệ! Tôi không có đầu mối nào tồn tại - cộng với đó là một điều thú vị nhỏ để viết mã :-)
andsens

2

Phiên bản này của chức năng sẽ chiếm N số lượng từ điển và chỉ từ điển - không có thông số không phù hợp nào có thể được thông qua, hoặc nó sẽ nâng TypeError. Việc hợp nhất tự nó chiếm các xung đột chính và thay vì ghi đè dữ liệu từ một từ điển xuống chuỗi hợp nhất, nó tạo ra một tập hợp các giá trị và nối vào đó; không có dữ liệu bị mất

Nó có thể không hiệu quả nhất trên trang, nhưng nó là kỹ lưỡng nhất và bạn sẽ không mất bất kỳ thông tin nào khi bạn hợp nhất 2 đến N dicts của bạ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})

đầu ra: {1: [1, 2], 2: {1: 2, 3: 1}, 4: 4}


2

Vì dictview hỗ trợ các hoạt động thiết lập, tôi có thể đơn giản hóa rất nhiều câu trả lời của 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])))

Bất kỳ nỗ lực nào để kết hợp một dict với non dict (về mặt kỹ thuật, một đối tượng với phương thức 'key' và một đối tượng không có phương thức 'key') sẽ đưa ra AttributionError. Điều này bao gồm cả cuộc gọi ban đầu đến chức năng và các cuộc gọi đệ quy. Đây chính xác là những gì tôi muốn vì vậy tôi đã bỏ nó. Bạn có thể dễ dàng bắt gặp một AttributionErrors được gọi bởi cuộc gọi đệ quy và sau đó mang lại bất kỳ giá trị nào bạn muốn.


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]

Điều này hoạt động như (và được xây dựng trên) dict.updatephương thức của Python . Nó trả về None(bạn luôn có thể thêm return dnếu bạn thích) khi nó cập nhật dict dtại chỗ. Các khóa trong vsẽ ghi đè lên bất kỳ khóa hiện có nào trongd (không cố gắng diễn giải nội dung của chính tả).

Nó cũng sẽ hoạt động cho các ánh xạ khác ("giống như dict").


1

Mã sẽ phụ thuộc vào quy tắc của bạn để giải quyết xung đột hợp nhất, tất nhiên. Đây là một phiên bản có thể lấy một số lượng các đối số tùy ý và hợp nhất chúng theo cách đệ quy đến một độ sâu tùy ý, mà không sử dụng bất kỳ đột biến đối tượng nào. Nó sử dụng các quy tắc sau để giải quyết xung đột hợp nhất:

  • từ điển được ưu tiên hơn các giá trị không chính tả ( {"foo": {...}}được ưu tiên hơn{"foo": "bar"} )
  • lập luận sau được ưu tiên hơn đối số trước đó (nếu bạn hợp nhất {"a": 1}, {"a", 2}{"a": 3}theo thứ tự, kết quả sẽ là {"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

Tôi đã có hai từ điển ( ab) mỗi từ có thể chứa bất kỳ số lượng từ điển lồng nhau. Tôi muốn hợp nhất đệ quy chúng, với bquyền ưu tiên hơna .

Coi những từ điển lồng nhau như những cái cây, điều tôi muốn là:

  • Để cập nhật ađể mọi đường dẫn đến mọi lá trong bsẽ được thể hiện tronga
  • Để ghi đè lên cây con anếu một chiếc lá được tìm thấy trong đường dẫn tương ứng trongb
    • Duy trì bất biến rằng tất cả bcác nút lá vẫn là lá.

Các câu trả lời hiện có một chút phức tạp đối với sở thích của tôi và để lại một số chi tiết trên kệ. Tôi đã hack cùng nhau sau đây, vượt qua các bài kiểm tra đơn vị cho tập dữ liệu của tôi.

  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

Ví dụ (được định dạng cho rõ ràng):

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

Các con đường bcần được duy trì là:

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

a có những con đường độc đáo và không xung đột về:

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

vì vậy chúng vẫn được thể hiện trong bản đồ hợp nhất.


1

Tôi có một giải pháp lặp lại - hoạt động tốt hơn nhiều với các công cụ lớn và rất nhiều trong số họ (ví dụ jsons, v.v.):

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

lưu ý rằng điều này sẽ sử dụng giá trị trong d2 để ghi đè d1, trong trường hợp chúng không phải là cả hai dicts. (giống như con trăndict.update() )

một số xét nghiệm:

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

Tôi đã thử nghiệm với khoảng ~ 1200 dicts - phương pháp này mất 0,4 giây, trong khi giải pháp đệ quy mất ~ 2,5 giây.


0

Điều này sẽ giúp hợp nhất tất cả các mục từ dict2vào 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]

Vui lòng kiểm tra nó và cho chúng tôi biết nếu đây là những gì bạn muốn.

BIÊN TẬP:

Giải pháp được đề cập ở trên chỉ hợp nhất một cấp độ, nhưng giải quyết chính xác ví dụ do OP đưa ra. Để hợp nhất nhiều cấp, nên sử dụng đệ quy.


1
Anh ta có một độ sâu làm tổ tùy ý
agf

Điều đó có thể được viết lại đơn giản như for k,v in dict2.iteritems(): dict1.setdefault(k,{}).update(v). Nhưng như @agf đã chỉ ra, điều này không hợp nhất các dicts lồng nhau.
Shawn Chin

@agf: Đúng vậy, có vẻ như OP cần giải pháp sử dụng tái phát. Nhờ các từ điển thực tế có thể thay đổi, điều này khá dễ thực hiện. Nhưng tôi nghĩ rằng câu hỏi không đủ cụ thể để nói điều gì sẽ xảy ra khi chúng ta đến những nơi có độ sâu khác nhau (ví dụ: cố gắng hợp nhất {'a':'b'}với {'a':{'c':'d'}).
Tadeck

0

Tôi đã thử nghiệm các giải pháp của bạn và quyết định sử dụng giải pháp này trong dự án của tôi:

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

Truyền các hàm làm tham số là chìa khóa để mở rộng giải pháp jterrace để hành xử như tất cả các giải pháp đệ quy khác.


0

Cách dễ nhất tôi có thể nghĩ là:

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

Đầu ra:

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

0

Tôi có một giải pháp hơi khác ở đây:

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

Theo mặc định, nó giải quyết xung đột có lợi cho các giá trị từ chính tả thứ hai, nhưng bạn có thể dễ dàng ghi đè lên điều này, với một số phép thuật bạn có thể thậm chí có thể loại bỏ các ngoại lệ ra khỏi nó. :).


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

Này, tôi cũng gặp vấn đề tương tự nhưng tôi mặc dù là một giải pháp và tôi sẽ đăng nó ở đây, trong trường hợp nó cũng hữu ích cho những người khác, về cơ bản hợp nhất các từ điển lồng nhau và thêm các giá trị, đối với tôi, tôi cần tính toán một số xác suất một người đã làm việc tuyệt vời:

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

bằng cách sử dụng phương pháp trên, chúng ta có thể hợp nhất:

mục tiêu = {'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} }

và điều này sẽ trở thành: {'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 }}

cũng lưu ý những thay đổi ở đây:

mục tiêu = {'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}}

hợp nhất = {'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} }

đừng quên thêm nhập để sao chép:

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

Đầu ra:

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

Mặc dù mã này có thể trả lời câu hỏi, cung cấp ngữ cảnh bổ sung về lý do và / hoặc cách mã này trả lời câu hỏi cải thiện giá trị lâu dài của nó.
xiawi

Tôi nghĩ rằng đây là một triển khai chung của việc hợp nhất một hoặc nhiều từ điển lồng nhau trong việc xem xét loại đối tượng sẽ bị lề
Dorcioman

0

hãy xem toolzgói

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

cho

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

0

Hàm sau hợp nhất b thành 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

0

Và chỉ một biến thể nhỏ khác:

Dưới đây là một bộ python3 thuần dựa trên chức năng cập nhật sâu. Nó cập nhật các từ điển lồng nhau bằng cách lặp qua một cấp độ tại một thời điểm và tự gọi nó để cập nhật từng cấp giá trị từ điển tiếp theo:

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

Một ví dụ đơn giản:

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

Còn câu trả lời khác thì sao?!? Điều này cũng tránh đột biến / tác dụng phụ:

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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.