関口宏司のLuceneブログ

OSS検索ライブラリのLuceneおよびそのサブプロジェクト(Solr/Tika/Mahoutなど)について
スポンサーサイト

一定期間更新がないため広告を表示しています

| スポンサードリンク | - | | - | - |
ReverseStringFilter(2.9)
Lucene 2.9のcontrib/analyzersに、これまでありそうでなかったReverseStringFilterというTokenFilterが導入された。これは名前の通り、トークンの文字列を逆転させたトークンを出力するTokenFilterである。

たとえば"ABCD"というトークンは"DCBA"と変換されて出力される。

https://issues.apache.org/jira/browse/LUCENE-1398

これはどのような時に使えばいいかというと、単語の「後方一致検索」をしたいときに使うとよい。

たとえば、"ing"で終わる単語を検索したいとする。WildcardQueryを使って"*ing"を検索することはできるが、このような検索は転置索引の当該フィールドの単語をすべてチェックすることになってしまい検索に時間がかかってしまう。

そこで単語の「後方一致検索」をしたいフィールドに関してはReverseStringFilterを使って"looking"という単語なら"gnikool"というように登録する。そうしておいてWildcardQueryで"gni*"と検索すると転置索引のうち、"gni"で始まる範囲だけを見ればよいので格段に検索速度があがる。

以下に使用例を示す:



public class TestReverseStringFilter {

static Directory dir = new RAMDirectory();
static Analyzer analyzer = new ReverseStringAnalyzer();
static final String F = "f";

public static void main(String[] args) throws Exception {
makeIndex();
searchIndex( "*ing" );
searchIndex( "*.com" );
}

static void makeIndex() throws Exception {
IndexWriter writer = new IndexWriter( dir, analyzer, true, MaxFieldLength.LIMITED );
addDoc( writer, "Apache" );
addDoc( writer, "Lucene" );
addDoc( writer, "looking" );
addDoc( writer, "hacking" );
addDoc( writer, "lucene.apache.org" );
addDoc( writer, "www.rondhuit.com" );
writer.close();
}

static void addDoc( IndexWriter writer, String val ) throws Exception {
Document doc = new Document();
doc.add( new Field( F, val, Store.YES, Index.ANALYZED ) );
writer.addDocument( doc );
}

static void searchIndex( String q ) throws Exception {
System.out.println( "¥nsearching ¥"" + q + "¥" ..." );
IndexSearcher searcher = new IndexSearcher( dir );
Query query = new WildcardQuery( new Term( F, ReverseStringFilter.reverse( q ) ) );
TopDocs docs = searcher.search( query, 10 );
for( ScoreDoc scoreDoc : docs.scoreDocs ){
Document doc = searcher.doc( scoreDoc.doc );
System.out.println( doc.get( F ) );
}
searcher.close();
}

static class ReverseStringAnalyzer extends Analyzer {
public TokenStream tokenStream( String field, Reader input ) {
TokenStream ts = new KeywordTokenizer( input );
return new ReverseStringFilter( ts );
}
}
}



そして、実行結果は次の通りである:



searching "*ing" ...
looking
hacking

searching "*.com" ...
www.rondhuit.com


| 関口宏司 | Luceneクラス解説 | 00:25 | comments(1) | trackbacks(0) |
メールの署名欄を取り除く方法(MLネタ)
LuceneのMLで「メールをインデックスに登録して検索できるようにしたいが、署名欄が会社名などでひっかからないように署名欄を取り除きたい。いい方法はないでしょうか」というような質問があった。LuceneのAPIではどうしようもない問題だが、それに対しおもしろい回答があったので紹介する。

1.メールの文面をパラグラフで分割する
2.パラグラフのハッシュを計算する
3.同じ送信者の他のハッシュと比較し、同じモノは署名とみてよい

実際には返信文面にメーラーにより自動的に挿入される">"などを取り除かないといけなかったりして簡単ではないと思われるが、日常使っているプログラミング手法で解決できそうなところがおもしろい。
| 関口宏司 | その他(分類不能) | 22:04 | comments(4) | trackbacks(0) |
contrib/xml-query-parserで複雑な検索式を解析する
Luceneの標準QueryParserはAND/OR/NOTなどの演算子や"(",")"などを使ったグルーピング、"^"を使った重み指定、"~"のスロップ指定など、これらの組み合わせからなる複雑な検索式を解析できる。QueryParserが解析できる検索式はこちらで見ることができる

QueryParserはほとんどの基本的な検索要件(検索式)を解析してQueryオブジェクトを生成できる。しかし万能ではない。たとえば、LuceneのQueryの一種であるSpanQueryなどは種類がいろいろあったりするので(SpanTermQuery、SpanFirstQuery、SpanOrQuery、SpanNotQuery、SpanNearQuery、SpanRegexQuery)複雑すぎて検索式をルール化するのが難しい。また、Queryクラスは今後いくらでも増える可能性がある。そのたびにQueryParserに新しいルールを持ち込むのも大変で検索式が複雑になるのもあまりよくない。

そのような背景から考えられたのがcontrib/xml-query-parserで、「検索式をXMLで表現しよう」というものである。

たとえば、次のような文書があるとする:



// AnalyzerはWhitespaceAnalyzerを使用
static final String[] docs = {
"A B C D",
"A B 1 C D",
"A B 1 2 C D",
"A B 1 2 3 C D",
"D C B A",
};



この中から「単語Bと単語Cがこの順番で出現し、単語間の距離が2以内のもの」をSpanNearQueryを使って検索するとする。SpanNearQueryをプログラムで組み立てる場合は、次のようになる:



static Query getSpanQuery(){
System.out.println( "*** use SpqnQuery classes ***" );
SpanQuery sqb = new SpanTermQuery( new Term( F, "B" ) );
SpanQuery sqc = new SpanTermQuery( new Term( F, "C" ) );
return new SpanNearQuery( new SpanQuery[]{ sqb, sqc }, 2, true );
}



同じSpanNearQueryをcontrib/xml-query-parserで取得するには、まず、次のようなXML(検索式)を用意する:



<?xml version="1.0" encoding="UTF-8"?>
<SpanNear fieldName="f" slop="2" inOrder="true" >
<SpanTerm>B</SpanTerm>
<SpanTerm>C</SpanTerm>
</SpanNear>



そしてcontrib/xml-query-parserで上記XMLを解析してQueryオブジェクトを取得するには次のように行う:



static Query getSpanQueryFromXml() throws Exception {
System.out.println( "¥n*** use xmlParser ***" );
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
// XML_FILEは上記XMLファイルへのパス
org.w3c.dom.Document document = builder.parse( new File( XML_FILE ) );
return new CorePlusExtensionsParser( F, analyzer ).getQuery( document.getDocumentElement() );
}



プログラムの全体は次のようになる:



public class TestParseSpanQueryXml {

static Directory dir = new RAMDirectory();
static Analyzer analyzer = new WhitespaceAnalyzer();
static final String[] docs = {
"A B C D",
"A B 1 C D",
"A B 1 2 C D",
"A B 1 2 3 C D",
"D C B A",
};
static final String F = "f";

public static void main(String[] args) throws Exception {
makeIndex();
printResults( getSpanQuery() );
printResults( getSpanQueryFromXml() );
}

static void makeIndex() throws IOException {
IndexWriter writer = new IndexWriter( dir, analyzer, MaxFieldLength.LIMITED );
for( String d : docs ){
Document doc = new Document();
doc.add( new Field( F, d, Store.YES, Index.ANALYZED ) );
writer.addDocument( doc );
}
writer.close();
}

static void printResults( Query query ) throws IOException {
System.out.println( "* query = " + query.toString() );
IndexSearcher searcher = new IndexSearcher( dir );
TopDocs docs = searcher.search( query, 10 );
for( ScoreDoc scoreDoc : docs.scoreDocs ){
float score = scoreDoc.score;
int doc = scoreDoc.doc;
System.out.println( score + "¥t: " + searcher.doc( doc ).get( F ) );
}
searcher.close();
}

static Query getSpanQuery(){
System.out.println( "*** use SpqnQuery classes ***" );
SpanQuery sqb = new SpanTermQuery( new Term( F, "B" ) );
SpanQuery sqc = new SpanTermQuery( new Term( F, "C" ) );
return new SpanNearQuery( new SpanQuery[]{ sqb, sqc }, 2, true );
}

static final String XML_FILE = "/tmp/query.xml";

static Query getSpanQueryFromXml() throws Exception {
System.out.println( "¥n*** use xmlParser ***" );
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
org.w3c.dom.Document document = builder.parse( new File( XML_FILE ) );
return new CorePlusExtensionsParser( F, analyzer ).getQuery( document.getDocumentElement() );
}
}



プログラムの実行結果は次の通りとなり、SpanQueryをプログラムで組み立てた場合とcontrib/xml-query-parserを使った場合で結果は同じとなる:



*** use SpqnQuery classes ***
* query = spanNear([f:B, f:C], 2, true)
0.47208688 : A B C D
0.35773432 : A B 1 C D
0.2742577 : A B 1 2 C D

*** use xmlParser ***
* query = spanNear([f:B, f:C], 2, true)
0.47208688 : A B C D
0.35773432 : A B 1 C D
0.2742577 : A B 1 2 C D



contrib/xml-query-parserはSpanQuery以外にももっといろいろなLuceneのQueryをサポートしている。もちろん、QueryParserがサポートしているような基本的な検索式もXMLで表現できるようになっている(そうでないと基本検索式はQueryParserで解析、それ以外の複雑な検索式はcontrib/xml-query-parserで解析・・・となってしまい、使い勝手が低下してしまう)。
| 関口宏司 | Luceneクラス解説 | 01:41 | comments(0) | trackbacks(0) |
+ Solrによるブログ内検索
+ PROFILE
1234567
891011121314
15161718192021
22232425262728
<< February 2009 >>
+ LINKS
検索エンジン製品 - 比較のポイント
商用検索エンジンを購入した企業担当者は読まないでください。ショックを受けますから・・・
>>製品比較 10のポイント
+ Lucene&Solrデモ
+ ThinkIT記事
+ RECOMMEND
Apache Solr入門 ―オープンソース全文検索エンジン
Apache Solr入門 ―オープンソース全文検索エンジン (JUGEMレビュー »)
関口 宏司,三部 靖夫,武田 光平,中野 猛,大谷 純
+ RECOMMEND
Lucene in Action
Lucene in Action (JUGEMレビュー »)
Erik Hatcher,Otis Gospodnetic,Mike McCandless
FastVectorHighlighterについて解説記事を寄稿しました。
+ RECOMMEND
+ SELECTED ENTRIES
+ RECENT COMMENTS
+ RECENT TRACKBACK
+ CATEGORIES
+ ARCHIVES
+ MOBILE
qrcode
+ SPONSORED LINKS