Apache Lucene は、ドキュメントの全文検索に使用されるJavaライブラリであり、次のような検索サーバーの中核です。 Solr そして Elasticsearch 。また、AndroidアプリやWebバックエンドなどのJavaアプリケーションに埋め込むこともできます。
Luceneの構成オプションは豊富ですが、 データベース開発者 テキストの一般的なコーパス。ドキュメントに特定の構造またはコンテンツの種類がある場合は、どちらかを利用して検索品質とクエリ機能を向上させることができます。
この種のカスタマイズの例として、このLuceneチュートリアルでは、のコーパスにインデックスを付けます。 プロジェクトグーテンベルク 、何千もの無料の電子書籍を提供しています。これらの本の多くは小説であることを私たちは知っています。私たちが特に興味を持っているとしましょう 対話 これらの小説の中で。 Lucene、Elasticsearch、Solrのいずれも、コンテンツをダイアログとして識別するためのすぐに使用できるツールを提供していません。実際、テキスト分析の初期段階で句読点を破棄します。これは、会話であるテキストの部分を識別できることに反します。したがって、カスタマイズを開始する必要があるのは、これらの初期段階です。
ザ・ Lucene分析JavaDoc テキスト分析パイプラインのすべての可動部分の概要を提供します。
大まかに言えば、分析パイプラインは、最初に文字の生のストリームを消費し、最後に単語にほぼ対応する「用語」を生成するものと考えることができます。
標準の分析パイプラインは 視覚化 など:
このパイプラインをカスタマイズして、二重引用符でマークされたテキストの領域を認識する方法を説明します。これをダイアログと呼び、それらの領域で検索するときに発生する一致を増やします。
ドキュメントが最初にインデックスに追加されるとき、文字はJavaから読み取られます InputStream 、およびファイル、データベース、Webサービス呼び出しなどから取得できます。ProjectGutenbergのインデックスを作成するには、電子書籍をダウンロードし、これらのファイルを読み取ってインデックスに書き込む小さなアプリケーションを作成します。 Luceneインデックスの作成とファイルの読み取りはよく行われているため、あまり詳しく説明しません。インデックスを作成するための重要なコードは次のとおりです。
IndexWriter writer = ...; BufferedReader reader = new BufferedReader(new InputStreamReader(... fileInputStream ...)); Document document = new Document(); document.add(new StringField('title', fileName, Store.YES)); document.add(new TextField('body', reader)); writer.addDocument(document);
各電子書籍が単一のLuceneに対応することがわかりますDocument
したがって、後で、検索結果は一致する本のリストになります。 Store.YES
を保存することを示します 題名 フィールド。これは単なるファイル名です。保存したくない 体 ただし、検索時に必要ではなく、ディスク領域を浪費するだけなので、電子書籍の
ストリームの実際の読み取りはaddDocument
で始まります。 IndexWriter
パイプラインの最後からトークンをプルします。このプルは、最初のステージであるTokenizer
がInputStream
から読み取るまでパイプを介して戻ります。
Luceneがこれを処理するため、ストリームを閉じないことにも注意してください。
Lucene StandardTokenizer 句読点を破棄するため、引用符を保持する必要があるため、ここからカスタマイズを開始します。
StandardTokenizer
のドキュメントソースコードをコピーしてニーズに合わせて調整することをお勧めしますが、このソリューションは不必要に複雑になります。代わりに、CharTokenizer
を拡張します。これにより、「受け入れる」文字を指定できます。「受け入れ」されない文字は、トークン間の区切り文字として扱われ、破棄されます。単語とその周囲の引用符に関心があるため、カスタムTokenizerは次のようになります。
public class QuotationTokenizer extends CharTokenizer { @Override protected boolean isTokenChar(int c) return Character.isLetter(c) }
[He said, 'Good day'.]
の入力ストリームが与えられると、生成されるトークンは[He]
、[said]
、['Good]
、[day']
になります。
C ++のガイド
引用符がトークン内にどのように散在しているかに注意してください。 Tokenizer
を書くことができます引用ごとに別々のトークンを生成しますが、Tokenizer
また、バッファリングやスキャンなど、面倒でねじ込みやすい詳細にも関心があるため、Tokenizer
を保持することをお勧めします。単純で、パイプラインのさらに先のトークンストリームをクリーンアップします。
トークナイザーの後に一連のTokenFilter
がありますオブジェクト。ちなみに、 フィルタ TokenFilter
のように、少し誤解されていますトークンを追加、削除、または変更できます。
Luceneが提供するフィルタークラスの多くは単一の単語を想定しているため、単語と引用符が混在するトークンがそれらに流れ込むことはありません。したがって、Luceneチュートリアルの次のカスタマイズは、QuotationTokenizer
の出力をクリーンアップするフィルターの導入である必要があります。
このクリーンアップには、追加の生成が含まれます 見積もりを開始 引用符が単語の先頭にある場合はトークン、または 見積もり終了 引用符が最後に表示されている場合はトークン。簡単にするために、一重引用符で囲まれた単語の処理は脇に置きます。
TokenFilter
の作成サブクラスには、incrementToken
という1つのメソッドの実装が含まれます。このメソッドはincrementToken
を呼び出す必要がありますパイプ内の前のフィルターで、その呼び出しの結果を操作して、フィルターが担当するすべての作業を実行します。 incrementToken
の結果Attribute
から入手できますトークン処理の現在の状態を説明するオブジェクト。 incrementToken
の実装後が返される場合、次のフィルター(またはパイプの最後にいる場合はインデックス)のトークンを設定するために属性が操作されていることが期待されます。
パイプラインのこの時点で関心のある属性は次のとおりです。
CharTermAttribute
:char[]
が含まれています現在のトークンの文字を保持するバッファ。見積もりを削除するか、見積もりトークンを生成するには、これを操作する必要があります。
TypeAttribute
:現在のトークンの「タイプ」が含まれます。トークンストリームに開始引用符と終了引用符を追加しているため、フィルターを使用して2つの新しいタイプを導入します。
OffsetAttribute
:Luceneは、オプションで、元のドキュメント内の用語の場所への参照を保存できます。これらの参照は「オフセット」と呼ばれ、元の文字ストリームへの開始インデックスと終了インデックスにすぎません。 CharTermAttribute
のバッファを変更した場合トークンの部分文字列のみを指すには、それに応じてこれらのオフセットを調整する必要があります。
トークンストリームを操作するためのAPIがなぜそれほど複雑なのか、特に、なぜString#split
のようなことを実行できないのか疑問に思われるかもしれません。着信トークンに。これは、Luceneが高速でオーバーヘッドの少ないインデックス作成用に設計されているためです。これにより、組み込みのトークナイザーとフィルターは、メガバイトのメモリのみを使用しながら、ギガバイトのテキストをすばやく処理できます。これを実現するために、トークン化とフィルタリング中に割り当てがほとんどまたはまったく行われないため、Attribute
上記のインスタンスは、一度割り当てて再利用することを目的としています。トークナイザーとフィルターがこのように記述されていて、それら自体の割り当てを最小限に抑えると、パフォーマンスを損なうことなくLuceneをカスタマイズできます。
これらすべてを念頭に置いて、['Hello]
などのトークンを受け取り、2つのトークン[']
を生成するフィルターを実装する方法を見てみましょう。および[Hello]
:
public class QuotationTokenFilter extends TokenFilter { private static final char QUOTE = '''; public static final String QUOTE_START_TYPE = 'start_quote'; public static final String QUOTE_END_TYPE = 'end_quote'; private final OffsetAttribute offsetAttr = addAttribute(OffsetAttribute.class); private final TypeAttribute typeAttr = addAttribute(TypeAttribute.class); private final CharTermAttribute termBufferAttr = addAttribute(CharTermAttribute.class);
最初に、以前に見た属性のいくつかへの参照を取得します。フィールド名には「Attr」という接尾辞を付けて、後で参照するときに明確になるようにします。いくつかのTokenizer
が可能です実装はこれらの属性を提供しないため、addAttribute
を使用します私たちの参照を取得します。 addAttribute
欠落している場合は属性インスタンスを作成し、それ以外の場合はそのタイプの属性への共有参照を取得します。 Luceneでは、同じ属性タイプの複数のインスタンスを一度に許可しないことに注意してください。
private boolean emitExtraToken; private int extraTokenStartOffset, extraTokenEndOffset; private String extraTokenType;
フィルタは元のストリームに存在しなかった新しいトークンを導入するため、incrementToken
の呼び出しの間にそのトークンの状態を保存する場所が必要です。既存のトークンを2つに分割しているため、新しいトークンのオフセットとタイプだけを知っていれば十分です。 incrementToken
への次の呼び出しかどうかを示すフラグもありますこの追加のトークンを発行します。 Luceneは、実際には2つのメソッドcaptureState
を提供しています。およびrestoreState
は、これを行います。ただし、これらの方法には、State
の割り当てが含まれます。オブジェクトであり、実際にはその状態を自分で管理するよりも難しい場合があるため、それらの使用は避けます。
@Override public void reset() throws IOException { emitExtraToken = false; extraTokenStartOffset = -1; extraTokenEndOffset = -1; extraTokenType = null; super.reset(); }
Luceneは、割り当てを積極的に回避する一環として、フィルターインスタンスを再利用できます。この状況では、reset
への呼び出しが予想されます。フィルタを初期状態に戻します。したがって、ここでは、追加のトークンフィールドをリセットするだけです。
@Override public boolean incrementToken() throws IOException { if (emitExtraToken) { advanceToExtraToken(); emitExtraToken = false; return true; } ...
今、私たちは興味深い部分に到達しています。 incrementToken
の実装時と呼ばれる、私たちはする機会があります ない 電話incrementToken
パイプラインの初期段階で。そうすることで、Tokenizer
からトークンを取得しないため、新しいトークンを効果的に導入します。
代わりに、advanceToExtraToken
と呼びます追加のトークンの属性を設定するには、emitExtraToken
を設定します次の呼び出しでこの分岐を回避するにはfalseに設定し、true
を返します。これは、別のトークンが使用可能であることを示します。
@Override public boolean incrementToken() throws IOException { ... (emit extra token) ... boolean hasNext = input.incrementToken(); if (hasNext) { char[] buffer = termBufferAttr.buffer(); if (termBuffer.length() > 1) { if (buffer[0] == QUOTE) { splitTermQuoteFirst(); } else if (buffer[termBuffer.length() - 1] == QUOTE) { splitTermWordFirst(); } } else if (termBuffer.length() == 1) { if (buffer[0] == QUOTE) { typeAttr.setType(QUOTE_END_TYPE); } } } return hasNext; }
incrementToken
の残りの部分3つの異なることのいずれかを実行します。 termBufferAttr
を思い出してくださいパイプを通過するトークンの内容を検査するために使用されます。
トークンストリームの最後に到達した場合(つまり、hasNext
がfalseの場合)、完了して単純に戻ります。
複数の文字のトークンがあり、それらの文字の1つが引用符である場合、トークンを分割します。
トークンが単独引用符である場合、それは終了引用符であると見なされます。理由を理解するために、開始引用符は常に単語の左側に表示されますが(つまり、中間句読点はありません)、終了引用符は句読点の後に続けることができます(文のように[He told us to 'go back the way we came.']
)。このような場合、終了引用符はすでに別のトークンになっているため、そのタイプを設定するだけで済みます。
splitTermQuoteFirst
およびsplitTermWordFirst
現在のトークンを単語または引用符にするための属性を設定し、残りの半分を後で消費できるように「追加」フィールドを設定します。 2つの方法は似ているので、splitTermQuoteFirst
だけを見ていきます。
Googleスプレッドシートでマクロを使用する
private void splitTermQuoteFirst() { int origStart = offsetAttr.startOffset(); int origEnd = offsetAttr.endOffset(); offsetAttr.setOffset(origStart, origStart + 1); typeAttr.setType(QUOTE_START_TYPE); termBufferAttr.setLength(1); prepareExtraTerm(origStart + 1, origEnd, TypeAttribute.DEFAULT_TYPE); }
このトークンを分割して、最初に引用符がストリームに表示されるようにするため、長さを1(つまり、1文字、つまり引用符)に設定してバッファーを切り捨てます。それに応じてオフセットを調整し(つまり、元のドキュメントの引用符を指す)、タイプを開始引用符に設定します。
prepareExtraTerm
extra*
を設定しますフィールドとセットemitExtraToken
本当に。これは、「余分な」トークン(つまり、引用符に続く単語)を指すオフセットで呼び出されます。
QuotationTokenFilter
の全体です GitHubで入手可能 。
余談ですが、このフィルターは1つの追加トークンしか生成しませんが、このアプローチを拡張して、任意の数の追加トークンを導入できます。 extra*
を置き換えるだけですコレクションを持つフィールド、または生成できる追加のトークンの数に制限がある場合は固定長の配列。見る SynonymFilter
とそのPendingInput
この例の内部クラス。
これらの引用符をトークンストリームに追加するためのすべての努力を行ったので、それらを使用してテキスト内のダイアログのセクションを区切ることができます。
最終目標は、用語がダイアログの一部であるかどうかに基づいて検索結果を調整することであるため、それらの用語にメタデータを添付する必要があります。 LuceneはPayloadAttribute
を提供しますこの目的のために。ペイロードは、インデックス内の用語と一緒に格納されるバイト配列であり、後で検索中に読み取ることができます。これは、フラグが1バイト全体を無駄に占有することを意味するため、スペースを節約するために追加のペイロードをビットフラグとして実装できます。
以下は、分析パイプラインの最後に追加される新しいフィルターDialoguePayloadTokenFilter
です。トークンがダイアログの一部であるかどうかを示すペイロードを添付します。
public class DialoguePayloadTokenFilter extends TokenFilter { private final TypeAttribute typeAttr = getAttribute(TypeAttribute.class); private final PayloadAttribute payloadAttr = addAttribute(PayloadAttribute.class); private static final BytesRef PAYLOAD_DIALOGUE = new BytesRef(new byte[] { 1 }); private static final BytesRef PAYLOAD_NOT_DIALOGUE = new BytesRef(new byte[] { 0 }); private boolean withinDialogue; protected DialoguePayloadTokenFilter(TokenStream input) { super(input); } @Override public void reset() throws IOException { this.withinDialogue = false; super.reset(); } @Override public boolean incrementToken() throws IOException { boolean hasNext = input.incrementToken(); while(hasNext) { boolean isStartQuote = QuotationTokenFilter .QUOTE_START_TYPE.equals(typeAttr.type()); boolean isEndQuote = QuotationTokenFilter .QUOTE_END_TYPE.equals(typeAttr.type()); if (isStartQuote) { withinDialogue = true; hasNext = input.incrementToken(); } else if (isEndQuote) { withinDialogue = false; hasNext = input.incrementToken(); } else { break; } } if (hasNext) { payloadAttr.setPayload(withinDialogue ? PAYLOAD_DIALOGUE : PAYLOAD_NOT_DIALOGUE); } return hasNext; } }
このフィルターは単一の状態withinDialogue
を維持するだけでよいので、はるかに簡単です。開始引用符は、ダイアログのセクション内にいることを示し、終了引用符は、ダイアログのセクションが終了したことを示します。いずれの場合も、引用トークンはincrementToken
を2回呼び出すことで破棄されるため、事実上、 見積もりを開始 または 見積もり終了 トークンがパイプラインのこの段階を通過することはありません。
たとえば、DialoguePayloadTokenFilter
トークンストリームを変換します:
[the], [program], [printed], ['], [hello], [world], [']`
この新しいストリームに:
[the][0], [program][0], [printed][0], [hello][1], [world][1]
Analyzer
通常はTokenizer
を組み合わせて、分析パイプラインを組み立てます。一連のTokenFilter
sを使用します。 Analyzer
sは、そのパイプラインが分析間でどのように再利用されるかを定義することもできます。コンポーネントはreset()
の呼び出し以外は何も必要としないため、これについて心配する必要はありません。 Luceneが常に行う使用の合間に。 Analyzer#createComponents(String)
を実装してアセンブリを実行する必要があります。
public class DialogueAnalyzer extends Analyzer { @Override protected TokenStreamComponents createComponents(String fieldName) { QuotationTokenizer tokenizer = new QuotationTokenizer(); TokenFilter filter = new QuotationTokenFilter(tokenizer); filter = new LowerCaseFilter(filter); filter = new StopFilter(filter, StopAnalyzer.ENGLISH_STOP_WORDS_SET); filter = new DialoguePayloadTokenFilter(filter); return new TokenStreamComponents(tokenizer, filter); } }
前に見たように、フィルターにはパイプラインの前のステージへの参照が含まれているため、フィルターをインスタンス化する方法です。また、StandardAnalyzer
からいくつかのフィルターをスライドさせます:LowerCaseFilter
およびStopFilter
。これらの2つはQuotationTokenFilter
の後に来る必要があります引用符が分離されていることを確認します。 DialoguePayloadTokenFilter
の後のどこにでもあるので、QuotationTokenFilter
の配置をより柔軟にすることができます。しましょう。 StopFilter
の後に置きますダイアログペイロードをに注入する時間を無駄にしないため ストップワード 最終的には削除されます。
これは、動作中の新しいパイプラインの視覚化です(削除した、またはすでに見た標準パイプラインの部分を除く)。
DialogueAnalyzer
他のストックと同じように使用できるようになりましたAnalyzer
これで、インデックスを作成して検索に進むことができます。
ダイアログのみを検索したい場合は、引用符の外にあるすべてのトークンを単に破棄するだけで済みます。代わりに、元のトークンをすべてそのままにしておくことで、会話を考慮したクエリを実行したり、会話をテキストの他の部分と同じように扱ったりする柔軟性を確保しました。
Excel用のpowerpivotとは
Luceneインデックスのクエリの基本は次のとおりです。 十分に文書化されている 。私たちの目的では、クエリがTerm
で構成されていることを知っていれば十分です。 MUST
などの演算子で結合されたオブジェクトまたはSHOULD
、およびそれらの用語に基づく一致ドキュメント。一致するドキュメントは、構成可能なSimilarity
に基づいてスコアリングされます。オブジェクト、およびそれらの結果は、スコア順に並べ替えたり、フィルタリングしたり、制限したりできます。たとえば、Luceneでは、両方の用語[hello]
を含む必要がある上位10個のドキュメントに対してクエリを実行できます。および[world]
。
ダイアログに基づいて検索結果をカスタマイズするには、ペイロードに基づいてドキュメントのスコアを調整します。このための最初の拡張ポイントはSimilarity
にあり、一致する用語の重み付けとスコアリングを担当します。
クエリは、デフォルトでDefaultSimilarity
を使用します。これは、ドキュメント内で発生する頻度に基づいて用語に重みを付けます。重みを調整するための優れた拡張ポイントであるため、ペイロードに基づいてドキュメントをスコアリングするためにも拡張します。メソッドDefaultSimilarity#scorePayload
この目的のために提供されています:
public final class DialogueAwareSimilarity extends DefaultSimilarity { @Override public float scorePayload(int doc, int start, int end, BytesRef payload) { if (payload.bytes[payload.offset] == 0) { return 0.0f; } return 1.0f; } }
DialogueAwareSimilarity
単に非ダイアログペイロードをゼロとしてスコアリングします。それぞれとしてTerm
複数回一致させることができ、複数のペイロードスコアを持つ可能性があります。 Query
までのこれらのスコアの解釈実装。
BytesRef
に細心の注意を払ってくださいペイロードを含む:バイト配列が以前に保存したペイロードと同じであると想定できないため、offset
でバイトをチェックする必要があります。インデックスを読み取るときに、LuceneはscorePayload
の呼び出しのためだけに別のバイト配列を割り当てるメモリを浪費しないため、既存のバイト配列への参照を取得します。 Lucene APIに対してコーディングする場合、開発者の利便性よりもパフォーマンスが優先されることに留意する必要があります。
これで、新しいSimilarity
ができました。実装後、IndexSearcher
に設定する必要がありますクエリの実行に使用:
IndexSearcher searcher = new IndexSearcher(... reader for index ...); searcher.setSimilarity(new DialogueAwareSimilarity());
これでIndexSearcher
ペイロードをスコアリングできる場合は、ペイロードを認識するクエリを作成する必要もあります。 PayloadTermQuery
単一のTerm
に一致させるために使用できますそれらの一致のペイロードもチェックしながら:
PayloadTermQuery helloQuery = new PayloadTermQuery(new Term('body', 'hello'), new AveragePayloadFunction());
このクエリは用語[hello]
に一致します以内 体 フィールド(ここにドキュメントの内容を配置することを思い出してください)。また、すべての用語の一致から最終的なペイロードスコアを計算する関数を提供する必要があるため、すべてのペイロードスコアを平均するAveragePayloadFunction
をプラグインします。たとえば、用語[hello]
の場合会話の内側で2回、会話の外側で1回発生すると、最終的なペイロードスコアは²⁄₃になります。この最終的なペイロードスコアは、DefaultSimilarity
によって提供されるスコアと乗算されます。ドキュメント全体。
多くの用語がダイアログの外に表示される検索結果を強調せず、ダイアログに用語がまったくないドキュメントのスコアをゼロにしたいため、平均を使用します。
複数のPayloadTermQuery
を作成することもできますBooleanQuery
を使用するオブジェクトダイアログに含まれる複数の用語を検索する場合(他のクエリタイプは位置を認識しますが、このクエリでは用語の順序は関係ありません):
PayloadTermQuery worldQuery = new PayloadTermQuery(new Term('body', 'world'), new AveragePayloadFunction()); BooleanQuery query = new BooleanQuery(); query.add(helloQuery, Occur.MUST); query.add(worldQuery, Occur.MUST);
このクエリを実行すると、クエリ構造と類似性の実装がどのように連携するかを確認できます。
クエリを実行するには、クエリをIndexSearcher
に渡します。
TopScoreDocCollector collector = TopScoreDocCollector.create(10); searcher.search(query, new PositiveScoresOnlyCollector(collector)); TopDocs topDocs = collector.topDocs();
Collector
オブジェクトは、一致するドキュメントのコレクションを準備するために使用されます。
コレクターは、ソート、制限、およびフィルタリングの組み合わせを実現するように構成できます。たとえば、会話に少なくとも1つの用語が含まれている上位10のスコアリングドキュメントを取得するには、TopScoreDocCollector
を組み合わせます。およびPositiveScoresOnlyCollector
。正のスコアのみを取得すると、ゼロスコアの一致(つまり、ダイアログに用語がないもの)が確実に除外されます。
このクエリの動作を確認するには、クエリを実行してから、IndexSearcher#explain
を使用します。個々のドキュメントがどのようにスコアリングされたかを確認するには:
for (ScoreDoc result : topDocs.scoreDocs) { Document doc = searcher.doc(result.doc, Collections.singleton('title')); System.out.println('--- document ' + doc.getField('title').stringValue() + ' ---'); System.out.println(this.searcher.explain(query, result.doc)); }
ここでは、TopDocs
のドキュメントIDを繰り返し処理します。検索によって取得されます。 IndexSearcher#doc
も使用します表示するタイトルフィールドを取得します。 'hello'
のクエリの場合、次のようになります。
--- Document whelv10.txt --- 0.072256625 = (MATCH) btq, product of: 0.072256625 = weight(body:hello in 7336) [DialogueAwareSimilarity], result of: 0.072256625 = fieldWeight in 7336, product of: 2.345208 = tf(freq=5.5), with freq of: 5.5 = phraseFreq=5.5 3.1549776 = idf(docFreq=2873, maxDocs=24796) 0.009765625 = fieldNorm(doc=7336) 1.0 = AveragePayloadFunction.docScore() --- Document daved10.txt --- 0.061311778 = (MATCH) btq, product of: 0.061311778 = weight(body:hello in 6873) [DialogueAwareSimilarity], result of: 0.061311778 = fieldWeight in 6873, product of: 3.3166249 = tf(freq=11.0), with freq of: 11.0 = phraseFreq=11.0 3.1549776 = idf(docFreq=2873, maxDocs=24796) 0.005859375 = fieldNorm(doc=6873) 1.0 = AveragePayloadFunction.docScore() ...
出力には専門用語が含まれていますが、カスタムSimilarity
がどのように表示されるかを確認できます。実装はスコアリングで使用され、MaxPayloadFunction
はどのように使用されましたか1.0
の乗数を生成しましたこれらの試合のために。これは、ペイロードがロードされてスコアリングされ、'Hello'
のすべての一致が一致することを意味します。対話の中で起こったので、これらの結果は私たちが期待するところの一番上にあります。
また、ペイロードを含むProject Gutenbergのインデックスのサイズは約4ギガバイトになりますが、私の控えめな開発マシンでは、クエリは即座に発生します。検索の目標を達成するために速度を犠牲にすることはありません。
Luceneは、文字の生のストリームを取得してトークンにバンドルし、それらを用語としてインデックスに保持する、強力な専用の全文検索ライブラリです。そのインデックスをすばやくクエリしてランク付けされた結果を提供し、効率を維持しながら拡張の十分な機会を提供します。
Luceneをアプリケーションで直接使用するか、サーバーの一部として使用することで、ギガバイトを超えるコンテンツをリアルタイムで全文検索できます。さらに、カスタム分析とスコアリングにより、ドキュメント内のドメイン固有の機能を利用して、結果またはカスタムクエリの関連性を向上させることができます。
wp-jsonとは
このLuceneチュートリアルの完全なコードリストは次のとおりです。 GitHubで入手可能 。リポジトリには2つのアプリケーションが含まれています:LuceneIndexerApp
インデックスを作成するため、およびLuceneQueryApp
クエリを実行するため。
入手可能なプロジェクト・グーテンベルクのコーパス BitTorrent経由のディスクイメージとして 、読む価値のある本がたくさん含まれています(Luceneを使用するか、昔ながらの方法で)。
ハッピーインデックス!