• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
2<html lang="ja">
3 <head>
4  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5  <title>MARISA: Matching Algorithm with Recursively Implemented StorAge</title>
6  <link rel="stylesheet" type="text/css" href="./style.css">
7 </head>
8 <body>
9  <div id="header">
10   <div class="left">MARISA: Matching Algorithm with Recursively Implemented StorAge</div>
11   <div class="right">Last modified: 14 Jun 2020</div>
12   <div class="end"></div>
13  </div><!-- header -->
14  <div id="body">
15   <h1>MARISA: Matching Algorithm with Recursively Implemented StorAge</h1>
16   <p id="abstract">
17    <span id="heading">Abstract: </span>
18     Matching Algorithm with Recursively Implemented StorAge (MARISA) は Trie をコンパクトに表現する程度の能力を持つデータ構造です.libmarisa は MARISA を C++ で実装したライブラリであり,MARISA による辞書を構築したり,辞書からの検索をおこなったりできます.libmarisa の基本的な機能に対応するコマンドラインツールを用意しているので,辞書のサイズがどのくらいになるのか,検索にどのくらい時間がかかるのか,などを手軽に試すことができます.
19   </p><!-- abstract -->
20
21   <div class="section">
22    <h2><a name="introduction">はじめに</a></h2>
23    <div class="subsection">
24     <h3><a name="overview">概要</a></h3>
25     <p>
26      Matching Algorithm with Recursively Implemented StorAge (MARISA) は Trie に対するコンパクトなデータ構造であり,動的な更新には対応していないものの,高い空間効率とそれなりの時間効率を実現できます.また,Trie の特徴を引き継いでいるため,MARISA による辞書は,単純な辞書引きだけでなく,逆引き,Common Prefix Search や Predictive Search を効率良く実現できます.
27     </p>
28     <p>
29      ほとんどの場合,MARISA による辞書は,登録文字列の集合を連結してできるテキストと比べて,ずっとコンパクトになります.そのため,登録文字列をそのまま保持する標準的な二分探索木やハッシュ表と比べると,ずーっとコンパクトです.一方で,確率的なデータ構造である Bloom Filter と比べてみると,空間効率の面では劣りますが,False Positive がなく,逆引きに加えて Common Prefix Search や Predictive Search を効率良く実現できることが特徴になります.
30     </p>
31     <p>
32      libmarisa は MARISA を C++ で実装したライブラリであり,MARISA による辞書を構築したり,辞書からの検索をおこなったりできます.libmarisa の基本的な機能に対応するコマンドラインツールを用意しているので,辞書のサイズがどのくらいになるのか,検索にどのくらい時間がかかるのか,などを手軽に試すことができます.
33     </p>
34    </div><!-- subsection -->
35    <div class="subsection">
36     <h3><a name="ability">できること</a></h3>
37     <p>
38      libmarisa による辞書の構築では,登録文字列に <var>0</var> から順に固有の ID を割り当てるようになっています.ID は自動的に割り当てられるので,登録文字列に任意の ID を割り当てることはできません.検索においては,文字列あるいは ID をクエリとして受け取り,適合する登録文字列と ID の組を検索結果として返すようになっています.
39     </p>
40     <ul>
41      <li>辞書引き(Lookup)
42       <ul>
43        <li>入力文字列が登録されているかどうかを確認します.</li>
44       </ul>
45      </li>
46      <li>逆引き(Reverse Lookup)
47       <ul>
48        <li>入力された ID から登録文字列を復元します.</li>
49       </ul>
50      </li>
51      <li>Common Prefix Search
52       <ul>
53        <li>入力文字列の前半部分に一致する登録文字列を検索します.</li>
54       </ul>
55      </li>
56      <li>Predictive Search
57       <ul>
58        <li>入力文字列で始まる登録文字列を検索します.</li>
59       </ul>
60      </li>
61     </ul>
62    </div><!-- subsection -->
63   </div><!-- section -->
64   <div class="section">
65    <h2><a name="source">ソースコード</a></h2>
66    <div class="subsection">
67     <h3><a name="license">ライセンス</a></h3>
68     <p>
69      libmarisa および付属のコマンドラインツールはフリーソフトウェアです.使用・再配布については,二条項 BSD ライセンスと LGPL のデュアルライセンスを採用しています.
70     </p>
71    </div><!-- subsection -->
72    <div class="subsection">
73     <h3><a name="download">ダウンロード</a></h3>
74     <p>
75      プロジェクトの管理やソースコードの配布には <a href="https://github.com/">GitHub</a> を利用しています.
76     </p>
77     <ul>
78      <li>プロジェクト
79       <ul>
80        <li><a href="https://github.com/s-yata/marisa-trie">https://github.com/s-yata/marisa-trie</a></li>
81       </ul>
82      </li>
83      <li>ソースコード
84       <ul>
85        <li><a href="https://github.com/s-yata/marisa-trie/archive/v0.2.6.tar.gz">marisa-0.2.6.tar.gz</a></li>
86       </ul>
87      </li>
88     </ul>
89    </div><!-- subsection -->
90   </div><!-- section -->
91
92   <div class="section">
93    <h2><a name="install">インストール</a></h2>
94    <div class="subsection">
95     <h3><a name="gcc">GCC &amp; Clang</a></h3>
96     <div class="float">
97      <pre class="console">$ tar zxf marisa-0.2.6.tar.gz
98$ cd marisa-0.2.6
99$ ./configure
100$ make
101$ make check
102$ make install</pre>
103     </div><!-- float -->
104     <p>
105      <kbd>configure</kbd> と <kbd>make</kbd> によりインストールできるようになっています.<kbd>make install</kbd> については,必要に応じて <kbd>sudo</kbd> を付けてご利用ください.また,特に指定がなければ libmarisa は共有ライブラリとしてインストールされるので,<kbd>ldconfig</kbd> が必要になるかもしれません.
106     </p>
107     <p>
108      POPCNT 命令が使える環境では,<kbd>configure</kbd> に <kbd>--enable-popcnt</kbd> を渡すことで,少し高速化することができます.同様に,<kbd>--enable-sse2</kbd>, <kbd>--enable-sse3</kbd>, <kbd>--enable-ssse3</kbd>, <kbd>--enable-sse4.1</kbd>, <kbd>--enable-sse4.2</kbd>, <kbd>--enable-sse4</kbd>, <kbd>--enable-sse4a</kbd>, <kbd>--enable-bmi</kbd>, <kbd>--enable-bmi2</kbd> というオプションがあります.<kbd>--enable-native-code</kbd> を渡せば,コンパイル環境で使える命令がすべて有効になります.また,スタティックライブラリが必要なときは,<kbd>configure</kbd> に <kbd>--enable-static</kbd> を渡すようにしてください.その他,<kbd>configure</kbd> のオプションについては <kbd>./configure --help</kbd> をご覧ください.
109     </p>
110    </div><!-- subsection -->
111    <div class="subsection">
112     <h3><a name="vc">Visual C++ 2008</a></h3>
113     <p>
114      Visual C++ 2008 にて作成したファイルを <kbd>vs2008/</kbd> 以下に配置しています.Visual C++ 2008 以降であれば,<kbd>vs2008/vs2008.sln</kbd> を開くだけでスタティックライブラリ <kbd>libmarisa.lib</kbd> とコマンドラインツールをビルドできます.
115     </p>
116     <p>
117      Visual C++ 2008 より古い環境では,新しくプロジェクトを作る必要があります.プロジェクトを作成すれば問題なくビルドできると思いますが,試していないので断言はできません.
118     </p>
119    </div><!-- subsection -->
120    <div class="subsection">
121     <h3><a name="vc">Perl バインディング</a></h3>
122     <div class="float">
123      <pre class="console">$ cd bindings/perl
124$ perl Makefile.PL
125$ make
126$ make install</pre>
127     </div><!-- float -->
128     <p>
129      <a href="http://www.swig.org/">SWIG</a> による Perl バインディングが <kbd>bindings/perl/</kbd> にあります.<kbd>perl Makefile.PL</kbd> により <kbd>Makefile</kbd> を作成し,<kbd>make install</kbd> を実行することでインストールできます.使い方については,<kbd>bindings/perl/sample.pl</kbd> を参考にしてください.
130     </p>
131    </div><!-- subsection -->
132    <div class="subsection">
133     <h3><a name="vc">Python バインディング</a></h3>
134     <div class="float">
135      <pre class="console">$ cd bindings/python
136$ python setup.py build
137$ python setup.py install</pre>
138     </div><!-- float -->
139     <p>
140      <a href="http://www.swig.org/">SWIG</a> による Python バインディングが <kbd>bindings/python/</kbd> にあります.<kbd>python setup.py install</kbd> により インストールできます.使い方については,<kbd>bindings/python/sample.py</kbd> を参考にしてください.
141     </p>
142    </div><!-- subsection -->
143    <div class="subsection">
144     <h3><a name="vc">Ruby バインディング</a></h3>
145     <div class="float">
146      <pre class="console">$ cd bindings/ruby
147$ ruby extconf.rb
148$ make
149$ make install</pre>
150     </div><!-- float -->
151     <p>
152      <a href="http://www.swig.org/">SWIG</a> による Ruby バインディングが <kbd>bindings/ruby/</kbd> にあります.<kbd>ruby extconf.rb</kbd> により <kbd>Makefile</kbd> を作成し,<kbd>make install</kbd> を実行することでインストールできます.使い方については,<kbd>bindings/ruby/sample.rb</kbd> を参考にしてください.
153     </p>
154    </div><!-- subsection -->
155    <div class="subsection">
156     <h3><a name="vc">その他</a></h3>
157     <p>
158      上記のバインディング以外にも,いくつかのバインディングがあります.
159     </p>
160     <ul>
161      <li>Python
162       <ul>
163        <li><a href="https://github.com/kmike/marisa-trie/">"https://github.com/kmike/marisa-trie/</a>高速・高機能な Python バインディング</li>
164       </ul>
165      </li>
166      <li>Node.js
167       <ul>
168        <li><a href="https://github.com/komiya-atsushi/node-marisa-trie">https://github.com/komiya-atsushi/node-marisa-trie</a>Node.js から使うためのモジュール</li>
169       </ul>
170      </li>
171     </p>
172    </div><!-- subsection -->
173   </div><!-- section -->
174
175   <div class="section">
176    <h2><a name="tools">コマンドラインツール</a></h2>
177    <div class="subsection">
178     <h3><a name="marisa-build">marisa-build</a></h3>
179     <div class="float">
180      <pre class="console">$ marisa-build &lt; keyset.txt &gt; keyset.dic
181#keys: 1342099
182#nodes: 1832373
183size: 7841664</pre>
184     </div><!-- float -->
185     <p>
186      <kbd>marisa-build</kbd> は辞書を構築するツールです.改行を区切りとして文字列を受け取り,構築した辞書を標準出力に書き出すようになっています.
187     </p>
188     <p>
189      構築する辞書のパラメータについては,オプションを使って指定できます.オプションの一覧は <kbd>marisa-build -h</kbd> により確認できます.
190     </p>
191     <p>
192      入力は改行区切りとなっていますが,水平タブが存在する行については,最後の水平タブ以降を文字列の重みとして扱うようになっています.文字列の出現頻度や出現確率を与えることにより,検索時間を短縮できる可能性があります.
193     </p>
194    </div><!-- subsection -->
195    <div class="subsection">
196     <h3><a name="marisa-lookup">marisa-lookup</a></h3>
197     <div class="float">
198      <pre class="console">$ marisa-lookup keyset.dic
199東方
200174385	東方
201とうほう
202-1	とうほう</pre>
203     </div><!-- float -->
204     <p>
205      <kbd>marisa-lookup</kbd> は単純な辞書引きをおこなうツールです.入力された文字列が登録されていれば ID とともに出力し,登録されていなければ <var>-1</var> とともに出力します.
206     </p>
207     <p>
208      オプションの一覧は <kbd>marisa-lookup -h</kbd> により確認できます.
209     </p>
210    </div><!-- subsection -->
211    <div class="subsection">
212     <h3><a name="marisa-reverse-lookup">marisa-reverse-lookup</a></h3>
213     <div class="float">
214      <pre class="console">$ marisa-reverse-lookup keyset.dic
215800000
216800000	紀元前475年</pre>
217     </div><!-- float -->
218     <p>
219      <kbd>marisa-reverse-lookup</kbd> は辞書に登録されている文字列を ID から復元するツールです.登録文字列には <var>0</var> から順に固有の ID が割り当てられるので,指定できる値は <var>0</var> 以上で<var>登録文字列数</var>より小さい整数となります.範囲外の値を入力するとエラーになるので注意してください.
220     </p>
221     <p>
222      オプションの一覧は <kbd>marisa-reverse-lookup -h</kbd> により確認できます.
223     </p>
224    </div><!-- subsection -->
225    <div class="subsection">
226     <h3><a name="marisa-common-prefix-search">marisa-common-prefix-search</a></h3>
227     <div class="float">
228      <pre class="console">$ marisa-common-prefix-search keyset.dic
229東方
2302 found
2313542	東	東方
232174385	東方	東方</pre>
233     </div><!-- float -->
234     <p>
235      <kbd>marisa-common-prefix-search</kbd> は Common Prefix Search をおこなうツールです.入力された文字列の前半部分に一致する登録文字列を ID とともに出力します.
236     </p>
237     <p>
238      オプションの一覧は <kbd>marisa-common-prefix-search -h</kbd> により確認できます.
239     </p>
240    </div><!-- subsection -->
241    <div class="subsection">
242     <h3><a name="marisa-predictive-search">marisa-predictive-search</a></h3>
243     <div class="float">
244      <pre class="console">$ marisa-predictive-search keyset.dic -n 2
245東方
246200 found
247174385	東方	東方
248639679	東方文花帖	東方</pre>
249     </div><!-- float -->
250     <p>
251      <kbd>marisa-predictive-search</kbd> は Predictive Search をおこなうツールです.入力された文字列で始まる登録文字列を ID とともに出力します.
252     </p>
253     <p>
254      オプションの一覧は <kbd>marisa-predictive-search -h</kbd> により確認できます.
255     </p>
256    </div><!-- subsection -->
257    <div class="subsection">
258     <h3><a name="marisa-benchmark">marisa-benchmark</a></h3>
259     <div class="float">
260      <pre class="console">$ marisa-benchmark keyset.txt
261Number of tries: 1 - 5
262TAIL mode: Text mode
263Node order: Descending weight order
264Cache level: Normal cache
265Number of keys: 1342099
266Total length: 28308027
267------+----------+--------+--------+--------+--------+--------
268#tries       size    build   lookup  reverse   prefix  predict
269                                      lookup   search   search
270          [bytes]    [K/s]    [K/s]    [K/s]    [K/s]    [K/s]
271------+----------+--------+--------+--------+--------+--------
272     1   11588904   506.45  1187.70  1109.17  1040.39   596.49
273     2    8467920   424.71   699.01   677.83   636.07   300.25
274     3    7841664   405.47   615.64   601.84   563.91   254.67
275     4    7633584   399.43   593.85   583.52   545.57   242.69
276     5    7548584   395.90   526.31   563.91   504.55   236.70
277------+----------+--------+--------+--------+--------+--------</pre>
278     </div><!-- float -->
279     <p>
280      <kbd>marisa-benchmark</kbd> は,<kbd>marisa-build</kbd> と同様の入力を受け取り,辞書のサイズや構築・検索にかかる時間を調べるツールです.辞書を構成する Trie の数を選択するのに有用です.
281     </p>
282     <p>
283      検索時間については,入力された文字列を一通り検索するのに要した時間を <code>std::clock()</code> で計測した結果を出力します.文字列を整列してから入力とした場合はキャッシュが効きやすい状況での検索時間になり,文字列をランダムに並べ替えてから入力とした場合はキャッシュが効きにくい状況での検索時間になります.
284     </p>
285     <p>
286      オプションの一覧は <kbd>marisa-benchmark -h</kbd> により確認できます.
287     </p>
288    </div><!-- subsection -->
289    <div class="subsection">
290     <h3><a name="marisa-dump">marisa-dump</a></h3>
291     <div class="float">
292      <pre class="console">$ marisa-dump keyset.dic | head -3
293input: keyset.dic
294295ファ
296ファン</pre>
297     </div><!-- float -->
298     <p>
299      <kbd>marisa-dump</kbd> は辞書に登録されている文字列をすべて出力するツールです.デフォルトの区切り文字は改行(LF)ですが,オプションにより変更することができます.
300     </p>
301     <p>
302      オプションの一覧は <kbd>marisa-dump -h</kbd> により確認できます.
303     </p>
304    </div><!-- subsection -->
305   </div><!-- section -->
306
307   <div class="section">
308    <h2><a name="library">ライブラリ</a></h2>
309    <div class="subsection">
310     <h3><a name="howto">使い方</a></h3>
311     <div class="float">
312      <pre class="code">// sample.cc
313#include &lt;iostream&gt;
314#include &lt;marisa.h&gt;
315
316int main() {
317  marisa::Keyset keyset;
318  keyset.push_back("a");
319  keyset.push_back("app");
320  keyset.push_back("apple");
321
322  marisa::Trie trie;
323  trie.build(keyset);
324
325  marisa::Agent agent;
326  agent.set_query("apple");
327  while (trie.common_prefix_search(agent)) {
328    std::cout.write(agent.key().ptr(), agent.key().length());
329    std::cout &lt;&lt; ": " &lt;&lt; agent.key().id() &lt;&lt; std::endl;
330  }
331  return 0;
332}</pre>
333     </div><!-- float -->
334     <div class="float">
335      <pre class="console">$ g++ sample.cc -lmarisa
336$ ./a.out
337a: 0
338app: 1
339apple: 2</pre>
340     </div><!-- float -->
341     <p>
342      libmarisa のヘッダは <kbd>marisa.h</kbd> です.名前空間には <code>marisa</code> を使っています.危険なので,<code>using namespace marisa</code> とするのは避けてください.最後に,<kbd>gcc</kbd> や <kbd>clang</kbd> によるリンクでは <kbd>-lmarisa</kbd> が必要となることに注意してください.
343     </p>
344     <p>
345      libmarisa の主要なクラスは <a href="#keyset">Keyset</a>, <a href="#agent">Agent</a>, <a href="#trie">Trie</a> の 3 つです.サンプルコードでは明示的に使っていませんが,例外のクラスとして <a href="#exception">Exception</a> があるほか,<code>Keyset</code>, <code>Agent</code> のメンバとして <a href="#key">Key</a> や <a href="#query">Query</a> が存在します.
346     </p>
347     <ul>
348      <li><code>Keyset</code>: 文字列の集合を格納するクラスです.辞書を構築するときの入力として使うほか,検索結果の保存にも利用できます.</li>
349      <li><code>Agent</code>: 検索の入出力と途中経過を格納するクラスです.検索用の関数はすべて Agent への参照を引数とします.</li>
350      <li><code>Trie</code>: 辞書のクラスです.</li>
351     </ul>
352     <p>
353      コマンドラインツールのソースコードが <kbd>tools/</kbd> にあり,例外処理やファイル入出力,Predictive Search などのサンプルとして利用できます.
354     </p>
355    </div><!-- subsection -->
356
357    <div class="subsection">
358     <h3><a name="enum">定数</a></h3>
359     <div class="subsubsection">
360      <h4>エラーコード</h4>
361      <div class="float">
362       <pre class="code">typedef enum marisa_error_code_ {
363  MARISA_OK           = 0,
364  MARISA_STATE_ERROR  = 1,
365  MARISA_NULL_ERROR   = 2,
366  MARISA_BOUND_ERROR  = 3,
367  MARISA_RANGE_ERROR  = 4,
368  MARISA_CODE_ERROR   = 5,
369  MARISA_RESET_ERROR  = 6,
370  MARISA_SIZE_ERROR   = 7,
371  MARISA_MEMORY_ERROR = 8,
372  MARISA_IO_ERROR     = 9,
373  MARISA_FORMAT_ERROR = 10,
374} marisa_error_code;</pre>
375      </div><!-- float -->
376      <p>
377       libmarisa では,ファイル入出力に失敗したときや辞書のサイズが上限に到達したときなどに,<code>Exception</code> を送出します.そして,<code>Exception</code> に格納される情報の 1 つが <code>marisa_error_code</code> です.
378      </p>
379      <p>
380       辞書の入出力に関するエラーコードである <var>MARISA_IO_ERROR</var> と <var>MARISA_FORMAT_ERROR</var> 以外については,バグによる可能性が高いと思います.各エラーコードの詳細については,<kbd>marisa/base.h</kbd> をご覧ください.
381      </p>
382     </div><!-- subsubsection -->
383     <div class="subsubsection">
384      <h4>トライの数</h4>
385      <div class="float">
386       <pre class="code">
387typedef enum marisa_num_tries_ {
388  MARISA_MIN_NUM_TRIES     = 0x00001,
389  MARISA_MAX_NUM_TRIES     = 0x0007F,
390  MARISA_DEFAULT_NUM_TRIES = 0x00003,
391} marisa_num_tries;</pre>
392      </div><!-- float -->
393      <p>
394       MARISA は複数の Patricia Trie を組み合わせて 1 つの Trie を構成することが特徴の 1 つであり,Patricia Trie の数を増やすほど,辞書はコンパクトになるものの,検索が遅くなるという傾向があります.<code>marisa_num_tries</code> では,辞書を構成する Patricia Trie の数について,最小値・最大値とデフォルトの値を提供します.
395      </p>
396      <p>
397       適切な設定は登録文字列やアプリケーションによって異なります.ほとんどの場合はデフォルトの設定で問題ないと思いますが,検索時間が問題になるときは,思い切って <var>1</var> にしてください.また,登録文字列が長くて少し複雑な構成になる場合,デフォルトより大きな値にすることで,辞書のサイズをさらに小さくできることがあります.設定が気になるときは,<kbd>marisa-benchmark</kbd> をお試しください.
398      </p>
399     </div><!-- subsubsection -->
400     <div class="subsubsection">
401      <h4>キャッシュのサイズ</h4>
402      <div class="float">
403       <pre class="code">typedef enum marisa_cache_level_ {
404  MARISA_HUGE_CACHE    = 0x00080,
405  MARISA_LARGE_CACHE   = 0x00100,
406  MARISA_NORMAL_CACHE  = 0x00200,
407  MARISA_SMALL_CACHE   = 0x00400,
408  MARISA_TINY_CACHE    = 0x00800,
409  MARISA_DEFAULT_CACHE = MARISA_NORMAL_CACHE
410} marisa_cache_level;</pre>
411      </div><!-- float -->
412      <p>
413       libmarisa では,検索時間の短縮を目的として,辞書にキャッシュを埋め込むようになっています.キャッシュの内容は通過する確率の高い遷移に関する情報であり,キャッシュを大きくすることによって,辞書は大きくなるものの,検索時間を短縮できます.
414      </p>
415      <p>
416       <code>marisa_cache_level</code> は,キャッシュのサイズを制御するための定数を提供します.<var>MARISA_NORMAL_CACHE</var> を基準として,<var>MARISA_LARGE_CACHE</var> は 2 倍,<var>MARISA_HUGE_CACHE</var> は 4 倍になり,<var>MARISA_SMALL_CACHE</var> は 1/2,<var>MARISA_TINY_CACHE</var> は 1/4 になります.
417      </p>
418     </div><!-- subsubsection -->
419     <div class="subsubsection">
420      <h4>TAIL の種類</h4>
421      <div class="float">
422       <pre class="code">typedef enum marisa_tail_mode_ {
423  MARISA_TEXT_TAIL    = 0x01000,
424  MARISA_BINARY_TAIL  = 0x02000,
425  MARISA_DEFAULT_TAIL = MARISA_TEXT_TAIL,
426} marisa_tail_mode;</pre>
427      </div><!-- float -->
428      <p>
429       libmarisa による辞書では,最後の Patiricia Trie について,ラベルをそのまま保存するようになっています.<code>marisa_tail_mode</code> はラベルの保存方法を選ぶためのパラメータです.
430      </p>
431      <p>
432       <var>MARISA_TEXT_TAIL</var> はラベルを <var>'\0'</var> を終端とする文字列として保存します.そのため,ラベルに <var>'\0'</var> が含まれるときは,自動的に <var>MARISA_BINARY_TAIL</var> へと切り替わるようになっています.明示的に <var>MARISA_BINARY_TAIL</var> を選ぶ理由はほとんどありません.
433      </p>
434      <p>
435       一方,<var>MARISA_BINARY_TAIL</var> では,ラベルの終端を検出するために,<var>'\0'</var> の代わりにビット列を使用します.そのため,ラベルの平均長が <var>8 bytes</var> を超えるときは <var>MARISA_TEXT_TAIL</var> の方がコンパクトになります.
436      </p>
437     </div><!-- subsubsection -->
438     <div class="subsubsection">
439      <h4>ノードの順序</h4>
440      <div class="float">
441       <pre class="code">typedef enum marisa_node_order_ {
442  MARISA_LABEL_ORDER   = 0x10000,
443  MARISA_WEIGHT_ORDER  = 0x20000,
444  MARISA_DEFAULT_ORDER = MARISA_WEIGHT_ORDER,
445} marisa_node_order;</pre>
446      </div><!-- float -->
447      <p>
448       libmarisa では,ノードの順序が辞書のパラメータになっています.選択肢は <var>MARISA_LABEL_ORDER</var> と <var>MARISA_WEIGHT_ORDER</var> の 2 つであり,前者はラベルが昇順になるようにノードを配列し,後者は重み(出現しやすさ)が降順になるようにノードを配列します.一般的な Trie の実装では <var>MARISA_LABEL_ORDER</var> の順序を用いますが,libmarisa では <var>MARISA_WEIGHT_ORDER</var> がデフォルトになっています.
449      </p>
450      <p>
451       <var>MARISA_WEIGHT_ORDER</var> の目的は,出現しやすいノードから順に並べておくことにより,線形探索の効率を高め,検索時間を短縮することにあります.日本語の単語やフレーズを用いた実験においては,辞書引きにかかる時間を 1/2 程度に短縮できることが確認されています.一方,<var>MARISA_LABEL_ORDER</var> については,検索時間は長くなるものの,Predictive Search の検索結果が文字列昇順になるという特徴があります.
452      </p>
453     </div><!-- subsubsection -->
454     <div class="subsubsection">
455      <h4>別名</h4>
456      <div class="float">
457       <pre class="code">namespace marisa {
458  typedef ::marisa_error_code ErrorCode;
459  typedef ::marisa_cache_level CacheLevel;
460  typedef ::marisa_tail_mode TailMode;
461  typedef ::marisa_node_order NodeOrder;
462}  // namespace marisa</pre>
463      </div><!-- float -->
464      <p>
465       以上の列挙型については,マクロとの衝突を避けるために,グローバル名前空間にて定義しています.<code>namespace marisa</code> に別名を用意しているので,お好きな方をご利用ください.
466      </p>
467     </div><!-- subsubsection -->
468    </div><!-- subsection -->
469
470    <div class="subsection">
471     <h3><a name="exception">class Exception</a></h3>
472     <div class="float">
473      <pre class="code">class Exception {
474 public:
475  const char *filename() const;
476  int line() const;
477  ErrorCode error_code() const;
478  const char *error_message() const;
479
480  const char *what() const;
481};</pre>
482     </div><!-- float -->
483     <p>
484      <code>Exception</code> は libmarisa が例外として送出するクラスです.エラーが検出されたファイルの名前(<code>__FILE__</code>)と行番号(<code>__LINE__</code>),さらにエラーコードを取り出せるようになっています.<code>what()</code> は使いやすさのために用意した関数であり,<code>error_message()</code> と同じく,<var>__FILE__:__LINE__: error_code: error_message</var> という書式の文字列を返します.
485     </p>
486    </div><!-- subsection -->
487
488    <div class="subsection">
489     <h3><a name="key">class Key</a></h3>
490     <div class="float">
491      <pre class="code">class Key {
492 public:
493  char operator[](std::size_t i) const;
494  const char *ptr() const;
495  std::size_t length() const;
496  std::size_t id() const;
497};</pre>
498     </div><!-- float -->
499     <p>
500      <code>Key</code> は後述する <a href="#keyset">Keyset</a> および <a href="#agent">Agent</a> のメンバになっているクラスです.登録しようとしている文字列や,検索で見つけた登録文字列の情報を格納するために使われています.基本的な使い方では,既に情報が格納されたインスタンスのみを目にすることになります.
501     </p>
502    </div><!-- subsection -->
503
504    <div class="subsection">
505     <h3><a name="query">class Query</a></h3>
506     <div class="float">
507      <pre class="code">class Query {
508 public:
509  char operator[](std::size_t i) const;
510  const char *ptr() const;
511  std::size_t length() const;
512  std::size_t id() const;
513};</pre>
514     </div><!-- float -->
515     <p>
516      <code>Query</code> は後述する <a href="#agent">Agent</a> のメンバになっているクラスです.検索しようとしている文字列や参照したい登録文字列の ID を格納するようになっています.<code>Query</code> に対する文字列や ID の設定は <code>Agent</code> を介しておこなうため,基本的な使い方では,意識する必要はありません.内容を確認したいときに参照する程度です.
517     </p>
518    </div><!-- subsection -->
519
520    <div class="subsection">
521     <h3><a name="keyset">class Keyset</a></h3>
522     <div class="float">
523      <pre class="code">class Keyset {
524 public:
525  Keyset();
526
527  void push_back(const Key &amp;key);
528  void push_back(const Key &amp;key, char end_marker);
529
530  void push_back(const char *str);
531  void push_back(const char *ptr,
532                 std::size_t length,
533                 float weight = 1.0);
534
535  const Key &amp;operator[](std::size_t i) const;
536  Key &amp;operator[](std::size_t i);
537
538  std::size_t num_keys();
539
540  bool empty() const;
541  std::size_t size() const;
542  std::size_t total_length() const;
543
544  void reset();
545
546  void clear();
547  void swap(Keyset &amp;rhs);
548};</pre>
549     </div><!-- float -->
550     <div class="subsubsection">
551      <h4>概要</h4>
552      <p>
553       <code>Keyset</code> は辞書に登録しようとしている文字列もしくは登録されている文字列を詰め込むためのクラスです.辞書を構築するときの入力として,あるいは検索結果を保存しておくために使います.
554      </p>
555     </div><!-- subsubsection -->
556     <div class="subsubsection">
557      <h4>辞書の構築</h4>
558      <p>
559       辞書の構築に使う場合,<code>push_back()</code> で登録したい文字列を追加してから,後述する <a href="#trie">Trie</a> の <code>build()</code> に渡します.<var>weight</var> は文字列の出現しやすさを示す重みであり,同じ文字列を繰り返し追加した場合,辞書を構築する段階で加算されるようになっています.
560      </p>
561      <p>
562       辞書を構築した後は,<code>operator[]()</code> により登録文字列の ID を確認できます.その代わり,<code>Key</code> は重みと ID を共用体のメンバとして持つため,辞書の構築に使用した重みを参照できなくなります.
563      </p>
564     </div><!-- subsubsection -->
565     <div class="subsubsection">
566      <h4>検索結果の保存</h4>
567      <p>
568       検索結果の保存に使う場合,後述する <a href="#agent">Agent</a> に格納されている検索結果を <code>push_back()</code> に渡すことで,文字列を複製し,ID を残しておくことができます.検索結果の文字列に終端記号を加えたいときは <var>end_marker</var> を利用してください.文字列の長さには影響を与えず,<var>end_marker</var> を終端文字として加えることができます.
569      </p>
570      <p>
571       検索結果を破棄して別の検索結果を保存するために再利用するという場合,<code>clear()</code> の代わりに <code>reset()</code> を使うことで,既に確保している領域を再利用できます.メモリの確保・解放にかかるオーバーヘッドが気になるときにご利用ください.
572      </p>
573      <p>
574       <code>empty()</code> は文字列が格納されていないかどうかを返す関数です.<code>size()</code> は <code>num_keys()</code> と同じく格納されている文字列の数を返す関数であり,<code>total_length()</code> は格納されている文字列の合計長を byte 単位で返す関数です.
575      </p>
576     </div><!-- subsubsection -->
577    </div><!-- subsection -->
578
579    <div class="subsection">
580     <h3><a name="agent">class Agent</a></h3>
581     <div class="float">
582      <pre class="code">class Agent {
583 public:
584  Agent();
585
586  const Query &amp;query() const;
587  const Key &amp;key() const;
588
589  void set_query(const char *str);
590  void set_query(const char *ptr,
591                 std::size_t length);
592  void set_query(std::size_t key_id);
593};</pre>
594     </div><!-- float -->
595     <p>
596      <code>Agent</code> は <code>Query</code> と <code>Key</code> をメンバとして持つクラスです.検索における入出力の受け渡し,および途中経過の保存に使います.後述する <a href="#trie">Trie</a> の検索関数は,例外なく <code>Agent</code> への参照を引数として受け取るようになっています.
597     </p>
598     <p>
599      検索の手順は,<code>set_query()</code> を使って検索したい文字列もしくは参照したい登録文字列の ID を設定し,<code>Trie</code> の関数に渡した後,<code>key()</code> により検索結果を取り出すという流れになります.
600     </p>
601    </div><!-- subsection -->
602
603    <div class="subsection">
604     <h3><a name="trie">class Trie</a></h3>
605     <div class="float">
606      <pre class="code">class Trie {
607 public:
608  Trie();
609
610  void build(Keyset &amp;keyset,
611             int config_flags = 0);
612
613  void mmap(const char *filename);
614  void map(const void *ptr,
615           std::size_t size);
616
617  void load(const char *filename);
618  void read(int fd);
619
620  void save(const char *filename) const;
621  void write(int fd) const;
622
623  bool lookup(Agent &amp;agent) const;
624  void reverse_lookup(Agent &amp;agent) const;
625  bool common_prefix_search(Agent &amp;agent) const;
626  bool predictive_search(Agent &amp;agent) const;
627
628  std::size_t num_tries() const;
629  std::size_t num_keys() const;
630  std::size_t num_nodes() const;
631
632  TailMode tail_mode() const;
633  NodeOrder node_order() const;
634
635  bool empty() const;
636  std::size_t size() const;
637  std::size_t io_size() const;
638
639  void clear();
640  void swap(Trie &amp;rhs);
641};</pre>
642     </div><!-- float -->
643     <div class="subsubsection">
644      <h4>概要</h4>
645      <p>
646       <code>Trie</code> は辞書のクラスです.libmarisa において最も重要なクラスであり,辞書の構築・検索からファイル入出力にいたるまで,あらゆる操作に必要となります.
647      </p>
648      <p>
649       実際には,辞書のハンドルに相当するクラスであり,辞書の実体がない状況では,<code>build()</code>, <code>mmap()</code>, <code>map()</code>, <code>load()</code>, <code>read()</code>, <code>clear()</code>, <code>swap()</code> 以外の関数を呼び出すと例外が送出されます.
650      </p>
651     </div><!-- subsubsection -->
652     <div class="subsubsection">
653      <h4>辞書の構築</h4>
654      <p>
655       辞書の構築には <code>build()</code> を使います.引数は,前述の <a href="#keyset">Keyset</a> と,辞書の設定を XOR(<code>|</code>) で組み合わせた <var>config_flags</var> です.<var>config_flags</var> については,<var>2 | MARISA_BINARY_TAIL</var> のように指定します.この例では,辞書を構成する Patricia Trie の数を <var>2</var> つに制限し,ラベルの保存方法を <var>MARISA_BINARY_TAIL</var> に固定します.省略されているノードの順序とキャッシュのサイズについては,デフォルトの設定である <var>MARISA_DEFAULT_ORDER</var> と <var>MARISA_DEFAULT_CACHE</var> が採用されます.
656      </p>
657      <p>
658       辞書の構築において登録文字列に割り当てられた ID は,<var>keyset</var> の <code>operator[]()</code> を使って確認できます.登録文字列に対して関連付ける情報がある場合にご利用ください.
659      </p>
660     </div><!-- subsubsection -->
661     <div class="subsubsection">
662      <h4>ファイル入出力</h4>
663      <p>
664       <code>mmap()</code> は,Memory Mapped I/O により,辞書全体をファイルから読み込むことなく検索できる状態にする関数です.少ししか検索しないのに辞書全体を読み込むのは勿体ないという状況や,異なるプロセスで同じ辞書を共有したいという状況で使うと効果的です.逆に,たくさんの文字列を検索する場合,あらかじめ辞書全体を読み込んでおかないと,外部記憶に対するランダムアクセスにより検索時間が極端に長くなる可能性があります.
665      </p>
666      <p>
667       <code>map()</code> はメモリ上に展開されている辞書のバイナリを使って検索できる状態にする関数です.<code>load()</code> と <code>read()</code> は辞書を入力する関数であり,<code>save()</code> と <code>write()</code> は辞書を出力する関数です.
668      </p>
669     </div><!-- subsubsection -->
670     <div class="subsubsection">
671      <h4>辞書からの検索</h4>
672      <p>
673       検索をおこなう関数は <code>lookup()</code>, <code>reverse_lookup()</code>, <code>common_prefix_search()</code>, <code>predictive_search()</code> の 4 種類です.
674      </p>
675      <ul>
676       <li>
677        <code>lookup()</code>: 文字列が登録されているかどうかを確認します.登録されていれば <var>true</var> を返します.このとき,<code>agent.key()</code> により検索結果を取り出すことができます.ただし,<code>agent.key().ptr()</code> については,入力として渡された文字列を指しているだけであり,文字列の複製を持っているわけではないことに注意してください.登録されていなければ <var>false</var> を返して終了です.
678       </li>
679       <li>
680        <code>reverse_lookup()</code>: ID から登録文字列を復元します.返り値はなく,復元された文字列は <var>agent.key()</var> を介してアクセスできます.文字列の実体は <var>agent</var> 内部に保持されています.<var>agent</var> を使って次の検索をおこなった段階で失われるものと考えてください.ID が範囲外であれば例外を送出して終了です.
681       </li>
682       <li>
683        <code>common_prefix_search()</code>: 入力文字列の前半部分に一致する登録文字列を検索します.該当する登録文字列があれば <var>true</var> を返します.このとき,<code>agent.key()</code> には検索結果が格納されています.<code>agent.key().ptr() == agent.query().ptr()</code> が成立することに注意してください.該当する登録文字列が複数ある場合,返り値が <var>false</var> になるまで繰り返し <code>common_prefix_search()</code> を呼び出すことにより,すべての検索結果を取得できます.
684       </li>
685       <li>
686        <code>predictive_search()</code>: 入力文字列で始まる登録文字列を検索します.該当する登録文字列があれば <var>true</var> を返します.検索によって復元された文字列には,<code>agent.key()</code> を介してアクセスできます.文字列の実体は,<var>agent</var> 内部に検索の途中経過として保持されているので,<var>agent</var> を使って次の検索をおこなった段階で失われるものと考えてください.該当する登録文字列が複数ある場合,返り値が <var>false</var> になるまで繰り返し <code>predictive_search()</code> を呼び出すことにより,すべての検索結果を取得できます.
687       </li>
688      </ul>
689      <p>
690       繰り返しにより検索が進行する <code>common_prefix_search()</code> と <code>predictive_search()</code> については,<code>agent</code> が検索の途中経過を保持するようになっています.そのため,<code>agent</code> を別の関数に渡したり,<code>agent.set_query()</code> を呼び出したりした時点で,検索の進行はリセットされます.
691      </p>
692      <p>
693       <code>empty()</code> は登録文字列が存在するかどうかを返す関数です.<code>size()</code> は <code>num_keys()</code> と同じく登録文字列の数を返す関数であり,<code>io_size()</code> は辞書をファイルに出力した場合のサイズを返す関数です.
694      </p>
695     </div><!-- subsubsection -->
696    </div><!-- subsection -->
697
698    <div class="subsection">
699     <h3><a name="stdio">stdio</a></h3>
700     <div class="float">
701      <pre class="code">void fread(std::FILE *file, Trie *trie);
702void fwrite(std::FILE *file, const Trie &amp;trie);</pre>
703     </div><!-- float -->
704     <p>
705      <code>std::FILE</code> を用いる関数は <kbd>marisa/stdio.h</kbd> で宣言されています.<code>#include &lt;cstdio&gt;</code> を入れたくないときは,<kbd>marisa.h</kbd> の代わりに <kbd>marisa/trie.h</kbd> を使ってください.
706     </p>
707    </div><!-- subsection -->
708
709    <div class="subsection">
710     <h3><a name="iostream">iostream</a></h3>
711     <div class="float">
712      <pre class="code">std::istream &amp;read(std::istream &amp;stream, Trie *trie);
713std::ostream &amp;write(std::ostream &amp;stream, const Trie &amp;trie);
714
715std::istream &amp;operator>>(std::istream &amp;stream, Trie &amp;trie);
716std::ostream &amp;operator<<(std::ostream &amp;stream, const Trie &amp;trie);</pre>
717     </div><!-- float -->
718     <p>
719      <code>std::iostream</code> を用いる関数は <kbd>marisa/iostream.h</kbd> で宣言されています.<code>#include &lt;iosfwd&gt;</code> を入れたくないときは,<kbd>marisa.h</kbd> の代わりに <kbd>marisa/trie.h</kbd> を使ってください.
720     </p>
721    </div><!-- subsection -->
722   </div><!-- section -->
723
724   <div class="section">
725    <h2><a name="compatibility">辞書の互換性</a></h2>
726    <p>
727     libmarisa により構築される辞書の書式はアーキテクチャに依存します.Little Endian な環境で構築した辞書は,Big Endian な環境では使えません.あらためて構築しなおす必要があります.また,Little Endian 形式の辞書は 32/64-bit 環境における互換性があるのに対し,Big Endian 形式の辞書は 32/64-bit 環境における互換性がありません.
728    </p>
729   </div><!-- section -->
730
731   <div class="section">
732    <h2><a name="references">参考資料</a></h2>
733    <ul>
734     <li><a href="http://www.anlp.jp/proceedings/annual_meeting/2011/html/program.html">言語処理学会第 17 回年次大会(NLP2011)</a>
735      <ul>
736       <li>言語処理学会年次大会の予稿集が公開されていて,MARISA に関する論文(<a href="http://www.anlp.jp/proceedings/annual_meeting/2011/pdf_dir/F2-6.pdf">F2-6.pdf</a>)をダウンロードできます.</li>
737      </ul>
738     </li>
739     <li><a href="http://d.hatena.ne.jp/s-yata/20110310/1299777228">発表資料(Prefix/Patricia Trie の入れ子による辞書圧縮)</a>
740      <ul>
741       <li>MARISA に関する発表資料(<a href="https://sites.google.com/site/headdythehero/cabine/2011/0311/NLP2011-F2-6.pptx?attredirects=0&amp;d=1">NLP2011-F2-6.pptx</a>, <a href="https://sites.google.com/site/headdythehero/cabine/2011/0311/NLP2011-F2-6.pdf?attredirects=0&amp;d=1">NLP2011-F2-6.pdf</a>, <a href="https://sites.google.com/site/headdythehero/cabine/2011/0311/NLP2011-F2-6-notes.pdf?attredirects=0&amp;d=1">NLP2011-F2-6-notes.pdf</a>)をダウンロードできます.</li>
742      </ul>
743     </li>
744    </ul>
745   </div><!-- section -->
746
747   <div class="section">
748    <h2><a name="conclusion">おわりに</a></h2>
749    <p>
750     質問などありましたら,欄外のメールアドレス宛てに,お気軽にご連絡ください.
751    </p>
752   </div><!-- section -->
753  </div><!-- body -->
754  <div id="footer">
755   <div class="left">MARISA: Matching Algorithm with Recursively Implemented StorAge</div>
756   <div class="right">
757  ‮moc.liamg@atay.umusus‭
758   </div>
759   <div class="end"></div>
760  </div><!-- footer -->
761 </body>
762</html>
763