중첩 된 사전을 병합하여 키 압축


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
UFO에

답변:


220

기본적으로 중첩 목록을 평평하게하는 것과 같은 방식으로 키 / 값으로 dict를 반복하고 새 사전에 대한 새 키를 만들고 최종 단계에서 사전을 만들기 위해 추가 작업을 수행하면됩니다.

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.
Björn Pollex

1
collections.MutableMapping좀 더 일반적인 것으로 테스트하도록 변경되었습니다 . 그러나 파이썬 <2.6의 경우 try..except아마도 가장 좋은 옵션 일 것입니다.
Imran

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))로 강제 변환하거나 문자열 대신 튜플로 키를 연결 하여 문제를 해결할 수 있습니다 (튜플도 dict 키일 수 있음).
Scott H

1
팽창 기능이 여기 있습니다
mitch

65

원본 포스터에서 고려해야 할 두 가지 큰 고려 사항이 있습니다.

  1. 키 스페이스 클로버 링 문제가 있습니까? 예를 들어 {'a_b':{'c':1}, 'a':{'b_c':2}}결과는 다음과 같습니다 {'a_b_c':???}. 아래 솔루션은 반복 가능한 쌍을 반환하여 문제를 피합니다.
  2. 성능이 문제인 경우 키 감소 기 기능 (여기서 '결합'이라고 함)은 전체 키 경로에 액세스해야합니까, 아니면 트리의 모든 노드에서 O (1)이 작동합니까? 을 말하고 joinedKey = '_'.join(*keys)싶다면 O (N ^ 2) 실행 시간이 걸립니다. 그러나 기꺼이 말하면 nextKey = previousKey+'_'+thisKeyO (N) 시간을 얻습니다. 아래의 솔루션을 사용하면 두 키를 모두 수행 할 수 있습니다 (모든 키를 연결 한 후 사후 처리 할 수 ​​있기 때문에).

(성능은 가능성이 문제가되지 않습니다,하지만 난 경우 다른 사람 걱정에서 두 번째 점에 정교합니다 :.이 구현에서, 수많은 위험한 선택이있다이 반복적으로 수율 및 재 수율, 또는 않으면 아무것도 하는 터치 동등한 실수로하기 쉬운 노드를 두 번 이상 사용하면 O (N) 대신 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=...)으로 많은 목적에 적합하고 원하는 것을 할 수 있습니다. 슬프게도 위의 성능 저하를 유발하지 않고이 기능의 게으른 버전을 만드는 것은 상당히 어렵습니다 (chain.from_iterable과 같은 많은 Python 내장 기능은 실제로 효율적이지 않습니다. 이 하나).

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)

데모 (문서 문자열에 넣을 것) :

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

... 한숨, 내 잘못이라고 생각하지 마십시오 ...


[조정 문제로 인한 중요하지 않은 기록]

Flatten의 중복 주장과 관련하여 Python의 사전 사전 (2 레벨) 사전 :

이 질문의 솔루션은을 수행 하여이 문제의 관점에서 구현할 수 있습니다 sorted( sum(flatten(...),[]) ). 그 반대의 경우는 불가능합니다. 고차 누산기를 매핑하여 의심되는 복제본에서 flatten(...)복구 할 수 있지만 키를 복구 할 수는 없습니다. (편집 : 또한 중복 된 소유자의 질문은 완전히 다른 것으로 나타났습니다. 단, 해당 페이지의 답변 중 하나가 일반적인 해결책을 제공하지만 정확히 2 단계 깊이의 사전 만 처리한다는 점입니다.)


2
이것이 질문과 관련이 있는지 확실하지 않습니다. 이 솔루션은 사전 목록의 사전 항목 (예 : { 'a': [{ 'aa': 1}, { 'ab': 2}]})을 평평하게하지 않습니다. flattenDict 기능은이 경우에 맞게 쉽게 변경할 수 있습니다.
Stewbaca

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


31

당신이 사용하는 경우 pandas에 숨겨진 기능이 pandas.io.json._normalize하나 라는 nested_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.
Eyal Levin

2
@EyalLevin 잘 잡아라! 이에서 변경되었으며 0.25.x답변을 업데이트했습니다. :)
Aaron N. Brock

28

다음은 일종의 "기능적", "한 줄짜리"구현입니다. 재귀 적이며 조건식과 dict 이해력을 기반으로합니다.

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)테스트 던지기에서 두 번째 키 대신 사용)에는 적용되지 않습니다.TypeError
alancalvitti

@alancalvitti 이것은 문자열이거나 +연산자 를 지원하는 다른 것으로 가정합니다 . 다른 것들을 위해서는 prefix + separator + k객체를 구성하기 위해 적절한 함수 호출에 적응해야 합니다.
dividebyzero

튜플 키와 관련된 또 다른 문제입니다. 귀하의 방법에 따라 일반화하는 방법을 별도로 게시했습니다. 그러나 ninjageko의 예를 올바르게 처리 할 수 ​​없습니다.{'a_b':{'c':1}, 'a':{'b_c':2}}
alancalvitti

2
나는 재귀를 사용하는 대답이없는 것을보고 걱정하고있었습니다. 요즘 젊은이들에게 무슨 문제가 있습니까?
Jakov

dict에 다음과 같이 dict 목록이 중첩 된 경우 아무 작업도 수행하지 않습니다.{'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=''함수를 호출 할 때 대신 함수 정의에서 기본값을 지정하려고 합니다. 이와 관련하여 다른 답변을 참조하십시오.
Acumenus

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에서 이것을 시도 할 때 "구문 오류"가 나타납니다. "** all"사용법이 합법적이지 않은 것 같습니다.
Ingo Fischer

파이썬 3.5부터 일합니다. 3.4에서 작동하지 않는다는 것을 몰랐습니다. 당신이 맞아요.이 책은 읽기 쉽지 않습니다. 나는 대답을 업데이트했다. 더 잘 읽을 수 있기를 바랍니다. :)
Rotareti

1
누락 된 수입 감소를 추가했습니다. 여전히 이해하기 어려운 코드를 찾아서 귀도 반 로섬 (Guido van Rossum) 자신이 2005 년에 람다의 사용을 줄이고, 축소하고, 필터링하고, 매핑하는 이유를 잘 보여주는 좋은 예라고 생각합니다. artima.com/weblogs/viewpost.jsp?thread=98196
Ingo Fischer

나는 동의한다. 파이썬은 실제로 함수형 프로그래밍을 위해 설계되지 않았습니다 . 여전히 reduce사전을 줄여야 할 경우에 좋습니다. 나는 대답을 업데이트했다. 좀 더 파이썬으로 보일 것입니다.
Rotareti

6

이것은 사전으로 제한되지 않지만 .items ()를 구현하는 모든 매핑 유형입니다. if 조건을 피하기 때문에 더 빠릅니다. 그럼에도 불구하고 크레딧은 Imran으로갑니다.

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있습니까? 하나보고 싶네요.
Trey Hunner

1
@ user6037143, 아니요 항목이 구현되지 않은 경우 정의에 따라 맵핑 유형이 아닙니다.
Davoud Taghawi-Nejad

@ DavoudTaghawi-Nejad, 내부적으로 평평하지 않아야하는 튜플과 같은 일반적인 키를 처리하도록 이것을 수정할 수 있습니까?
alancalvitti

5

발전기를 사용하는 Python 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 이외의 유효한 키 유형 (튜플 포함)을 처리하도록 확장 할 수 있습니까? 문자열 연결 대신 튜플에 연결하십시오.
alancalvitti 2016 년

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빈 사전으로 인스턴스화 하여 중첩 된 모든 값을 original에서 가져옵니다 dictionary.

if accumulator is None:
    accumulator = {}

사전 값을 반복 할 때 모든 값에 대한 키를 구성합니다. parent_key인수는 것 None우리가 그 키를 붙일 수 있도록 모든 중첩 된 사전을 위해, 그것은, 그것을 가리키는 키를 포함하는 동안, 첫 번째 통화.

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

vk가 가리키는 값 이 사전 인 경우 함수는 자체를 호출하여 중첩 된 사전, 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

이것은 imran과 ralu의 대답과 비슷합니다. 생성기를 사용하지 않고 클로저로 재귀를 사용합니다.

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}

함수 가 리턴되지 않거나 리턴되지 않을 것으로 예상 되므로 용어 " closure "를 사용하는 것이 올바른지 확실하지 않습니다 _flatten_dict. 대신 하위 기능 또는 동봉 된 기능 이라고 할 수 있습니다 .
Acumenus

3

Davoud의 솔루션은 매우 훌륭하지만 중첩 된 dict에 dict 목록이 포함되어있을 때 만족스러운 결과를 얻지 못하지만 그의 코드는 해당 경우에 맞게 조정됩니다.

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

다음은 우아하고 적절한 교체를위한 알고리즘입니다. Python 2.7 및 Python 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

이것은 우리의 중첩 딕셔너리 내부 목록으로 작동하지만 사용자 정의 구분 옵션이 없습니다
Nikhil VJ

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)

‌ 놀랍게도 키를 즉석에서 추가하거나 표준 받아쓰기를 사용하여 얻을 수있는 이점 :

>>> 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']에 할당하지만 기존 값을 유지하는 것은 매우 놀라운 일입니다. 그것은 정기적 인 지시가 작동하는 방식이 아닙니다.
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).
Cristian Ciupitu

1

간단한 중첩 목록과 같은 재귀에서 dict.popitem () 사용 :

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

OP가 요구 한 것이 아니라 많은 사람들이 키 값 json 객체와 배열 및 json 객체를 배열 내부에 중첩 할 수있는 실제 중첩 된 JSON 데이터를 평탄화하는 방법을 찾고 있습니다. JSON에는 튜플이 포함되어 있지 않으므로 걱정할 필요가 없습니다.

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

내가해야 할 일을하는 Annd : 복잡한 json을 던져서 평평하게 만듭니다.

모든 크레딧은 https://github.com/ScriptSmith 입니다.


1

나는 실제로 그렇게 자주해야했기 때문에 실제로이 종류의 물건을 다루기 위해 최근 cherrypicker라는 패키지를 작성했습니다!

다음 코드는 당신이 쫓는 것을 정확하게 줄 것이라고 생각합니다.

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에 더 많은 문서와 지침이 있습니다 .

다른 방법이 더 빠를 수 있지만이 패키지의 우선 순위는 그러한 작업을 쉽게하는 것 입니다. 평면화 할 객체 목록이 많을 경우 병렬 처리를 사용하여 속도를 높이도록 CherryPicker에 지시 할 수도 있습니다.


나는 다른 접근법을 좋아한다.
Gergely M

0

나는 항상 액세스 선호 dict를 통해 객체를 .items()그래서 나는 다음과 같은 재귀 생성기를 사용 dicts 평탄화를 들어, 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

max_level 및 사용자 정의 감속기로 압축 하여이 중첩 된 사전의 변형입니다 .

  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

이 페이지에서 일부 솔루션을 시도했지만 전부는 아니지만 중첩 된 dict 목록을 처리하지 못했습니다.

다음과 같은 구술을 고려하십시오.

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 쌍 과 같은 빈 받아쓰기를 유지하지 않습니다 .

  • 파이썬 튜플이 목록과 비슷하게 작동한다는 사실을 사용하여 쉽게 추가 할 수는 있지만 중첩 된 튜플의 dicts를 평평하게 만들지는 않습니다.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.