SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

高速かつ省メモリで文字列を扱うデータ構造「wavelet tree」

大規模な文字列データの文字検索/カウントを効率的に行う


ダウンロード ソースコード (6.8 KB)
ダウンロード バイナリファイル (51.6 KB)

WTの使い方

 WTは、任意の整数列を引数に渡して構築します。任意の整数列unsigend int* Tもしくはunsigned char* Tを用意し、WTのコンストラクタに渡します。具体的なソースコードは以下の通りです。

WTの構築
#include "waveletTree.hpp"
#define alphaSize 0x100
using namespace cs;
...
unsigend char T[10] = "abccbbab"
waveletTree wt(T,10,alphaSize);

 実際にrankselectを利用する場合はwaveletTreeのメンバ関数であるrank()select()を呼び出します。以下では、上述の例のrankselectを実際に求めています。

WTの利用
printf("rank(5,b)=%d rank(5,c)=%d select(2,b)=%d select(2,c)=%d\n",
    opb.rank(5,'b'),opb.rank(5,'c'),
    opb.select(2,'b'),opb.select(2,'c'));

 waveletTreeを構築、利用する一連の流れのサンプルは「waveletTree_unitTest.cpp」にあります。このコードでは最初にランダムな整数列を生成した後、それからwaveletTreeを構築します。その後、実際直接rankselectを求めた場合と値をチェックした後、速度計測をしています。

 waveletTreeは圧縮率の高い整数集合コンテナとしても利用できますが、その真価を発揮するのは圧縮索引などで利用する場合です。これらのデータ構造での使用例については、今後の記事を参考にしてください。

技術解説

 waveletTreeは[1]で圧縮索引用にはじめて登場しました。詳しい解説は[2]に書いてあります。ここでは、T[0...9] = abccbbabcaに対するwaveleTreeの例を通して簡単な説明をします。waveletTreeは、内部データ構造は木構造であり、各木の節点にはrank/select操作を備えたbit vectorが付随されてます(図1)。

図1
図1

 この木は入力データから求められたHuffman木(図2)と同じ形をしています。

図2
図2

 次に各節点のbit vectorがどのように定義されるかを説明します。まず根に付随するbit vectorはT[0...n-1]の各文字をHuffman符号で符号化した場合の最初のbitを並べたものです。今回の例ではbの最初のbitが'0'、aとcの最初のbitが'1'なので、bが出現した位置に'0'、aまたはcが出現した位置に'1'を配置したbit vectorが根に付随するbit vectorとなります。次に、Huffman木に沿ってTの各文字を元の順序関係を保ったまま左右の子に移動させます。今回の例の場合、bが左側の子、a,cが右側の子に移動します。T=abccbbabcaでのa,cはa_cc__a_caと出現しているので、右側の子にはaccacaが移動します。そして今度は2番目のbitを順に並べたものを各節点のbit vectorにします。a、cの2番目のbitはそれぞれ0、1なので、右側の子に付随するbit vectorは011010となります。これを葉にたどり着くまで再帰的に繰り返します。

 Waveletの名前の由来はおそらく、Huffman符号に従って各文字を再帰的にfilteringしている部分にあると考えられます。

 次にこのデータ構造を用いて、どのようにしてrankselectを求めるかを例を通して説明します。はじめの例としてrank(5,c)を求める場合を扱います。rankの場合、根からcの葉に向かって各節点でrankを求めていきます。まず根の節点において、cの最初のbitが1なので、rank(5,1)を求めます。この場合の答えは3=rank(5,1)です。この3の意味はaまたはcが5番目の位置までで3回出現しているという意味です。次に右の子に移動し、rank(3,1)を求めます。この場合の答えは2=rank(3,1)なので、rank(5,c)の答えが2だと分かります。

図3
図3

 次にselect(1,a)の例を扱います。selectの場合、aの葉から根に向かって各節点でselectを求めていきます。まずaの親の節点において、select(1,0)を求めます。この場合の答えは3=select(1,0)です。次に根である親の節点においてselect(3,1)を求めます。この場合の答えは6=select(3,1)なので、select(1,a)の答えが6だと分かります。

図4
図4

 waveletTreeでは、任意の文字列に対するrankselectを、各節点におけるbit vectorに対するrankselectで実現しています。bit vectorに対するrankselectsucBVの記事で扱っているように、さまざまな方法が既に提案されています。今回の実装では以前の記事で紹介したsucBVを使っています。

 wavelet treeを利用するのに必要な作業領域量は、理論的には入力テキストのH0(0次エントロピー)*N bitです。なぜなら、全節点のbit vectorをつなぎ合わせたものの長さが、元のテキストをHuffman符号で符号化した時の長さと一致するからです。しかし、実際には各節点のbit vectorでrankselectを定数時間で実現するためには、約2倍のオーバーヘッドが必要なので、大体 3*H0*N bit必要ということになります。H0はデータによりますが、英文の場合大体半分から0.75ぐらいなので、元テキストの大体2倍必要になります。

 select操作をrank操作の2分探索に置き換えるなど、速度を犠牲にすることで作業領域量を情報理論的な限界に近づけることが可能です。今後の研究や実用上の改良によって、作業領域量が大幅に改善されると思います。

まとめ

 今回紹介したデータ構造wavelet treeは、任意の文字列に対するrank/select操作を定数時間で実現するものです。このデータ構造はsucBV同様、もともと圧縮索引やSuccinct Data Structureのために提案されたものですが、その他の応用用途も考えられます。例えば、文字集合として単語集合を考えた場合、転置ファイルに置き換えるものとしてwavelet treeを利用できます。

参考資料

  1. SODA '03High-order entropy-compressed text indexes』 R. Grossi・A. Gupta・J. S. Vitter 著、2003年1月
  2. Technical Report TR/DCC-2005-7 『Compressed Full Text Indexes』 V. Makinen・G. Navarro 著、2005年1月
  3. (ftp://ftp.dcc.uchile.cl/pub/users/gnavarro/survcompr.ps.gz)
  4. 圧縮索引とその周辺』 岡野原大輔 著、2005年12月

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
特集記事連載記事一覧

もっと読む

この記事の著者

岡野原 大輔(オカノハラ ダイスケ)

データ圧縮やデータ構造、またそれらの応用としての自然言語処理、機械学習に興味があります。http://hillbig.cocolog-nifty.com/(blog)

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/261 2006/06/29 14:50

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー