次のコードがあります。
public static void main(String[] args) throws Throwable {
String[] texts = new String[]{
"starts_with k mer",
"starts_with mer",
"starts_with bleue est mer",
"starts_with mer est bleue",
"starts_with mer bla1 bla2 bla3 bla4 bla5",
"starts_with bleue est la mer",
"starts_with la mer est bleue",
"starts_with la mer"
};
//write:
Set<String> stopWords = new HashSet<String>();
StandardAnalyzer stdAn = new StandardAnalyzer(Version.LUCENE_36, stopWords);
Directory fsDir = FSDirectory.open(INDEX_DIR);
IndexWriterConfig iwConf = new IndexWriterConfig(Version.LUCENE_36,stdAn);
iwConf.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
IndexWriter indexWriter = new IndexWriter(fsDir,iwConf);
for(String text:texts) {
Document document = new Document();
document.add(new Field("title",text,Store.YES,Index.ANALYZED));
indexWriter.addDocument(document);
}
indexWriter.commit();
//read
IndexReader indexReader = IndexReader.open(fsDir);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//get query:
//Query query = getQueryFromString("mer");
Query query = getQueryFromAPI("mer");
//explain
System.out.println("======== Query: "+query+"\n");
TopDocs hits = indexSearcher.search(query, 10);
for (ScoreDoc scoreDoc : hits.scoreDocs) {
Document doc = indexSearcher.doc(scoreDoc.doc);
System.out.println(">>> "+doc.get("title"));
System.out.println("Explain:");
System.out.println(indexSearcher.explain(query, scoreDoc.doc));
}
}
private static Query getQueryFromString(String searchString) throws Throwable {
Set<String> stopWords = new HashSet<String>();
Query query = new QueryParser(Version.LUCENE_36, "title",new StandardAnalyzer(Version.LUCENE_36, stopWords)).parse("("+searchString+") \"STARTS_WITH "+searchString+"\"");
return query;
}
private static Query getQueryFromAPI(String searchString) throws Throwable {
Set<String> stopWords = new HashSet<String>();
Query searchStringTermsMatchTitle = new QueryParser(Version.LUCENE_36, "title", new StandardAnalyzer(Version.LUCENE_36, stopWords)).parse(searchString);
PhraseQuery titleStartsWithSearchString = new PhraseQuery();
titleStartsWithSearchString.add(new Term("title","STARTS_WITH".toLowerCase()+" "+searchString));
BooleanQuery query = new BooleanQuery(true);
BooleanClause matchClause = new BooleanClause(searchStringTermsMatchTitle, Occur.SHOULD);
query.add(matchClause);
BooleanClause startsWithClause = new BooleanClause(titleStartsWithSearchString, Occur.SHOULD);
query.add(startsWithClause);
return query;
}
基本的に、いくつかの文字列にインデックスを付けています。次に、ユーザー入力から Lucene クエリを作成する 2 つの方法があります。1 つは対応する Lucene クエリ文字列を「手動で」(文字列連結を介して) 単純に作成する方法で、もう 1 つはクエリを作成するために Lucene の API を使用する方法です。クエリのデバッグ出力にはまったく同じクエリ文字列が表示されるため、同じクエリを作成しているように見えますが、検索結果は同じではありません。
文字列連結によって作成されたクエリを実行すると、次のようになります (引数「mer」の場合):
title:mer title:"starts_with mer"
この場合、それを検索すると、title:"starts_with mer"
最初にその部分に一致するドキュメントが取得されると考えられます。explain
最初の結果は次のとおりです。
>>> starts_with mer
Explain:
1.2329358 = (MATCH) sum of:
0.24658716 = (MATCH) weight(title:mer in 1), product of:
0.4472136 = queryWeight(title:mer), product of:
0.882217 = idf(docFreq=8, maxDocs=8)
0.50692016 = queryNorm
0.55138564 = (MATCH) fieldWeight(title:mer in 1), product of:
1.0 = tf(termFreq(title:mer)=1)
0.882217 = idf(docFreq=8, maxDocs=8)
0.625 = fieldNorm(field=title, doc=1)
0.9863486 = (MATCH) weight(title:"starts_with mer" in 1), product of:
0.8944272 = queryWeight(title:"starts_with mer"), product of:
1.764434 = idf(title: starts_with=8 mer=8)
0.50692016 = queryNorm
1.1027713 = fieldWeight(title:"starts_with mer" in 1), product of:
1.0 = tf(phraseFreq=1.0)
1.764434 = idf(title: starts_with=8 mer=8)
0.625 = fieldNorm(field=title, doc=1)
Lucene クエリ ヘルパー ツールを介して作成されたクエリを実行すると、明らかに同一のクエリが生成されます。
title:mer title:"starts_with mer"
しかし今回は、実際にはtitle:"starts_with mer"
部品が一致していないため、結果は同じではありません。explain
最初の結果は次のとおりです。
>>> starts_with mer
Explain:
0.15185544 = (MATCH) sum of:
0.15185544 = (MATCH) weight(title:mer in 1), product of:
0.27540696 = queryWeight(title:mer), product of:
0.882217 = idf(docFreq=8, maxDocs=8)
0.312176 = queryNorm
0.55138564 = (MATCH) fieldWeight(title:mer in 1), product of:
1.0 = tf(termFreq(title:mer)=1)
0.882217 = idf(docFreq=8, maxDocs=8)
0.625 = fieldNorm(field=title, doc=1)
私の質問は、同じ結果が得られないのはなぜですか? BooleanQuery(disableCoord)
特に、使用したいオプションがあり、Lucene クエリ文字列に直接表現する方法が本当にわからないため、ここでクエリ ヘルパー ツールを使用できるようにしたいと考えています。(はい、私の例では「true」が渡されます。「false」でも試しましたが、同じ結果です)。
===更新
femtoRgon の答えは素晴らしいです。問題は、最初に検索文字列を用語に分割してから、それぞれをクエリに追加するのではなく、検索文字列全体を用語として追加していたことです。
入力文字列が1つの用語で構成されている場合、femtoRgonが与える答えは問題なく機能します。この場合、「STARTS_WITH」テキストを1つの用語として個別に追加し、次に検索文字列を2番目の用語として追加します。
ただし、ユーザーが複数の用語でトークン化されるものを入力した場合は、最初にそれを用語に分割し (できればインデックス作成時に使用したのと同じアナライザーおよび/またはトークナイザーを使用して、一貫した結果を得る)、次に追加する必要があります。クエリへの各用語。
私がやったことは、インデックス作成に使用したのと同じアナライザーを使用して、クエリ文字列を用語に分割する関数を作成することです。
private static List<String> getTerms(String text) throws Throwable {
Analyzer analyzer = getAnalyzer();
StringReader textReader = new StringReader(text);
TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME_TITLE, textReader);
tokenStream.reset();
List<String> terms = new ArrayList<String>();
CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
while (tokenStream.incrementToken()) {
String term = charTermAttribute.toString();
terms.add(term);
}
textReader.close();
tokenStream.close();
analyzer.close();
return terms;
}
次に、最初に「STARTS_WITH」を 1 つの用語として追加し、次にリスト内の各要素を個別の用語として追加します。
PhraseQuery titleStartsWithSearchString = new PhraseQuery();
titleStartsWithSearchString.add(new Term("title","STARTS_WITH".toLowerCase()));
for(String term:getTerms(searchString)) {
titleStartsWithSearchString.add(new Term("title",term));
}