2012-11-19 [長年日記]
■Pythonで多次元の辞書を扱う
nodes[0][9][3]=3 nodes[0][9][4]=5
みたいな感じに使いたい*1。
今、添え字は数字にしてあるけど、実際はこの部分は辞書*2のキーの扱いにしたくて、つまりは文字列とかもつっこみたい。
検索すると、辞書のキーにはタプルが使えるので、
nodes[(0, 9, 3)]=2 nodes[(0, 9, 4)]=3
みたいに書けますよ、というのはでてくるけど、
for i in nodes[(0,9)]: print i #=>3(改行)4(改行) とでてほしい
みたいな書き方はできない。
これは今回の目的にはそぐわない。
ちょっと横道にそれるけど、実はdefaultdicみたいな書き方もしたかったりする。
print nodes[0][9][8] #=>0 とか。これをKeyErrorにしたくない
という感じ。
from pprint import pprint from collections import defaultdict nodes=defaultdict(int) nodes[0] = 5 nodes[2] = 10 pprint(nodes) #=> defaultdict(<type 'int'>, {0: 5, 2: 10}) pprint(nodes[4]) #=>0 pprint(nodes) #=> defaultdict(<type 'int'>, {0: 5, 2: 10, 4: 0})
となる。
おさらい終わり。
nodes = defaultdict(dict)
と書いてみる。
nodes[0][0]=5 nodes[0][2]=10
ok。2次元配列みたいに代入できる。
nodes[0]に初めてアクセスしたときに空の辞書がセットされるからだ。
でもこれだと、
pprint(nodes[0][4]) #occurs KeyError
うん当然。nodes[0]は普通の辞書なわけだから。
じゃあdefaultdictのデフォルト値がdefaultdictになればいいんじゃない?
nodes = defaultdict(defaultdict(int)) #occurs TypeError: first argument must be callable
defaultdict(int)がcallableじゃないっていわれる。
ふむ。
callableならいいんだね? ならばlambdaだ。
nodes = defaultdict(lambda : defaultdict(int)) nodes[0][0]=5 nodes[0][2]=10 pprint(nodes[0][4]) #=>0
いいかんじ。
次は3次元。
nodes = defaultdict(lambda : defaultdict(lambda : defaultdict(int))) nodes[0][1][0]=5 nodes[0][1][2]=10 pprint(nodes[0][1][4]) #=>0
ふむ……。なるほど。
nodes = defaultdict(lambda : defaultdict(lambda : defaultdict(int))) ^^^^^^^^^^^^^^^^^^^^^^^^^
ここの部分が、一段上のdefaultdicに与えてやった初期値に等くなる。
defaultdic.default_factoryで引っ張ってこれそう。
def multi_dimension_dict(dimension): nodes = defaultdict(int) for i in range(dimension-1): lm = nodes.default_factory nodes = defaultdict(lambda : defaultdict(lm)) return nodes
nodes = multi_dimension_dict(2) nodes[0][0]=5 nodes[0][2]=10 pprint(nodes[0]) #=>defaultdict(<type 'int'>, {0: 5, 2: 10}) pprint(nodes[0][4]) #=> 0 pprint(nodes[0]) #=>defaultdict(<type 'int'>, {0: 5, 2: 10, 4: 0})
nodes = multi_dimension_dict(3) nodes[0][9][5]=3 pprint(nodes[0][9][5]) #=> 3 pprint(nodes[0][9]) #=>defaultdict(<function <lambda> at 0x108067938>, {5: 3}) pprint(nodes[0]) #=>defaultdict(<function <lambda> at 0x103d7a938>, {9: defaultdict(<function <lambda> at 0x103d7a938>, {5: 3})})
よしよし。
nodes = multi_dimension_dict(3) nodes['aa']['bb']['cc']=4 nodes['aa']['bb']['dd']=3 pprint(nodes['aa']['bb']) #=>defaultdict(<function <lambda> at 0x109a51b18>, {'cc': 4, 'dd': 3}) sum=0 for key3rd in nodes['aa']['bb']: sum += nodes['aa']['bb'][key3rd] pprint(sum) #=>7 nodes['dd']['aa']['cc']=8 nodes['dd']['bb']['dd']=6 sum=0 for i,n in nodes.iteritems(): for j,o in n.iteritems(): for k,p in o.iteritems(): sum += nodes[i][j][k] #sum += p と等価 pprint(sum) #=>21(4+3+8+6)
まあいいんじゃないですか?
でもこれだとintしか入れられない。
def multi_dimension_dict(dimension, callable_obj=int): nodes = defaultdict(callable_obj) for i in range(dimension-1): p = nodes.copy() nodes = defaultdict(lambda : defaultdict(p.default_factory)) return nodes
にしたらいいかも!!
問題は、
nodes = multi_dimension_dict(3) nodes[0][1]=3 nodes[0][1][3]=0 #occurs TypeError
とか次元を間違えて代入しちゃうとリカバリが効かないこと。
これは解決方法が見つからないので使う時に注意するしかないかなー。