İç içe sözlükleri düzleştir, tuşları sıkıştırma


Yanıtlar:


221

Temel olarak, iç içe bir listeyi düzleştirdiğiniz gibi, dikteyi anahtar / değer ile yinelemek, yeni sözlüğünüz için yeni anahtarlar oluşturmak ve son adımda sözlük oluşturmak için ekstra iş yapmanız yeterlidir.

import collections

def flatten(d, parent_key='', sep='_'):
    items = []
    for k, v in d.items():
        new_key = parent_key + sep + k if parent_key else k
        if isinstance(v, collections.MutableMapping):
            items.extend(flatten(v, new_key, sep=sep).items())
        else:
            items.append((new_key, v))
    return dict(items)

>>> flatten({'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]})
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}

7
Eğer isinstancebir try..exceptblok ile değiştirirseniz, türetilmemiş olsa bile herhangi bir eşleme için çalışır dict.
Björn Pollex

1
collections.MutableMappingDaha genel hale getirmek için test edilecek şekilde değiştirildi . Ancak Python <2.6 için, try..exceptmuhtemelen en iyi seçenektir.
İmran

5
Düzleştirilmiş versiyonda korunmuş boş sözlükler istiyorsanızif isinstance(v, collections.MutableMapping):if v and isinstance(v, collections.MutableMapping):
tarequeh

3
new_key = parent_key + sep + k if parent_key else kAnahtarların her zaman dize olduğunu varsaydığına dikkat edin, aksi takdirde yükselecektir TypeError: cannot concatenate 'str' and [other] objects. Bununla birlikte, sadece kstring ( str(k)) öğesine zorlayarak veya anahtarları bir dize yerine bir tuple içine birleştirerek düzeltebilirsiniz (tuples da dict tuşları olabilir).
Scott H

1
Ve şişirme işlevi burada
mitch

66

Orijinal posterin dikkate alması gereken iki büyük husus vardır:

  1. Keyspace clobbering sorunları var mı? Örneğin, {'a_b':{'c':1}, 'a':{'b_c':2}}sonuçlanır {'a_b_c':???}. Aşağıdaki çözüm, yinelenebilir bir çift döndürerek sorunu ortadan kaldırır.
  2. Performans bir sorunsa, anahtar azaltma işlevi (burada 'katıl' olarak adlandırıyorum) tüm anahtar yoluna erişim gerektiriyor mu yoksa sadece O (1) ağaçtaki her düğümde çalışabilir mi? Söyleyebilmek istiyorsanız joinedKey = '_'.join(*keys), bu size O (N ^ 2) çalışma süresine mal olacaktır. Ancak söylemeye istekli iseniz nextKey = previousKey+'_'+thisKey, bu size O (N) zamanı kazandırır. Aşağıdaki çözüm her ikisini de yapmanıza izin verir (çünkü sadece tüm anahtarları birleştirebilir ve sonra bunları işleyebilirsiniz).

(Performans muhtemelen bir sorun değildir, ancak başka birinin umurunda olması durumunda ikinci noktayı ele alacağım: Bunu uygularken, çok sayıda tehlikeli seçenek var. Bunu tekrar tekrar yaparsanız, verim ve yeniden verim veya dokunan eşdeğeri bir şey varsa Birden fazla düğüm (yanlışlıkla yapılması oldukça kolaydır), O (N) yerine potansiyel olarak O (N ^ 2) çalışması yapıyorsunuz, bunun nedeni belki de ao a_1zaman bir anahtar hesaplamanızdır a_1_i... ao a_1zaman a_1_ii..., ama gerçekten a_1tekrar hesaplamak zorunda kalmamalısınız .. Yeniden hesaplamasanız bile, yeniden elde etmek ('seviye-seviye' yaklaşımı) kadar kötüdür. performansı hakkında düşünmek {1:{1:{1:{1:...(N times)...{1:SOME_LARGE_DICTIONARY_OF_SIZE_N}...}}}})

Aşağıda yazdığım flattenDict(d, join=..., lift=...), birçok amaca uyarlanabilen ve istediğinizi yapabilen bir işlev var . Ne yazık ki yukarıdaki performans cezalarına maruz kalmadan bu fonksiyonun tembel bir versiyonunu yapmak oldukça zordur (chain.from_iterable gibi birçok python yerleşimi aslında verimli değildir, ki bu kodun üç farklı versiyonunun kapsamlı testlerinden sonra Bu).

from collections import Mapping
from itertools import chain
from operator import add

_FLAG_FIRST = object()

def flattenDict(d, join=add, lift=lambda x:x):
    results = []
    def visit(subdict, results, partialKey):
        for k,v in subdict.items():
            newKey = lift(k) if partialKey==_FLAG_FIRST else join(partialKey,lift(k))
            if isinstance(v,Mapping):
                visit(v, results, newKey)
            else:
                results.append((newKey,v))
    visit(d, results, _FLAG_FIRST)
    return results

Neler olup bittiğini daha iyi anlamak için, aşağıda reduce(solda) bilmeyenler için (aksi halde "sol katlama" olarak bilinen) bir diyagram bulunmaktadır . Bazen k0 yerine bir başlangıç ​​değeri ile çizilir (listenin bir parçası değil, işleve geçirilir). İşte Jbizim joinfonksiyonumuz. Her k için ön işlemeden n ile lift(k).

               [k0,k1,...,kN].foldleft(J)
                           /    \
                         ...    kN
                         /
       J(k0,J(k1,J(k2,k3)))
                       /  \
                      /    \
           J(J(k0,k1),k2)   k3
                    /   \
                   /     \
             J(k0,k1)    k2
                 /  \
                /    \
               k0     k1

Bu aslında ile aynıdır functools.reduce, ancak fonksiyonumuz bunu ağacın tüm anahtar yollarına yapar.

>>> reduce(lambda a,b:(a,b), range(5))
((((0, 1), 2), 3), 4)

Gösteri (aksi takdirde doktora yaptığım):

>>> testData = {
        'a':1,
        'b':2,
        'c':{
            'aa':11,
            'bb':22,
            'cc':{
                'aaa':111
            }
        }
    }
from pprint import pprint as pp

>>> pp(dict( flattenDict(testData, lift=lambda x:(x,)) ))
{('a',): 1,
 ('b',): 2,
 ('c', 'aa'): 11,
 ('c', 'bb'): 22,
 ('c', 'cc', 'aaa'): 111}

>>> pp(dict( flattenDict(testData, join=lambda a,b:a+'_'+b) ))
{'a': 1, 'b': 2, 'c_aa': 11, 'c_bb': 22, 'c_cc_aaa': 111}    

>>> pp(dict( (v,k) for k,v in flattenDict(testData, lift=hash, join=lambda a,b:hash((a,b))) ))
{1: 12416037344,
 2: 12544037731,
 11: 5470935132935744593,
 22: 4885734186131977315,
 111: 3461911260025554326}

Verim:

from functools import reduce
def makeEvilDict(n):
    return reduce(lambda acc,x:{x:acc}, [{i:0 for i in range(n)}]+range(n))

import timeit
def time(runnable):
    t0 = timeit.default_timer()
    _ = runnable()
    t1 = timeit.default_timer()
    print('took {:.2f} seconds'.format(t1-t0))

>>> pp(makeEvilDict(8))
{7: {6: {5: {4: {3: {2: {1: {0: {0: 0,
                                 1: 0,
                                 2: 0,
                                 3: 0,
                                 4: 0,
                                 5: 0,
                                 6: 0,
                                 7: 0}}}}}}}}}

import sys
sys.setrecursionlimit(1000000)

forget = lambda a,b:''

>>> time(lambda: dict(flattenDict(makeEvilDict(10000), join=forget)) )
took 0.10 seconds
>>> time(lambda: dict(flattenDict(makeEvilDict(100000), join=forget)) )
[1]    12569 segmentation fault  python

... iç çek, birinin benim hatam olduğunu düşünme ...


[denetleme sorunları nedeniyle önemsiz tarihsel not]

Flatten'in iddia edilen kopyasıyla ilgili olarak, Python'daki listelerin sözlükleri (2 seviye derinlik) sözlüğü :

Bu sorunun çözümü bu açıdan uygulanabilir sorted( sum(flatten(...),[]) ). Ters mümkün değildir: o doğrudur iken değerleri arasında flatten(...)yüksek bir dereceden akümülatörü eşleyerek iddia edilen iki kopya kurtarıldı edilebilir bir anahtarlarını kurtaramazsınız. (değiştir: Ayrıca iddia edilen kopya sahibinin sorusunun tamamen farklı olduğu ortaya çıkıyor, çünkü bu sayfadaki cevaplardan biri genel bir çözüm sunuyor olsa da, sadece 2 seviyeli derinlikteki sözlüklerle ilgileniyor.)


2
Bunun soru ile alakalı olup olmadığından emin değilim. Bu çözüm sözlükler listesindeki bir sözlük öğesini düzleştirmez, yani {'a': [{'aa': 1}, {'ab': 2}]}. FlattenDict işlevi bu duruma uyacak şekilde kolayca değiştirilebilir.
Stewbaca

56

Veya zaten pandalar kullanıyorsanız, bunu şu şekilde yapabilirsiniz json_normalize():

import pandas as pd

d = {'a': 1,
     'c': {'a': 2, 'b': {'x': 5, 'y' : 10}},
     'd': [1, 2, 3]}

df = pd.io.json.json_normalize(d, sep='_')

print(df.to_dict(orient='records')[0])

Çıktı:

{'a': 1, 'c_a': 2, 'c_b_x': 5, 'c_b_y': 10, 'd': [1, 2, 3]}

5
ya da sadece sep argümanı geç :)
Blue Moon

3
Biraz utanç verici listeleri işlemez :)
Roelant

32

Eğer kullanıyorsanız pandasgizli bir fonksiyon vardır pandas.io.json._normalize1 denilen nested_to_recordtam olarak bunu yapar.

from pandas.io.json._normalize import nested_to_record    

flat = nested_to_record(my_dict, sep='_')

1 Panda versiyonlarında 0.24.xve eski kullanımda pandas.io.json.normalize(olmadan _)


2
Benim için işe yarayan buydu from pandas.io.json._normalize import nested_to_record. Daha _önce alt çizgiye ( ) dikkat edin normalize.
Eyal Levin

3
@EyalLevin İyi yakala! Bu değişti 0.25.x, cevabı güncelledim. :)
Aaron N. Brock

29

İşte bir tür "işlevsel", "tek katlı" uygulama. Özyinelemeli ve koşullu bir ifadeye ve dik bir kavrayışa dayanır.

def flatten_dict(dd, separator='_', prefix=''):
    return { prefix + separator + k if prefix else k : v
             for kk, vv in dd.items()
             for k, v in flatten_dict(vv, separator, kk).items()
             } if isinstance(dd, dict) else { prefix : dd }

Ölçek:

In [2]: flatten_dict({'abc':123, 'hgf':{'gh':432, 'yu':433}, 'gfd':902, 'xzxzxz':{"432":{'0b0b0b':231}, "43234":1321}}, '.')
Out[2]: 
{'abc': 123,
 'gfd': 902,
 'hgf.gh': 432,
 'hgf.yu': 433,
 'xzxzxz.432.0b0b0b': 231,
 'xzxzxz.43234': 1321}

1
Bu, genel sözlükler için, özellikle, tuple anahtarlarıyla çalışmaz, örneğin ('hgf',2), test atışlarınızda 2. anahtarın yerineTypeError
alancalvitti

1
@alancalvitti Bu bir dize ya da +işleci destekleyen başka bir şey olduğunu varsayar . Başka bir şey prefix + separator + kiçin nesneleri oluşturmak için uygun işlev çağrısına uyum sağlamanız gerekir .
dividebyzero

1
Tuş anahtarlarıyla ilgili bir başka sorun. Yönteminize göre nasıl genelleme yapılacağını ayrı ayrı yayınladım. Ancak ninjageko örneğini doğru bir şekilde işleyemez:{'a_b':{'c':1}, 'a':{'b_c':2}}
alancalvitti

3
Endişeleniyordum, özyineleme kullanarak hiçbir cevap görmüyordum. Bugünlerde gençliğimizde sorun ne?
Jakov

1
bir dikte dikte listesini iç içe geçmişse, hiçbir şey yapmaz:{'name': 'Steven', 'children': [{'name': 'Jessica', 'children': []}, {'name': 'George', 'children': []}]}
Gergely M

13

Kod:

test = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]}

def parse_dict(init, lkey=''):
    ret = {}
    for rkey,val in init.items():
        key = lkey+rkey
        if isinstance(val, dict):
            ret.update(parse_dict(val, key+'_'))
        else:
            ret[key] = val
    return ret

print(parse_dict(test,''))

Sonuçlar:

$ python test.py
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}

Python sürümünüz için güncelleme python3.2 kullanıyorum.


Büyük olasılıkla lkey='', işlevi çağırmak yerine işlev tanımınızda varsayılan değerini belirtmek istersiniz . Bu konudaki diğer cevaplara bakınız.
Acumenus

7

Nasıl hakkında işlevsel Python3.5 ve ölçülebilir çözümü?

from functools import reduce


def _reducer(items, key, val, pref):
    if isinstance(val, dict):
        return {**items, **flatten(val, pref + key)}
    else:
        return {**items, pref + key: val}

def flatten(d, pref=''):
    return(reduce(
        lambda new_d, kv: _reducer(new_d, *kv, pref), 
        d.items(), 
        {}
    ))

Bu daha da performanslıdır:

def flatten(d, pref=''):
    return(reduce(
        lambda new_d, kv: \
            isinstance(kv[1], dict) and \
            {**new_d, **flatten(kv[1], pref + kv[0])} or \
            {**new_d, pref + kv[0]: kv[1]}, 
        d.items(), 
        {}
    ))

Kullanımda:

my_obj = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y': 10}}, 'd': [1, 2, 3]}

print(flatten(my_obj)) 
# {'d': [1, 2, 3], 'cby': 10, 'cbx': 5, 'ca': 2, 'a': 1}

2
Okunabilir ve çalışan bir çözüme ne dersiniz? ;) Bunu hangi sürümde test ettiniz? Python 3.4.3'te denerken "Sözdizimi hatası" alıyorum. Görünüşe göre "** all" kullanımı yasal değil.
Ingo Fischer

Python 3.5'ten beri çalışıyorum. 3.4 ile çalışmadığını bilmiyordum. Haklısın bu çok okunabilir değil. Cevabı güncelledim. Umarım şimdi daha okunabilir. :)
Rotareti

1
Eklenen eksik içe aktarmayı azalt. Hala kodu anlamak zor ve Guido van Rossum'un kendisinin neden zaten 2005'te lambda, azaltma, filtre ve harita kullanımını reddettiğinin
Ingo Fischer

Katılıyorum. Python gerçekten işlevsel programlama için tasarlanmamıştır . reduceSözlükleri azaltmanız gerektiğinde hala harika olduğunu düşünüyorum . Cevabı güncelledim. Şimdi biraz daha pitonik görünmeli.
Rotareti

7

Bu yalnızca sözlüklerle sınırlı değildir, .items () yöntemini uygulayan her eşleme türüyle sınırlıdır. Bir if koşulundan kaçındığı için daha hızlı istersiniz. Yine de krediler İmran'a gider:

def flatten(d, parent_key=''):
    items = []
    for k, v in d.items():
        try:
            items.extend(flatten(v, '%s%s_' % (parent_key, k)).items())
        except AttributeError:
            items.append(('%s%s' % (parent_key, k), v))
    return dict(items)

2
Eğer dolmayan bir dictama uygulamıyor özel bir haritalama tipi items, sizin fonksiyon sonra ve orada başarısız olur. Bu nedenle, her haritalama türü için değil, yalnızca uygulayanlar için çalışır items().
user6037143

1
@ user6037143 Hiç uygulanmayan bir eşleme türüyle karşılaştınız itemsmı? Birini görmek isterdim.
Trey Hunner

2
@ user6037143, Hayır, öğeler uygulanmadıysa tanımına göre eşleme türü yoktur.
Davoud Taghawi-Nejad

1
@ DavoudTaghawi-Nejad, bunu genel anahtarları işlemek için değiştirebilir misiniz, örneğin dahili olarak düzleştirilmemesi gereken tuples.
alancalvitti

6

Jeneratörler kullanarak My Python 3.3 Çözümü:

def flattenit(pyobj, keystring=''):
   if type(pyobj) is dict:
     if (type(pyobj) is dict):
         keystring = keystring + "_" if keystring else keystring
         for k in pyobj:
             yield from flattenit(pyobj[k], keystring + k)
     elif (type(pyobj) is list):
         for lelm in pyobj:
             yield from flatten(lelm, keystring)
   else:
      yield keystring, pyobj

my_obj = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y': 10}}, 'd': [1, 2, 3]}

#your flattened dictionary object
flattened={k:v for k,v in flattenit(my_obj)}
print(flattened)

# result: {'c_b_y': 10, 'd': [1, 2, 3], 'c_a': 2, 'a': 1, 'c_b_x': 5}

str (tuple dahil) dışında herhangi bir geçerli anahtar türünü işlemek için genişletebilir misiniz? Dize birleştirme yerine, bunları bir demet halinde birleştirin.
alancalvitti

5

Basit ve insan tarafından okunabilir halde tutarak özyineleme kullanmak:

def flatten_dict(dictionary, accumulator=None, parent_key=None, separator="."):
    if accumulator is None:
        accumulator = {}

    for k, v in dictionary.items():
        k = f"{parent_key}{separator}{k}" if parent_key else k
        if isinstance(v, dict):
            flatten_dict(dictionary=v, accumulator=accumulator, parent_key=k)
            continue

        accumulator[k] = v

    return accumulator

Çağrı basit:

new_dict = flatten_dict(dictionary)

veya

new_dict = flatten_dict(dictionary, separator="_")

varsayılan ayırıcıyı değiştirmek istiyorsak.

Biraz arıza:

Fonksiyon ilk olarak çağrıldığında, sadece dictionarydüzleştirmek istediğimizden geçmek denir . accumulatorParametre Daha sonra göreceğimiz destek özyineleme, için burada. Bu nedenle, accumulatoriç içe geçmiş tüm değerleri orijinalden koyacağımız boş bir sözlüğe başlarız dictionary.

if accumulator is None:
    accumulator = {}

Sözlüğün değerlerini yineledikçe, her değer için bir anahtar oluştururuz. parent_keyArgüman olacak Noneo tuşa prepend yani her iç içe sözlük için, bu o işaret anahtarı içermesi olurken, ilk çağrı için.

k = f"{parent_key}{separator}{k}" if parent_key else k

Durumda değer vanahtarı kişaret ettiği bir sözlük olduğu, fonksiyon, iç içe sözlüğü geçen kendisini çağıran accumulator(kendisine yapılan tüm değişiklikler aynı örneğinde yapılır, böylece referans olarak geçirilir olan) ve anahtar kböylece bizim birlikte birleştirilmiş anahtarı oluşturabilir. İfadeye dikkat edin continue. Bir sonraki satırı, ifbloğun dışında atlamak istiyoruz , böylece içiçe sözlük accumulatoralt anahtarda kalmayacakk .

if isinstance(v, dict):
    flatten_dict(dict=v, accumulator=accumulator, parent_key=k)
    continue

Öyleyse, değerin vsözlük olmaması durumunda ne yapacağız ? Sadece değişmeden içine koy accumulator.

accumulator[k] = v

İşimiz bittikten accumulatorsonra, orijinal dictionaryargümana dokunulmadan geri dönüyoruz.

NOT

Bu, yalnızca anahtar dizesi olan sözlüklerde işe yarar. __repr__Yöntemi uygulayan yıkanabilir nesnelerle çalışır , ancak istenmeyen sonuçlar verir.


4

İç içe sözlükleri düzleştirmek için basit bir işlev. Python 3 için, yerini .iteritems()ile.items()

def flatten_dict(init_dict):
    res_dict = {}
    if type(init_dict) is not dict:
        return res_dict

    for k, v in init_dict.iteritems():
        if type(v) == dict:
            res_dict.update(flatten_dict(v))
        else:
            res_dict[k] = v

    return res_dict

Fikir / gereksinim şöyleydi: Üst anahtar bulundurmadan düz sözlükler alın.

Kullanım örneği:

dd = {'a': 3, 
      'b': {'c': 4, 'd': 5}, 
      'e': {'f': 
                 {'g': 1, 'h': 2}
           }, 
      'i': 9,
     }

flatten_dict(dd)

>> {'a': 3, 'c': 4, 'd': 5, 'g': 1, 'h': 2, 'i': 9}

Üst anahtarları tutmak da basittir.


3

Bu hem imran hem de ralu'nun cevabına benzer. Bir jeneratör kullanmaz, bunun yerine bir kapakla özyineleme kullanır:

def flatten_dict(d, separator='_'):
  final = {}
  def _flatten_dict(obj, parent_keys=[]):
    for k, v in obj.iteritems():
      if isinstance(v, dict):
        _flatten_dict(v, parent_keys + [k])
      else:
        key = separator.join(parent_keys + [k])
        final[key] = v
  _flatten_dict(d)
  return final

>>> print flatten_dict({'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]})
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}

" Kapanış " terimini kullanmanın burada doğru olup olmadığından emin değilim , çünkü işlev _flatten_dictasla döndürülmez ve döndürülmesi de beklenmez. Bunun yerine bir alt işlev veya kapalı bir işlev olarak da adlandırılabilir .
Acumenus

3

Davoud'un çözümü çok güzel ama iç içe dikte aynı zamanda dikte listeleri içerdiğinde tatmin edici sonuçlar vermiyor, ancak kodu bu durum için uyarlandı:

def flatten_dict(d):
    items = []
    for k, v in d.items():
        try:
            if (type(v)==type([])): 
                for l in v: items.extend(flatten_dict(l).items())
            else: 
                items.extend(flatten_dict(v).items())
        except AttributeError:
            items.append((k, v))
    return dict(items)

Öğesinin type([])her öğesi için bir işlev çağrısından kaçınmanın sonucunu önbelleğe alabilirsiniz dict.
bfontaine

2
Lütfen isinstance(v, list)bunun yerine kullanın
Druska

2

Yukarıdaki cevaplar gerçekten işe yarıyor. Yazdığım düz olmayan fonksiyonu ekleyeceğimi düşündüm:

def unflatten(d):
    ud = {}
    for k, v in d.items():
        context = ud
        for sub_key in k.split('_')[:-1]:
            if sub_key not in context:
                context[sub_key] = {}
            context = context[sub_key]
        context[k.split('_')[-1]] = v
    return ud

Not: Bu, düzleştirilmiş meslektaşları gibi tuşlarda zaten bulunan '_' ifadesini içermez.


2

İşte zarif, yerinde değiştirme için bir algoritma. Python 2.7 ve Python 3.5 ile test edilmiştir. Nokta karakterini ayırıcı olarak kullanma.

def flatten_json(json):
    if type(json) == dict:
        for k, v in list(json.items()):
            if type(v) == dict:
                flatten_json(v)
                json.pop(k)
                for k2, v2 in v.items():
                    json[k+"."+k2] = v2

Misal:

d = {'a': {'b': 'c'}}                   
flatten_json(d)
print(d)
unflatten_json(d)
print(d)

Çıktı:

{'a.b': 'c'}
{'a': {'b': 'c'}}

Eşleme fonksiyonu ile birlikte bu kodu burada yayınladım unflatten_json.


2

Eğer iç içe sözlük düz ve tüm benzersiz anahtarlar listesi istiyorsanız o zaman çözüm İşte:

def flat_dict_return_unique_key(data, unique_keys=set()):
    if isinstance(data, dict):
        [unique_keys.add(i) for i in data.keys()]
        for each_v in data.values():
            if isinstance(each_v, dict):
                flat_dict_return_unique_key(each_v, unique_keys)
    return list(set(unique_keys))

2
def flatten(unflattened_dict, separator='_'):
    flattened_dict = {}

    for k, v in unflattened_dict.items():
        if isinstance(v, dict):
            sub_flattened_dict = flatten(v, separator)
            for k2, v2 in sub_flattened_dict.items():
                flattened_dict[k + separator + k2] = v2
        else:
            flattened_dict[k] = v

    return flattened_dict

2
def flatten_nested_dict(_dict, _str=''):
    '''
    recursive function to flatten a nested dictionary json
    '''
    ret_dict = {}
    for k, v in _dict.items():
        if isinstance(v, dict):
            ret_dict.update(flatten_nested_dict(v, _str = '_'.join([_str, k]).strip('_')))
        elif isinstance(v, list):
            for index, item in enumerate(v):
                if isinstance(item, dict):
                    ret_dict.update(flatten_nested_dict(item,  _str= '_'.join([_str, k, str(index)]).strip('_')))
                else:
                    ret_dict['_'.join([_str, k, str(index)]).strip('_')] = item
        else:
            ret_dict['_'.join([_str, k]).strip('_')] = v
    return ret_dict

bu, iç içe geçmişimizin içindeki listelerle çalışır, ancak özel bir ayırıcı seçeneği yoktur
Nikhil VJ

2

Anahtarları otomatik olarak düzleştirmek için UserDict bir alt sınıf düşünüyordum.

class FlatDict(UserDict):
    def __init__(self, *args, separator='.', **kwargs):
        self.separator = separator
        super().__init__(*args, **kwargs)

    def __setitem__(self, key, value):
        if isinstance(value, dict):
            for k1, v1 in FlatDict(value, separator=self.separator).items():
                super().__setitem__(f"{key}{self.separator}{k1}", v1)
        else:
            super().__setitem__(key, value)

Keys Anahtarların anında eklenebilmesinin veya standart diksiyon örneğinin kullanılmasıyla sürpriz yapılmasının avantajları:

>>> fd = FlatDict(
...    {
...        'person': {
...            'sexe': 'male', 
...            'name': {
...                'first': 'jacques',
...                'last': 'dupond'
...            }
...        }
...    }
... )
>>> fd
{'person.sexe': 'male', 'person.name.first': 'jacques', 'person.name.last': 'dupond'}
>>> fd['person'] = {'name': {'nickname': 'Bob'}}
>>> fd
{'person.sexe': 'male', 'person.name.first': 'jacques', 'person.name.last': 'dupond', 'person.name.nickname': 'Bob'}
>>> fd['person.name'] = {'civility': 'Dr'}
>>> fd
{'person.sexe': 'male', 'person.name.first': 'jacques', 'person.name.last': 'dupond', 'person.name.nickname': 'Bob', 'person.name.civility': 'Dr'}

1
Fd ['person ”e atamak, ancak mevcut değerini korumak oldukça şaşırtıcı. Düzenli zorlamalar bu şekilde çalışmaz.
tbm

1

Jeneratörleri kullanma:

def flat_dic_helper(prepand,d):
    if len(prepand) > 0:
        prepand = prepand + "_"
    for k in d:
        i=d[k]
        if type(i).__name__=='dict':
            r = flat_dic_helper(prepand+k,i)
            for j in r:
                yield j
        else:
            yield (prepand+k,i)

def flat_dic(d): return dict(flat_dic_helper("",d))

d={'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]}
print(flat_dic(d))


>> {'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}

2
type(i).__name__=='dict'ile değiştirilebilir type(i) is dictveya belki de daha iyisi olabilir isinstance(d, dict)(veya Mapping/ MutableMapping).
Cristian Ciupitu

1

Basit iç içe geçmiş liste benzeri özyinelemede dict.popitem () kullanımı:

def flatten(d):
    if d == {}:
        return d
    else:
        k,v = d.popitem()
        if (dict != type(v)):
            return {k:v, **flatten(d)}
        else:
            flat_kv = flatten(v)
            for k1 in list(flat_kv.keys()):
                flat_kv[k + '_' + k1] = flat_kv[k1]
                del flat_kv[k1]
            return {**flat_kv, **flatten(d)}

1

Tam olarak OP'nin sorduğu gibi değil, ancak birçok kişi buraya, anahtar değer json nesnelerini ve dizileri ve dizilerin içindeki json nesnelerini iç içe yerleştirmiş olabilecek gerçek dünya iç içe JSON verilerini düzleştirmenin yollarını arıyor. JSON tuples içermez, bu yüzden bunlardan korkmak zorunda değiliz.

Listenin-içerme bir uygulama buldum @roneo tarafından yorumun için @Imran tarafından gönderildi cevap :

https://github.com/ScriptSmith/socialreaper/blob/master/socialreaper/tools.py#L8

import collections
def flatten(dictionary, parent_key=False, separator='.'):
    """
    Turn a nested dictionary into a flattened dictionary
    :param dictionary: The dictionary to flatten
    :param parent_key: The string to prepend to dictionary's keys
    :param separator: The string used to separate flattened keys
    :return: A flattened dictionary
    """

    items = []
    for key, value in dictionary.items():
        new_key = str(parent_key) + separator + key if parent_key else key
        if isinstance(value, collections.MutableMapping):
            items.extend(flatten(value, new_key, separator).items())
        elif isinstance(value, list):
            for k, v in enumerate(value):
                items.extend(flatten({str(k): v}, new_key).items())
        else:
            items.append((new_key, value))
    return dict(items)

Dene:

flatten({'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3] })

>> {'a': 1, 'c.a': 2, 'c.b.x': 5, 'c.b.y': 10, 'd.0': 1, 'd.1': 2, 'd.2': 3}

Yapmam gereken işi yapan ve bu konuda karmaşık bir json atıyorum ve bunu benim için düzleştiriyor.

Https://github.com/ScriptSmith'e yapılan tüm krediler .


1

Aslında son zamanlarda bu tür bir şeyle başa çıkmak için cherrypicker adlı bir paket yazdım çünkü sık sık yapmak zorunda kaldım!

Aşağıdaki kod size tam olarak ne peşinde olduğunu düşünüyorum:

from cherrypicker import CherryPicker

dct = {
    'a': 1,
    'c': {
        'a': 2,
        'b': {
            'x': 5,
            'y' : 10
        }
    },
    'd': [1, 2, 3]
}

picker = CherryPicker(dct)
picker.flatten().get()

Paketi aşağıdakilerle kurabilirsiniz:

pip install cherrypicker

... ve https://cherrypicker.readthedocs.io adresinde daha fazla doküman ve rehberlik var .

Diğer yöntemler daha hızlı olabilir, ancak bu paketin önceliği bu tür görevleri kolaylaştırmaktır . Düzleştirmek için geniş bir nesne listeniz varsa, CherryPicker'a işleri hızlandırmak için paralel işleme kullanmasını da söyleyebilirsiniz.


Alternatif yaklaşımı seviyorum.
Gergely M

0

Erişim dictnesnelerini her zaman üzerinden tercih ederim .items(), bu yüzden dikte etmek için aşağıdaki özyinelemeli jeneratörü kullanıyorum flat_items(d). dictTekrar sahip olmak istiyorsanız , sadece şu şekilde sarın:flat = dict(flat_items(d))

def flat_items(d, key_separator='.'):
    """
    Flattens the dictionary containing other dictionaries like here: /programming/6027558/flatten-nested-python-dictionaries-compressing-keys

    >>> example = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]}
    >>> flat = dict(flat_items(example, key_separator='_'))
    >>> assert flat['c_b_y'] == 10
    """
    for k, v in d.items():
        if type(v) is dict:
            for k1, v1 in flat_items(v, key_separator=key_separator):
                yield key_separator.join((k, k1)), v1
        else:
            yield k, v

0

Bu düzleştirilmiş iç içe sözlüklerin varyasyonu, tuşları max_level ve özel redüktör ile sıkıştırma .

  def flatten(d, max_level=None, reducer='tuple'):
      if reducer == 'tuple':
          reducer_seed = tuple()
          reducer_func = lambda x, y: (*x, y)
      else:
          raise ValueError(f'Unknown reducer: {reducer}')

      def impl(d, pref, level):
        return reduce(
            lambda new_d, kv:
                (max_level is None or level < max_level)
                and isinstance(kv[1], dict)
                and {**new_d, **impl(kv[1], reducer_func(pref, kv[0]), level + 1)}
                or {**new_d, reducer_func(pref, kv[0]): kv[1]},
                d.items(),
            {}
        )

      return impl(d, reducer_seed, 0)

0

Özyinelemeli işlevlere aldırmazsanız, işte bir çözüm. Ayrıca bir dışlama dahil etme özgürlüğünü de aldım , korumak istediğiniz bir veya daha fazla değer olması durumunda parametresi .

Kod:

def flatten_dict(dictionary, exclude = [], delimiter ='_'):
    flat_dict = dict()
    for key, value in dictionary.items():
        if isinstance(value, dict) and key not in exclude:
            flatten_value_dict = flatten_dict(value, exclude, delimiter)
            for k, v in flatten_value_dict.items():
                flat_dict[f"{key}{delimiter}{k}"] = v
        else:
            flat_dict[key] = value
    return flat_dict

Kullanımı:

d = {'a':1, 'b':[1, 2], 'c':3, 'd':{'a':4, 'b':{'a':7, 'b':8}, 'c':6}, 'e':{'a':1,'b':2}}
flat_d = flatten_dict(dictionary=d, exclude=['e'], delimiter='.')
print(flat_d)

Çıktı:

{'a': 1, 'b': [1, 2], 'c': 3, 'd.a': 4, 'd.b.a': 7, 'd.b.b': 8, 'd.c': 6, 'e': {'a': 1, 'b': 2}}

0

Bu sayfadaki çözümlerin bazılarını denedim - hepsi olmasa da - ama denediklerim iç içe geçmiş diksiyon listesini ele alamadı.

Bunun gibi bir diksiyon düşünün:

d = {
        'owner': {
            'name': {'first_name': 'Steven', 'last_name': 'Smith'},
            'lottery_nums': [1, 2, 3, 'four', '11', None],
            'address': {},
            'tuple': (1, 2, 'three'),
            'tuple_with_dict': (1, 2, 'three', {'is_valid': False}),
            'set': {1, 2, 3, 4, 'five'},
            'children': [
                {'name': {'first_name': 'Jessica',
                          'last_name': 'Smith', },
                 'children': []
                 },
                {'name': {'first_name': 'George',
                          'last_name': 'Smith'},
                 'children': []
                 }
            ]
        }
    }

İşte geçici çözümüm:

def flatten_dict(input_node: dict, key_: str = '', output_dict: dict = {}):
    if isinstance(input_node, dict):
        for key, val in input_node.items():
            new_key = f"{key_}.{key}" if key_ else f"{key}"
            flatten_dict(val, new_key, output_dict)
    elif isinstance(input_node, list):
        for idx, item in enumerate(input_node):
            flatten_dict(item, f"{key_}.{idx}", output_dict)
    else:
        output_dict[key_] = input_node
    return output_dict

hangi üretir:

{
  owner.name.first_name: Steven,
  owner.name.last_name: Smith,
  owner.lottery_nums.0: 1,
  owner.lottery_nums.1: 2,
  owner.lottery_nums.2: 3,
  owner.lottery_nums.3: four,
  owner.lottery_nums.4: 11,
  owner.lottery_nums.5: None,
  owner.tuple: (1, 2, 'three'),
  owner.tuple_with_dict: (1, 2, 'three', {'is_valid': False}),
  owner.set: {1, 2, 3, 4, 'five'},
  owner.children.0.name.first_name: Jessica,
  owner.children.0.name.last_name: Smith,
  owner.children.1.name.first_name: George,
  owner.children.1.name.last_name: Smith,
}

Derme çatma bir çözüm ve mükemmel değil.
NOT:

  • address: {}k / v çifti gibi boş dikte tutmaz .

  • iç içe tuples dikte düzleştirmek olmaz - python tuples listelerine benzer hareket gerçeğini kullanarak eklemek kolay olurdu.


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.