중첩 된 사전을 구현하는 가장 좋은 방법은 무엇입니까?


201

본질적으로 중첩 된 사전에 해당하는 데이터 구조가 있습니다. 다음과 같이 가정 해 봅시다.

{'new jersey': {'mercer county': {'plumbers': 3,
                                  'programmers': 81},
                'middlesex county': {'programmers': 81,
                                     'salesmen': 62}},
 'new york': {'queens county': {'plumbers': 9,
                                'salesmen': 36}}}

자, 이것을 유지하고 만드는 것은 꽤 고통 스럽습니다. 새로운 주 / 군 / 직업을 가질 때마다 난해한 try / catch 블록을 통해 하위 계층 사전을 만들어야합니다. 또한 모든 값을 다루려면 성가신 중첩 반복자를 만들어야합니다.

튜플을 키로 사용할 수도 있습니다.

{('new jersey', 'mercer county', 'plumbers'): 3,
 ('new jersey', 'mercer county', 'programmers'): 81,
 ('new jersey', 'middlesex county', 'programmers'): 81,
 ('new jersey', 'middlesex county', 'salesmen'): 62,
 ('new york', 'queens county', 'plumbers'): 9,
 ('new york', 'queens county', 'salesmen'): 36}

이렇게하면 값을 매우 간단하고 자연스럽게 반복 할 수 있지만 집계와 같은 작업을 수행하고 사전의 하위 집합을 보는 것이 더 구문 적으로 고통 스럽습니다 (예 : 상태별로 이동하려는 경우).

기본적으로 때로는 중첩 사전을 플랫 사전으로 생각하고 때로는 복잡한 계층 구조로 생각하고 싶습니다. 나는 이것을 모두 한 수업에 넣을 수는 있지만 누군가가 이미 이것을 한 것처럼 보입니다. 또는,이를 위해 정말 우아한 구문 구조가있을 수 있습니다.

어떻게하면 더 잘할 수 있습니까?

부록 : 알고 setdefault()있지만 실제로 구문을 깨끗하게 만들지는 않습니다. 또한 생성 한 각 하위 사전은 여전히 setdefault()수동으로 설정해야합니다.

답변:


178

파이썬에서 중첩 사전을 구현하는 가장 좋은 방법은 무엇입니까?

이것은 나쁜 생각입니다.하지 마십시오. 대신 일반 사전을 사용 dict.setdefault하고 apropos를 사용하십시오 . 따라서 일반적인 사용법으로 키가 누락되면 예상을 얻습니다 KeyError. 이 행동을 취해야한다고 주장하는 경우, 발로 자신을 쏘는 방법은 다음과 같습니다.

새 인스턴스를 설정하고 반환하기 __missing__위해 dict서브 클래스를 구현하십시오 .

이 접근법은 Python 2.5부터 사용 가능 하고 문서화 되어 있으며 (특히 나에게 귀중한) 자동 활성화 된 defaultdict의 추악한 인쇄 대신 일반 dict처럼 인쇄합니다.

class Vividict(dict):
    def __missing__(self, key):
        value = self[key] = type(self)() # retain local pointer to value
        return value                     # faster to return than dict lookup

(메모 self[key]는 과제의 왼쪽에 있으므로 재귀는 없습니다.)

데이터가 있다고 가정 해보십시오.

data = {('new jersey', 'mercer county', 'plumbers'): 3,
        ('new jersey', 'mercer county', 'programmers'): 81,
        ('new jersey', 'middlesex county', 'programmers'): 81,
        ('new jersey', 'middlesex county', 'salesmen'): 62,
        ('new york', 'queens county', 'plumbers'): 9,
        ('new york', 'queens county', 'salesmen'): 36}

사용 코드는 다음과 같습니다.

vividict = Vividict()
for (state, county, occupation), number in data.items():
    vividict[state][county][occupation] = number

그리고 지금:

>>> import pprint
>>> pprint.pprint(vividict, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
                                  'programmers': 81},
                'middlesex county': {'programmers': 81,
                                     'salesmen': 62}},
 'new york': {'queens county': {'plumbers': 9,
                                'salesmen': 36}}}

비판

이 유형의 컨테이너에 대한 비판은 사용자가 키의 철자를 잘못 입력하면 코드가 자동으로 실패 할 수 있다는 것입니다.

>>> vividict['new york']['queens counyt']
{}

또한 이제 데이터에 철자가 틀린 카운티가 있습니다.

>>> pprint.pprint(vividict, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
                                  'programmers': 81},
                'middlesex county': {'programmers': 81,
                                     'salesmen': 62}},
 'new york': {'queens county': {'plumbers': 9,
                                'salesmen': 36},
              'queens counyt': {}}}

설명:

우리는 Vividict키에 액세스 할 때마다 클래스의 또 다른 중첩 인스턴스를 제공하고 있습니다. (값 할당을 반환하면 dict에서 getter를 추가로 호출하지 않아도되므로 설정이 완료되면 반환 할 수 없기 때문에 유용합니다.)

이것들은 가장 많이 응답 된 답변과 동일한 의미이지만 코드 라인의 절반에 해당합니다-nosklo의 구현 :

class AutoVivification(dict):
    """Implementation of perl's autovivification feature."""
    def __getitem__(self, item):
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            value = self[item] = type(self)()
            return value

사용법의 데모

다음은이 dict를 사용하여 중첩 된 dict 구조를 즉시 작성하는 방법에 대한 예입니다. 이를 통해 원하는만큼 깊이 계층 적 트리 구조를 신속하게 만들 수 있습니다.

import pprint

class Vividict(dict):
    def __missing__(self, key):
        value = self[key] = type(self)()
        return value

d = Vividict()

d['foo']['bar']
d['foo']['baz']
d['fizz']['buzz']
d['primary']['secondary']['tertiary']['quaternary']
pprint.pprint(d)

어떤 출력 :

{'fizz': {'buzz': {}},
 'foo': {'bar': {}, 'baz': {}},
 'primary': {'secondary': {'tertiary': {'quaternary': {}}}}}

마지막 줄에서 알 수 있듯이 수동 검사를 위해 아름답게 인쇄됩니다. 그러나 데이터를 시각적으로 검사 __missing__하려면 클래스의 새 인스턴스를 키로 설정하고 반환하는 것이 훨씬 나은 솔루션입니다.

대조를위한 다른 대안들 :

dict.setdefault

asker는 이것이 깨끗하지 않다고 생각하지만, Vividict나 자신 보다 선호한다고 생각합니다 .

d = {} # or dict()
for (state, county, occupation), number in data.items():
    d.setdefault(state, {}).setdefault(county, {})[occupation] = number

그리고 지금:

>>> pprint.pprint(d, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
                                  'programmers': 81},
                'middlesex county': {'programmers': 81,
                                     'salesmen': 62}},
 'new york': {'queens county': {'plumbers': 9,
                                'salesmen': 36}}}

철자가 틀리면 실패 할 수 있으며 잘못된 정보로 인해 데이터가 복잡해지지 않습니다

>>> d['new york']['queens counyt']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'queens counyt'

또한 setdefault가 루프에서 사용될 때 훌륭하게 작동한다고 생각하며 키에 대해 무엇을 얻을지 모르지만 반복적 인 사용법은 상당히 부담이되며 사람은 다음을 유지하고 싶지는 않습니다.

d = dict()

d.setdefault('foo', {}).setdefault('bar', {})
d.setdefault('foo', {}).setdefault('baz', {})
d.setdefault('fizz', {}).setdefault('buzz', {})
d.setdefault('primary', {}).setdefault('secondary', {}).setdefault('tertiary', {}).setdefault('quaternary', {})

또 다른 비판은 setdefault가 사용 여부에 관계없이 새 인스턴스를 필요로한다는 것입니다. 그러나 파이썬 (또는 적어도 CPython)은 사용되지 않고 참조되지 않은 새로운 인스턴스를 처리하는 데 다소 똑똑합니다. 예를 들어 메모리의 위치를 ​​재사용합니다.

>>> id({}), id({}), id({})
(523575344, 523575344, 523575344)

자동 활성화 된 defaultdict

이것은 깔끔하게 보이는 구현이며 데이터를 검사하지 않는 스크립트의 사용법은 구현하는 것만 큼 유용합니다 __missing__.

from collections import defaultdict

def vivdict():
    return defaultdict(vivdict)

그러나 데이터를 검사해야하는 경우 동일한 방식으로 데이터로 채워진 자동 활성화 된 기본 결과는 다음과 같습니다.

>>> d = vivdict(); d['foo']['bar']; d['foo']['baz']; d['fizz']['buzz']; d['primary']['secondary']['tertiary']['quaternary']; import pprint; 
>>> pprint.pprint(d)
defaultdict(<function vivdict at 0x17B01870>, {'foo': defaultdict(<function vivdict 
at 0x17B01870>, {'baz': defaultdict(<function vivdict at 0x17B01870>, {}), 'bar': 
defaultdict(<function vivdict at 0x17B01870>, {})}), 'primary': defaultdict(<function 
vivdict at 0x17B01870>, {'secondary': defaultdict(<function vivdict at 0x17B01870>, 
{'tertiary': defaultdict(<function vivdict at 0x17B01870>, {'quaternary': defaultdict(
<function vivdict at 0x17B01870>, {})})})}), 'fizz': defaultdict(<function vivdict at 
0x17B01870>, {'buzz': defaultdict(<function vivdict at 0x17B01870>, {})})})

이 출력은 매우 우아하지 않으며 결과를 읽을 수 없습니다. 일반적으로 제공되는 솔루션은 수동 검사를 위해 재귀 적으로 사전으로 변환하는 것입니다. 이 사소한 해결책은 독자를위한 연습으로 남습니다.

공연

마지막으로 성능을 살펴 보겠습니다. 인스턴스화 비용을 빼고 있습니다.

>>> import timeit
>>> min(timeit.repeat(lambda: {}.setdefault('foo', {}))) - min(timeit.repeat(lambda: {}))
0.13612580299377441
>>> min(timeit.repeat(lambda: vivdict()['foo'])) - min(timeit.repeat(lambda: vivdict()))
0.2936999797821045
>>> min(timeit.repeat(lambda: Vividict()['foo'])) - min(timeit.repeat(lambda: Vividict()))
0.5354437828063965
>>> min(timeit.repeat(lambda: AutoVivification()['foo'])) - min(timeit.repeat(lambda: AutoVivification()))
2.138362169265747

성능에 dict.setdefault따라 가장 잘 작동합니다. 실행 속도에 관심이있는 경우 프로덕션 코드로 사용하는 것이 좋습니다.

IPython 노트북에서 대화 형 사용을 위해 이것이 필요한 경우 성능은 실제로 중요하지 않습니다.이 경우 출력의 가독성을 위해 Vividict와 함께 할 것입니다. AutoVivification 객체 ( 이 목적으로 만들어진 __getitem__대신 대신 사용 __missing__)에 비해 훨씬 뛰어납니다.

결론

새 인스턴스를 설정하고 반환하기 __missing__위해 서브 클래스 dict에 구현 하는 것은 대안보다 약간 어렵지만 다음과 같은 이점이 있습니다.

  • 쉬운 인스턴스화
  • 쉬운 데이터 수집
  • 쉬운 데이터보기

수정하는 것보다 덜 복잡하고 성능이 좋기 때문에 __getitem__해당 방법보다 선호되어야합니다.

그럼에도 불구하고 단점이 있습니다.

  • 잘못된 조회는 자동으로 실패합니다.
  • 잘못된 조회는 사전에 남아 있습니다.

따라서 나는 개인적으로 setdefault다른 솔루션을 선호 하며 모든 종류의 상황에서 이런 종류의 행동이 필요했습니다.


훌륭한 답변! 에 대한 유한 깊이와 리프 유형을 지정하는 방법이 Vividict있습니까? 예 3list함께 채울 수 있었다리스트의 DICT의 DICT의 DICT에 대한 d['primary']['secondary']['tertiary'].append(element). 각 깊이에 대해 3 개의 서로 다른 클래스를 정의 할 수 있지만 더 깨끗한 솔루션을 찾고 싶습니다.
Eric Duminil

@EricDuminil- d['primary']['secondary'].setdefault('tertiary', []).append('element')?? 칭찬에 감사드립니다, 그러나 솔직하게 말하십시오 – 나는 실제로 사용하지 않습니다 __missing__– 나는 항상 사용 setdefault합니다. 아마도 내 결론 / 소개를 업데이트해야 할 것입니다.
Aaron Hall

@AaronHall 올바른 동작은 코드가 필요한 경우 dict를 작성해야한다는 것입니다. 이 경우 이전에 할당 된 값을 재정의합니다.
nehem

@AaronHall 또한 The bad lookup will remain in the dictionary.이 솔루션 사용을 고려할 때의 의미를 이해하도록 도와 줄 수 있습니까?. 매우 감사. Thx
nehem

@AaronHall setdefault둘 이상의 깊이 수준으로 중첩되면 문제가 발생 합니다. 설명 된 것처럼 Python의 구조가 진정한 활력을 제공 할 수없는 것처럼 보입니다. 나는이 진술 방법에 대한 하나에 만족해야했다 get_nested위해 및 하나 set_nestedDICT과 중첩 된 속성 목록에 대한 참조를 허용한다.
nehem

188
class AutoVivification(dict):
    """Implementation of perl's autovivification feature."""
    def __getitem__(self, item):
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            value = self[item] = type(self)()
            return value

테스트 :

a = AutoVivification()

a[1][2][3] = 4
a[1][3][3] = 5
a[1][2]['test'] = 6

print a

산출:

{1: {2: {'test': 6, 3: 4}, 3: {3: 5}}}

파이썬 3.x로 옮길 때이 문제가 있습니까? stackoverflow.com/questions/54622935/…
Jason Jason

@jason pickle은 파이썬 버전 사이에서 끔찍합니다. 보관하려는 데이터를 저장하는 데 사용하지 마십시오. 캐시 및 원하는대로 덤프 및 재생성 할 수있는 항목에만 사용하십시오. 장기 저장 또는 직렬화 방법이 아닙니다.
nosklo

이러한 객체를 저장하기 위해 무엇을 사용합니까? 내 autovivification 객체에는 팬더 데이터 프레임과 문자열 만 포함되어 있습니다.
Jason

@jason 데이터에 따라 JSON, csv 파일 또는 sqlite데이터베이스를 사용하여 저장하는 것을 좋아합니다.
nosklo

30

내가이 작은 것을 보지 못했기 때문에 여기에 땀을 들이지 않고 원하는만큼 중첩되는 구술이 있습니다.

# yo dawg, i heard you liked dicts                                                                      
def yodict():
    return defaultdict(yodict)

2
@ wberry : 실제로 필요한 것은 yodict = lambda: defaultdict(yodict)입니다.
martineau

1
허용되는 버전은의 서브 클래스 dict이므로 완전히 동등하게 x = Vdict(a=1, b=2)작업 해야 합니다.
wberry

@wberry : 수용된 답변에 무엇이 있는지에 관계없이 서브 클래스가 dict되는 것은 OP가 명시한 요구 사항이 아니며, 구현을위한 "최상의 방법"만을 요청한 것 외에는 그렇지 않습니다. 어쨌든 파이썬에서 그렇게 중요합니다.
martineau

24

YAML 파일을 만들고 PyYaml 을 사용하여 읽을 수 있습니다 .

1 단계 : "employment.yml"YAML 파일을 만듭니다.

new jersey:
  mercer county:
    pumbers: 3
    programmers: 81
  middlesex county:
    salesmen: 62
    programmers: 81
new york:
  queens county:
    plumbers: 9
    salesmen: 36

2 단계 : 파이썬으로 읽어보기

import yaml
file_handle = open("employment.yml")
my_shnazzy_dictionary = yaml.safe_load(file_handle)
file_handle.close()

이제 my_shnazzy_dictionary모든 가치가 있습니다. 이 작업을 즉시 수행해야하는 경우 YAML을 문자열로 생성하여에 입력 할 수 있습니다 yaml.safe_load(...).


4
YAML은 깊이 중첩 된 많은 데이터 (및 구성 파일, databaes 모형 등)를 입력하기위한 나의 선택입니다. OP가 추가 파일을 원치 않으면 일부 파일에서 일반 Python 문자열을 사용하고 YAML로 구문 분석하십시오.
kmelvn

YAML 문자열 생성에 대한 좋은 점 : "tempfile"모듈을 반복적으로 사용하는 것보다 훨씬 깔끔한 방법입니다.
Pete

18

스타 스키마 디자인이 있으므로 관계형 테이블처럼 구성하고 사전보다는 덜 구성 할 수 있습니다.

import collections

class Jobs( object ):
    def __init__( self, state, county, title, count ):
        self.state= state
        self.count= county
        self.title= title
        self.count= count

facts = [
    Jobs( 'new jersey', 'mercer county', 'plumbers', 3 ),
    ...

def groupBy( facts, name ):
    total= collections.defaultdict( int )
    for f in facts:
        key= getattr( f, name )
        total[key] += f.count

이런 종류의 일은 SQL 오버 헤드없이 데이터웨어 하우스와 같은 디자인을 만드는 데 큰 도움이 될 수 있습니다.


14

중첩 수준의 수가 적 으면 이것을 사용 collections.defaultdict합니다.

from collections import defaultdict

def nested_dict_factory(): 
  return defaultdict(int)
def nested_dict_factory2(): 
  return defaultdict(nested_dict_factory)
db = defaultdict(nested_dict_factory2)

db['new jersey']['mercer county']['plumbers'] = 3
db['new jersey']['mercer county']['programmers'] = 81

사용 defaultdict지저분한 많은 같이 피한다 setdefault(), get()


+1 : defaultdict는 파이썬에서 내가 가장 좋아하는 것 중 하나입니다. 더 이상 .setdefault ()가 없습니다!
John Fouhy

8

임의의 깊이로 중첩 된 사전을 반환하는 함수입니다.

from collections import defaultdict
def make_dict():
    return defaultdict(make_dict)

다음과 같이 사용하십시오.

d=defaultdict(make_dict)
d["food"]["meat"]="beef"
d["food"]["veggie"]="corn"
d["food"]["sweets"]="ice cream"
d["animal"]["pet"]["dog"]="collie"
d["animal"]["pet"]["cat"]="tabby"
d["animal"]["farm animal"]="chicken"

다음과 같이 모든 것을 반복하십시오.

def iter_all(d,depth=1):
    for k,v in d.iteritems():
        print "-"*depth,k
        if type(v) is defaultdict:
            iter_all(v,depth+1)
        else:
            print "-"*(depth+1),v

iter_all(d)

인쇄됩니다 :

- food
-- sweets
--- ice cream
-- meat
--- beef
-- veggie
--- corn
- animal
-- pet
--- dog
---- labrador
--- cat
---- tabby
-- farm animal
--- chicken

결국 새 항목을 dict에 추가 할 수 없도록 만들 수도 있습니다. 이 모든 것들을 재귀 적 defaultdict으로 정상으로 쉽게 변환 할 수 dict있습니다.

def dictify(d):
    for k,v in d.iteritems():
        if isinstance(v,defaultdict):
            d[k] = dictify(v)
    return dict(d)

7

나는 setdefault매우 유용하다고 생각한다. 키가 있는지 확인하고 없으면 키를 추가합니다.

d = {}
d.setdefault('new jersey', {}).setdefault('mercer county', {})['plumbers'] = 3

setdefault항상 관련 키를 반환하므로 실제로 ' d' 의 값을 실제로 업데이트하고 있습니다 .

반복에 관해서는, 이미 파이썬에 존재하지 않는 발전기를 충분히 쉽게 작성할 수 있다고 확신합니다.

def iterateStates(d):
    # Let's count up the total number of "plumbers" / "dentists" / etc.
    # across all counties and states
    job_totals = {}

    # I guess this is the annoying nested stuff you were talking about?
    for (state, counties) in d.iteritems():
        for (county, jobs) in counties.iteritems():
            for (job, num) in jobs.iteritems():
                # If job isn't already in job_totals, default it to zero
                job_totals[job] = job_totals.get(job, 0) + num

    # Now return an iterator of (job, number) tuples
    return job_totals.iteritems()

# Display all jobs
for (job, num) in iterateStates(d):
    print "There are %d %s in total" % (job, num)

이 솔루션이 마음에 들지만 시도 할 때 : count.setdefault (a, {}). setdefault (b, {}). setdefault (c, 0) + = 1 "확장 된 할당에 대한 잘못된 표현"
dfrankow

6

다른 사람들이 제안했듯이 관계형 데이터베이스가 더 유용 할 수 있습니다. 인 메모리 sqlite3 데이터베이스를 데이터 구조로 사용하여 테이블을 생성 한 다음 쿼리 할 수 ​​있습니다.

import sqlite3

c = sqlite3.Connection(':memory:')
c.execute('CREATE TABLE jobs (state, county, title, count)')

c.executemany('insert into jobs values (?, ?, ?, ?)', [
    ('New Jersey', 'Mercer County',    'Programmers', 81),
    ('New Jersey', 'Mercer County',    'Plumbers',     3),
    ('New Jersey', 'Middlesex County', 'Programmers', 81),
    ('New Jersey', 'Middlesex County', 'Salesmen',    62),
    ('New York',   'Queens County',    'Salesmen',    36),
    ('New York',   'Queens County',    'Plumbers',     9),
])

# some example queries
print list(c.execute('SELECT * FROM jobs WHERE county = "Queens County"'))
print list(c.execute('SELECT SUM(count) FROM jobs WHERE title = "Programmers"'))

이것은 간단한 예일뿐입니다. 주, 카운티 및 직책에 대해 별도의 테이블을 정의 할 수 있습니다.


5

collections.defaultdict중첩 된 dict를 만들기 위해 서브 클래 싱 될 수 있습니다. 그런 다음 유용한 반복 메소드를 해당 클래스에 추가하십시오.

>>> from collections import defaultdict
>>> class nesteddict(defaultdict):
    def __init__(self):
        defaultdict.__init__(self, nesteddict)
    def walk(self):
        for key, value in self.iteritems():
            if isinstance(value, nesteddict):
                for tup in value.walk():
                    yield (key,) + tup
            else:
                yield key, value


>>> nd = nesteddict()
>>> nd['new jersey']['mercer county']['plumbers'] = 3
>>> nd['new jersey']['mercer county']['programmers'] = 81
>>> nd['new jersey']['middlesex county']['programmers'] = 81
>>> nd['new jersey']['middlesex county']['salesmen'] = 62
>>> nd['new york']['queens county']['plumbers'] = 9
>>> nd['new york']['queens county']['salesmen'] = 36
>>> for tup in nd.walk():
    print tup


('new jersey', 'mercer county', 'programmers', 81)
('new jersey', 'mercer county', 'plumbers', 3)
('new jersey', 'middlesex county', 'programmers', 81)
('new jersey', 'middlesex county', 'salesmen', 62)
('new york', 'queens county', 'salesmen', 36)
('new york', 'queens county', 'plumbers', 9)

1
이것은 내가 찾던 것에 가장 가까운 대답입니다. 그러나 이상적으로는 help_keys ()와 같은 모든 종류의 도우미 함수가 있습니다. 나는 이것을 할 표준 라이브러리에 아무것도 없다는 것에 놀랐다.
YGA

4

"명백한 시도 / 캐치 블록"은 다음과 같습니다.

d = {}
d.setdefault('key',{}).setdefault('inner key',{})['inner inner key'] = 'value'
print d

수확량

{'key': {'inner key': {'inner inner key': 'value'}}}

이것을 사용하여 플랫 사전 형식에서 구조적 형식으로 변환 할 수 있습니다.

fd = {('new jersey', 'mercer county', 'plumbers'): 3,
 ('new jersey', 'mercer county', 'programmers'): 81,
 ('new jersey', 'middlesex county', 'programmers'): 81,
 ('new jersey', 'middlesex county', 'salesmen'): 62,
 ('new york', 'queens county', 'plumbers'): 9,
 ('new york', 'queens county', 'salesmen'): 36}

for (k1,k2,k3), v in fd.iteritems():
    d.setdefault(k1, {}).setdefault(k2, {})[k3] = v

4

Addict를 사용할 수 있습니다 : https://github.com/mewwts/addict

>>> from addict import Dict
>>> my_new_shiny_dict = Dict()
>>> my_new_shiny_dict.a.b.c.d.e = 2
>>> my_new_shiny_dict
{'a': {'b': {'c': {'d': {'e': 2}}}}}

4

defaultdict() 당신의 친구입니다!

2 차원 사전의 경우 다음을 수행 할 수 있습니다.

d = defaultdict(defaultdict)
d[1][2] = 3

더 많은 차원에서 다음을 수행 할 수 있습니다.

d = defaultdict(lambda :defaultdict(defaultdict))
d[1][2][3] = 4

이 답변은 최고 세 수준에서만 작동합니다. 임의의 수준에 대해서는 이 답변을 고려 하십시오 .
Acumenus 2016 년

3

중첩 된 사전을 쉽게 반복하려면 간단한 생성기를 작성하는 것이 어떻습니까?

def each_job(my_dict):
    for state, a in my_dict.items():
        for county, b in a.items():
            for job, value in b.items():
                yield {
                    'state'  : state,
                    'county' : county,
                    'job'    : job,
                    'value'  : value
                }

따라서 복잡한 중첩 사전이 있으면 반복하는 것이 간단 해집니다.

for r in each_job(my_dict):
    print "There are %d %s in %s, %s" % (r['value'], r['job'], r['county'], r['state'])

분명히 생성기는 유용한 데이터 형식을 생성 할 수 있습니다.

트리를 읽기 위해 try catch 블록을 사용하는 이유는 무엇입니까? 키를 검색하기 전에 dict에 키가 있는지 여부를 쿼리하는 것이 쉽고 안전합니다. 가드 절을 사용하는 함수는 다음과 같습니다.

if not my_dict.has_key('new jersey'):
    return False

nj_dict = my_dict['new jersey']
...

또는 아마도 다소 장황한 방법은 get 방법을 사용하는 것입니다.

value = my_dict.get('new jersey', {}).get('middlesex county', {}).get('salesmen', 0)

그러나 다소 간결한 방법으로 python 2.5 이후 표준 라이브러리의 일부인 collections.defaultdict 를 사용하는 것이 좋습니다.

import collections

def state_struct(): return collections.defaultdict(county_struct)
def county_struct(): return collections.defaultdict(job_struct)
def job_struct(): return 0

my_dict = collections.defaultdict(state_struct)

print my_dict['new jersey']['middlesex county']['salesmen']

여기서 데이터 구조의 의미에 대해 가정하고 있지만 실제로 수행하려는 작업에 맞게 쉽게 조정할 수 있습니다.


2

수업이 포장 및 구현의 생각처럼 내가 __getitem__하고 __setitem__그들이 간단한 쿼리 언어를 구현하도록 :

>>> d['new jersey/mercer county/plumbers'] = 3
>>> d['new jersey/mercer county/programmers'] = 81
>>> d['new jersey/mercer county/programmers']
81
>>> d['new jersey/mercer country']
<view which implicitly adds 'new jersey/mercer county' to queries/mutations>

화려하고 싶다면 다음과 같이 구현할 수도 있습니다.

>>> d['*/*/programmers']
<view which would contain 'programmers' entries>

그러나 대부분 나는 그런 일이 정말 재미 있다고 생각합니다. : D


나는 이것이 나쁜 생각이라고 생각합니다. 키의 구문을 예측할 수는 없습니다. 여전히 getitemsetitem을 대체 하지만 튜플을 가져 오십시오 .
YGA

3
@YGA 아마 옳을 지 모르지만, 이런 미니 언어를 구현하는 것은 재미 있습니다.
Aaron Maenpaa

1

데이터 세트가 아주 작게 유지되지 않는 한 관계형 데이터베이스 사용을 고려할 수 있습니다. 원하는 것을 정확하게 수행합니다. 카운트를 쉽게 추가하고, 카운트의 하위 세트를 선택하고, 주, 카운티, 직업 또는 이들의 조합으로 카운트를 집계 할 수도 있습니다.


1
class JobDb(object):
    def __init__(self):
        self.data = []
        self.all = set()
        self.free = []
        self.index1 = {}
        self.index2 = {}
        self.index3 = {}

    def _indices(self,(key1,key2,key3)):
        indices = self.all.copy()
        wild = False
        for index,key in ((self.index1,key1),(self.index2,key2),
                                             (self.index3,key3)):
            if key is not None:
                indices &= index.setdefault(key,set())
            else:
                wild = True
        return indices, wild

    def __getitem__(self,key):
        indices, wild = self._indices(key)
        if wild:
            return dict(self.data[i] for i in indices)
        else:
            values = [self.data[i][-1] for i in indices]
            if values:
                return values[0]

    def __setitem__(self,key,value):
        indices, wild = self._indices(key)
        if indices:
            for i in indices:
                self.data[i] = key,value
        elif wild:
            raise KeyError(k)
        else:
            if self.free:
                index = self.free.pop(0)
                self.data[index] = key,value
            else:
                index = len(self.data)
                self.data.append((key,value))
                self.all.add(index)
            self.index1.setdefault(key[0],set()).add(index)
            self.index2.setdefault(key[1],set()).add(index)
            self.index3.setdefault(key[2],set()).add(index)

    def __delitem__(self,key):
        indices,wild = self._indices(key)
        if not indices:
            raise KeyError
        self.index1[key[0]] -= indices
        self.index2[key[1]] -= indices
        self.index3[key[2]] -= indices
        self.all -= indices
        for i in indices:
            self.data[i] = None
        self.free.extend(indices)

    def __len__(self):
        return len(self.all)

    def __iter__(self):
        for key,value in self.data:
            yield key

예:

>>> db = JobDb()
>>> db['new jersey', 'mercer county', 'plumbers'] = 3
>>> db['new jersey', 'mercer county', 'programmers'] = 81
>>> db['new jersey', 'middlesex county', 'programmers'] = 81
>>> db['new jersey', 'middlesex county', 'salesmen'] = 62
>>> db['new york', 'queens county', 'plumbers'] = 9
>>> db['new york', 'queens county', 'salesmen'] = 36

>>> db['new york', None, None]
{('new york', 'queens county', 'plumbers'): 9,
 ('new york', 'queens county', 'salesmen'): 36}

>>> db[None, None, 'plumbers']
{('new jersey', 'mercer county', 'plumbers'): 3,
 ('new york', 'queens county', 'plumbers'): 9}

>>> db['new jersey', 'mercer county', None]
{('new jersey', 'mercer county', 'plumbers'): 3,
 ('new jersey', 'mercer county', 'programmers'): 81}

>>> db['new jersey', 'middlesex county', 'programmers']
81

>>>

편집 : 이제 와일드 카드 ( None)로 쿼리 할 때 사전을 반환 하고 그렇지 않으면 단일 값을 반환합니다.


왜 반품 목록입니까? 사전 (각 숫자가 나타내는 것을 알 수 있음) 또는 합계 (목록으로 실제로 할 수있는 모든 것)를 반환해야합니다.
Ben Blank

0

비슷한 일이 있습니다. 내가하는 경우가 많이 있습니다.

thedict = {}
for item in ('foo', 'bar', 'baz'):
  mydict = thedict.get(item, {})
  mydict = get_value_for(item)
  thedict[item] = mydict

그러나 많은 수준의 깊이가 있습니다. ".get (item, {})"은 다른 사전이없는 경우 다른 사전을 작성하므로 핵심입니다. 한편, 나는 이것을 더 잘 처리하는 방법을 생각하고 있습니다. 지금은 많이 있습니다

value = mydict.get('foo', {}).get('bar', {}).get('baz', 0)

대신에 나는 :

def dictgetter(thedict, default, *args):
  totalargs = len(args)
  for i,arg in enumerate(args):
    if i+1 == totalargs:
      thedict = thedict.get(arg, default)
    else:
      thedict = thedict.get(arg, {})
  return thedict

다음과 같은 경우 동일한 효과가 있습니다.

value = dictgetter(mydict, 0, 'foo', 'bar', 'baz')

보다 나은? 나도 그렇게 생각해.


0

람다와 defaultdict에서 재귀를 사용할 수 있으며 이름을 정의 할 필요가 없습니다.

a = defaultdict((lambda f: f(f))(lambda g: lambda:defaultdict(g(g))))

예를 들면 다음과 같습니다.

>>> a['new jersey']['mercer county']['plumbers']=3
>>> a['new jersey']['middlesex county']['programmers']=81
>>> a['new jersey']['mercer county']['programmers']=81
>>> a['new jersey']['middlesex county']['salesmen']=62
>>> a
defaultdict(<function __main__.<lambda>>,
        {'new jersey': defaultdict(<function __main__.<lambda>>,
                     {'mercer county': defaultdict(<function __main__.<lambda>>,
                                  {'plumbers': 3, 'programmers': 81}),
                      'middlesex county': defaultdict(<function __main__.<lambda>>,
                                  {'programmers': 81, 'salesmen': 62})})})

0

나는이 기능을 사용했었다. 안전하고 빠르며 쉽게 관리 할 수 ​​있습니다.

def deep_get(dictionary, keys, default=None):
    return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)

예 :

>>> from functools import reduce
>>> def deep_get(dictionary, keys, default=None):
...     return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)
...
>>> person = {'person':{'name':{'first':'John'}}}
>>> print (deep_get(person, "person.name.first"))
John
>>> print (deep_get(person, "person.name.lastname"))
None
>>> print (deep_get(person, "person.name.lastname", default="No lastname"))
No lastname
>>>
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.