Sözlük sözlükleri nasıl birleştirilir?


129

Birden çok sözlüğü birleştirmem gerekiyor, işte sahip olduğum şeyler:

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

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

ile A B CveD ağacın yapraklarını olmak gibi{"info1":"value", "info2":"value2"}

Bilinmeyen bir sözlük seviyesi (derinliği) var, olabilir {2:{"c":{"z":{"y":{C}}}}}

Benim durumumda, düğümlerin dokümanlar olduğu ve dosya olarak bırakıldığı bir dizin / dosya yapısını temsil eder.

Şunları elde etmek için onları birleştirmek istiyorum:

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

Python ile bunu nasıl kolayca yapabileceğimi bilmiyorum.


Keyfi sözlük derinliğiniz için ne istiyorsunuz? ySeviyeye kadar düzleşmek mi istiyorsun cyoksa ne? Örneğiniz eksik.
agf

NestedDict sınıfımı buradan kontrol edin: stackoverflow.com/a/16296144/2334951 Birleştirme ve daha fazlası gibi iç içe geçmiş sözlük yapılarını yönetir .
SzieberthAdam

3
Çözüm arayan herkese bir uyarı: Bu soru yalnızca iç içe yerleştirilmiş sözler hakkındadır. Cevapların çoğu, yapı içindeki daha karmaşık diktat listeleri durumunu düzgün bir şekilde ele almıyor. Buna ihtiyacınız varsa aşağıdaki @Osiloke cevabını deneyin: stackoverflow.com/a/25270947/1431660
SHernandez

Ayrıca bakınız: python dpath merge
dreftymac

Yanıtlar:


143

bu aslında oldukça zordur - özellikle, yinelenen ancak tutarlı girişleri doğru bir şekilde kabul ederken, işler tutarsız olduğunda yararlı bir hata mesajı istiyorsanız (burada başka hiçbir yanıtın yapmadığı bir şey ...)

Çok sayıda girişiniz olmadığını varsayarsak, özyinelemeli bir işlev en kolayıdır:

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

bu mutasyonlara dikkat edin a- içeriği beklenir a(bu da döndürülür). Eğer tutmak aistiyorsan, beğenebilirsin merge(dict(a), b).

agf, (aşağıda) ikiden fazla dikte sahip olabileceğinizi belirtti, bu durumda kullanabilirsiniz:

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

her şeyin dict1'e ekleneceği yer.

[not - ilk argümanı değiştirmek için ilk cevabımı düzenledim; bu, "azaltmanın" açıklamasını kolaylaştırır]

python 3'te ps, ayrıca ihtiyacınız olacak from functools import reduce


1
Daha sonra bunu iki yerine reducerastgele bir sayıda dicts ile çalışmak için bir veya eşdeğer döngünün içine yapıştırabilirsiniz . Bununla birlikte, onun da istediğini yaptığından emin değilim (o net değildi), Sonunda 2: {'c': {'z': {'y': {'info1': 'value', 'info2': 'value2'}}}, 'b': {'info1': 'value', 'info2': 'value2'}}ikinci örneğini elde edersiniz , onu isteyip istemediğinden zve ydüzleşip düzeltilmediğinden emin değilim ?
agf

1
bunlar dizin yapılarıdır, bu yüzden herhangi bir şeyin düzleştirilmesini istediğini sanmıyorum? oh, üzgünüm, "birden çok sözlük" kaçırdım. evet, azaltmak iyi olur. bunu ekleyecektir.
andrew cooke

Bu tam olarak istediğimi yapıyor! Özür dilerim, yeterince net değildim ... Python ile iyi olduğumu düşündüm, öyle görünüyor: - / İç içe girmiş diktlerden dolayı özyinelemeli bir işleve ihtiyacım vardı, bu çalışıyor ve anlayabiliyorum :) Yapmıyorum azaltma ile çalışmasını sağlayabilecek gibi görünüyor ...
fdhex

2
Dicts altında nihai iç içe seviyesi olarak listelere olan herkes için, bunun yerine iki listeyi birleştirmek için hatayı yükseltilmesi yapabilirsiniz: a[key] = a[key] + b[key]. Faydalı cevap için teşekkürler.
kevinmicke

1
> bir tutmak istiyorsanız, onu birleştirme gibi çağırabilirsiniz (dict (a), b) İç içe yerleştirilmiş diktelerin yine de değişime uğrayacağını unutmayın. Bundan kaçınmak için kullanın copy.deepcopy.
2019

31

Jeneratörleri kullanarak bunu yapmanın kolay bir yolu:

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

Bu şunu yazdırır:

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

Oluşturucu temasını korumak istiyorsanız, zincirleme yapabilirsiniz (dict1.keys (), dict2.keys ())
andrew cooke

Yinelenen anahtarlar olmaz mı?
jterrace

Bu, en azından benim veri setimde işi yapıyor gibi görünüyor, ancak verimi ve jeneratörleri asla iyi anlamadığım için, nedenini hemen hemen kaybettim, ancak biraz daha fazla deneyeceğim, yararlı olabilir!
fdhex

ah, evet, yinelenen anahtarlar alırdı. Yine de bir sete sarmanız gerekiyor, üzgünüm.
andrew cooke

2
Bunu özellikle yararlı buldum. Ancak işin güzel tarafı, fonksiyonun çatışmaları bir parametre olarak çözmesine izin vermektir.
mentatkgs

25

Bu sorunun bir sorunu, diktenin değerlerinin keyfi olarak karmaşık veri parçaları olabilmesidir. Bunlara ve diğer yanıtlara dayanarak şu kodu buldum:

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

Kullanım durumum, yalnızca olası veri türlerinin bir alt kümesiyle uğraşmam gereken YAML dosyalarını birleştirmek . Bu nedenle tuple'ları ve diğer nesneleri görmezden gelebilirim. Benim için mantıklı bir birleştirme mantığı demek

  • skalerleri değiştir
  • listeleri ekle
  • eksik anahtarları ekleyerek ve mevcut anahtarları güncelleyerek dikleri birleştirin

Diğer her şey ve öngörülemeyenler bir hatayla sonuçlanır.


1
Fantastik. Json çöplüklerinde de iyi çalışıyor. Hata işlemeyi kaldırdım. (Tembel olmak, eminim json için uygun olanları yapabilirim)
dgBP

3
"isinstance" dizisi w / isinstance(a, (str, unicode, int, long, float))isnt 'it ile değiştirilebilir ?
simahawk

12

Sözlük sözlükleri birleşiyor

Bu kanonik bir soru olduğu için (bazı genel olmayanlıklara rağmen) bu sorunu çözmek için kanonik Pythonic yaklaşımı sunuyorum.

En Basit Durum: "Yapraklar, boş diktlerle biten iç içe geçmiş diktlerdir":

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

Bu özyineleme için en basit durumdur ve iki naif yaklaşım tavsiye ederim:

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

Sanırım ikinciyi birinciye tercih edeceğime inanıyorum, ancak ilkinin orijinal halinin kaynağından yeniden inşa edilmesi gerektiğini unutmayın. İşte kullanım:

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

Karmaşık Durum: "yapraklar başka türdendir:"

Öyleyse, diktlerle bitiyorlarsa, bu basit bir durum, sondaki boş sözlerin birleştirilmesidir. Değilse, o kadar da önemsiz değil. Dizeler varsa, onları nasıl birleştirirsiniz? Kümeler de benzer şekilde güncellenebilir, bu yüzden bu muameleyi yapabiliriz, ancak birleştirildikleri sırayı kaybediyoruz. Yani düzen önemli mi?

Bu nedenle, daha fazla bilgi yerine, en basit yaklaşım, eğer her iki değer de dikte değilse, onlara standart güncelleme işlemini vermek olacaktır: yani, ikinci diktenin değeri, ikinci diktenin değeri Yok ve ilkinin değeri bir olsa bile, birincinin üzerine yazacaktır. çok fazla bilgi ile dikte edin.

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

Ve şimdi

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

İadeler

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

Orijinal soruya başvuru:

Harflerin etrafındaki küme parantezlerini kaldırmak ve bunun okunaklı Python olması için onları tek tırnak içine koymak zorunda kaldım (aksi takdirde Python 2.7+ 'de sabit değerler olacaktı) ve eksik bir parantez ekledim:

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

ve rec_merge(dict1, dict2)şimdi şunu döndürür:

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

Orijinal sorunun istenen sonucuyla eşleşen (değiştirdikten sonra, örneğin {A}olarak 'A'.)


10

@Andrew cooke'a göre. Bu sürüm, iç içe geçmiş dikt listelerini işler ve ayrıca değerleri güncelleme seçeneğine de izin verir

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
Teşekkürler, bu çok yardımcı oldu. Yapılarımda her zaman dikt listeleri oluyor, diğer çözümler bunu tam anlamıyla birleştiremiyor.
SHernandez

7

Bu basit yinelemeli prosedür, çakışan anahtarları geçersiz kılarken bir sözlüğü diğeriyle birleştirir:

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

Çıktı:

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

7

@Andrew cooke'un yanıtlarına göre. İç içe geçmiş listeleri daha iyi bir şekilde halleder.

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]

sezgisel ve simetrik. Liste işleme için +1 :)
vdwees

6

Bilinmeyen bir sözlük seviyeniz varsa, o zaman özyinelemeli bir işlev öneririm:

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

genel bakış

Aşağıdaki yaklaşım, diktlerin derin bir şekilde birleşmesi sorununu alt bölümlere ayırır:

  1. İki dikteyi birleştirmek merge(f)(a,b)için bir işlev kullanan parametreli bir sığ birleştirme işlevi vefab

  2. İle fbirlikte kullanılacak özyinelemeli bir birleşme işlevimerge


uygulama

İki (iç içe olmayan) dikti birleştirmek için bir işlev birçok şekilde yazılabilir. Şahsen beğeniyorum

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

Uygun bir özyinelemeli birleştirme işlevini tanımlamanın güzel bir yolu, bağımsız değişkenlerinin türüne bağlı olarak farklı yollar boyunca değerlendiren işlevlerin tanımlanmasına izin veren multipledispatchf kullanmaktır .

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)

Misal

İç içe geçmiş iki dikteyi birleştirmek için merge(f)örneğin şunları kullanın :

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

Notlar:

Bu yaklaşımın avantajları şunlardır:

  • İşlev, her biri tek bir şey yapan daha küçük işlevlerden oluşturulmuştur, bu da kodu akıl yürütmeyi ve test etmeyi kolaylaştırır.

  • Davranış sabit kodlu değildir, ancak gerektiğinde değiştirilip genişletilebilir, bu da kodun yeniden kullanımını iyileştirir (aşağıdaki örneğe bakın).


Özelleştirme

Bazı cevaplar, örneğin diğer (potansiyel olarak iç içe geçmiş) sözler gibi listeleri içeren dikteler olarak kabul edilir. Bu durumda, listeler üzerinde harita istenebilir ve konumlara göre birleştirilebilir. Bu, birleşme işlevine başka bir tanım eklenerek yapılabilir f:

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

4

Birisi bu soruna başka bir yaklaşım daha isterse , işte benim çözümüm.

Erdemler : kısa, açıklayıcı ve stil olarak işlevsel (özyinelemeli, mutasyon yapmaz).

Olası Dezavantaj : Bu, aradığınız birleştirme olmayabilir. Anlambilim için docstring'e başvurun.

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
        }

Çok ilginç cevap, paylaştığınız için teşekkürler. Return ifadesinden sonra hangi sözdizimini kullandınız? Ben aşina değilim.
dev_does_software

4

Birleşmeyi deneyebilirsiniz .


Kurulum

$ pip3 install mergedeep

kullanım

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

Seçeneklerin tam listesi için dokümanlara göz atın !


3

Andrew cookes'in cevabında küçük bir sorun var: Bazı durumlarda b, döndürülen dikteyi değiştirdiğinizde ikinci argümanı değiştirir. Özellikle bu satırdan dolayı:

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

Eğer b[key]bir olduğunu dict, sadece atanacak aolduğu yapılan sonraki değişiklikler anlamına dicthem etkileyecektir ave 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)

Bunu düzeltmek için, satırın bununla değiştirilmesi gerekir:

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

Nerede 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

Hala. Bu tabii ki dikkate almaz list, setve diğer şeyler, ama ben birleştirme çalışırken o tuzaklardan göstermektedir umut dicts.

Ve bütünlük uğruna, işte benim versiyonum, onu birden çok kez geçebilirsiniz 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, {})

Neden deepcopyyerine değil clone_dict?
Armando Pérez Marqués

1
Çünkü python stdlib lanet olası devasa ve muhteşem! Bunun var olduğuna dair hiçbir fikrim yoktu - ayrıca kodlamak için eğlenceli küçük bir şeydi :-)
andsens

2

İşlevin bu sürümü, N sayıda sözlükten ve yalnızca sözlüklerden sorumlu olacaktır - uygun olmayan parametreler geçilemez veya bir TypeError oluşturacaktır. Birleştirmenin kendisi temel çatışmaları hesaba katar ve bir sözlükteki verilerin üzerine birleştirme zincirinin daha aşağısına yazmak yerine, bir dizi değer oluşturur ve buna ekler; hiçbir veri kaybolmaz.

Sayfadaki en etkili olmayabilir, ancak en kapsamlı olanıdır ve 2 ila N diktenizi birleştirdiğinizde hiçbir bilgi kaybetmeyeceksiniz.

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

çıktı: {1: [1, 2], 2: {1: 2, 3: 1}, 4: 4}


2

Dikte görünümleri set işlemlerini desteklediğinden, jterrace'in yanıtını büyük ölçüde basitleştirebildim.

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

Bir dikteyi dikte olmayan bir ile birleştirme girişimleri (teknik olarak, 'anahtarlar' yöntemine sahip bir nesne ve 'anahtarlar' yöntemi olmayan bir nesne) bir Öznitelik Hatası ortaya çıkaracaktır. Bu, hem işleve ilk çağrıyı hem de özyinelemeli çağrıları içerir. Bu tam olarak istediğim şey bu yüzden bıraktım. Özyinelemeli çağrı tarafından atılan bir AttributeErrors'ı kolayca yakalayabilir ve ardından istediğiniz herhangi bir değeri verebilirsiniz.


2

Kısa-n-tatlı:

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]

Bu, Python'un dict.updateyöntemi gibi çalışır (ve üzerine inşa edilmiştir) . Yerinde dikteyi güncelledikçe döner None(isterseniz her zaman ekleyebilirsiniz return d) d. Anahtarlar v, içerideki mevcut anahtarların üzerine yazılır d(diktenin içeriğini yorumlamaya çalışmaz).

Ayrıca diğer ("dikte benzeri") eşlemeler için de çalışacaktır.


1

Elbette kod, birleştirme çakışmalarını çözme kurallarınıza bağlı olacaktır. Burada, herhangi bir nesne mutasyonu kullanmadan, rastgele sayıda argümanı alıp bunları keyfi bir derinlikte özyinelemeli olarak birleştiren bir sürüm var. Birleştirme çakışmalarını çözmek için aşağıdaki kuralları kullanır:

  • sözlükler dikte olmayan değerlere {"foo": {...}}göre önceliklidir ( önceliklidir{"foo": "bar"} )
  • (birleştirme eğer sonradan argümanlar önceki tart önceliklidir {"a": 1}, {"a", 2}ve {"a": 3}sırayla, sonuç olacaktır {"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

Her biri herhangi bir sayıda iç içe geçmiş sözlük içerebilen iki sözlüğüm ( ave b) vardı . Ben yinelemeli ile, bunları birleştirmek istiyordu bönceliklidir a.

İç içe geçmiş sözlükleri ağaç olarak düşünürsek, istediğim şey şuydu:

  • aHer yaprağa giden her yolun içinde btemsil edilmesi için güncellemeka
  • Aşağıdaki ailgili yolda bir yaprak bulunursa alt ağaçlarının üzerine yazmak içinb
    • Tüm byaprak düğümlerinin yaprak olarak kaldığı değişmezliği koruyun .

Mevcut cevaplar benim zevkime göre biraz karışıktı ve rafta bazı detaylar bıraktı. Veri kümem için birim testlerinden geçen aşağıdakileri birlikte hackledim.

  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

Örnek (anlaşılır olması için biçimlendirilmiştir):

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

İçinde btutulması gereken yollar şunlardı:

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

a benzersiz ve çakışmayan yollara sahipti:

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

dolayısıyla birleştirilmiş haritada hala temsil ediliyorlar.


1

Yinelemeli bir çözümüm var - büyük diktlerde ve birçoğunda çok daha iyi çalışıyor (örneğin jsons vb.):

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

her ikisinin de dikt olmaması durumunda, bunun d2'deki değeri d1'i geçersiz kılmak için kullanacağını unutmayın. (python ile aynıdict.update() )

bazı testler:

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

Yaklaşık 1200 diktle test ettim - bu yöntem 0.4 saniye sürdü, yinelemeli çözüm ise ~ 2.5 saniye sürdü.


0

Bu, tüm öğeleri şuradan dict2içine birleştirmeye yardımcı olacaktır 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]

Lütfen test edin ve istediğiniz şeyin bu olup olmadığını bize bildirin.

DÜZENLE:

Yukarıda bahsedilen çözüm sadece bir seviyeyi birleştirir, ancak OP tarafından verilen örneği doğru bir şekilde çözer. Birden çok düzeyi birleştirmek için özyineleme kullanılmalıdır.


1
Keyfi bir yuvalama derinliğine sahip
agf

Bu basitçe olarak yeniden yazılabilir for k,v in dict2.iteritems(): dict1.setdefault(k,{}).update(v). Ancak @agf'ın da belirttiği gibi, bu iç içe yerleştirilmiş diktleri birleştirmiyor.
Shawn Çene

@agf: Doğru, bu yüzden OP'nin yinelemeyi kullanan çözüme ihtiyacı var gibi görünüyor. Sözlüklerin değiştirilebilir olması sayesinde, bunun yapılması oldukça kolay olmalı. Ama soru derinliklerinde farklı düzeylerde yerlere ile gelip zaman ne olacağını (örneğin. Birleştirme çalışırken anlatmak için spesifik yeterli olmadığını düşünüyorum {'a':'b'}ile {'a':{'c':'d'}).
Tadeck

0

Çözümlerinizi test ediyorum ve bunu projemde kullanmaya karar verdim:

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

Fonksiyonları parametre olarak aktarmak, jterrace çözümünü diğer tüm özyinelemeli çözümler gibi davranacak şekilde genişletmenin anahtarıdır.


0

Aklıma gelen en kolay yol:

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

Çıktı:

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

0

Burada biraz farklı bir çözümüm var:

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

Varsayılan olarak, ikinci diktedeki değerler lehine çatışmaları çözer, ancak bunu kolayca geçersiz kılabilirsiniz, bazı büyücülükle istisnaları bile atabilirsiniz. :).


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

hey orada da aynı sorunu yaşadım ama bir çözüm olduğunu düşündüm ve burada yayınlayacağım, başkaları için de yararlı olması durumunda, temelde iç içe geçmiş sözlükleri birleştirip değerleri de ekleyerek, benim için bazı olasılıkları hesaplamam gerekiyordu, bu yüzden bu biri harika çalıştı:

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

yukarıdaki yöntemi kullanarak şunları birleştirebiliriz:

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

ve bu şu şekilde olacaktır: {'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 }}

ayrıca buradaki değişiklikleri de fark edin:

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

birleştirme = {'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} }

kopyalamak için içe aktarmayı da eklemeyi unutmayın:

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

Çıktı:

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

Bu kod soruyu yanıtlayabilirken, bu kodun soruyu neden ve / veya nasıl yanıtladığına ilişkin ek bağlam sağlamak, uzun vadeli değerini artırır.
xiawi

Bunun, sınırlanacak nesnelerin türünü dikkate alarak bir veya daha fazla iç içe geçmiş sözlüğü birleştirmenin genel bir uygulaması olduğunu düşünüyorum
Dorcioman

0

pakete bir göz attoolz

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

verir

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

0

Aşağıdaki işlev, b'yi a ile birleştirir.

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

Ve bir başka küçük varyasyon:

İşte saf python3 set tabanlı derin güncelleme işlevi. İç içe geçmiş sözlükleri, her seferinde bir düzey boyunca döngü yaparak günceller ve her bir sonraki sözlük değeri düzeyini güncellemeye çağırır:

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

Basit bir örnek:

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

Başka bir cevap nasıl olur?!? Bu aynı zamanda mutasyonu / yan etkileri de önler:

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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.