そんなの五秒で実装できるのでは?
後輩A君から「Pythonで階層構造を持った辞書を作りたい」という質問がきた。当然、
「そんなの五秒でできるジャン」
と思ったわけである。しかし結局、質問に答えるまでに30分。解説するのに30分ほど時間を費やしてしまった。また、実装しろと言われたら忘れてしまいそうなので、メモとして残しておきたい。
そもそも、後輩君の質問は単に2重構造、3重構造の辞書を作りたいということではなく、下記のような入力を受け取って
A1,B1,C1,3
A1,B1,C2,1
A1,B1,C3,5
A1,B2,C1,4
A1,B2,C2,3
A1,B2,C3,1
A1,B3,C1,3
A1,B3,C2,2
A1,B3,C3,5
A2,B1,C1,3
A2,B1,C2,5
A2,B1,C3,3
A2,B2,C1,2
A2,B2,C2,1
A2,B2,C3,3
A2,B3,C1,4
A2,B3,C2,4
A2,B3,C3,5
自動的に以下のような階層的なdictionaryをつくりたいということであった。
{'A1': {'B1': {'C1': 1,
'C2': 1,
'C3': 3},
'B2': {'C1': 3,
'C2': 3,
'C3': 4},
'B3': {'C1': 2,
'C2': 5,
'C3': 5}},
'A2': {'B1': {'C1': 4,
'C2': 4,
'C3': 1},
'B2': {'C1': 1,
'C2': 3,
'C3': 3},
'B3': {'C1': 4,
'C2': 2,
'C3': 3}}}
五秒じゃできないじゃん
で、これに案外てこずってしまった。まず思いついたのが、defaultdictを使う方法である
import collections
hoge = collections.defaultdict(lambda : collections.defaultdict(lambda : collections.defaultdict(int))
みたいにlambda式を使ってやれば、3重にネストされたdictを作成することができる。しかし。この方法は微妙だ。そもそも、事前に階層の数を知ってる必要があるし、階層の数が、要素によって違う場合もあるだろうし、あまり一般性がない。さらに、lambda式でdefault値を定義したdefaultdictはpickle化するのも大変だ。
で考えたのが以下の方法。
import pprint
def make_tree_dict(inputs):
tree_dict = {}
for i, ainput in enumerate(inputs):
pre_dict = tree_dict
for j, key in enumerate(ainput):
if j == len(ainput)-2:
pre_dict[key] = ainput[-1]
break
elif key not in pre_dict:
pre_dict[key] = {}
else:
pass
pre_dict = pre_dict[key]
return tree_dict
if __name__ == "__main__":
pp = pprint.PrettyPrinter(width=10,compact=True)
inputs = []
with open("example.csv") as f:
for line in f:
line = line.rstrip().split(",")
inputs.append(line)
hoge = make_tree_dict(inputs)
pp.pprint(hoge)
実際に上記のプログラムを動かすと、先に示したような階層的なdictのoutputを得ることができる。
一度も、tree_dictに直接代入が行われないのに、tree_dictの中身が更新されていくという、なんとも不思議な感じがするプログラムだが、これが動くのである。解説まで載せようと思ったが、時間がないのでまた今度に、、、、
ついでに上記のスクリプトは、下記のように要素ごとに階層の数が異なるinputに対しても適用できる。
A1,B1,C1,1
A1,B1,C2,D1,3
A1,B1,C3,5
A1,B2,C1,D1,5
A1,B2,C2,2
A1,B2,C3,5
A1,B3,C1,2
A1,B3,C2,D1,4
A1,B3,C2,D2,10
A1,B3,C3,2
A2,B1,C1,4
A2,B1,C2,D1,5
A2,B1,C3,5
A2,B2,C1,D1,6
A2,B2,C2,3
A2,B2,C3,D1,8
A2,B3,C1,2
A2,B3,C2,5
A2,B3,C3,4
でこんな感じのdictが得られる
{'A1': {'B1': {'C1': '1',
'C2': {'D1': '3'},
'C3': '5'},
'B2': {'C1': {'D1': '5'},
'C2': '2',
'C3': '5'},
'B3': {'C1': '2',
'C2': {'D1': '4',
'D2': '10'},
'C3': '2'}},
'A2': {'B1': {'C1': '4',
'C2': {'D1': '5'},
'C3': '5'},
'B2': {'C1': {'D1': '6'},
'C2': '3',
'C3': {'D1': '8'}},
'B3': {'C1': '2',
'C2': '5',
'C3': '4'}}}
私はアホだった。
冷静に考えれば、defaultdictを再起的に定義すればいいだけだったわということに3年経って気づいてしまった。。。
from collections import defaultdict
recursive_defaultdict = lambda: defaultdict(recursive_defaultdict)
hoge = recursive_defaultdict()
hoge["A"]["T"]["G"]["C"] = 1
print(hoge["A"]["T"]["G"]["C"])
あー。ちょっとアホだったなぁ。。。。