Частичное соответствие Lucene SpanNearQuery

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

Но это не удается.

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

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

  • givenSingleMatch - ПРОЙДЕН
  • givenTwoMatches - ПРОЙДЕН
  • givenThreeMatches - ПРОЙДЕН
  • givenSingleMatch_andExtraTerm - НЕИСПРАВНОСТЬ

...

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);
    }
}

person Franz See    schedule 07.01.2010    source источник
comment
Примечание: все токены находятся в одном поле. Спасибо, danben, за указание на эту недостающую информацию.   -  person Franz See    schedule 08.01.2010


Ответы (1)


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

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

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

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

alt text
(источник: lucidimagination.com)

В этом примере текста Люцен находится в пределах 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);

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

person danben    schedule 07.01.2010
comment
Построчное изображение, поясняющее расстояние, является приятным штрихом. - person Brian; 07.01.2010
comment
Это то, что я изначально предполагал. Однако данный документ не был возвращен в результате моего поиска. - person Franz See; 08.01.2010
comment
Пожалуйста, ознакомьтесь с упрощенной версией проблемы, к которой я имею отношение. - person Franz See; 08.01.2010
comment
Я снова изменил свой пост и уточнил запрошенную информацию - первые 3 прохода и четвертый провал. - person Franz See; 09.01.2010
comment
Повторное редактирование 2: Да, именно так :) что возвращает нас к моему исходному вопросу: как выполнить частичное сопоставление с помощью SpanNearQuery (или какого-либо запроса с учетом близости). - person Franz See; 09.01.2010
comment
Re edit3: Re SpanNearQuery - спасибо. Вот почему я заявляю, что это не работает, и поэтому я спрашиваю, как это обойти? По моему конкретному варианту использования: это то, что есть :) Учитывая условия, мне нужно найти совпадения, в которых я даю более высокий балл, если они вместе (означает, что это, скорее всего, то, что ищет пользователь). Тем не менее, мне нужно, чтобы он был достаточно небрежным, чтобы, если не все термины найдены, они возвращались в результате поиска (но все же более высокая близость означает более высокую оценку). - person Franz See; 10.01.2010
comment
Спасибо! Это то, что я сделал (за исключением того, что мой коэффициент повышения равен количеству токенов, чтобы компенсировать более высокие оценки или запросы, которые обычно выдаются). Иногда результаты имеют смысл, а иногда нет. Думаю, мне нужно выяснить, что это за другие факторы. Спасибо! Повторите свой совет: имейте это в виду. Спасибо за ваше терпение ! :-) - person Franz See; 11.01.2010