समतल नेस्टेड शब्दकोशों, कुंजी दबाने


172

मान लीजिए कि आपके पास एक शब्दकोश है:

{'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': [1, 2, 3]}

2
इसके अलावा, इसके लिए एक पुस्तकालय भी है: github.com/ianlini/flatten-dict
Ufos

जवाबों:


220

मूल रूप से उसी तरह जब आप एक नेस्टेड सूची को समतल करेंगे, तो आपको केवल कुंजी / मान द्वारा ताना को पुनरावृत्त करने के लिए अतिरिक्त काम करना होगा, अपने नए शब्दकोश के लिए नई कुंजी बनाने और अंतिम चरण पर शब्दकोश बनाने के लिए।

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
यदि आप isinstanceकिसी try..exceptब्लॉक से प्रतिस्थापित करते हैं , तो यह किसी भी मैपिंग के लिए काम करेगा, भले ही यह व्युत्पन्न न हो dict
ब्योर्न पॉलेक्स

1
collections.MutableMappingइसे और अधिक सामान्य बनाने के लिए परीक्षण करने के लिए इसे बदल दिया । लेकिन पायथन <2.6 के लिए, try..exceptशायद सबसे अच्छा विकल्प है।
इमरान

5
आप खाली शब्दकोशों चपटा संस्करण में संरक्षित चाहते हैं तो आप परिवर्तन करना चाह सकते हैं if isinstance(v, collections.MutableMapping):करने के लिएif v and isinstance(v, collections.MutableMapping):
tarequeh

3
ध्यान दें कि new_key = parent_key + sep + k if parent_key else kमान लें कि चाबियाँ हमेशा तार होती हैं, अन्यथा यह ऊपर उठाएगी TypeError: cannot concatenate 'str' and [other] objects। हालाँकि, आप इसे ठीक कर सकते हैं कि kस्ट्रिंग ( str(k)) के लिए ज़बरदस्ती करके , या एक स्ट्रिंग के बजाय टूपल में कुंजी को घुमाएं (ट्यूपल्स तानाशाही कुंजी भी हो सकते हैं)।
स्कॉट एच

1
और फुलाया कार्य यहाँ है
मिच

65

मूल पोस्टर पर विचार करने के लिए दो बड़े विचार हैं:

  1. वहाँ keyspace clobbering मुद्दे हैं? उदाहरण के लिए, इसमें {'a_b':{'c':1}, 'a':{'b_c':2}}परिणाम होगा {'a_b_c':???}। नीचे दिए गए समाधान जोड़े की पुनरावृत्ति को वापस करके समस्या को हल करते हैं।
  2. यदि प्रदर्शन एक मुद्दा है, तो की-रिड्यूसर फ़ंक्शन (जिसे मैं 'जॉइन' के रूप में संदर्भित करता हूं) को पूरे की-पाथ तक पहुंच की आवश्यकता होती है, या क्या यह पेड़ में प्रत्येक नोड पर O (1) काम कर सकता है? यदि आप कहना चाहते हैं joinedKey = '_'.join(*keys), तो आपको O (N ^ 2) को चलाने में समय लगेगा। हालाँकि यदि आप यह कहना चाहते हैं nextKey = previousKey+'_'+thisKeyकि आपको O (N) समय मिलता है। नीचे दिए गए समाधान से आप दोनों कर सकते हैं (चूंकि आप सभी कुंजियों को केवल संक्षिप्त कर सकते हैं, फिर उन्हें पोस्टप्रोसेस कर सकते हैं)।

(प्रदर्शन की संभावना नहीं है कि कोई समस्या हो, लेकिन मैं दूसरे बिंदु पर विस्तार से बताऊंगा कि कोई और परवाह करता है: इसे लागू करने में, कई खतरनाक विकल्प हैं। यदि आप इसे पुनरावर्ती करते हैं और उपज और पुन: उपज, या कुछ भी जो छूता है के बराबर है। एक बार से अधिक नोड्स (जो गलती से ऐसा करने के लिए काफी आसान है), तो आप संभवतः हे (एन ^ 2) बल्कि हे (एन) की तुलना में काम कर रहे हैं। इसका कारण यह है हो सकता है आप एक महत्वपूर्ण गणना कर रहे हैं aतो a_1फिर a_1_i..., और फिर की गणना aतो a_1फिर a_1_ii..., लेकिन वास्तव में आपको a_1फिर से गणना नहीं करनी चाहिए । यहां तक ​​कि अगर आप इसे पुनर्गणना नहीं कर रहे हैं, तो इसे फिर से उपजाना ('स्तर-दर-स्तर' दृष्टिकोण) उतना ही बुरा है। एक अच्छा उदाहरण। पर प्रदर्शन के बारे में सोचने के लिए {1:{1:{1:{1:...(N times)...{1:SOME_LARGE_DICTIONARY_OF_SIZE_N}...}}}})

नीचे एक फ़ंक्शन है flattenDict(d, join=..., lift=...)जो मैंने लिखा था जिसे कई उद्देश्यों के लिए अनुकूलित किया जा सकता है और आप जो चाहते हैं वह कर सकते हैं। अफसोस की बात है कि उपरोक्त प्रदर्शन का दंड दिए बिना इस समारोह का एक आलसी संस्करण बनाना काफी कठिन है (कई अजगर बिल्डरों जैसे कि चेन ।from_iterable वास्तव में कुशल नहीं हैं, जो मुझे केवल इस कोड के तीन अलग-अलग संस्करणों के व्यापक परीक्षण के बाद पता चला है यह वाला)।

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

यह समझने के लिए कि क्या चल रहा है, नीचे reduce(बाएं) से अपरिचित लोगों के लिए एक आरेख है , अन्यथा "तह बाएं" के रूप में जाना जाता है। कभी-कभी इसे k0 के स्थान पर प्रारंभिक मान के साथ खींचा जाता है (सूची का हिस्सा नहीं, फ़ंक्शन में पारित)। यहाँ, Jहमारा joinकार्य है। हम प्रत्येक k n के साथ प्रीप्रोसेस करते हैं 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

यह वास्तव में वैसा ही है functools.reduce, लेकिन जहां हमारा कार्य पेड़ के सभी प्रमुख रास्तों पर होता है।

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

प्रदर्शन (जो मैं अन्यथा docstring में डालूंगा):

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

प्रदर्शन:

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

... आह, यह मत सोचो कि एक मेरी गलती है ...


[मॉडरेशन मुद्दों के कारण महत्वहीन ऐतिहासिक नोट]

पायथन में सूचियों के एक शब्दकोश (2 स्तरों गहरे) के शब्दकोश के कथित नकल के बारे में :

इस प्रश्न का समाधान इस एक के संदर्भ में कार्यान्वित किया जा सकता है sorted( sum(flatten(...),[]) )। रिवर्स संभव नहीं है: जबकि यह सच है कि मूल्यों की flatten(...)एक उच्च क्रम संचायक मानचित्रण द्वारा कथित डुप्लिकेट से बरामद किया जा सकता है, एक कुंजी को ठीक नहीं कर सकते। (संपादित करें: यह भी पता चलता है कि कथित डुप्लिकेट स्वामी का प्रश्न पूरी तरह से अलग है, इसमें वह केवल 2-स्तरीय गहन शब्दकोशों के साथ ही व्यवहार करता है, हालांकि उस पृष्ठ का एक उत्तर सामान्य समाधान देता है।)


2
मुझे यकीन नहीं है कि यह सवाल के लिए प्रासंगिक है। यह समाधान शब्दकोशों की एक सूची के शब्दकोश आइटम को समतल नहीं करता है, अर्थात {'a': [{'a': 1}, {'ab': 2}]}। इस मामले को समायोजित करने के लिए फ्लैटनडिक्ट फ़ंक्शन को आसानी से बदला जा सकता है।
स्टीवर्का

55

या यदि आप पहले से ही पांडा का उपयोग कर रहे हैं, तो आप इसे इस 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])

आउटपुट:

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

4
या बस sep का तर्क पास करें :)
Blue Moon

2
शर्म की बात है कि यह सूचियों को संभाल नहीं करता है :)
Roelant

31

यदि आप उपयोग कर रहे हैं तो 1pandas में छिपा हुआ एक फंक्शन है जिसे बुलाया जाता है।pandas.io.json._normalizenested_to_record

from pandas.io.json._normalize import nested_to_record    

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

1 पांडा संस्करणों 0.24.xऔर पुराने उपयोग में pandas.io.json.normalize(बिना _)


1
क्या मेरे लिए काम किया था from pandas.io.json._normalize import nested_to_record_पहले अंडरस्कोर ( ) नोटिस करें normalize
ईयाल लेविन

2
@EyalLp3 अच्छा कैच! यह बदल गया है 0.25.x, मैंने उत्तर अपडेट कर दिया है। :)
आरोन एन। ब्रॉक

28

यहां एक प्रकार का "कार्यात्मक", "वन-लाइनर" कार्यान्वयन है। यह पुनरावर्ती है, और एक सशर्त अभिव्यक्ति और एक तानाशाही समझ पर आधारित है।

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 }

परीक्षा:

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}

यह सामान्य शब्दकोशों के लिए काम नहीं करता है, विशेष रूप से, टपल कुंजी के साथ, उदाहरण के ('hgf',2)लिए आपके टेस्ट थ्रो में 2 वीं कुंजी के लिए स्थानापन्नTypeError
alancalvitti

@alancalvitti यह मानता है कि यह एक स्ट्रिंग है, या कुछ और जो +ऑपरेटर का समर्थन करता है । किसी और चीज़ के लिए आपको prefix + separator + kवस्तुओं की रचना करने के लिए उपयुक्त फ़ंक्शन कॉल के अनुकूल होना होगा।
dividebyzero

टपल कुंजी के लिए प्रासंगिक एक और मुद्दा। मैंने अलग-अलग पोस्ट किया है कि आपकी विधि के आधार पर सामान्यीकरण कैसे किया जाए। हालाँकि यह निन्जेकोको के उदाहरण को सही ढंग से नहीं संभाल सकता है:{'a_b':{'c':1}, 'a':{'b_c':2}}
अलंकाल्विती

2
मुझे चिंता हो रही थी, कोई जवाब नहीं आ रहा था। इन दिनों हमारे युवाओं के साथ क्या गलत है?
जकोव

कुछ भी नहीं अगर एक तानाशाह ने dicts की सूची को नेस्टेड किया हो, जैसे:{'name': 'Steven', 'children': [{'name': 'Jessica', 'children': []}, {'name': 'George', 'children': []}]}
Gergely M

12

कोड:

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

परिणाम:

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

मैं python3.2 का उपयोग कर रहा हूं, अपने अजगर के संस्करण के लिए अपडेट करें।


आप शायद lkey=''फ़ंक्शन को कॉल करने के बजाय अपने फ़ंक्शन परिभाषा में डिफ़ॉल्ट मान निर्दिष्ट करना चाहते हैं । इस संबंध में अन्य उत्तर देखें।
एक्यूमेनस

6

Python3.5 में एक कार्यात्मक और प्रदर्शनकारी समाधान के बारे में कैसे ?

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(), 
        {}
    ))

यह और भी अधिक अच्छा है:

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(), 
        {}
    ))

उपयोग में:

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
एक पठनीय और काम करने वाले समाधान के बारे में कैसे? ;) आप किस संस्करण पर यह परीक्षण किया? पायथन 3.4.3 में इसे आज़माते समय मुझे "सिंटैक्स त्रुटि" हो रही है। ऐसा लगता है कि "** सभी" का उपयोग वैध नहीं है।
इंगो फिशर

मैं पायथन 3.5 से काम करता हूं। नहीं पता था कि यह 3.4 के साथ काम नहीं करता है। आप सही हैं यह बहुत पठनीय नहीं है। मैंने जवाब अपडेट किया। आशा है कि अब यह अधिक पठनीय है। :)
रोटेरेटी

1
जोड़ा हुआ आयात कम कर रहा है। फिर भी समझने के लिए कोड कठिन लगता है और मुझे लगता है कि यह एक अच्छा उदाहरण है कि क्यों Guido van Rossum ने खुद 2005 में पहले ही लंबोदा
इंगो फिशर

मैं सहमत हूँ। पायथन वास्तव में कार्यात्मक प्रोग्रामिंग के लिए डिज़ाइन नहीं किया गया है । फिर भी मुझे लगता reduceहै कि आपको शब्दकोशों को कम करने की आवश्यकता है। मैंने जवाब अपडेट किया। अब थोड़ा और पाइथोनिक दिखना चाहिए।
रोटेरेटी

6

यह केवल शब्दकोशों तक ही सीमित नहीं है, बल्कि प्रत्येक मैपिंग प्रकार जो लागू करता है ।items ()। इसके अलावा अगर यह एक शर्त है तो यह और तेज होता है। फिर भी श्रेय इमरान को जाता है:

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)

1
अगर dनहीं है एक dictहै, लेकिन एक कस्टम मैपिंग प्रकार है कि लागू नहीं करता है items, अपने कार्य सही और फिर वहाँ विफल हो जाएगा। तो, यह हर मैपिंग प्रकार के लिए काम नहीं करता है, लेकिन केवल वे ही लागू होते हैं items()
user6037143

@ user6037143 क्या आपने कभी एक मैपिंग प्रकार का सामना किया है जो लागू नहीं होता है items? मैं एक को देखने के लिए उत्सुक हूँ।
ट्रे हुनर

1
@ user6037143, यदि आप आइटम लागू नहीं है, तो यह परिभाषा के अनुसार नहीं है, यह कोई मैपिंग प्रकार नहीं है।
दावूद तागावी-नेजाद

@ DavoudTaghawi-Nejad, क्या आप इसे सामान्य कुंजियों जैसे टुपल्स को संभालने के लिए संशोधित कर सकते हैं जिन्हें आंतरिक रूप से फ़्लिप नहीं किया जाना चाहिए।
अलंकालविट्टी

5

जनरेटर का उपयोग करके मेरा पायथन 3.3 समाधान:

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 सहित) के अलावा किसी भी मान्य कुंजी प्रकार को संभालने के लिए बढ़ा सकते हैं? स्ट्रिंग संघनन के बजाय, उन्हें एक ट्यूपल में शामिल करें।
अलंकलवित्ति

4

नेस्टेड शब्दकोशों को समतल करने के लिए सरल कार्य। पायथन 3 के लिए, प्रतिस्थापित करें.iteritems() साथ.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

विचार / आवश्यकता थी: बिना माता-पिता की चाबियों के साथ समतल शब्दकोश प्राप्त करें।

उपयोग का उदाहरण:

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}

माता-पिता की चाबियां रखना भी सरल है।


4

पुनरावृत्ति का उपयोग, इसे सरल और मानवीय पठनीय बनाए रखना:

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

कॉल सरल है:

new_dict = flatten_dict(dictionary)

या

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

अगर हम डिफ़ॉल्ट विभाजक को बदलना चाहते हैं।

थोड़ा ब्रेकडाउन:

जब फ़ंक्शन को पहली बार कहा जाता है, तो इसे केवल उस पास कहा जाता है जिसे dictionaryहम समतल करना चाहते हैं। accumulatorपैरामीटर समर्थन प्रत्यावर्तन, जो हम बाद में देखने के लिए यहाँ है। इसलिए, हम accumulatorएक खाली शब्दकोष में जाते हैं, जहां हम मूल से सभी नेस्टेड मान डालेंगे dictionary

if accumulator is None:
    accumulator = {}

जैसे ही हम शब्दकोश के मूल्यों पर पुनरावृत्ति करते हैं, हम हर मूल्य के लिए एक कुंजी बनाते हैं। parent_keyतर्क हो जाएगा None, पहली कॉल के लिए है, जबकि हर नेस्टेड शब्दकोश के लिए, यह कुंजी यह की ओर इशारा करते में शामिल होंगे, तो हम उस कुंजी पहले जोड़ें।

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

यदि मान vजिस kओर इशारा कर रहा है वह एक शब्दकोष है, तो फ़ंक्शन खुद को नेस्टेड डिक्शनरी से गुजरता है, accumulator(जो संदर्भ द्वारा पारित किया जाता है, इसलिए इसमें किए गए सभी परिवर्तन एक ही उदाहरण पर किए जाते हैं) और कुंजी kताकि हम संक्षिप्त कुंजी का निर्माण कर सकते हैं। continueकथन पर ध्यान दें। हम ifब्लॉक के बाहर, अगली पंक्ति को छोड़ना चाहते हैं , ताकि नेस्टेड शब्दकोश accumulatorअंडर की में समाप्त न हो k

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

तो, हम क्या करते हैं अगर मूल्य vएक शब्दकोश नहीं है? बस इसे अंदर अपरिवर्तित रखें accumulator

accumulator[k] = v

एक बार जब हम कर लेते हैं तो हम accumulatorमूल रिटर्न को dictionaryअछूता छोड़कर वापस लौट आते हैं ।

ध्यान दें

यह केवल उन शब्दकोशों के साथ काम करेगा जिनके पास कुंजी के रूप में तार हैं। यह __repr__विधि को लागू करने वाली हैशेबल वस्तुओं के साथ काम करेगा , लेकिन अवांछित परिणाम देगा।


3

यह इमरान और लालू के जवाब दोनों के समान है। यह एक जनरेटर का उपयोग नहीं करता है, बल्कि एक बंद होने के साथ पुनरावृत्ति को नियोजित करता है:

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}

मुझे यकीन नहीं है कि अगर " क्लोजर " शब्द का उपयोग करना यहां सही है, क्योंकि फ़ंक्शन _flatten_dictकभी भी वापस नहीं किया जाता है, और न ही कभी वापस आने की उम्मीद है। इसे संभवतः इसके बजाय एक सबफ़ंक्शन या एक संलग्न फ़ंक्शन के रूप में संदर्भित किया जा सकता है ।
एक्यूमेनस

3

दावॉड का समाधान बहुत अच्छा है लेकिन संतोषजनक परिणाम नहीं देता है जब नेस्टेड डिक्टेट में भी डिटॉक्स की सूची होती है, लेकिन उसके कोड को उस मामले के लिए अनुकूलित किया जाना चाहिए:

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)

आप type([])प्रत्येक आइटम के लिए फ़ंक्शन कॉल से बचने के लिए परिणाम को कैश कर सकते हैं dict
bfontaine

2
isinstance(v, list)इसके बजाय का उपयोग करें
Druska

2

उपरोक्त उत्तर वास्तव में अच्छी तरह से काम करते हैं। बस मैंने सोचा था कि मैंने जो अनफ़ल्टेन फ़ंक्शन लिखा था, उसे जोड़ूंगा:

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

नोट: यह '_' के लिए पहले से ही कुंजी में मौजूद नहीं है, सपाट समकक्षों की तरह।


2

यहाँ सुरुचिपूर्ण, इन-प्लेस प्रतिस्थापन के लिए एक एल्गोरिथ्म है। पायथन 2.7 और पायथन 3.5 के साथ परीक्षण किया गया। एक विभाजक के रूप में डॉट चरित्र का उपयोग करना।

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

उदाहरण:

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

आउटपुट:

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

मैंने इस कोड को मिलान फ़ंक्शन के साथ यहां प्रकाशित किया unflatten_jsonहै।


2

यदि आप नेस्टेड डिक्शनरी को फ्लैट करना चाहते हैं और सभी विशिष्ट कुंजी सूची चाहते हैं, तो यहां समाधान है:

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

यह हमारे नेस्टेड तानाशाही के अंदर सूचियों के साथ काम करता है, लेकिन इसमें कस्टम विभाजक विकल्प नहीं है
निखिल वीजे

2

मैं चाबियों को स्वचालित रूप से फ्लैट करने के लिए UserDict के एक उपवर्ग के बारे में सोच रहा था।

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)

Fly फायदे यह है कि चाबियाँ मक्खी पर जोड़ी जा सकती हैं, या बिना आश्चर्य के मानक तानाशाहों का उपयोग कर सकते हैं:

>>> 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
एफडी ['व्यक्ति'] को सौंपना लेकिन इसके मौजूदा मूल्य को बनाए रखना काफी आश्चर्यजनक है। यह नियमित रूप से कैसे काम नहीं करता है।
tbm

1

जनरेटर का उपयोग करना:

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'के साथ type(i) is dictया शायद और भी बेहतर isinstance(d, dict)(या Mapping/ MutableMapping) बदला जा सकता है ।
क्रिस्टियन सियुपिटु

1

तानाशाही का उपयोग करना। सीधे-सीधे नेस्टेड-सूची जैसी पुनरावृत्ति में)

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

ओपी ने पूछा कि वास्तव में क्या नहीं है, लेकिन बहुत से लोग यहां आ रहे हैं, जो वास्तविक दुनिया के नेस्टेड JSON डेटा को समतल करने के तरीकों की तलाश कर रहे हैं, जिनमें नेस्ट-वैल्यू json ऑब्जेक्ट्स और arrays और json ऑब्जेक्ट्स सरणियों के अंदर और इतने पर नेस्टेड हो सकते हैं। JSON में tuples शामिल नहीं है, इसलिए हमें उन पर झल्लाहट नहीं करनी है।

मैं इस सूची में शामिल किए जाने-के एक कार्यान्वयन पाया @roneo द्वारा टिप्पणी करने के लिए जवाब @Imran द्वारा पोस्ट की गई :

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)

झसे आज़माओ:

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}

और वह काम जो मुझे करने की आवश्यकता है: मैं इस पर कोई भी जटिल जुबान फेंकता हूं और यह मेरे लिए इसे समतल कर देता है।

सभी क्रेडिट https://github.com/ScriptSmith पर


1

मैंने वास्तव में चेरीपिकर नामक एक पैकेज हाल ही में इस तरह की चीज़ से निपटने के लिए लिखा था क्योंकि मुझे ऐसा अक्सर करना पड़ता था!

मुझे लगता है कि निम्नलिखित कोड आपको वही देगा जो आप इसके बाद कर रहे हैं:

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

आप के साथ पैकेज स्थापित कर सकते हैं:

pip install cherrypicker

... और https://cherrypicker.readthedocs.io पर अधिक डॉक्स और मार्गदर्शन है ।

अन्य विधियां तेज हो सकती हैं, लेकिन इस पैकेज की प्राथमिकता ऐसे कार्यों को आसान बनाना है । यदि आपके पास समतल करने के लिए वस्तुओं की एक बड़ी सूची है, तो आप चीजों को गति देने के लिए समानांतर प्रसंस्करण का उपयोग करने के लिए चेरीपिकर को भी बता सकते हैं।


मुझे वैकल्पिक दृष्टिकोण पसंद है।
Gergely M

0

मैं हमेशा एक्सेस dictऑब्जेक्ट्स को प्राथमिकता देता हूं .items(), इसलिए चपटा करने के लिए मैं निम्नलिखित पुनरावर्ती जनरेटर का उपयोग करता हूं flat_items(d)। यदि आपको dictफिर से पसंद है , तो बस इसे इस तरह से लपेटें: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

इस Flatten नेस्टेड डिक्शनरी का वेरिएशन , अधिकतम_लेवल और कस्टम रिड्यूसर के साथ कंप्रेसिंग कीज

  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

यदि आप पुनरावर्ती कार्यों को बुरा नहीं मानते हैं, तो यहां एक समाधान है। मैंने एक बहिष्करण को शामिल करने के लिए स्वतंत्रता भी ली हैयदि आपने एक या अधिक मूल्य बनाए रखने की इच्छा है, पैरामीटर ।

कोड:

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

उपयोग:

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)

आउटपुट:

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

मैंने इस पृष्ठ पर कुछ समाधानों की कोशिश की - हालांकि सभी नहीं - लेकिन जिन लोगों ने कोशिश की, वे तानाशाह की नेस्टेड सूची को संभालने में विफल रहे।

इस तरह एक तानाशाही पर विचार करें:

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': []
                 }
            ]
        }
    }

यहाँ मेरा अस्थायी समाधान है:

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

जो पैदा करता है:

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

एक अस्थायी समाधान और यह सही नहीं है।
ध्यान दें:

  • यह address: {}k / v जोड़ी जैसे खाली डिकेट नहीं रखता है ।

  • यह नेस्टेड ट्यूपल्स में डायट को समतल नहीं करेगा - हालांकि यह इस तथ्य का उपयोग करके जोड़ना आसान होगा कि पायथन ट्यूपल सूची के समान कार्य करते हैं।


-1

बस उपयोग करें python-benedict, यह एक तानाशाह उपवर्ग है जो कई विशेषताएं प्रदान करता है, जिसमें एक flattenविधि भी शामिल है । पाइप का उपयोग करके इसे स्थापित करना संभव है:pip install python-benedict

https://github.com/fabiocaccamo/python-benedict#flatten

from benedict import benedict 

d = benedict(data)
f = d.flatten(separator='_')
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.