修訂. | 時間 | 作者 | 訊息 |
---|---|---|---|
r88 | 2010-08-30 00:42:09 | phjgt | gcc-4.4.3でのコンパイルエラー(#include不足)に対応 |
r87 | 2010-07-02 18:09:40 | phjgt | 動的構築時にバグが発生するテキスト(ワードリスト)を登録 |
r86 | 2010-06-28 05:34:38 | phjgt | StaticAllocator.x_checkで条件によってはSegmentationFault... |
r85 | 2010-06-08 07:59:02 | phjgt | バージョンアップに伴い不正になっていたRubyバインディング... |
r84 | 2010-04-23 20:35:38 | phjgt | 1] 要素数が0の時にeach_common_prefixメソッドを呼び出すと... |
r83 | 2010-04-22 13:03:18 | phjgt | オンメモリで動作するSearcher実装中 |
r82 | 2010-04-22 12:03:16 | phjgt | 新しいeach_common_prefixメソッド追加(experimental?) - 検... |
r81 | 2010-04-22 09:05:16 | phjgt | 途中経過保存: 新しいeach_common_prefixメソッド追加中 |
r80 | 2010-04-22 07:54:37 | phjgt | 二引数をとるSearcherBase::searchを削除。 (TAIL配列を使用... |
r79 | 2010-04-22 03:09:06 | phjgt | DynamicAllocator: lnk配列の最後の要素は先頭(0)をポイント... |
[概要] Doarは、DoubleArrayの構築・検索を行うためのC++ライブラリです。 現在(2010/03/01)で実装されている機能は以下の通りです。 ・DoubleArrayの構築・保存 ・ソート済みのキーセットから、DoubleArrayを構築 ※ 構築時の使用メモリ容量が少ない ・任意の順番のキーセットから、DoubleArrayを構築 ・構築したDoubleArrayをファイルに保存 ・一度保存したデータを読み込んで、更新(要素追加)、再保存 ・保存した二つのDoubleArrayインデックスのマージ ・上で作成したDoubleArrayデータから、キーを検索する ・各キーを、0から始まるユニークなIDにマッピング (IDは挿入順で割り振られる) ・Rubyバインディング ※ 現在は、データ読込・検索のみ [環境] ・現在は、sizeof(int)==4、となる環境を前提としています [移植性に関する注意] ・std::vector.data() ・mmap_t ・バイトエンディアン [コンパイル・動作確認] ・WindowsXP ・VisualC++ 2005/2008 ・unix系(g++ 4.x.x) ※ 以下は、SourceForge.jpのコンパイラフォーム(http://sourceforge.jp/docs/コンパイルファームFAQ)での検証環境 ・x86 Linux Kernel 2.6(Debian GNU/Linux 4.0 etch) ・x86 NetBSD 4.0 ・PowerPC G5 MacOS X 10.5 ・AMD64 Linux Kernel 2.6(Debian GNU/Linux 4.0 etch) [使い方] ・ルートディレクトリでmakeを実行することで、サンプルコマンドがbin以下に作成されます ・mkdoar: DoubleArray構築コマンド ・doar: 簡易検索コマンド (通常検索, common-prefix検索) ・ckdoar: 構築したDoubleArrayのテスト & ベンチマークコマンド ・merge-douar: DoubleArrayマージコマンド ・具体的な使い方は、src/comman/以下のファイルを参照してください [簡易API] namespace Doar { //=== ファイル: src/doar/builder.h === class Builder { // 引数のファイル(ソート済み)から、DoubleArrayを構築する // [返り値] // 成功: 0 // 失敗(ファイルオープン): -1 // 失敗(未ソートファイル): -2 ※ キーがユニークでは無い場合もこのエラーを返す int build(const char* filepath); // 引数の文字列配列(ソート済み)から、DoubleArrayを構築する // [返り値] // 成功: 0 // 失敗(未ソート文字列配列): -2 ※ キーがユニークでは無い場合もこのエラーを返す void build(const char** strs, unsigned str_count); // 引数のファイルにDoubleArrayを保存する bool save(const char* filepath); // DoubleArrayに格納されているキー数を取得する unsigned size() const; }; //=== ファイル: src/doar/node.h === struct Node { // IDを取得する unsigned id() const; // ノードが有効化どうかを返す operator bool() const; // ノードが葉(終端)かどうかを返す bool is_leaf() const; } //=== ファイル: src/doar/searcher.h === class Searcher { // DoubleArrayを引数のファイルから読み込む Searcher(cosnt char* filepath); // (コンストラクタで)無事に読み込めた場合はtrueを返す operator bool() const; // コンストラクタ呼び出し後のSearcherの状態 // 0 : 検索可能 // -1: ファイルオープンに失敗 // -2: ファイルフォーマットが不正 // -3: ファイルが壊れている const int status; // DoubleArrayに格納されているキー数を取得する unsigned size() const; // ルートノードを取得する Node root_node() const; // キーに対応するノードを探す // 検索に失敗した場合は、Node.valid()==falseとなる Node search(const char* key) const; // common-prefix検索(走査)を行う // fnは、void fn(const char* key, unsigned key_offset, unsigned id)形式のファンクタ // fnは、keyに一致するノードが見つかった場合に、その都度呼び出される // ※ 現在、fnはconst指定されているので、fnが関数オブジェクトで、かつ可変メンバを使いたい場合は、そのメンバにmutableを指定して下さい template<typename Callback> void each_common_prefix(const char* key, const Callback& fn) const; // common-prefix検索(走査)を行う // root_nodeを指定することで検索を開始するノードが指定できる // fnは、void fn(const char* key, unsigned key_offset, unsigned id, Node node)形式のファンクタ // fnの引数のnodeは、一致したノードの親ノード ※ 終端ノードで一致した場合は、node.is_leaf()==trueとなる // fnは、keyに一致するノードが見つかった場合に、その都度呼び出される template<typename Callback> void each_common_prefix(const char* key, Node root_node, const Callback& fn) const; // common-prefix検索(走査)を行う // inは、ストリーム形式の検索キー。以下のメソッドを備えている必要がある。 // - read: 呼び出されるごとに、一つのDoar::Codeを返す。ストリームの終端に達した場合は、Doar::TERMINAL_CODEを返す。 // ※ charからDoar::Codeへの変換には、Doar::char2codeを用いる // fnは、void fn(CustomKeyStream in, unsigned id)形式のファンクタ // - read(): read one code from stream. If end of stream reached, must return TERMINAL_CODE. // - including(const char* tail): a predicate method. If tail is included in the rest of this stream, return true. template<typename CustomKeyStream, typename Callback> void each_common_prefix(CustomKeyStream& in, const Callback& fn) const; // parentの子ノード(のindex)を、走査する // fnは、 void fn(char arc_char, Node child_node, const char* tail)形式のファンクタ // child_node.is_leaf()の場合は、tailには遷移文字に続く文字列へのポインタが渡される (それ以外はNULL) template<typename Callback> void children(Node parent, const Callback& fn) const; }; //=== ファイル: src/doar/double_array.h === class DoubleArray { DoubleArray(); // keyをDoubleArrayに追加する // ※ keyはNULL終端で、途中に値が0xFFの文字を含むことはできない // 既にキーが挿入されている場合は、falseを返す bool insert(const char* key); // Searcherクラスの同メソッドと同様に働く // ただし、検索系のメソッドは、Searcherクラスのそれの方が最適化されている可能性がある unsigned size() const; Node root_node() const; Node search(const char* key) const; Node search(const char* key, Node &root_node) const; template <typename Callback> void common_prefix_search(const char* key, Callback& fn) const; template <typename Callback> void common_prefix_search(const char* key, Node root_node, Callback& fn) const; template <typename Callback> void children(Node parent, const Callback& fn) const; // pathに構築したtrieデータを保存する bool save(const char* path); // データを初期化する void clear(); // pathからtrieデータを読み込む // 読み込んだ後は、そのデータに対して、さらにinsertを行うことが可能 // ※ ただし、挿入効率は、ゼロから構築したtrieに比べて劣る // // [返り値] // 0 : 成功 // -1: ファイルオープンに失敗 // -2: ファイルフォーマットが不正 // -3: ファイルが壊れている int load(const char* path); } //=== ファイル: src/doar/merger.h === class Merger { // path1とpath2のDoubleArrayインデックスをマージする // 返り値は、DoubleArray::loadメソッドのそれと同様 // ※ 二つのDoubleArrayには、重複したキーが存在しないことが望ましい // 重複キーが存在する場合、path2の方の該当キーが無視される(ただし、そのためのID値およびスペースは消費されるので無駄) // なお、path2の方の全てのIDには、path1の最大IDが加算される int merge(const char* path1, const char* path2); // filepathに構築したtrieデータを保存する // do_shrink_tailがtrueの場合は、TAIL配列の圧縮を行ってから保存される bool save(const char* filepath, bool do_shrink_tail=true); } } [Ruby 簡易API & 使用例] ## インストール方法 # ruby extconf.rb # make # sudo make install require "doar" trie = Doar::Searcher.new(".../doar.dat") trie.size --> 1000000 # キー数 trie.key?("形態素") --> true trie["形態素"] --> 10 # ID trie["morpheme"] --> nil # trie.key?("...")==falseの場合 trie.common_prefix_search("形態素") --> [[368117, 3], [368161, 6], [368162, 9]] # [ID, 一致した文字列の位置]の配列 trie.each_common_prefix("形態素"){|id,pos| puts "#{id}: #{'形態素'[0,pos]}" } 368117: 形 368161: 形態 368162: 形態素 --> nil trie.each{|id, key| # 格納されている全てのキーをiterate # ... } trie.each("日本"){|id, key| # "日本"で始める全てのキーをiterate # keyは、先頭に"日本"という文字列を必ず含む # 第二引数にfalseを渡した場合、共通(第一引数)部分が除去された文字列が渡される } [TODO] ・ポータビリティ向上 ・削除機能 (検討) => おそらく実装しない(2010/03/01) ・キーに対応する任意の4byte値を格納可能にする => おそらく実装しない(2010/03/01) [メモ] ・Searcher::search(key, root)のAPIには欠陥(?)がある ・元々、rootの意図は、keyを包含する文字列がtrieの中にある場合に、その一致部分までの情報を保持して、別のkeyでその部分から検索を行えるようにすることにある。 ・ただし、現在はkeyのユニークな末尾部分はまとめてtail配列に格納してしまっているので、その部分の一致情報を取り出すことはできない。 ・そのため、keyを包含する文字列がtrieにあったとしても、その部分までも一致情報がrootに格納されるかどうかは、その時のtrie内のキーセットに依存することになり、実際上は使えなくなるのではないかと思う。