昔勉強会で使ったもの
トライとダブル配列の基礎@kampersanda
View Slide
キー検索と文字列照合2
キー検索3¨ 文字列のキーを検索して,レコードを取り出す¤ 人間が辞書から検索するのと同じ¤ 応用:辞書検索、かな漢字変換、etc…キー(読み) 内容(表記)レコード1 あっぷる アップル,etc…レコード2 こころ 心,ココロ,此処路,etc…レコード3 かお 顔,買お,^^;,∧( ’Θ’ )∧,etc…レコード4 ぴかちゅう ピカチュウ,光宙,etc…
代表的なキー検索手法4¨ 線形探索(Linear Search)¤ キー集合を探索¨ 二分探索(Binary Search)¤ 整列済みのキー集合を探索¨ 二分探索木(Binary Search Tree)¤ キーの大小関係に基いて二分木を構築¨ ハッシュ表(Hash Table)¤ ハッシュ関数による分配¨ トライ(Trie)¤ トライ木を用いた探索O(mn)O(m lg n)O(m lg n)O(m)O(m)n:キー数、m:パターン長
文字列照合とは5¨ テキストに含まれる特定のパターンを見つける¤ 特定のパターン = キー¤ 例)力任せのアルゴリズム O(mn)n m:パターン長、n:テキスト長n テキスト: “ピーチボーイリバーサイド”n パターン: “リバー”ピ ー チ ボ ー イ リ バ ー サ イ ドリ バ ーずらして照合していく
代表的な文字列照合手法6¨ 状況による分類¤ 長いテキストから少数のキーを検索する場合n 手法:接尾辞配列,接尾辞木,転置索引,etc…n 応用:全文検索,ウェブ検索,ゲノム解析,etc…¤ 短いテキストから多数のキーを検索する場合n 手法:トライ,AC法,etc…n 応用:形態素解析,フィルタリング,ウイルス検知,etc…¨ 参考書籍¤ 情報検索アルゴリズム [北ら 02]
トライ7
トライ8¨ キー集合を表現するラベル付き木¤ キーの構成文字をラベルとする¤ 例n “嶺上開花”,“嶺上牌”,“四風連打”,“四槓子”連子牌開上風槓花打嶺四
トライにおける検索9¨ 根から葉までの移動¤ 入力された文字を辿るn 葉に到達 ➔ Foundn 到達不可 ➔ Not Found連子牌開上風槓花打嶺四
トライにおける検索10¨ 根から葉までの移動¤ “嶺上開花”を検索n 葉に到達 ➔ Found¤ “四槓流れ”を検索n 到達不可 ➔ Not Found連子牌開上風槓花打嶺四
トライにおける終端文字11¨ キーの終端を表す文字‘#’¤ 部分キーを判別するために用いることがある¤ 例n “嶺上”,“嶺上開花”,“嶺上牌”,“四風連打”,“四槓子”連子牌開#上風槓打##花##嶺四
トライの特徴12¨ 時間効率¤ 理論的にはキー数の影響を受けない¤ 該当キーの長さのみに影響を受ける¨ 空間効率¤ 共通の接頭辞は共有される¨ 機能¤ 入力文字列に含まれるキーの検出(形態素解析)¤ 照合失敗位置の検出(スペルチェック)¤ キーの補完(入力補完,文書校正)
トライの実装13¨ 遷移関数 goto を実現する¤ 節点 cur から節点 next への遷移が文字 label に対して定義されている場合、goto(cur, label) = next¤ そうでなければ、goto(cur, label) = –1nextcurlabel
代表的なトライのデータ構造14¨ 行列¤ 時間効率 ➔ 良い O(m)¤ 空間効率 ➔ 悪い O(nσ)¨ 二分木¤ 時間効率 ➔ 悪い O(mσ)¤ 空間効率 ➔ 良い O(n)¨ ダブル配列¤ 時間効率 ➔ 良い O(m)¤ 空間効率 ➔ 良い O(n)m:パターン長n:節点数σ:アルファベットサイズ
行列によるトライ15¨ 次のノードへの参照を配列に格納¤ 遷移時間 O(1)n 高速¤ 空間効率 O(nσ)n 悪い(疎)¨ def goto(cur, label)¤ return M[cur][label]7:牌4:開3:#2:上8:#5:花 6:#1:嶺0:# 嶺 上 開 花 牌0 11 22 3 4 7…σn
二分木によるトライ16¨ 次のノードへの参照を連結リストに格納¤ 遷移時間 O(σ)n 低速¤ 空間効率 O(n)n 良い(密)id label child sibnode structure4 開 5 7例7:牌4:開3:#2:上8:#5:花 6:#1:嶺0:
二分木によるトライの遷移17¨ def goto(cur, label)¤ next := cur.child¤ loopn if next = –1 then return –1n if next.label = label then return nextn next := next.sib3 # 4 4 開 5 7 7 牌 87:牌4:開3:#2:上2 上 3childsib sib
ダブル配列18
コンセプト19¨ 疎な行列をうまく畳み込む# 嶺 上 開 花 牌0 11 22 3 6 834 45 567 78
# 嶺 上 開 花 牌0 11 22 3 6 834 45 5 567 7 78コンセプト20¨ 疎な行列をうまく畳み込む+3+5+7上下で衝突しないようにスライド
# 嶺 上 開 花 牌0 11 22 3 6 834 45 5 567 7 78コンセプト21¨ 疎な行列をうまく畳み込む+3+5+7上下で衝突しないようにスライド畳み込むとダブル配列になる
畳み込みのご様子220 1 2 3 4 5 6 7 8BASECHECK?:牌?:開?:#?:上?:#?:花 ?:#?:嶺0:# 嶺 上 開 花 牌0 ✔+001
畳み込みのご様子230 1 2 3 4 5 6 7 8BASE 0CHECK 0?:牌?:開?:#?:上?:#?:花 ?:#1:嶺0:# 嶺 上 開 花 牌1 ✔+012
畳み込みのご様子240 1 2 3 4 5 6 7 8BASE 0 0CHECK 0 1?:牌?:開?:#2:上?:#?:花 ?:#1:嶺0:# 嶺 上 開 花 牌2 ✔ ✔ ✔
畳み込みのご様子250 1 2 3 4 5 6 7 8BASE 0 0CHECK 0 1# 嶺 上 開 花 牌2 ✔ ✔ ✔+32 2 2?:牌?:開?:#2:上?:#?:花 ?:#1:嶺0:368
ダブル配列26¨ BASE, CHECKという2つの配列によるトライ表現¤ 遷移時間 O(1) ➔ 高速¤ 空間効率 O(n) ➔ 良い0 1 2 3 4 5 6 7 8BASE 0 0 3 5 0 7CHECK 0 1 2 6 4 2 8 28:牌6:開3:#2:上7:#4:花 5:#1:嶺0:
ダブル配列における遷移27¨ def goto(cur, label)¤ next := BASE[cur] + label¤ if CHECK[next] = curn return next¤ return -1# 嶺 上0 1 28:牌6:開3:#2:上0 1 2 3 4 5 6 7 8BASE 0 0 3 5 0 7CHECK 0 1 2 6 4 2 8 26 := BASE[2] + 開 = 3 + 3開 花 牌3 4 5CHECK[6] = 2
ダブル配列の構築28¨ 静的¤ ソート済みのキー集合から構築¤ 他の構築済みのトライから変形¨ 動的¤ ずらして挿入¨ 詳細は略¤ 効率的なダブル配列の構築はめんどくさい¤ とりわけ動的は職人芸
トライの比較29¨ 時間効率¤ ダブル配列 >= 行列 > 二分木¨ 空間効率¤ ダブル配列 > 二分木 > 行列¨ 実装難度¤ ダブル配列 > 二分木 > 行列¨ 結論¤ ダブル配列は優秀¤ 多くのケースで十分に小さく、十分に高速
ダブル配列の応用例30¨ 辞書検索¤ Darts、Darts-clone¨ 形態素解析¤ ChaSen,MeCab¨ 係り受け解析¤ CaboCha¨ ウイルス検知¤ ClamAV¨ Nグラム言語モデル¤ DALM¨ 全文検索エンジン¤ groonga¨ オンラインテキスト処理¤ Cedar¨ 圧縮文字列辞書¤ Xcdat
ダブル配列の圧縮31
ダブル配列の圧縮32¨ ポインタベースなデータ構造なので、簡潔データ構造と比べるとさすがに大きい¤ ダブル配列: Ω(n lg n) ビット¤ LOUDS: 2n + n lg σ + o(n) ビット¨ 圧縮方策¤ 節点削減:冗長な節点を削減する¤ 要素圧縮:要素あたりのメモリ消費を抑える
CHECKの圧縮 [Yata+ 07]33¨ CHECKに親番号ではなく遷移文字を記入する¤ CHECKが log n から log σ に¤ BASE[i] ≠ BASE[i’] for any i ≠ i’0 1 2 3 4 5 6 7 8 9BASE 0 2 3 7 1 9CHECK 嶺 # 上 花 開 # 牌 #6 := BASE[4] + 開 = 3 + 3CHECK[6] = 開# 嶺 上0 1 2開 花 牌3 4 56:開4:上親の参照はできない!!
BASE、CHECKの圧縮 [Kanda+ 17]34¨ 基本的なアイデア¤ 遷移条件さえ満たせば節点は自由に配置できる¤ BASE値とCHECK値が、その要素番号と近くなるように節点を配置し、その差分を代用する¤ ランダムアクセス可能な可変長符号で配列を表現0500001000001500002000002500000 50000 100000 150000 200000 250000CHECK[i] XOR iAddress: iXORほとんどの値が1バイトで表せる0500001000001500002000002500000 50000 100000 150000 200000 250000CHECK[i]Address: i 親の参照もできます