関口宏司のLuceneブログ

OSS検索ライブラリのLuceneおよびそのサブプロジェクト(Solr/Tika/Mahoutなど)について
"{x TO y]" や "[x TO y}"の範囲検索
現在のLuceneの範囲検索では"{"(中カッコ)と"]"(カギカッコ)を組み合わせて"{3 TO 6]"という検索をしようとすると、QueryParserによって次のように怒られる:
Exception in thread "main" org.apache.lucene.queryParser.ParseException: Cannot parse '{3 TO 6]': Encountered "" at line 1, column 8.
Was expecting:
    "}" ...
これをLucene 3.1にて可能にしようという提案がある:

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

Lucene 2.9以前で"{3 TO 6]"をするにはどうすればよいかという質問がMLに流れていて、それへの回答が"{3 TO 6} OR 6"でできますね、とあった。う〜ん、なるほど。

上記Q&AはQueryParserを使う前提である。しかしプログラムによりRangeQueryを次のように作成すれば、Lucene 2.9以前でも"{3 TO 6]"を表現することはできる:

RangeQuery query = new RangeQuery( F, "3", "6", false, true );

以下にQueryParserやRangeQueryを使った範囲検索のプログラム例を示す:

public class MixIncExRange {
  
  static Directory dir = new RAMDirectory();
  static Analyzer analyzer = new WhitespaceAnalyzer();
  static final String F = "f";
  static final String[] DOCS = { "1", "2", "3", "4", "5", "6", "7", "8" };

  public static void main(String[] args) throws Exception {
    makeIndex();
    testQuery();
    testParser();
  }

  static void makeIndex() throws IOException {
    System.out.println( "***** DATA *****" );
    IndexWriter writer = new IndexWriter( dir, analyzer, true, MaxFieldLength.LIMITED );
    for( String s : DOCS ){
      System.out.print( s + " " );
      Document doc = new Document();
      doc.add( new Field( F, s, Store.YES, Index.ANALYZED ) );
      writer.addDocument( doc );
    }
    writer.close();
    System.out.println();  // print '¥n'
  }
  
  static void testQuery() throws IOException {
    System.out.println( "¥n***** testQuery *****" );
    // 3 < n < 6
    RangeQuery rq1 = new RangeQuery( F, "3", "6", false, false );
    exec( "3 < n < 6", rq1 );
    // 3 <= n <= 6
    RangeQuery rq2 = new RangeQuery( F, "3", "6", true, true );
    exec( "3 <= n <= 6", rq2 );
    // 3 < n <= 6
    RangeQuery rq3 = new RangeQuery( F, "3", "6", false, true );
    exec( "3 < n <= 6", rq3 );
  }
  
  static void testParser() throws Exception {
    System.out.println( "¥n***** testParser *****" );
    // 3 < n < 6
    execParser( "3 < n < 6", "{3 TO 6}" );
    // 3 <= n <= 6
    execParser( "3 <= n <= 6", "[3 TO 6]" );
    // 3 < n <= 6
    execParser( "3 < n <= 6", "{3 TO 6} OR 6" );
  }
  
  static void exec( String q, Query query ) throws IOException {
    System.out.print( q + " ---> ");
    System.out.print( query.toString() + " ---> " );
    IndexSearcher searcher = new IndexSearcher( dir );
    TopDocs docs = searcher.search( query, 10 );
    for( ScoreDoc scoreDoc: docs.scoreDocs ){
      int docId = scoreDoc.doc;
      Document doc = searcher.doc( docId );
      System.out.print( doc.get( F ) + " " );
    }
    searcher.close();
    System.out.println();  // print '¥n'
  }
  
  static void execParser( String q, String parsedQuery ) throws Exception {
    QueryParser parser = new QueryParser( F, analyzer );
    Query query = parser.parse( parsedQuery );
    System.out.print( parsedQuery + " ---> " );
    exec( q, query );
  }
}

このプログラムを実行すると、次のように表示される:

***** DATA *****
1 2 3 4 5 6 7 8 

***** testQuery *****
3 < n < 6 ---> f:{3 TO 6} ---> 4 5 
3 <= n <= 6 ---> f:[3 TO 6] ---> 3 4 5 6 
3 < n <= 6 ---> f:{3 TO 6] ---> 4 5 6 

***** testParser *****
{3 TO 6} ---> 3 < n < 6 ---> f:{3 TO 6} ---> 4 5 
[3 TO 6] ---> 3 <= n <= 6 ---> f:[3 TO 6] ---> 3 4 5 6 
{3 TO 6} OR 6 ---> 3 < n <= 6 ---> f:{3 TO 6} f:6 ---> 6 4 5 
| 関口宏司 | Hello, Lucene (入門者向けプログラム例) | 18:34 | comments(0) | trackbacks(0) |
フィールド値を持つ文書を検索する
あるフィールドに値がある(値が何であるかは気にしない)文書を検索したい場合は、ConstantScoreRangeQueryを使って次のようなQueryを作成し、それを使ってsearch()すればよい:
Query query = new ConstantScoreRangeQuery( "field", null, null, false, false );
テストプログラムを以下に示す。
public class GetAllDocsThatHasValues {

  static Directory dir = new RAMDirectory();
  static Analyzer analyzer = new WhitespaceAnalyzer();
  static final String F_ID = "id";
  static final String F_NAME = "name";
  static final String[] IDS = {
    "1", "2", "3", "4", "5"
  };
  static final String[] NAMES = {
    "a", "b", "", null, "c"
  };

  public static void main(String[] args) throws IOException {
    makeIndex();
    searcIndex();
  }

  static void makeIndex() throws IOException {
    IndexWriter writer = new IndexWriter( dir, analyzer, true, MaxFieldLength.LIMITED );
    for( int i = 0; i < IDS.length; i++ ){
      Document doc = new Document();
      doc.add( new Field( F_ID, IDS[i], Store.YES, Index.ANALYZED ) );
      if( NAMES[i] != null )
	doc.add( new Field( F_NAME, NAMES[i], Store.YES, Index.ANALYZED ) );
      writer.addDocument( doc );
    }
    writer.close();
  }

  static void searcIndex() throws IOException {
    Query query = new ConstantScoreRangeQuery( F_NAME, null, null, false, false );
    IndexSearcher searcher = new IndexSearcher( dir );
    TopDocs docs = searcher.search( query, 10 );
    for( ScoreDoc scoreDoc : docs.scoreDocs ){
      int docId = scoreDoc.doc;
      Document doc = searcher.doc( docId );
      System.out.println( doc.get( F_ID ) + ":" + doc.get( F_NAME ) );
    }
    searcher.close();
  }
}
実行結果は次の通りとなる。
1:a
2:b
5:c
| 関口宏司 | Hello, Lucene (入門者向けプログラム例) | 23:29 | comments(0) | trackbacks(0) |
Lucene付属デモの使い方(初心者向き)
最近Lucene/Solrの利用者人口の増加を実感している。

初心者向きにモノを書いたり話したりすることが多くなり(もちろん利用経験者とのやりとりも増加中である)、そのため初めての人向きのネタが必要になることが増えてきている。

そこでこの記事では現在の安定版であるLucene 2.3.2の付属デモの使い方をまとめておこうと思う。

この記事では付属デモの「使い方」だけで、その中で使われるクラスなどの解説は後々別のところで書くこととする。

オペレーションの説明を具体的にするためにWindowsのC:ドライブの下にworkというディレクトリを作成し、その下で作業することを想定している。他のOSを使用する場合などは適宜読み替えて欲しい。なお、Java SDKは1.4以上があらかじめインストールされている必要があり、binディレクトリがPATH環境変数に含まれていなければならない。

1.LuceneのダウンロードとZIPの展開

以下のページに提示されているURLのいずれかをクリックしてダウンロードページに移動し、lucene-2.3.2.zipをダウンロードする:

http://www.apache.org/dyn/closer.cgi/lucene/java/

ダウンロードしたらZIPファイルをC:¥workに展開する。コマンドプロンプトを開いてC:¥workでdirコマンドを実行すると、次のように表示されるはずである:



C:¥work>dir

2008/06/23 09:08 <DIR> .
2008/06/23 09:08 <DIR> ..
2008/06/22 12:04 <DIR> lucene-2.3.2
2008/06/22 11:57 11,587,514 lucene-2.3.2.zip
1 個のファイル 11,587,514 バイト
3 個のディレクトリ 8,833,630,208 バイトの空き領域

C:¥work>



またC:¥work¥lucene-2.3.2の下は次のようになっているはずだ:



C:¥work>dir lucene-2.3.2

2008/06/22 12:04 <DIR> .
2008/06/22 12:04 <DIR> ..
2008/05/01 13:28 3,716 BUILD.txt
2008/05/01 13:32 10,184 build.xml
2008/05/01 13:28 105,519 CHANGES.txt
2008/06/22 11:58 <DIR> contrib
2008/06/22 11:58 <DIR> docs
2008/05/01 13:28 11,358 LICENSE.txt
2008/05/01 13:31 665,686 lucene-core-2.3.2.jar
2008/05/01 13:32 52,989 lucene-demos-2.3.2.jar
2008/05/01 13:32 668,864 luceneweb.war
2008/05/01 13:28 362 NOTICE.txt
2008/05/01 13:28 1,157 README.txt
2008/06/22 11:58 <DIR> src
9 個のファイル 1,519,835 バイト
6 個のディレクトリ 8,833,630,208 バイトの空き領域

C:¥work>



2.インデックスの作成

付属デモのIndexFilesプログラムを使って、ディレクトリ(ツリー)に保存されているテキストファイルをインデックスに登録する。ここでは登録するテキストファイルとして「このデモのJavaソースファイル」を利用することとする。

まずはC:¥work¥lucene-2.3.2ディレクトリに移動する:



C:¥work>cd lucene-2.3.2

C:¥work¥lucene-2.3.2>



Lucene本体はlucene-core-2.3.2.jarに、コンパイル済みのデモプログラムはlucene-demos-2.3.2.jarにパッケージされているので、デモ起動時のクラスパスにはこの2つのJARファイルを指定すればよい。

また、IndexFilesプログラムの第一引数にはインデックスに登録するテキストファイルのトップディレクトリを指定する。ここではデモのJavaソースが保存されているディレクトリsrcを指定する。

よってIndexFiles起動コマンド全体は次のようになる:



C:¥work¥lucene-2.3.2>java -cp lucene-core-2.3.2.jar;lucene-demos-2.3.2.jar org.apache.lucene.demo.IndexFiles src
Indexing to directory 'index'...
adding src¥demo¥org¥apache¥lucene¥demo¥DeleteFiles.java
adding src¥demo¥org¥apache¥lucene¥demo¥FileDocument.java
adding src¥demo¥org¥apache¥lucene¥demo¥html¥Entities.java
adding src¥demo¥org¥apache¥lucene¥demo¥html¥HTMLParser.java
adding src¥demo¥org¥apache¥lucene¥demo¥html¥HTMLParser.jj
adding src¥demo¥org¥apache¥lucene¥demo¥html¥HTMLParserConstants.java
adding src¥demo¥org¥apache¥lucene¥demo¥html¥HTMLParserTokenManager.java
adding src¥demo¥org¥apache¥lucene¥demo¥html¥ParseException.java
adding src¥demo¥org¥apache¥lucene¥demo¥html¥ParserThread.java
adding src¥demo¥org¥apache¥lucene¥demo¥html¥SimpleCharStream.java
adding src¥demo¥org¥apache¥lucene¥demo¥html¥Tags.java
adding src¥demo¥org¥apache¥lucene¥demo¥html¥Test.java
adding src¥demo¥org¥apache¥lucene¥demo¥html¥Token.java
adding src¥demo¥org¥apache¥lucene¥demo¥html¥TokenMgrError.java
adding src¥demo¥org¥apache¥lucene¥demo¥HTMLDocument.java
adding src¥demo¥org¥apache¥lucene¥demo¥IndexFiles.java
adding src¥demo¥org¥apache¥lucene¥demo¥IndexHTML.java
adding src¥demo¥org¥apache¥lucene¥demo¥SearchFiles.java
adding src¥jsp¥configuration.jsp
adding src¥jsp¥footer.jsp
adding src¥jsp¥header.jsp
adding src¥jsp¥index.jsp
adding src¥jsp¥README.txt
adding src¥jsp¥results.jsp
adding src¥jsp¥WEB-INF¥web.xml
Optimizing...
1129 total milliseconds

C:¥work¥lucene-2.3.2>



現在のディレクトリの下にindexという名前のディレクトリが次のように作成されていれば成功である:



C:¥work¥lucene-2.3.2>dir index

2008/06/23 09:35 <DIR> .
2008/06/23 09:35 <DIR> ..
2008/06/23 09:35 20 segments.gen
2008/06/23 09:35 45 segments_3
2008/06/23 09:35 42,445 _0.cfs
3 個のファイル 42,510 バイト
2 個のディレクトリ 8,833,810,432 バイトの空き領域

C:¥work¥lucene-2.3.2>



3.検索の実行

検索するにはSearchFilesというプログラムを使用する。クラスパスはIndexFilesと同じものを指定する。

SearchFilesを起動すると"Enter query:"というプロンプトが表示されるので、readerやwriterなど、デモプログラムに含まれていそうな単語を入力して[Enter]で検索を実行する:



C:¥work¥lucene-2.3.2>java -cp lucene-core-2.3.2.jar;lucene-demos-2.3.2.jar org.apache.lucene.demo.SearchFiles
Enter query:
reader
Searching for: reader
8 total matching documents
1. src¥demo¥org¥apache¥lucene¥demo¥FileDocument.java
2. src¥demo¥org¥apache¥lucene¥demo¥SearchFiles.java
3. src¥demo¥org¥apache¥lucene¥demo¥DeleteFiles.java
4. src¥demo¥org¥apache¥lucene¥demo¥html¥Test.java
5. src¥demo¥org¥apache¥lucene¥demo¥IndexHTML.java
6. src¥demo¥org¥apache¥lucene¥demo¥HTMLDocument.java
7. src¥demo¥org¥apache¥lucene¥demo¥html¥HTMLParser.jj
8. src¥demo¥org¥apache¥lucene¥demo¥html¥HTMLParser.java
Enter query:
writer
Searching for: writer
4 total matching documents
1. src¥demo¥org¥apache¥lucene¥demo¥IndexFiles.java
2. src¥demo¥org¥apache¥lucene¥demo¥IndexHTML.java
3. src¥demo¥org¥apache¥lucene¥demo¥html¥HTMLParser.jj
4. src¥demo¥org¥apache¥lucene¥demo¥html¥HTMLParser.java
Enter query:



"Enter query:"のプロンプトに2語以上を入れるとOR検索の意味になる:



Enter query:
reader writer
Searching for: reader writer
9 total matching documents
1. src¥demo¥org¥apache¥lucene¥demo¥IndexHTML.java
2. src¥demo¥org¥apache¥lucene¥demo¥html¥HTMLParser.jj
3. src¥demo¥org¥apache¥lucene¥demo¥IndexFiles.java
4. src¥demo¥org¥apache¥lucene¥demo¥html¥HTMLParser.java
5. src¥demo¥org¥apache¥lucene¥demo¥FileDocument.java
6. src¥demo¥org¥apache¥lucene¥demo¥SearchFiles.java
7. src¥demo¥org¥apache¥lucene¥demo¥DeleteFiles.java
8. src¥demo¥org¥apache¥lucene¥demo¥html¥Test.java
9. src¥demo¥org¥apache¥lucene¥demo¥HTMLDocument.java
Enter query:



AND検索を実行したい場合は、次のようにANDを明示する必要がある:



Enter query:
reader AND writer
Searching for: +reader +writer
3 total matching documents
1. src¥demo¥org¥apache¥lucene¥demo¥IndexHTML.java
2. src¥demo¥org¥apache¥lucene¥demo¥html¥HTMLParser.jj
3. src¥demo¥org¥apache¥lucene¥demo¥html¥HTMLParser.java
Enter query:



このように"Enter query:"のプロンプトにはいわゆるLucene標準の検索式を指定することができる。なお、この検索式のシンタックスは次のページで説明されている:

http://lucene.apache.org/java/2_3_2/queryparsersyntax.html

| 関口宏司 | Hello, Lucene (入門者向けプログラム例) | 10:06 | comments(9) | trackbacks(1) |
Luceneアプリケーションの開発環境の紹介
Luceneアプリケーションの開発環境を紹介する。私は「Apache Ant」のまえがきにも書いたとおりのAnt大好き人間なので、Luceneアプリの開発環境も迷うことなくAntである。

ここで紹介するのは、Luceneアプリのコンパイルと実行が簡単に行えるような開発環境である。開発環境のディレクトリ構造は、次の通りである:



C:¥Project¥blog¥lucene>tree/F
フォルダ パスの一覧
ボリューム シリアル番号は XXXXXXXX XXXX:XXXX です
C:.
│ build.properties
│ build.xml
│ myenv.properties

└─src
HelloLucene.java


C:¥Project¥blog¥lucene>



開発環境の「ホーム」ディレクトリに、build.xmlと2つのプロパティファイルmyenv.propertiesおよびbuild.propertiesファイルがある。
また、「ホーム」ディレクトリの下にsrcディレクトリがあり、このディレクトリ以下にLuceneアプリケーションのソースコードを配置する。

Antのビルドファイルであるbuild.xmlは、冒頭で上記2つのプロパティファイルを次のようにインポートしている:



<property file="myenv.properties"/>
<property file="build.properties"/>



これらのプロパティファイルでは、build.xmlで使用するプロパティを設定している。

プロパティファイルを2つに分けている理由は、開発環境依存のものとそうでないものにプロパティ設定を分けるためである。
myenv.propertiesの方は開発環境に依存する設定がなされており、build.propertiesの方には開発環境には依存せず普遍的なプロパティ設定が集められている。

myenv.propertiesは私の現在の環境では、次のようになっている:



LUCENE_HOME = c:/lucene-1.9.1
LUCENE_JA_HOME = c:/lucene-ja
SEN_HOME = c:/sen-1.2.1



上から順にLucene 1.9.1のインストールディレクトリ、Lucene-jaのインストールディレクトリ、Sen 1.2.1のインストールディレクトリである。
イコールの左辺のプロパティ名は、build.xmlにて次にインポートされるbuild.propertiesの中で参照されている。
build.propertiesは次のようである:



proj.name = blog.lucene

compile.encoding = MS932
compile.debug = true
compile.deprecation = ON

src.dir = src
cls.dir = classes
lucene.dir = ${LUCENE_HOME}
lucene-ja.dir = ${LUCENE_JA_HOME}
lucene-ja.lib.dir = ${lucene-ja.dir}/lib
sen.dir = ${SEN_HOME}

sen.jar = sen.jar
lucene-ja.jar = lucene-ja.jar
cmnlog.jar = commons-logging.jar

logger.class = org.apache.commons.logging.impl.NoOpLog



build.properties中で、いくつかのプロパティ設定の右辺がmyenv.propertiesで設定しているプロパティを参照していることがわかるだろう。
したがって、build.xmlの中で2つのプロパティファイルをインポートする順番に注意する必要がある。

build.xmlは次のようになっている:



<?xml version="1.0" encoding="Shift_JIS"?>
<project name="blog.lucene" default="all" basedir=".">

<property file="myenv.properties"/>
<property file="build.properties"/>

<path id="id.path.lucene">
<fileset dir="${lucene.dir}" includes="**/*.jar"/>
<fileset dir="${lucene-ja.lib.dir}" includes="${sen.jar}"/>
<fileset dir="${lucene-ja.lib.dir}" includes="${lucene-ja.jar}"/>
<fileset dir="${lucene-ja.lib.dir}" includes="${cmnlog.jar}"/>
</path>

<target name="all" depends="compile"/>

<target name="compile" description="全サンプルソースコードのコンパイル">
<mkdir dir="${cls.dir}"/>
<javac srcdir="${src.dir}"
destdir="${cls.dir}"
encoding="${compile.encoding}"
deprecation="${compile.deprecation}"
debug="${compile.debug}">
<classpath refid="id.path.lucene"/>
</javac>
</target>

<target name="run" depends="compile">
<fail unless="p" message="起動するクラス名をプロパティpに指定してください。"/>
<java classname="${p}" fork="yes">
<jvmarg line="-Dorg.apache.commons.logging.Log=${logger.class} -Dsen.home=${sen.dir}"/>
<classpath path="."/>
<classpath path="${cls.dir}"/>
<classpath refid="id.path.lucene"/>
</java>
</target>

<target name="clean">
<delete dir="${cls.dir}"/>
<delete>
<fileset dir="." includes="**/*~" defaultexcludes="no"/>
</delete>
</target>

</project>



主要なターゲットはcompileとrunである。これらのターゲットで使用しているクラスパスは、次のようにbuild.xmlの中で設定している。



<path id="id.path.lucene">
<fileset dir="${lucene.dir}" includes="**/*.jar"/>
<fileset dir="${lucene-ja.lib.dir}" includes="${sen.jar}"/>
<fileset dir="${lucene-ja.lib.dir}" includes="${lucene-ja.jar}"/>
<fileset dir="${lucene-ja.lib.dir}" includes="${cmnlog.jar}"/>
</path>



これにより、日本語Luceneアプリケーション(JapaneseAnalyzerを使うという意味で)のコンパイルと実行に必要なクラスパス設定を行っている。

ちなみに上記の最後のfilesetでは、Jakarta Commons LoggingのJarファイルを指定しているが、これはLuceneアプリケーションのコンパイル時には必要ない。実行時に必要となってくるものである。
Jakarta Commons LoggingはLuceneアプリケーションのコンパイル時には不要なので(アプリの中でLoggingを使っていなければ)、実行時にクラスパスに指定することをつい忘れがちである。
このJarファイルをクラスパスに含めずにJapaneseAnalyzerのインスタンスを作成するアプリケーションを実行すると、次のようなエラーが発生する:



Exception in thread "main" org.apache.lucene.analysis.ja.JapaneseAnalyzerException: Can't load a Japanese tokenizer: null
at org.apache.lucene.analysis.ja.JapaneseAnalyzer.tokenStream(Unknown Source)
at org.apache.lucene.index.DocumentWriter.invertDocument(DocumentWriter.java:162)
at org.apache.lucene.index.DocumentWriter.addDocument(DocumentWriter.java:93)
at org.apache.lucene.index.IndexWriter.addDocument(IndexWriter.java:450)
at org.apache.lucene.index.IndexWriter.addDocument(IndexWriter.java:436)
at HelloLucene.makeIndex(HelloLucene.java:60)
at HelloLucene.main(HelloLucene.java:48)



これは、JapaneseAnalyzerが使用しているSenのライブラリがJakarta Commons Loggingを使用しており、そのためJakarta Commons LoggingのJarをクラスパスに含めないで実行すると、JapaneseAnalyzerのtokenStream()メソッドの中でSenのクラスのインスタンスの取得に失敗するためである。

このbuild.xmlを使ってLuceneアプリケーションのコンパイルを行うには、srcディレクトリの下にソースを配置し、次のようにAntを実行すればよい:



C:¥Project¥blog¥lucene>ant
Buildfile: build.xml

compile:
[mkdir] Created dir: C:¥Project¥blog¥lucene¥classes
[javac] Compiling 1 source file to C:¥Project¥blog¥lucene¥classes

all:

BUILD SUCCESSFUL
Total time: 2 seconds
C:¥Project¥blog¥lucene>



すると、classesというディレクトリが作成され、コンパイルされたバイトコードがそのディレクトリの下に作られる:



C:¥Project¥blog¥lucene>tree/F
フォルダ パスの一覧
ボリューム シリアル番号は XXXXXXXX XXXX:XXXX です
C:.
│ build.properties
│ build.xml
│ myenv.properties

├─classes
│ HelloLucene.class

└─src
HelloLucene.java


C:¥Project¥blog¥lucene>



このようにしてコンパイルしたLuceneアプリケーションHelloLuceneをAntから実行するには、次のように行う:



C:¥Project¥blog¥lucene>ant run -Dp=HelloLucene
Buildfile: build.xml

compile:

run:

[java] ***** query = "カツオ" 6件ヒットしました。 *****
[java] カツオは長男
[java] カツオはサザエの弟
[java] ワカメはカツオの妹
[java] 舟はカツオの母
[java] マスオはカツオの義兄
[java] カツオはタラちゃんの叔父

(中略)

[java] ***** query = "サザエ AND ワカメ" 1件ヒットしました。 *****
[java] サザエはワカメの姉

[java] ***** query = "三河屋さん" 0件ヒットしました。 *****

BUILD SUCCESSFUL
Total time: 4 seconds
C:¥Project¥blog¥lucene>



Antを実行するときのコマンドラインに指定した引数"-Dp=HelloLucene"の意味は、プロパティ p にHelloLuceneを設定する、ということである。
runターゲットの<java>Antタスクでは実行するクラス名を${p}で参照しているので、このように呼び出すことにより、あらゆるLuceneアプリケーションを起動できるようにしている。

"-Dp="を忘れてAntのrunターゲットを呼び出した場合、runターゲットの最初のAntタスク<fail>のチェックにひっかかるので、次のようにrunの実行が失敗する:



C:¥Project¥blog¥lucene>ant run
Buildfile: build.xml

compile:

run:

BUILD FAILED
C:¥Project¥blog¥lucene¥build.xml:28: 起動するクラス名をプロパティpに指定してください。

Total time: 1 second
C:¥Project¥blog¥lucene>


| 関口宏司 | Hello, Lucene (入門者向けプログラム例) | 11:43 | comments(0) | trackbacks(0) |
はじめてのLucene全文検索プログラム
子供のころ、日曜日の夕方になるとしばしば不安感に襲われたことを思い出す。
それは「月曜日からまた学校が始まる」ということのほかに、あるテレビ番組も原因のひとつだったように思う。
それは「サザエさん」である。
思えばフシギな番組であった(今でも続いているが・・・)。何がフシギかというと、カツオとタラちゃん、ワカメとタラちゃんは兄弟(姉弟)ではない、ということだ。
これは子供心にとても理解しがたい事実であった。あの三人はどうみても兄弟であろう。しかしどうだ。実際はサザエ・カツオ・ワカメが兄弟姉妹の関係なのだ。
カツオとワカメは小学生のようだが、彼らの姉は結婚して子供もいるのである。サザエさんとその下の二人はとても歳が離れているのだ。サザエさんが生まれた後、十年ほど経ってからカツオとワカメが相次いで生まれた計算だ。
そのとき波平と舟の間に何があったのだろう。小学生のころはそんなことは考えなかったと思うが、今はとても気になる。
・・・そういうことなので、タラちゃんにとってカツオはお兄さんではなく、ワカメはお姉さんではなく、おじさん、おばさんであり、カツオやワカメから見てタラちゃんは弟ではなく甥、ということを子供のころに学習した。
また、作者の「長谷川町子」という名前、子供のころはこの苗字も最初はなんと読むのかさっぱりわからなかった(ほかにも「東海林さだお」もよくわからなかった)。そんなことがあり、「サザエさん」は子供のころの教材であった。
もっとも、無垢な子供のころはなんでも教材になるものだ。

そういうわけで、Luceneの入門用プログラムにもサザエさんをコンテンツに選択する。サザエさんのコンテンツは、次のようなものである:



private static final String[] contents = {
"カツオはサザエの弟", "サザエはワカメの姉", "ワカメはカツオの妹",
"カツオは長男", "サザエは長女", "ワカメは次女",
"マスオはサザエの夫", "波平は舟の夫", "タラちゃんのパパはマスオ",
"サザエとマスオは夫婦", "波平はタラちゃんの祖父", "舟はカツオの母",
"マスオはカツオの義兄", "カツオはタラちゃんの叔父", "舟はワカメの母"
};



上記のように、contentsという文字列配列に、サザエさんファミリーの人間関係を表した短い文を設定している。ここではこのひとつひとつがドキュメントである。
ドキュメントは、全文検索の対象となるオブジェクトで、この例で言えばひとつの要素contents[i]がひとつのHTMLファイルなりPDFファイルの代わりである。
入門用プログラムなので、全文検索対象となるドキュメントは、HTMLやPDFではなくこのような簡単な文字列にしよう、ということだ。

上記のようなコンテンツに対し、検索質問の文字列も次のような文字列配列で用意する:



private static final String[] queries = {
"カツオ", "ワカメ", "サザエ ワカメ", "サザエ AND ワカメ", "三河屋さん"
};



プログラムでは、contentsのドキュメントでインデックスを作成し、次に検索質問語であるqueriesの分だけforループをまわし、インデックスを検索する。
ではプログラムを紹介する前に、実行結果を先に見ておこう。それは、次のようになる:



***** query = "カツオ" 6件ヒットしました。 *****
カツオは長男
カツオはサザエの弟
ワカメはカツオの妹
舟はカツオの母
マスオはカツオの義兄
カツオはタラちゃんの叔父

***** query = "ワカメ" 4件ヒットしました。 *****
ワカメは次女
サザエはワカメの姉
ワカメはカツオの妹
舟はワカメの母

***** query = "サザエ ワカメ" 8件ヒットしました。 *****
サザエはワカメの姉
ワカメは次女
ワカメはカツオの妹
舟はワカメの母
サザエは長女
カツオはサザエの弟
マスオはサザエの夫
サザエとマスオは夫婦

***** query = "サザエ AND ワカメ" 1件ヒットしました。 *****
サザエはワカメの姉

***** query = "三河屋さん" 0件ヒットしました。 *****



実行結果は、queries[0]の"カツオ"を検索したときにcontents[]のドキュメントのうち6件がヒットし、その内容を表示している。以下、queries[1]の検索結果の表示、queries[2]の検索結果の表示、・・・と続く。

プログラムの全体は、次のようである:



import java.io.IOException;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.ja.JapaneseAnalyzer;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.Query;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.queryParser.ParseException;

public class HelloLucene {

private static final String FIELD_CONTENT = "content";
private static final Directory directory = new RAMDirectory();
private static final Analyzer analyzer = new JapaneseAnalyzer();
private static final QueryParser qp = new QueryParser( FIELD_CONTENT, analyzer );

private static final String[] contents = {
"カツオはサザエの弟", "サザエはワカメの姉", "ワカメはカツオの妹",
"カツオは長男", "サザエは長女", "ワカメは次女",
"マスオはサザエの夫", "波平は舟の夫", "タラちゃんのパパはマスオ",
"サザエとマスオは夫婦", "波平はタラちゃんの祖父", "舟はカツオの母",
"マスオはカツオの義兄", "カツオはタラちゃんの叔父", "舟はワカメの母"
};
private static final String[] queries = {
"カツオ", "ワカメ", "サザエ ワカメ", "サザエ AND ワカメ", "三河屋さん"
};

public static void main( String[] args ) throws IOException, ParseException {
makeIndex();
for( int i = 0; i < queries.length; i++ )
searchIndex( queries[i] );
if( directory != null )
directory.close();
}

private static void makeIndex() throws IOException {
IndexWriter writer = new IndexWriter( directory, analyzer, true );
for( int i = 0; i < contents.length; i++ ){
Document doc = new Document();
doc.add( new Field( FIELD_CONTENT, contents[i], Field.Store.YES, Field.Index.TOKENIZED ) );
writer.addDocument( doc );
}
writer.close();
}

private static void searchIndex( final String q ) throws IOException, ParseException {
IndexSearcher searcher = new IndexSearcher( directory );
Query query = qp.parse( q );
Hits hits = searcher.search( query );
int length = hits.length();
System.out.print( "¥n***** query = " + "¥"" + q + "¥" " );
System.out.println( Integer.toString( length ) + "件ヒットしました。 *****" );
for( int i = 0; i < length; i++ ){
Document doc = hits.doc( i );
System.out.println( "¥t" + doc.get( FIELD_CONTENT ) );
}
searcher.close();
}
}



プログラムのメインストリーム(main()の中)は、最初にmakeIndex()を呼んでインデックスを作成し、次にqueries[]の大きさだけforループをまわしてqueries[]要素を引数にしてsearchIndex()を呼んで検索を実行している。

makeIndex()では、IndexWriterのインスタンスを作成し、addDocument()でDocumentをインデックスに登録している。Documentはcontents[]の要素である。
FieldはDocumentを構成する要素で、ここでは次のようにDocumentにadd()している:



Document doc = new Document();
doc.add( new Field( FIELD_CONTENT, contents[i], Field.Store.YES, Field.Index.TOKENIZED ) );



ここで、FIELD_CONTENTはフィールド名、contents[i]はフィールドの文字列である。Field.Store.YESはフィールドの文字列をインデックスに登録することを示し、Field.Index.TOKENIZEDはフィールド文字列をアナライザーで分析しながら単語を索引付けすることを示している。

searchIndex()では、IndexSearcherインスタンスを作成し、search()メソッドで全文検索を実行している。検索結果はHitsオブジェクトで返るので、ヒット件数(hits.length())とその内容を表示している。
| 関口宏司 | Hello, Lucene (入門者向けプログラム例) | 11:32 | comments(0) | trackbacks(1) |
+ Solrによるブログ内検索
+ PROFILE
     12
3456789
10111213141516
17181920212223
24252627282930
<< June 2018 >>
+ 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