Apache Luceneの紹介

Apache Luceneの概要

1. 概要

Apache Luceneは、さまざまなプログラミング言語から使用できる全文検索エンジンです。

この記事では、ライブラリのコアコンセプトを理解し、簡単なアプリケーションを作成することを試みます。

2. Mavenセットアップ

開始するには、最初に必要な依存関係を追加しましょう。


    org.apache.lucene
    lucene-core
    7.1.0

最新バージョンはhereで見つけることができます。

また、検索クエリを解析するには、次のものが必要です。


    org.apache.lucene
    lucene-queryparser
    7.1.0

最新バージョンhereを確認します。

3. コアコンセプト

3.1. 索引付け

簡単に言えば、Luceneはデータの「転置インデックス」を使用します–instead of mapping pages to keywords, it maps keywords to pagesは、本の最後にある用語集のようです。

これにより、テキストを直接検索するのではなく、インデックスを検索するため、検索応答が高速になります。

3.2. 書類

ここで、ドキュメントはフィールドのコレクションであり、各フィールドには値が関連付けられています。

通常、インデックスは1つ以上のドキュメントで構成され、検索結果は最適なドキュメントのセットです。

必ずしもプレーンテキストのドキュメントであるとは限りません。データベーステーブルやコレクションの場合もあります。

3.3. フィールド

ドキュメントはフィールドデータを持つことができます。通常、フィールドはデータ値を保持するキーです。

title: Goodness of Tea
body: Discussing goodness of drinking herbal tea...

ここで、titlebodyはフィールドであり、一緒にまたは個別に検索できることに注意してください。

3.4. 分析

分析とは、検索を簡単にするために、指定されたテキストをより小さく正確な単位に変換することです。

テキストには、キーワードの抽出、一般的な単語と句読点の削除、単語の小文字への変更などのさまざまな操作が行われます。

この目的のために、複数の組み込みアナライザーがあります。

  1. StandardAnalyzer –基本的な文法に基づいて分析し、「a」、「an」などのストップワードを削除します。 小文字にも変換します

  2. SimpleAnalyzer –文字なしの文字に基づいてテキストを分割し、小文字に変換します

  3. WhiteSpaceAnalyzer –空白に基づいてテキストを分割します

使用およびカスタマイズできるアナライザーは他にもあります。

3.5. 検索中

インデックスが作成されると、QueryIndexSearcher.を使用してそのインデックスを検索できます。検索結果は通常、取得したデータを含む結果セットです。

IndexWritterはインデックスの作成を担当し、IndexSearcherはインデックスの検索を担当することに注意してください。

3.6. クエリ構文

Luceneは、非常に動的で記述しやすいクエリ構文を提供します。

フリーテキストを検索するには、クエリとしてテキストStringを使用します。

特定のフィールドのテキストを検索するには、次を使用します。

fieldName:text

eg: title:tea

範囲検索:

timestamp:[1509909322,1572981321]

ワイルドカードを使用して検索することもできます。

dri?nk

ワイルドカード「?」の代わりに単一の文字を検索します

d*k

「d」で始まり「k」で終わる単語を検索します。複数の文字が間にあります。

uni*

「uni」で始まる単語を検索します。

これらのクエリを組み合わせて、より複雑なクエリを作成することもできます。 AND、NOT、ORなどの論理演算子を含めます。

title: "Tea in breakfast" AND "coffee"

クエリ構文hereの詳細。

4. シンプルなアプリケーション

簡単なアプリケーションを作成して、いくつかのドキュメントにインデックスを付けましょう。

まず、メモリ内インデックスを作成し、それにいくつかのドキュメントを追加します。

...
Directory memoryIndex = new RAMDirectory();
StandardAnalyzer analyzer = new StandardAnalyzer();
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
IndexWriter writter = new IndexWriter(memoryIndex, indexWriterConfig);
Document document = new Document();

document.add(new TextField("title", title, Field.Store.YES));
document.add(new TextField("body", body, Field.Store.YES));

writter.addDocument(document);
writter.close();

ここでは、TextFieldを使用してドキュメントを作成し、IndexWriter.を使用してそれらをインデックスに追加します。TextFieldコンストラクターの3番目の引数は、フィールドの値も格納するかどうかを示します。 。

アナライザーは、データまたはテキストをチャンクに分割し、それらからストップワードを除外するために使用されます。 ストップワードは、「a」、「am」、「is」などの単語です。 これらは特定の言語に完全に依存しています。

次に、検索クエリを作成して、追加されたドキュメントのインデックスを検索しましょう。

public List searchIndex(String inField, String queryString) {
    Query query = new QueryParser(inField, analyzer)
      .parse(queryString);

    IndexReader indexReader = DirectoryReader.open(memoryIndex);
    IndexSearcher searcher = new IndexSearcher(indexReader);
    TopDocs topDocs = searcher.search(query, 10);
    List documents = new ArrayList<>();
    for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
        documents.add(searcher.doc(scoreDoc.doc));
    }

    return documents;
}

search()メソッドでは、2番目の整数引数は、返される上位の検索結果の数を示します。

それでは、テストしてみましょう。

@Test
public void givenSearchQueryWhenFetchedDocumentThenCorrect() {
    InMemoryLuceneIndex inMemoryLuceneIndex
      = new InMemoryLuceneIndex(new RAMDirectory(), new StandardAnalyzer());
    inMemoryLuceneIndex.indexDocument("Hello world", "Some hello world");

    List documents
      = inMemoryLuceneIndex.searchIndex("body", "world");

    assertEquals(
      "Hello world",
      documents.get(0).get("title"));
}

ここでは、「title」と「body」の2つのフィールドを持つ簡単なドキュメントをインデックスに追加し、検索クエリを使用して同じものを検索します。

6. Luceneクエリ

インデックス作成と検索の基本に慣れてきたので、もう少し詳しく見てみましょう。

前のセクションでは、基本的なクエリ構文と、QueryParser.を使用してそれをQueryインスタンスに変換する方法について説明しました。

Luceneは、さまざまな具体的な実装も提供します。

6.1. TermQuery

Termは検索の基本単位であり、フィールド名と検索対象のテキストが含まれています。

TermQueryは、単一の用語で構成されるすべてのクエリの中で最も単純です。

@Test
public void givenTermQueryWhenFetchedDocumentThenCorrect() {
    InMemoryLuceneIndex inMemoryLuceneIndex
      = new InMemoryLuceneIndex(new RAMDirectory(), new StandardAnalyzer());
    inMemoryLuceneIndex.indexDocument("activity", "running in track");
    inMemoryLuceneIndex.indexDocument("activity", "Cars are running on road");

    Term term = new Term("body", "running");
    Query query = new TermQuery(term);

    List documents = inMemoryLuceneIndex.searchIndex(query);
    assertEquals(2, documents.size());
}

6.2. PrefixQuery

「で始まる」という単語を含むドキュメントを検索するには:

@Test
public void givenPrefixQueryWhenFetchedDocumentThenCorrect() {
    InMemoryLuceneIndex inMemoryLuceneIndex
      = new InMemoryLuceneIndex(new RAMDirectory(), new StandardAnalyzer());
    inMemoryLuceneIndex.indexDocument("article", "Lucene introduction");
    inMemoryLuceneIndex.indexDocument("article", "Introduction to Lucene");

    Term term = new Term("body", "intro");
    Query query = new PrefixQuery(term);

    List documents = inMemoryLuceneIndex.searchIndex(query);
    assertEquals(2, documents.size());
}

6.3. WildcardQuery

名前が示すように、検索にワイルドカード「*」または「?」を使用できます。

// ...
Term term = new Term("body", "intro*");
Query query = new WildcardQuery(term);
// ...

6.4. PhraseQuery

ドキュメント内の一連のテキストを検索するために使用されます。

// ...
inMemoryLuceneIndex.indexDocument(
  "quotes",
  "A rose by any other name would smell as sweet.");

Query query = new PhraseQuery(
  1, "body", new BytesRef("smell"), new BytesRef("sweet"));

List documents = inMemoryLuceneIndex.searchIndex(query);
// ...

PhraseQueryコンストラクターの最初の引数はslop,と呼ばれることに注意してください。これは、一致する用語間の単語数の距離です。

6.5. FuzzyQuery

類似するものを検索するときにこれを使用できますが、必ずしも同一ではありません。

// ...
inMemoryLuceneIndex.indexDocument("article", "Halloween Festival");
inMemoryLuceneIndex.indexDocument("decoration", "Decorations for Halloween");

Term term = new Term("body", "hallowen");
Query query = new FuzzyQuery(term);

List documents = inMemoryLuceneIndex.searchIndex(query);
// ...

「ハロウィン」というテキストを検索しようとしましたが、「ハロウィン」というつづりが間違っています。

6.6. BooleanQuery

2つ以上の異なる種類のクエリを組み合わせて、複雑な検索を実行する必要がある場合があります。

// ...
inMemoryLuceneIndex.indexDocument("Destination", "Las Vegas singapore car");
inMemoryLuceneIndex.indexDocument("Commutes in singapore", "Bus Car Bikes");

Term term1 = new Term("body", "singapore");
Term term2 = new Term("body", "car");

TermQuery query1 = new TermQuery(term1);
TermQuery query2 = new TermQuery(term2);

BooleanQuery booleanQuery
  = new BooleanQuery.Builder()
    .add(query1, BooleanClause.Occur.MUST)
    .add(query2, BooleanClause.Occur.MUST)
    .build();
// ...

7. 検索結果の並べ替え

特定のフィールドに基づいて検索結果ドキュメントを並べ替えることもできます。

@Test
public void givenSortFieldWhenSortedThenCorrect() {
    InMemoryLuceneIndex inMemoryLuceneIndex
      = new InMemoryLuceneIndex(new RAMDirectory(), new StandardAnalyzer());
    inMemoryLuceneIndex.indexDocument("Ganges", "River in India");
    inMemoryLuceneIndex.indexDocument("Mekong", "This river flows in south Asia");
    inMemoryLuceneIndex.indexDocument("Amazon", "Rain forest river");
    inMemoryLuceneIndex.indexDocument("Rhine", "Belongs to Europe");
    inMemoryLuceneIndex.indexDocument("Nile", "Longest River");

    Term term = new Term("body", "river");
    Query query = new WildcardQuery(term);

    SortField sortField
      = new SortField("title", SortField.Type.STRING_VAL, false);
    Sort sortByTitle = new Sort(sortField);

    List documents
      = inMemoryLuceneIndex.searchIndex(query, sortByTitle);
    assertEquals(4, documents.size());
    assertEquals("Amazon", documents.get(0).getField("title").stringValue());
}

取得したドキュメントをタイトルフィールドで並べ替えようとしました。タイトルフィールドは川の名前です。 SortFieldコンストラクターのブール引数は、ソート順を逆にするためのものです。

8. インデックスからドキュメントを削除

指定されたTerm:に基づいて、インデックスからいくつかのドキュメントを削除してみましょう。

// ...
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
IndexWriter writer = new IndexWriter(memoryIndex, indexWriterConfig);
writer.deleteDocuments(term);
// ...

これをテストします:

@Test
public void whenDocumentDeletedThenCorrect() {
    InMemoryLuceneIndex inMemoryLuceneIndex
      = new InMemoryLuceneIndex(new RAMDirectory(), new StandardAnalyzer());
    inMemoryLuceneIndex.indexDocument("Ganges", "River in India");
    inMemoryLuceneIndex.indexDocument("Mekong", "This river flows in south Asia");

    Term term = new Term("title", "ganges");
    inMemoryLuceneIndex.deleteDocument(term);

    Query query = new TermQuery(term);

    List documents = inMemoryLuceneIndex.searchIndex(query);
    assertEquals(0, documents.size());
}

9. 結論

この記事は、Apache Luceneを使い始めるための簡単な紹介です。 また、さまざまなクエリを実行し、取得したドキュメントをソートしました。

いつものように、例のコードはover on Githubにあります。