Частичное соответствие Lucene SpanNearQuery - PullRequest
1 голос
/ 07 января 2010

Учитывая документ {'foo', 'bar', 'baz'}, я хочу сопоставить использование SpanNearQuery с токенами {'baz', 'extra'}

Но это не удалось.

Как мне обойти это?

Образец теста (с использованием lucene 2.9.1) со следующими результатами:

  • дано SingleMatch - PASS
  • с учетом двух соответствий - PASS
  • учитывая три соответствия - ПРОЙДЕН
  • даноSingleMatch_andExtraTerm - FAIL

...

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.spans.SpanNearQuery;
import org.apache.lucene.search.spans.SpanQuery;
import org.apache.lucene.search.spans.SpanTermQuery;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.Version;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;

public class SpanNearQueryTest {

    private RAMDirectory directory = null;

    private static final String BAZ = "baz";
    private static final String BAR = "bar";
    private static final String FOO = "foo";
    private static final String TERM_FIELD = "text";

    @Before
    public void given() throws IOException {
        directory = new RAMDirectory();
        IndexWriter writer = new IndexWriter(
                directory,
                new StandardAnalyzer(Version.LUCENE_29),
                IndexWriter.MaxFieldLength.UNLIMITED);

        Document doc = new Document();
        doc.add(new Field(TERM_FIELD, FOO, Field.Store.NO, Field.Index.ANALYZED));
        doc.add(new Field(TERM_FIELD, BAR, Field.Store.NO, Field.Index.ANALYZED));
        doc.add(new Field(TERM_FIELD, BAZ, Field.Store.NO, Field.Index.ANALYZED));

        writer.addDocument(doc);
        writer.commit();
        writer.optimize();
        writer.close();
    }

    @After
    public void cleanup() {
        directory.close();
    }

    @Test
    public void givenSingleMatch() throws IOException {

        SpanNearQuery spanNearQuery = new SpanNearQuery(
                new SpanQuery[] {
                        new SpanTermQuery(new Term(TERM_FIELD, FOO))
                }, Integer.MAX_VALUE, false);

        TopDocs topDocs = new IndexSearcher(IndexReader.open(directory)).search(spanNearQuery, 100);

        Assert.assertEquals("Should have made a match.", 1, topDocs.scoreDocs.length);
    }

    @Test
    public void givenTwoMatches() throws IOException {

        SpanNearQuery spanNearQuery = new SpanNearQuery(
                new SpanQuery[] {
                        new SpanTermQuery(new Term(TERM_FIELD, FOO)),
                        new SpanTermQuery(new Term(TERM_FIELD, BAR))
                }, Integer.MAX_VALUE, false);

        TopDocs topDocs = new IndexSearcher(IndexReader.open(directory)).search(spanNearQuery, 100);

        Assert.assertEquals("Should have made a match.", 1, topDocs.scoreDocs.length);
    }

    @Test
    public void givenThreeMatches() throws IOException {

        SpanNearQuery spanNearQuery = new SpanNearQuery(
                new SpanQuery[] {
                        new SpanTermQuery(new Term(TERM_FIELD, FOO)),
                        new SpanTermQuery(new Term(TERM_FIELD, BAR)),
                        new SpanTermQuery(new Term(TERM_FIELD, BAZ))
                }, Integer.MAX_VALUE, false);

        TopDocs topDocs = new IndexSearcher(IndexReader.open(directory)).search(spanNearQuery, 100);

        Assert.assertEquals("Should have made a match.", 1, topDocs.scoreDocs.length);
    }

    @Test
    public void givenSingleMatch_andExtraTerm() throws IOException {

        SpanNearQuery spanNearQuery = new SpanNearQuery(
                new SpanQuery[] {
                        new SpanTermQuery(new Term(TERM_FIELD, BAZ)),
                        new SpanTermQuery(new Term(TERM_FIELD, "EXTRA"))
                },
                Integer.MAX_VALUE, false);

        TopDocs topDocs = new IndexSearcher(IndexReader.open(directory)).search(spanNearQuery, 100);

        Assert.assertEquals("Should have made a match.", 1, topDocs.scoreDocs.length);
    }
}

1 Ответ

6 голосов
/ 07 января 2010

SpanNearQuery позволяет находить термины, которые находятся на определенном расстоянии друг от друга.

Пример (от http://www.lucidimagination.com/blog/2009/07/18/the-spanquery/):

Скажем, мы хотим найти люцен в течение 5 положения дуга, с последующим дугом Lucene (порядок имеет значение) - вы можете использовать следующий SpanQuery:

new SpanNearQuery(new SpanQuery[] {
  new SpanTermQuery(new Term(FIELD, "lucene")),
  new SpanTermQuery(new Term(FIELD, "doug"))},
  5,
  true);

альтернативный текст http://www.lucidimagination.com/blog/wp-content/uploads/2009/07/spanquery-dia1.png

В этом примере текста Lucene находится в пределах 3 Дуга

Но для вашего примера единственное совпадение, которое я вижу, состоит в том, что и ваш запрос, и целевой документ имеют "cd" (и я предполагаю, что все эти термины находятся в одном поле). В этом случае вам не нужно использовать какой-либо специальный тип запроса. Используя стандартные механизмы, вы получите ненулевой вес, основанный на том факте, что они оба содержат один и тот же термин в одном и том же поле.

Редактировать 3 - в ответ на последний комментарий ответ заключается в том, что вы не можете использовать SpanNearQuery для выполнения чего-либо, кроме того, для чего он предназначен, то есть для выяснения, есть ли несколько терминов в Документы встречаются в определенном количестве мест друг друга. Я не могу сказать, каков ваш конкретный вариант использования / ожидаемые результаты (не стесняйтесь опубликовать его), но в последнем случае, если вы хотите узнать только, находится ли один или несколько из ("BAZ", "EXTRA") в документ, BooleanQuery будет работать нормально.

Редактировать 4 - теперь, когда вы опубликовали свой вариант использования, я понимаю, что вы хотите сделать. Вот как вы можете это сделать: используйте BooleanQuery, как упомянуто выше, чтобы объединить отдельные термины, которые вы хотите, а также SpanNearQuery, и установите усиление на SpanNearQuery.

Итак, запрос в текстовом виде будет выглядеть так:

BAZ OR EXTRA OR "BAZ EXTRA"~100^5

(в качестве примера - это будет соответствовать всем документам, содержащим либо «BAZ», либо «EXTRA», но назначать более высокий балл документам, в которых термины «BAZ» и «EXTRA» встречаются в пределах 100 мест друг от друга; корректируйте положение и увеличивайте, сколько хотите. Этот пример взят из поваренной книги Solr, поэтому он может не анализироваться в Lucene или может давать нежелательные результаты. Это нормально, потому что в следующем разделе я покажу вам, как создать это с помощью API).

Программно вы должны построить это следующим образом:

Query top = new BooleanQuery();

// Construct the terms since they will be used more than once
Term bazTerm = new Term("Field", "BAZ");
Term extraTerm = new Term("Field", "EXTRA");

// Add each term as "should" since we want a partial match
top.add(new TermQuery(bazTerm), BooleanClause.Occur.SHOULD);
top.add(new TermQuery(extraTerm), BooleanClause.Occur.SHOULD);

// Construct the SpanNearQuery, with slop 100 - a document will get a boost only
// if BAZ and EXTRA occur within 100 places of each other.  The final parameter means
// that BAZ must occur before EXTRA.
SpanNearQuery spanQuery = new SpanNearQuery(
                              new SpanQuery[] { new SpanTermQuery(bazTerm), 
                                                new SpanTermQuery(extraTerm) }, 
                              100, true);

// Give it a boost of 5 since it is more important that the words are together
spanQuery.setBoost(5f);

// Add it as "should" since we want a match even when we don't have proximity
top.add(spanQuery, BooleanClause.Occur.SHOULD);

Надеюсь, это поможет! В будущем постарайтесь начать с публикации именно тех результатов, которые вы ожидаете - даже если это очевидно для вас, это может быть не так для читателя, а ясность может избежать необходимости повторяться туда и обратно столько раз.

...