[解決済み】ネストされた辞書を実装するのに最適な方法は?
質問
基本的にネストされた辞書に相当するデータ構造を持っています。それは次のように見えるとしましょう。
{'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()
を手動で設定する必要があります。
どのように解決するのですか?
Pythonでネストされた辞書を実装するための最良の方法は何ですか?
これは悪い考えです、やらないでください。代わりに、通常の辞書を使用し
dict.setdefault
を使うようにします。そうすれば、通常の使い方でキーが欠落したときに、期待通りの
KeyError
. もしこの動作にこだわるのであれば、ここで自分の足を撃つ方法を紹介します。
実装
__missing__
の上に
dict
のサブクラスで新しいインスタンスを設定し、返します。
このアプローチでは、これまで (そして文書化された) は Python 2.5 以降、そして (私にとって特に価値のある) それはかなり普通のディクショナリのように表示されます。 のように印刷されます。
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', {})
もう一つの批判は、setdefaultが使われているかどうかに関わらず新しいインスタンスを必要とすることです。しかし、Python(あるいは少なくともCPython)は未使用で参照されない新しいインスタンスの処理についてはかなりスマートで、例えばメモリ内の場所を再利用します。
>>> id({}), id({}), id({})
(523575344, 523575344, 523575344)
自動生成されたデフォルトディクショナリ
この実装は見た目がきれいで、データを検査しないスクリプトで使用する場合は
__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>, {})})})
この出力はかなり不格好で、結果もかなり読めません。典型的な解決策は、手動で検査するために再帰的にdictに変換し直すことです。この非自明な解決策は、読者のための練習として残されています。
パフォーマンス
最後に、パフォーマンスを見てみましょう。インスタンス化のコストを差し引いています。
>>> 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
を好みますし、私がこの種の動作を必要としたすべての状況でそうしてきました。
関連
-
Python機械学習Githubが8.9Kstarsに達したモデルインタープリタLIME
-
Python入門 openを使ったファイルの読み書きの方法
-
[解決済み] 'DataFrame' オブジェクトに 'sort' 属性がない
-
[解決済み] Pythonのリストメソッドであるappendとextendの違いは何ですか?
-
[解決済み] 辞書のリストを辞書の値でソートするにはどうしたらいいですか?
-
[解決済み] Pythonで型をチェックする標準的な方法は何ですか?
-
[解決済み] Vimのマッピングコマンドであるremap, noremap, nnoremap, vnoremapの違いは何ですか?
-
[解決済み] Python 3で文字列をバイトに変換する最良の方法?
-
[解決済み] pandasを使った "大量データ "ワークフロー【終了しました
-
[解決済み】__str__と__repr__の違いは何ですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
Pythonの非常に便利な2つのデコレーターを解説
-
Python 可視化 big_screen ライブラリ サンプル 詳細
-
[解決済み】TypeErrorの修正方法。Unicodeオブジェクトは、ハッシュ化する前にエンコードする必要がある?
-
[解決済み】ilocが「IndexError: single positional indexer is out-of-bounds」を出す。
-
[解決済み】TypeError: 系列を <class 'float'> に変換することができません。
-
[解決済み】「SyntaxError.Syntax」は何ですか?Missing parentheses in call to 'print'」はPythonでどういう意味ですか?
-
[解決済み] 'int'オブジェクトに'__getitem__'属性がない。
-
[解決済み】defaultdictのdefaultdict?
-
[解決済み】ネストした辞書の値を安全に取得する方法
-
[解決済み] 辞書の辞書をマージする方法は?