15分でわかる(とうれしい)Suffix Arrayの簡単な圧縮法
文字列が超長い場合SuffixArrayが大きすぎてメモリが一杯になってしまう。
ので圧縮して小さくしたい。基本的な圧縮法である差分圧縮を使った簡単なSuffixArrayの圧縮法を紹介する。
参考:
さて以下のSuffixArrayを差分圧縮したい。
文字列 : b a n a n a SuffixArray: 6 4 2 1 5 3
SuffixArrayの差分を取る。
SuffixArray: 6 4 2 1 5 3 差分 : 6 -2 -2 -1 4 -2
マイナスの数がたくさん出てきた。負の数があると差分圧縮できない。ので差分が正の数になるようにSuffixArrayをいじる。
SuffixArrayのそれぞれの数字に1から順に番号をふる。
Index : 1 2 3 4 5 6 SuffixArray: 6 4 2 1 5 3
これをIndexとよぶ。Indexは順番にふった数字なので差分をとると
Index: 1 2 3 4 5 6 差分 : 1 1 1 1 1 1
となる。マイナスがなく、数が小さい。理想的。
理想的なのでIndexを使う。SuffixArrayさん、さようなら。
・・・というわけにはいかない。そもそもSuffixArrayを消しては意味が無い。
のでSuffixArrayを一意に復元できるようなIndexを考える。
文字列 : b a n a n a Index : 1 2 3 4 5 6 SuffixArray: 6 4 2 1 5 3
Indexに対応するSuffixArrayの値に1足した値をSuffixArrayから探す。例えばIndex=3に対して
# Index=3 に SuffixArray=2 が対応 Index : 1 2 [3] 4 5 6 SuffixArray: 6 4 [2] 1 5 3 # SuffixArray=(2+1)=3を探す Index : 1 2 [3] 4 5 6 SuffixArray: 6 4 [2] 1 5 [3]
となる。ここで見つけたSuffixArray=3に対応するIndexを新Indexとしてみる。
# SuffixArray=3 に Index=6 が対応 Index : 1 2 [3] 4 5 [6] SuffixArray: 6 4 [2] 1 5 [3] # Index=6をIndex=3に対する新Indexにする Index : 1 2 [3] 4 5 [6] SuffixArray: 6 4 [2] 1 5 [3] 新Index : 6
同じことを他のIndexに対してもやってみる。ただしSuffixArray=(6+1)=1とする。
Index : 1 2 3 4 5 6 SuffixArray: 6 4 2 1 5 3 新Index : 4 5 6 3 1 2
この新Indexに対して2点気になることがある。つまり
- 差分圧縮できるの? - この新IndexからSuffixArrayが復元できるの?
ということ。これらを検証する。
まず差分圧縮できるか?実は全体に対してはできないのだが、部分的に可能
SuffixArray: [6] [4] [2] [1 5] [3] 新Index : [4 5 6] [3] [1 2]
差分圧縮可能な箇所(差分が正の数になってるところ)を[]でまとめた。新Indexのほうが圧縮できる範囲が大きい。
次。SuffixArrayの復元。こっちも実は新Indexだけではできないのだが(またかよ)、SuffixArrayの値が1になる位置(サンプル位置)を余分にもっておけば復元できる。
今回の場合以下のとおり
SuffixArray: 6 4 2 [1] 5 3 新Index : 4 5 6 3 1 2 サンプル位置は4だよ
まずSuffixArrayの4番目の値が1なのはサンプル位置が4なのですぐわかる。
新Index : 4 5 6 3 1 2 SuffixArray: 1
つぎにIndexの4番目の位置にある値をみる。今回は3。
新Index : 4 5 6 [3] 1 2 SuffixArray: 1
SuffixArrayの3番目の位置に2を入れる。
新Index : 4 5 6 [3] 1 2 SuffixArray: 2 1
つぎにIndexの3番目の位置にある値をみる。今回は6。
新Index : 4 5 [6] 3 1 2 SuffixArray: 2 1
SuffixArrayの6番目の位置に3を入れる。
新Index : 4 5 [6] 3 1 2 SuffixArray: 2 1 3
これを繰り返すと
新Index : 4 5 6 3 1 2 SuffixArray: 6 4 2 1 5 3
となる。ここで得られたSuffixArrayが最初のSuffixArrayと同じであることは本記事の最初の部分を見て確認されたい。
なお今回は簡単のためにサンプル位置としてSuffixArray=1の点を使ったが、実際はSuffixArrayがNの倍数の位置全部をサンプル位置にする。Nが小さいほどメモリをたくさん使うが、SuffixArrayの復元時間は短くなる。N=1のときが普通のSuffixArrayとなる。
余談だが、ここで紹介した簡単な圧縮法は一般にCompressed Suffix Arrayと呼ばれている。