DAWG(4-3): MPHFライブラリ
前回までで最小完全ハッシュ関数(MPHF)の実装方法(の一つ)が分かったので、C++から使いやすくするためにライブラリ(と云う程の規模ではないけど)としてまとめておいた。
・mphf-0.0.1
実装の大枠は、前回のcommon lisp版のものと同様なので略*1。
ハッシュ関数の数は二個に固定*2。
例:
コマンドの実行例。
参照: wikipedia各国語タイトルの取得方法
# ビルド $ cd mphf-0.0.1 $ make # データ: wikipediaの各国語タイトル $ ls -lh wiki.title -rw-r--r-- 1 user user 103M 2010-06-23 01:10 wiki.title $ wc -l wiki.title 5340378 wiki.title # 約530万語 # MPHF生成 $ time ./mkmphf mphf.wiki.idx ~/dev/cc/louds/wiki.title = Initialize == key count: 5340378 # キー数 == hash value limit: 11161390 # ハッシュ値の上限 (キー数*2.09) DONE = Mapping Step: == loop: 1/32 # Mapping Stepのグラフ生成/チェックループ == loop: 2/32 # ハッシュ値計算方法がいいかげんなためか期待値(三割前後)よりも成功率が低い == loop: 3/32 == loop: 4/32 == loop: 5/32 == loop: 6/32 == loop: 7/32 == loop: 8/32 == loop: 9/32 Done = Assigning Step: Done = Ranking Step: Done = Save: mphf.wiki.idx Done real 0m31.833s user 0m31.346s sys 0m0.488s $ ls -lh mphf.wiki.idx -rw-r--r-- 1 user user 2237164 2010-07-21 03:56 mphf.wiki.idx # 2.2MB: キー1つにつき約3.4bit # ハッシュ値計算 $ head wiki.title ! !! !!! !!!Fuck_You!!! !!!_(album) != !? !Avast !Kwi !LOUD! $ head wiki.title | ./mphf mphf.wiki.idx 3459282 5311644 984391 2303434 5104951 450980 450982 3505548 2184885 1526653 $ ./mphf mphf.wiki.idx < wiki.title | sort -n | head 0 1 2 3 4 5 6 7 8 9 $ ./mphf mphf.wiki.idx < wiki.title | sort -n | tail 5340368 5340369 5340370 5340371 5340372 5340373 5340374 5340375 5340376 5340377 $ ./mphf mphf.wiki.idx < wiki.title | sort -n | uniq | wc -l 5340378
*1:本質的に差異があるのは次の二点のみ:
1] phfからmphfへの変換方法:
- 上のライブラリでは『Simple and Space-Efficient Minimal Perfect Hash Functions』の4.1節(「Imporveing the space」)の最後で示唆されている方法を採用している。
- 使用ハッシュ関数を決定してからrankしていたのを、rankしてから使用ハッシュ関数を決めるように。
- 若干処理は多くなるが、省スペース。詳細は省略。
2] Mapping Stepでのグラフの循環チェック(and Assigning Step用の並び替え):
- 前回は、各頂点(ハッシュ値)が接続する全ての枝をリストとして保持する配列をチェックのために用意していた。
-- 循環性のチェックは、枝リストのサイズが一の頂点の削除を繰り返すことで行う。
-- 処理ステップ的には効率が良さそうな気がするけど、結構スペースを消費する。
-- オリジナルのグラフ(キーごとのハッシュ値セット) + リストの配列 + 削除順に並び替えを行ったグラフ 分の領域が必要。
-- Mapping Stepが全体を通して一番スペースを要する。
- 今回(?)は、チェック用に補助的なデータ構造は用意しない。
-- 枝の片方の頂点でソート、ユニークな頂点を持つ枝(= 末端の枝)の除去(実際には先頭との入れ替え)、除去分を除いてもう片方の頂点でソート、... を繰り返すことでチェック(and 並び替え)を行う。
-- 全て削除できたのなら非循環。
-- 複数回ソート及び走査を行う必要があるが、作業領域はグラフ一個分 = キーセットのサイズ x sizeof(unsigned) x Nで済む。※ Nはハッシュ関数の数、ハッシュ値の型はunsigned intと仮定
*2:二個の方が実装が簡単になるため。ハッシュ関数を三個にした方が非循環グラフの生成は容易(Mapping Stepでのループ回数を減らせる)で、最終的なデータサイズも節約できるので、いつか変更するかもしれない。