วิธีการรวมพจนานุกรมของพจนานุกรม?


129

ฉันต้องการรวมพจนานุกรมหลาย ๆ พจนานุกรมนี่คือสิ่งที่ฉันมีเช่น:

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 ได้อย่างไร


คุณต้องการอะไรสำหรับความลึกของพจนานุกรมโดยพลการของคุณ? คุณต้องการyแบนขึ้นถึงcระดับหรืออะไร? ตัวอย่างของคุณไม่สมบูรณ์
agf

ตรวจสอบคลาส NestedDict ของฉันที่นี่: stackoverflow.com/a/16296144/2334951มันจัดการโครงสร้างพจนานุกรมที่ซ้อนกันเช่นการรวมและอื่น ๆ
SzieberthAdam

3
คำเตือนสำหรับทุกคนที่กำลังมองหาวิธีแก้ปัญหา: คำถามนี้เกี่ยวกับคำสั่งที่ซ้อนกันเท่านั้น คำตอบส่วนใหญ่ไม่สามารถจัดการกับกรณีที่ซับซ้อนมากขึ้นของรายการคำสั่งภายในโครงสร้างได้อย่างถูกต้อง ถ้าคุณต้องการลองคำตอบของ @Osiloke ด้านล่าง: stackoverflow.com/a/25270947/1431660
SHernandez

ดูเพิ่มเติมที่: python dpath merge
dreftymac

คำตอบ:


143

สิ่งนี้ค่อนข้างยุ่งยากโดยเฉพาะอย่างยิ่งหากคุณต้องการข้อความแสดงข้อผิดพลาดที่เป็นประโยชน์เมื่อสิ่งต่าง ๆ ไม่สอดคล้องกันในขณะที่ยอมรับรายการที่ซ้ำกัน แต่สอดคล้องกันอย่างถูกต้อง (สิ่งที่ไม่มีคำตอบอื่นที่นี่ ... )

สมมติว่าคุณไม่มีรายการจำนวนมากฟังก์ชันเรียกซ้ำจะง่ายที่สุด:

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(ซึ่งจะถูกส่งคืนด้วย) ถ้าคุณต้องการที่จะให้คุณสามารถเรียกมันเหมือนamerge(dict(a), b)

agf ชี้ให้เห็น (ด้านล่าง) ว่าคุณอาจมีมากกว่าสองคำสั่งซึ่งในกรณีนี้คุณสามารถใช้:

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

ที่ทุกอย่างจะถูกเพิ่มลงใน dict1

[หมายเหตุ - ฉันแก้ไขคำตอบเริ่มต้นเพื่อเปลี่ยนอาร์กิวเมนต์แรก ที่ทำให้ "ลด" อธิบายได้ง่ายขึ้น]

ps ใน python 3 คุณจะต้องมีไฟล์ from functools import reduce


1
จากนั้นคุณสามารถติดสิ่งนี้ไว้ใน a reduceหรือลูปที่เท่ากันเพื่อทำงานกับจำนวนdicts โดยพลการแทนที่จะเป็นสอง อย่างไรก็ตามฉันไม่แน่ใจว่านี่เป็นสิ่งที่เขาต้องการหรือไม่ (เขาไม่ชัดเจน) คุณจบลงด้วย2: {'c': {'z': {'y': {'info1': 'value', 'info2': 'value2'}}}, 'b': {'info1': 'value', 'info2': 'value2'}}ตัวอย่างที่สองของเขาฉันไม่แน่ใจว่าเขาต้องการzและyแบนหรือไม่?
agf

1
พวกเขาเป็นโครงสร้างไดเรกทอรีดังนั้นฉันไม่คิดว่าเขา / เขาต้องการอะไรที่แบนราบ? ขออภัยพลาด "พจนานุกรมหลายเล่ม" ใช่ลดจะดี จะเพิ่มสิ่งนั้น
andrew cooke

นี่เป็นสิ่งที่ฉันต้องการ! ฉันขอโทษที่ฉันไม่ชัดเจนพอ ... ฉันคิดว่าฉันโอเคกับ Python ดูเหมือนว่าจะไม่ใช่: - / ฉันต้องการฟังก์ชั่นซ้ำเนื่องจากคำสั่งที่ซ้อนกันอันนี้ใช้งานได้และฉันสามารถเข้าใจได้ :) ฉันไม่ ดูเหมือนว่าจะสามารถทำให้มันทำงานได้โดยลดลงแม้ว่า ...
fdhex

2
สำหรับทุกคนที่มีรายชื่อเป็นระดับที่ซ้อนกันเป็นครั้งสุดท้ายภายใต้ dicts a[key] = a[key] + b[key]คุณสามารถทำเช่นนี้แทนของการเพิ่มข้อผิดพลาดในการเชื่อมทั้งสองรายการ: ขอบคุณสำหรับคำตอบที่เป็นประโยชน์
kevinmicke

1
> ถ้าคุณต้องการเก็บไว้คุณสามารถเรียกมันว่า merge (dict (a), b) โปรดทราบว่าคำสั่งที่ซ้อนกันจะยังคงถูกกลายพันธุ์ copy.deepcopyเพื่อหลีกเลี่ยงนี้ใช้
rcorre

31

นี่เป็นวิธีง่ายๆโดยใช้เครื่องกำเนิดไฟฟ้า:

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

หากคุณต้องการเก็บธีมเครื่องกำเนิดไฟฟ้าไว้คุณสามารถต่อโซ่ (dict1.keys (), dict2.keys ())
andrew cooke

จะไม่ได้รับคีย์ซ้ำ?
jterrace

สิ่งนี้ดูเหมือนจะใช้งานได้อย่างน้อยก็ในชุดข้อมูลของฉัน แต่เนื่องจากฉันไม่เคยเข้าใจผลผลิตและเครื่องกำเนิดไฟฟ้าเลยฉันค่อนข้างหลงว่าทำไม แต่ฉันจะพยายามให้หนักขึ้นอีกหน่อยอาจมีประโยชน์!
fdhex

ใช่มันจะได้รับคีย์ที่ซ้ำกัน คุณยังคงต้องห่อเป็นชุดขออภัย
andrew cooke

2
ฉันพบว่าสิ่งนี้เป็นประโยชน์อย่างยิ่ง แต่สิ่งที่ดีที่สุดคือการปล่อยให้ฟังก์ชันแก้ปัญหาความขัดแย้งเป็นพารามิเตอร์
mentatkgs

25

ปัญหาหนึ่งของคำถามนี้คือค่าของ 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ที่ฉันต้องจัดการกับชุดข้อมูลย่อยที่เป็นไปได้เท่านั้น ดังนั้นฉันจึงไม่สนใจสิ่งที่เพิ่มขึ้นและวัตถุอื่น ๆ สำหรับฉันหมายถึงตรรกะการผสานที่สมเหตุสมผล

  • แทนที่สเกลาร์
  • ต่อท้ายรายการ
  • ผสานคำสั่งโดยเพิ่มคีย์ที่หายไปและอัปเดตคีย์ที่มีอยู่

ทุกสิ่งทุกอย่างและสิ่งที่ไม่คาดคิดส่งผลให้เกิดข้อผิดพลาด


1
น่าอัศจรรย์ ทำงานได้ดีกับ json ทิ้งด้วย เพิ่งลบการจัดการข้อผิดพลาด (เป็นคนขี้เกียจสามารถทำสิ่งที่เหมาะสมสำหรับ json ฉันแน่ใจ)
dgBP

3
ลำดับ "isinstance" สามารถแทนที่ได้โดยisinstance(a, (str, unicode, int, long, float))ไม่ใช้หรือไม่
simahawk

12

พจนานุกรมของพจนานุกรมรวมเข้าด้วยกัน

เนื่องจากนี่เป็นคำถามที่ยอมรับได้ (แม้ว่าจะไม่ใช่เรื่องทั่วไปบางประการ) ฉันจึงให้วิธี Pythonic ที่เป็นที่ยอมรับในการแก้ปัญหานี้

กรณีที่ง่ายที่สุด: "leaves are nested dicts that end in empty dicts":

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

นี่เป็นกรณีที่ง่ายที่สุดสำหรับการเรียกซ้ำและฉันขอแนะนำสองวิธีที่ไร้เดียงสา:

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

Complex Case: "leaves เป็นประเภทอื่น:"

ดังนั้นหากพวกเขาลงท้ายด้วยการเขียนตามคำบอกมันเป็นกรณีง่ายๆของการรวมจุดสิ้นสุดที่ว่างเปล่า ถ้าไม่เป็นเช่นนั้นก็ไม่สำคัญ ถ้าสตริงคุณจะรวมเข้าด้วยกันได้อย่างไร? สามารถอัปเดตชุดได้ในทำนองเดียวกันดังนั้นเราจึงสามารถให้การรักษานั้นได้ แต่เราสูญเสียลำดับที่รวมเข้าด้วยกัน คำสั่งมีความสำคัญหรือไม่?

ดังนั้นแทนข้อมูลเพิ่มเติมวิธีการที่ง่ายที่สุดคือให้การปรับปรุงมาตรฐานหากทั้งสองค่าไม่ได้เป็นคำสั่งกล่าวคือค่าของ dict ที่สองจะเขียนทับค่าแรกแม้ว่าค่าของ dict ที่สองจะไม่มีและค่าแรกคือ a เขียนตามข้อมูลมากมาย

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}to 'A'.)


10

อ้างอิงจาก @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

1
ขอบคุณสิ่งนี้มีประโยชน์มาก ฉันมีรายการของคำสั่งในโครงสร้างของฉันตลอดเวลาโซลูชันอื่น ๆ ไม่สามารถรวมสิ่งนี้ได้อย่างถูกต้อง
SHernandez

7

ขั้นตอนการเรียกซ้ำอย่างง่ายนี้จะรวมพจนานุกรมหนึ่งเข้าด้วยกันในขณะที่แทนที่คีย์ที่ขัดแย้งกัน:

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

7

อ้างอิงจากคำตอบของ @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]

ใช้งานง่ายและสมมาตร +1 สำหรับการจัดการรายการ :)
vdwees

6

หากคุณมีระดับพจนานุกรมที่ไม่รู้จักฉันขอแนะนำให้ใช้ฟังก์ชันเรียกซ้ำ:

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

ภาพรวม

วิธีการต่อไปนี้แบ่งย่อยปัญหาของการรวมคำสั่งอย่างลึกซึ้งออกเป็น:

  1. ฟังก์ชันการผสานตื้นแบบกำหนดพารามิเตอร์merge(f)(a,b)ที่ใช้ฟังก์ชันfเพื่อผสานสองคำสั่งaและb

  2. ฟังก์ชันการควบรวมแบบเรียกซ้ำfที่จะใช้ร่วมกับmerge


การดำเนินงาน

ฟังก์ชันสำหรับการรวมคำสั่งสองตัว (ไม่ซ้อนกัน) สามารถเขียนได้หลายวิธี ส่วนตัวชอบ

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)

ตัวอย่าง

ในการผสานสองคำสั่งที่ซ้อนกันให้ใช้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)]

4

ในกรณีที่มีคนต้องการแนวทางอื่นสำหรับปัญหานี้นี่คือวิธีแก้ปัญหาของฉัน

คุณธรรม : สั้นเปิดเผยและใช้งานได้อย่างมีสไตล์ (เรียกซ้ำไม่มีการกลายพันธุ์)

ข้อเสียเปรียบที่อาจเกิดขึ้น : นี่อาจไม่ใช่การรวมที่คุณกำลังมองหา ปรึกษา 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
        }

คำตอบที่น่าสนใจมากขอบคุณสำหรับการแบ่งปัน คุณใช้ไวยากรณ์อะไรหลังจากคำสั่ง return? ฉันไม่คุ้นเคยกับมัน
dev_does_software

4

คุณสามารถลองผสานขั้นตอน


การติดตั้ง

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

สำหรับรายการตัวเลือกทั้งหมดโปรดดูเอกสาร !


3

มีปัญหาเล็กน้อยกับคำตอบของ andrew cookes: ในบางกรณีจะแก้ไขอาร์กิวเมนต์ที่สอง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

ยังคง นี้เห็นได้ชัดไม่บัญชีสำหรับ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, {})

ทำไมไม่deepcopyแทนที่จะเป็นclone_dict?
Armando PérezMarqués

1
เพราะงูหลาม stdlib นั้นใหญ่และอลังการมาก! ฉันไม่รู้ว่าสิ่งนี้มีอยู่จริง - บวกกับรหัส :-)
andsens

2

ฟังก์ชันเวอร์ชันนี้จะอธิบายถึงจำนวนพจนานุกรม N และพจนานุกรมเท่านั้น - ไม่สามารถส่งผ่านพารามิเตอร์ที่ไม่เหมาะสมได้หรือจะเพิ่ม TypeError การผสานรวมบัญชีสำหรับความขัดแย้งของคีย์และแทนที่จะเขียนทับข้อมูลจากพจนานุกรมลงไปในห่วงโซ่การผสานจะสร้างชุดของค่าและผนวกเข้ากับสิ่งนั้น ไม่มีข้อมูลสูญหาย

อาจไม่ได้มีประสิทธิภาพมากที่สุดในหน้านี้ แต่เป็นข้อมูลที่ละเอียดที่สุดและคุณจะไม่สูญเสียข้อมูลใด ๆ เมื่อคุณรวม 2 ถึง N Dict

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

เนื่องจากการใช้งานชุดคำสั่งนั้นสนับสนุนการตั้งค่าฉันจึงสามารถทำให้คำตอบของ 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])))

ความพยายามใด ๆ ที่จะรวมคำสั่งเข้ากับ non dict (ในทางเทคนิคอ็อบเจ็กต์ที่มีเมธอด 'คีย์' และอ็อบเจ็กต์ที่ไม่มีเมธอด 'คีย์') จะทำให้ AttributeError เพิ่มขึ้น ซึ่งรวมถึงการเรียกใช้ฟังก์ชันครั้งแรกและการเรียกซ้ำ นี่คือสิ่งที่ฉันต้องการดังนั้นฉันจึงทิ้งมันไว้ คุณสามารถจับ AttributeErrors ที่เกิดจากการเรียกแบบเรียกซ้ำได้อย่างง่ายดายจากนั้นให้ค่าใด ๆ ที่คุณต้องการ


2

สั้น n หวาน:

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]

วิธีนี้ใช้งานได้เหมือน (และต่อยอด) dict.updateวิธีการของ Python มันจะส่งคืนNone(คุณสามารถเพิ่มได้ตลอดเวลาreturn dหากต้องการ) ในขณะที่อัปเดตคำสั่งdในสถานที่ คีย์ในvจะเขียนทับคีย์ที่มีอยู่d(ไม่พยายามตีความเนื้อหาของ dict)

นอกจากนี้ยังสามารถใช้กับการแมปอื่น ๆ ("dict-like")


1

รหัสจะขึ้นอยู่กับกฎของคุณในการแก้ไขข้อขัดแย้งในการผสานแน่นอน นี่คือเวอร์ชันที่สามารถรับอาร์กิวเมนต์ได้ตามจำนวนที่กำหนดและรวมเข้าด้วยกันซ้ำ ๆ ไปยังระดับความลึกโดยพลการโดยไม่ต้องใช้การกลายพันธุ์ของวัตถุใด ๆ ใช้กฎต่อไปนี้เพื่อแก้ไขข้อขัดแย้งในการผสาน:

  • พจนานุกรมมีความสำคัญเหนือกว่าค่าที่ไม่ใช่คำสั่ง ( {"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  

1

ฉันมีพจนานุกรมสองเล่ม ( aและb) ซึ่งแต่ละพจนานุกรมสามารถมีพจนานุกรมซ้อนกันกี่เล่มก็ได้ ผมอยากจะซ้ำผสานพวกเขาด้วยการมีความสำคัญมากกว่า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

ฉันมีวิธีแก้ปัญหาซ้ำ ๆ - ทำงานได้ดีขึ้นมากกับคำสั่งขนาดใหญ่และจำนวนมาก (เช่น 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

โปรดทราบว่าสิ่งนี้จะใช้ค่าใน 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

ฉันได้ทดสอบโดยใช้คำสั่งประมาณ 1200 - วิธีนี้ใช้เวลา 0.4 วินาทีในขณะที่การแก้ปัญหาแบบวนซ้ำใช้เวลาประมาณ 2.5 วินาที


0

นี้จะช่วยในการผสานทุกรายการจากdict2เข้า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]

โปรดทดสอบและบอกเราว่านี่คือสิ่งที่คุณต้องการหรือไม่

แก้ไข:

โซลูชันที่กล่าวถึงข้างต้นผสานเพียงระดับเดียว แต่แก้ไขตัวอย่างที่กำหนดโดย OP ได้อย่างถูกต้อง ในการผสานหลายระดับควรใช้การเรียกซ้ำ


1
เขามีความลึกโดยพลการของการทำรัง
AGF

for k,v in dict2.iteritems(): dict1.setdefault(k,{}).update(v)ที่สามารถนำมาเขียนใหม่เป็นเพียง แต่ตามที่ @agf ชี้ให้เห็นสิ่งนี้ไม่ได้รวมคำสั่งที่ซ้อนกัน
Shawn Chin

@agf: ถูกต้องดังนั้นดูเหมือนว่า OP ต้องการโซลูชันที่ใช้การเกิดซ้ำ ต้องขอบคุณที่พจนานุกรมความจริงไม่แน่นอนจึงควรทำค่อนข้างง่าย แต่ฉันคิดว่าคำถามไม่ได้เจาะจงเพียงพอที่จะบอกว่าควรเกิดอะไรขึ้นเมื่อเราหาสถานที่ที่มีระดับความลึกต่างกัน (เช่นพยายามรวมเข้า{'a':'b'}ด้วยกัน{'a':{'c':'d'})
Tadeck

0

ฉันได้ทดสอบโซลูชันของคุณแล้วและตัดสินใจใช้วิธีนี้ในโครงการของฉัน:

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

วิธีที่ง่ายที่สุดที่ฉันคิดได้คือ:

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

ฉันมีวิธีอื่นที่แตกต่างกันเล็กน้อยที่นี่:

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

โดยค่าเริ่มต้นจะแก้ไขความขัดแย้งที่สนับสนุนค่าจากคำสั่งที่สอง แต่คุณสามารถลบล้างสิ่งนี้ได้อย่างง่ายดายด้วยเวทมนตร์บางอย่างคุณอาจสามารถโยนข้อยกเว้นออกไปได้ :)


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

เดี๋ยวก่อนฉันก็มีปัญหาเดียวกัน แต่ฉันมีวิธีแก้ปัญหาและฉันจะโพสต์ไว้ที่นี่ในกรณีที่มันมีประโยชน์สำหรับคนอื่น ๆ โดยทั่วไปแล้วการรวมพจนานุกรมที่ซ้อนกันและการเพิ่มค่าสำหรับฉันฉันต้องคำนวณความน่าจะเป็นบางอย่างดังนั้นสิ่งนี้ หนึ่งทำงานได้ดี:

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

ผสาน = {'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
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}}

แม้ว่ารหัสนี้อาจตอบคำถาม แต่การให้บริบทเพิ่มเติมเกี่ยวกับสาเหตุและ / หรือวิธีที่รหัสนี้ตอบคำถามช่วยเพิ่มมูลค่าในระยะยาว
xiawi

ฉันคิดว่านี่เป็นการใช้งานทั่วไปของการรวมพจนานุกรมที่ซ้อนกันอย่างน้อยหนึ่งรายการโดยคำนึงถึงประเภทของวัตถุที่จะเข้าร่วม
Dorcioman


0

ฟังก์ชันต่อไปนี้จะรวม 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

0

และอีกรูปแบบเล็กน้อย:

นี่คือฟังก์ชั่นการอัปเดตระดับลึกที่ใช้ชุด 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}

0

อีกคำตอบได้ไง!? สิ่งนี้ยังหลีกเลี่ยงการกลายพันธุ์ / ผลข้างเคียง:

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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.