首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Lucene:从String解析的同一个查询和通过query构建的查询不会产生相同的结果

Lucene:从String解析的同一个查询和通过query构建的查询不会产生相同的结果
EN

Stack Overflow用户
提问于 2013-02-25 14:55:05
回答 1查看 663关注 0票数 0

我有以下代码:

代码语言:javascript
复制
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查询,一种是“手动”构建相应的Lucene查询字符串(通过字符串连接),另一种是使用Lucene的API构建查询。它们似乎构建相同的查询,因为查询的调试输出显示完全相同的查询字符串,但搜索结果并不相同:

  • 运行通过字符串连接生成的查询(对于参数"mer"): 标题:mer标题:“starts_with mer"

在这种情况下,当我使用它进行搜索时,首先会得到与title:"starts_with mer"部分匹配的文档。下面是第一个结果的explain

代码语言:javascript
复制
>>> 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查询助手工具构建的查询会产生一个明显相同的查询: 标题:mer标题:“starts_with mer"

但这一次的结果并不相同,因为实际上title:"starts_with mer"部分是不匹配的。下面是第一个结果的explain

代码语言:javascript
复制
>>> 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",同样的结果)。

===UPDATE

femtoRgon的回答很棒:问题是,我将整个搜索字符串作为一个术语添加,而不是首先将其拆分成术语,然后将每个字符串添加到查询中。

如果输入字符串由一个术语组成,那么答案femtoRgon给出的结果是确定的:在本例中,将"STARTS_WITH“文本单独添加为一个术语,然后将搜索字符串作为第二个术语进行添加。

但是,如果用户输入的内容将由多个术语标记,则必须首先将其拆分为术语(最好使用索引时使用的相同的分析器和/或标记器--以获得一致的结果),然后将每个术语添加到查询中。

我最后所做的是使用与索引相同的分析器,创建一个将查询字符串拆分为术语的函数:

代码语言:javascript
复制
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“作为一个术语添加,然后将列表中的每个元素作为单独的术语添加:

代码语言:javascript
复制
PhraseQuery titleStartsWithSearchString = new PhraseQuery();
titleStartsWithSearchString.add(new Term("title","STARTS_WITH".toLowerCase()));
for(String term:getTerms(searchString)) {
    titleStartsWithSearchString.add(new Term("title",term));
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2013-02-25 22:50:42

我认为您遇到的问题是将整个短语作为单个术语添加到您的PhraseQuery中。在索引和QueryParser解析的查询中,这将被拆分为术语"starts_with""mer",它们必须连续地找到。但是,在您构建的查询中,您的PhraseQuery中只有一个术语,即术语"starts_with mer",它在索引中不作为单个术语存在。

您应该能够将构建PhraseQuery的位置更改为:

代码语言:javascript
复制
PhraseQuery titleStartsWithSearchString = new PhraseQuery();
titleStartsWithSearchString.add(new Term("title","STARTS_WITH".toLowerCase())
titleStartsWithSearchString.add(new Term("title",searchString));
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/15069803

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档