Apache Lucene で全文検索するようなアプリケーションを作成中。とりあえず CJKAnalyzer かと思っていたら こちら で NGramTokenizer という便利そうなものが紹介されていたので使おうとしてみた。
まず Analyzer が必要なので以下のようなものを作成。
インデックスの作成はこれでうまくいく。
ところが、検索のほうは minGram と maxGram が異なるケースではうまくいかない。
ほんの少しソースを見ただけだけど、どうも QueryParserが、TokenStream が返す Token の位置情報を使わずに PhraseQuery を構築してしまっているのが原因っぽい気がする。
QueryParserをいじればいいんだけど、標準配布物をいじるのはどうも気が進まないので、家に帰ってからこんなのを捏造してみた(動作未確認)。
基本は maxGram で分割し、検索語の長さが maxGram 未満の場合だけ、検索語の文字数で分割する(といっても1つのトークンになるだけ)。検索のときだけこっちを使うようにしたらうまくいけばいいなあということで明日試す。
まず Analyzer が必要なので以下のようなものを作成。
public class NGramAnalyzer extends Analyzer { protected int minGram; protected int maxGram; public NGramAnalyzer(int minGram, int maxGram) { this.minGram = minGram; this.maxGram = maxGram; } public TokenStream tokenStream(String fieldName, Reader reader) { return new NGramTokenizer(reader, minGram, maxGram); } }
インデックスの作成はこれでうまくいく。
Analyzer analyzer = new NGramAnalyzer(1, 3); IndexWriter writer = new IndexWriter(new RAMDirectory(), analyzer); // ドキュメントの追加...
ところが、検索のほうは minGram と maxGram が異なるケースではうまくいかない。
Analyzer analyzer = new NGramAnalyzer(1, 3); QueryParser parser = new QueryParser(queryString, analyzer); // queryString が 1文字の場合以外はマッチしない
ほんの少しソースを見ただけだけど、どうも QueryParserが、TokenStream が返す Token の位置情報を使わずに PhraseQuery を構築してしまっているのが原因っぽい気がする。
QueryParserをいじればいいんだけど、標準配布物をいじるのはどうも気が進まないので、家に帰ってからこんなのを捏造してみた(動作未確認)。
public class NGramAnalyzerForQuery extends NGramAnalyzer { public NGramAnalyzerForQuery(int minGram, int maxGram) { super(minGram, maxGram); } public TokenStream tokenStream(String fieldName, Reader reader) { int read = 0; try { char[] buf = new char[maxGram]; // 検索語の文字数を確認する while (read < maxGram) { int c = reader.read(); if (c == -1) { break; } buf[read] = (char)c; read++; } if (read > 0) { // 読んでしまった分を戻す PushbackReader pbReader = new PushbackReader(reader, read); pbReader.unread(buf, 0, read); reader = pbReader; } } catch (IOException e) { throw new RuntimeException(e); } if (read < maxGram) { return new NGramTokenizer(reader, read, read); } else { return new NGramTokenizer(reader, maxGram, maxGram); } } }
基本は maxGram で分割し、検索語の長さが maxGram 未満の場合だけ、検索語の文字数で分割する(といっても1つのトークンになるだけ)。検索のときだけこっちを使うようにしたらうまくいけばいいなあということで明日試す。
Analyzer analyzer = new NGramAnalyzerForQuery(1, 3); QueryParser parser = new QueryParser(queryString, analyzer);