文字列処理
@ey429
文字列
文字列
● 文字の列
● This is a string.
● 文字≒数字
なぜ文字列か?
● 大量の文字列を
高速に処理したいことはよくある
● ログ
● DNA 情報
● 自然言語処理
● 検索エンジン
…
身の回りの文字列
● 概算してみる
● 例 1 : NPCA の Skype 部屋
1 つのコメントが 10~50 文字くらい
1 日平均 100 コメントくらい?
1 年の量 : (1000~5000) 365✕
    ≒ 10^5~6
身の回りの文字列
● 例 2 : Twitter
1 ヶ月で約 20 億ツイート
1 年の量 : 2×10^9×12
≒2×10^10
● 例 3 : ウェブサイト
サイトの数だけでも n 億
中身も入れると…
例えば
● 文字列 S に部分文字列 T が何回現れるかを
求めたい ( 検索エンジンなど)
● 愚直にやると O((|S|-|T|)×|T|)
計算量
Skype : 10^6
Twitter : 10^10
|S|=10^10,|T|=10^9
→(|S|-|T|)×|T|
≒ 10^18
→ 死
例えば
● 文字列 S に部分文字列 T が何回現れるかを
求めたい
● 愚直にやると O((|S|-|T|)*|T|)
→ 死
● O(Alog B) (A,B |S|,|T|)≦ ぐらいで求めたい
高速な処理のために
● 重要なのは
– 処理効率(計算量)
– 作業領域量(メモリ)
→ 適切なデータ構造を使うことが必要
高速な処理のために
● 重要なのは
– 処理効率(計算量)
– 作業領域量(メモリ)
→ 適切なデータ構造を使うことが必要
簡潔データ構造  
簡潔データ構造
● 最適に近いデータサイズ
● 索引を利用して高速な処理
を可能にするデータ構造
● 一番基本&重要
→ 完備辞書
完備辞書
● ビット列 T に対して (b=0,1)
access(T,p) = T[p]
rankb(T,p) = T の p 番目より前の b の数
selectb(T,p) = T の中で (p+1) 番目の b の位置
を求める機能を備えたデータ構造
● 簡潔!
単純な実装方法
● D[i]=i 番目より前に 1 が何個あるか
とすると
rank1(p) = D[p] : O(1)
rank0(p) = p – D[p] : O(1)
access(p) = D[p] – D[p-1] : O(1)
selectb(p) = rankb(x) = p+1
となる x を二分探索 : O(log |D|)
応用例
● 完備辞書は汎用
● データをビットで表すことで多くのデータ
に応用できる
● 集合
B[i]=i が集合に含まれる ? 1 : 0
x が集合に含まれるかどうか : access(B,x)
x 未満の最大要素 : select1(rank1(B,x)-1)
e 以上 s 未満の要素数 : rank1(B,s)-rank1(B,e)
応用例
● データ圧縮の際の符号化
● ウェーブレット木/行列
ウェーブレット木  
ウェーブレット木
● 完備辞書の応用の一つ
● 二分木
● 文字列に対する多くの処理を実現できる
● 葉が各文字を表す
● 各頂点はビット列を持つ
a b c a c d a
00 01 10 00 10 11 00
0010110(abcacda)
0100(abaa) 001(ccd)
a b c d
0
0 0
1
11
1 ビット目
2 ビット目
文字
あっ…(察し)
ウェーブレット木
● 文字を 2 進数で表したとき、次に見るビッ
トが 0 なら左、 1 なら右の子に下り、各頂
点にはそれぞれの文字の次に下るビットを
追加する
● できたビット列それぞれに対し完備辞書を
適用することで各操作を実現する
● 深さは log σ (σ は文字の種類 )
各操作
● access(p): 元の文字列の p 番目を復元する
● rankc(p): p 番目より前の c の数を求める
● selectc(p): (p+1) 番目の c の位置を求める
● quantile(s,e,r): s 番目〜 (e-1) 番目で (r+1) 番
目に大きいものを求める
● topk(s,e,k) : s~e で頻度の多い順に k 個返す
● rangefreq(s,e,a,b) :
s~e で a~b の値の出現数を返す
各操作
● rangemax(/min)k(s,e,k) :
s~e で大きい ( /小さい ) 順に k 個返す
● intersect(s1,e1,s2,e2) :
s1~e1,s2~e2 で共通する要素を返す
…
● 列に対する多くの処理を実現できる
接尾辞配列
接尾辞 (suffix)
● Sk : 文字列 S の (k+1) 番目から最後までの
  部分文字列
● S = “thatzatthatthatzatstar” なら
– S2 = “atzatthatzatstar”
– S4 = “zatthatzatstar”
– S16 = “zatstar”
接尾辞配列 (suffix array)
● S0~S|S|-1 を辞書順にならべた配列
● ソートされていることからある文字列は連
続した区間内の接尾辞配列の接頭辞として
現れる
● O(|S|log |S|) ほどで構築するアルゴリズムが
存在する
接尾辞文字列の例
● “akasakas”
akas
akasakas
as
asakas
kas
kasakas
s
sakas
接尾辞文字列の例
● “akasakas”
akas
akasakas
as
asakas
kas
kasakas
s
sakas
接尾配列の特徴
● S0~S|S|-1 を辞書順にならべた配列
● ソートされていることからある文字列は連
続した区間内の接尾辞配列の接頭辞として
現れる
● T が接頭辞となっている区間を s~e とする
と、 T が S 内に現れる回数は e-s+1
● e,s は二分探索で O(log |S|) で求められる
● よって最初の問題は O(|T|log |S|) で解ける
BWT   
BWT(Burrows Wheeler Transform)
● より使用メモリを少なくするために接尾配
列を圧縮したもの
接尾配列を SA とすると
Bt[p] = T[SA[i]-1] (SA[i]>0)
T[|T|-1] (SA[i]=0)
● Wheeler が考案したが
Wheeler
「自明であり、公開するまでもない」
BWT(Burrows Wheeler Transform)
● より使用メモリを少なくするために接尾配
列を圧縮したもの
接尾配列を SA とすると
Bt[p] = T[SA[i]-1] (SA[i]>0)
T[|T|-1] (SA[i]=0)
● Wheeler が考案し、現在でも文字列解析に
欠かせないものとなっている
BWT の特徴
● 接尾配列には同じ部分列は連続した区間に
現れる
→ 繰り返し同じワードが出てくる文字列で
は同じ文字が BWT に連続して現れやすい
→ 連長圧縮( nnnpccaaaa→n3p1c2a4) をす
ると比較的効率的に圧縮可能
● 復元も可能
FM-index
● 接尾配列上で文字列 Q が区間 [s,e] に対応
しているとき c+Q に対応する区間について
s = rankc(Bt,s)+C[c]
e = rankc(Bt,e)+C[c]
(C[c] は T の中の c 未満の文字の数 )
が成り立ち、ウェーブレット木を用いて最
初の問題に O(Mlog σ) で答えられる
(M はクエリ文字列の長さ ,σ は文字種類 )
テキストマイニング  
その他の操作
● 文書 d1,…,dn とそれぞれの重み w1,...,wn を考
えるとき、
df(Q) : Q が出現する文書数
mdf(Q) : Q の出現数の合計
cd(Q1,...,Qm) :
Q1,...,Qm を全て含む文書を求める
● 文書の種類 ( 論文の分野の特定 ) 判別など
文書を連結する
● S=d1$d2$…$dn($ は特殊文字)を考える
● S に対して接尾辞配列を構築する
→Q の出現範囲 [s,e] が求まる
● また
D[i] : 接尾辞配列で i 番目の文書番号のう
ち一番最近出てきたもののインデックス
とする
df(Q)
● 同じ文書番号が複数出るとき
i 番目が一回目→ D[i]<s
i 番目が二回目以降→ D[i] s≧
● よって D についてウェーブレット木を構築
し rangefreq(s,e,0,s) を求めればよい
mdf(Q)
● Q の出現回数 = e-s+1
cd(Q)
● 各 Qk について対応する範囲 [sk,ek] を求め、
それらの共通要素 intersect をウェーブレッ
ト木で求める
共通部分文字列
● 接尾辞木という接尾辞配列を拡張した木を
構築することで O(min(|S|,|T|)) で求めること
ができる
XBW
● BWT を木に拡張したもの
● キーワードを管理し、 p を接頭語に持ち s
を接尾語に持つキーワードを求めたりでき
る
データ圧縮
● 演算に比べメモリへのアクセスは遅い
→ 上位メモリで操作ができるようにしたい
→ 使う記憶領域を減らしたい
● 符号化( Γ 符号、ハフマン符号)
● フレーズ(文字列単位)に分けて圧縮
( LZ 圧縮、転置索引、 N-gram 索引)
● 透過的圧縮(圧縮したまま操作可能)
~fin~
Thank you for listening!

文字列処理