ネストされた辞書を実装する最良の方法は何ですか?


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()手動で設定する必要があります。

回答:


179

Pythonでネストされた辞書を実装する最良の方法は何ですか?

これは悪い考えです。やらないでください。代わりに、通常の辞書を使用してdict.setdefaultwhere aproposを使用しますKeyError。これにより、通常の使用法でキーが欠落している場合、期待どおりの結果が得られます。あなたがこの振る舞いを得ることを主張するならば、これは足で自分を撃つ方法です:

サブインスタンスを実装__missing__してdict、新しいインスタンスを設定して返します。

このアプローチはPython 2.5以降で利用可能(および文書化)であり、(特に私にとっては貴重です)autovivified 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でゲッターをさらに呼び出すことを回避するので役立ちます。残念ながら、設定されているため、返すことはできません。)

これらは最も支持されている回答と同じセマンティクスですが、コードの半分の行であることに注意してください-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

質問者はこれはきれいではないと考えていますが、私は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', {})

もう1つの批判は、setdefaultが使用されているかどうかに関係なく、新しいインスタンスが必要であることです。ただし、Python(または少なくともCPython)は、未使用で参照されていない新しいインスタンスの処理についてはかなりスマートです。たとえば、メモリ内の場所を再利用します。

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

自動有効化されたdefaultdict

これは見栄えの良い実装であり、データを検査していないスクリプトでの使用は、実装と同じくらい便利です__missing__

from collections import defaultdict

def vivdict():
    return defaultdict(vivdict)

ただし、データを検査する必要がある場合、同じ方法でデータが入力された自動ビビティ化されたdefaultdictの結果は次のようになります。

>>> 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 of dictですd['primary']['secondary']['tertiary'].append(element)。深さごとに3つの異なるクラスを定義できますが、より明確な解決策を見つけたいです。
Eric Duminil 2017

@EricDuminil- d['primary']['secondary'].setdefault('tertiary', []).append('element')?? お世辞に感謝しますが、正直に言って-私は実際には使用しません__missing__-私はいつも使用していますsetdefault。私はおそらく私の結論/紹介を更新する必要があります...
アーロンホール

@AaronHall正しい動作は、必要に応じてコードがdictを作成することです。この場合、以前に割り当てられた値を上書きします。
ネヘム

@AaronHallまたThe bad lookup will remain in the dictionary.、私がこのソリューションの使用を検討しているときに何が意味するのかを理解するのを手伝ってくれますか?とても有難い。THX
nehem

@AaronHall setdefault2つを超えるレベルの深度をネストすると、問題が失敗します。説明されているように、Pythonのどの構造も真の活性化を提供できないようです。私は、dictの参照とネストされた属性のリストを受け入れるget_nestedための方法と、2つの方法を説明する必要がset_nestedありました。
ネヘム

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

彼らがpython 3.xに移行したときに誰かがこの問題を抱えていますか?stackoverflow.com/questions/54622935/...
ジェイソン

@jason pickleはpythonバージョン間でひどいです。保持したいデータの保存に使用しないでください。キャッシュや、ダンプして自由に再生成できるものにのみ使用してください。長期保存やシリアル化の方法ではありません。
nosklo

これらのオブジェクトを格納するために何を使用しますか?私のautovivificationオブジェクトには、pandasデータフレームと文字列のみが含まれています。
ジェイソン

@jasonデータに応じて、JSON、csvファイル、またはsqliteデータベースを使用して格納するのが好きです。
nosklo

30

私がこれほど小さいものを見たことがないからといって、ここにあなたが好きなように入れ子になるdictがあり、汗はありません:

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

2
@wberry:実際に必要なのはだけですyodict = lambda: defaultdict(yodict)
martineau 2013

1
承認されたバージョンはのサブクラスでdictあるため、完全に同等になるx = Vdict(a=1, b=2)ように作業する必要があります。
wberry 2013

@wberry:承認された回答の内容に関係なく、のサブクラスでdictあることは、OPがそれらを実装するための「最善の方法」のみを要求した要件ではなく、そのうえ、/すべきではありませんとにかく、Pythonではそれほど重要です。
martineau 14年

24

YAMLファイルを作成し、PyYamlを使用してそれを読み取ることができます。

ステップ1:YAMLファイル「employment.yml」を作成します。

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

ステップ2:Pythonで読む

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は、多くの深くネストされたデータ(および構成ファイル、データベースのモックアップなど)を入力するための私の選択です。OPが余分なファイルを必要としない場合は、いくつかのファイルで通常のPython文字列を使用し、それをYAMLで解析します。
kmelvn 2009年

YAML文字列を作成する際の良い点:これは、 "tempfile"モジュールを繰り返し使用するよりもはるかにクリーンな方法です。
ピート

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は、私のお気に入りのpythonの追加機能の1つです。.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

最終的には、新しいアイテムを辞書に追加できないようにすることができます。これらすべて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' の値を適切に更新しています。

反復に関して言えば、Pythonにジェネレーターがまだない場合でも、ジェネレーターを簡単に作成できると思います。

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
これが私が探していたものに最も近い答えです。しかし、理想的には、walk_keys()などのあらゆる種類のヘルパー関数があるでしょう。標準ライブラリにこれを行うものがないことに驚いています。
YGA

4

「不愉快なtry / catchブロック」に関しては:

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

defaultdict() あなたの友だちです!

2次元辞書の場合、次のことができます。

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

より多くの次元については、次のことができます。

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

この回答は、せいぜい3つのレベルでのみ機能します。任意のレベルについては、この回答を検討してください
Acumenus 2017年

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


これは悪い考えだと思います。キーの構文を予測することはできません。あなたはまだオーバーライドしますGetItemメソッドおよびSetItem関数をが、それらはタプルを取る持っています。
YGA

3
@YGAおそらくその通りですが、このようなミニ言語の実装について考えるのは楽しいです。
アーロンメンパー09年

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.