Поиск в спящем режиме с автозаполнением и нечеткой функциональностью - PullRequest
0 голосов
/ 16 апреля 2020

Я пытаюсь создать представление Hibernate Search для метода StingUtils containsIgnoreCase () вместе с сопоставлением нечеткого поиска .

Предположим, пользователь пишет письмо «p», и они получат все совпадения, которые включают в себя букву «p» (независимо от того, находится ли буква в начале, в середине или в конце соответствующих совпадений).

Поскольку они образуют такие слова, как « Питер ", они также должны получать нечеткие совпадения, например," Petar "," Petaer "и" Peder ".

Я использую пользовательский анализатор запросов и индексов, предоставленный в отличном ответе здесь , потому что мне нужно minGramSize в 1, чтобы разрешить функцию автозаполнения, в то же время я также ожидаю многословный ввод пользователя, разделенный пробелами, такими как "EUR Account of Peter", который может быть в разных случаи (нижний или верхний).

Таким образом, пользователь должен иметь возможность набрать «И» и получить приведенный выше пример в качестве совпадения.

В настоящее время я использую следующий код ery:

  org.apache.lucene.search.Query fuzzySearchByName = qb.keyword().fuzzy()
                                                   .withEditDistanceUpTo(1).onField("name")
                                                   .matching(userInput).createQuery();
  booleanQuery.add(fuzzySearchByName, BooleanClause.Occur.MUST);

Однако , случаи точного соответствия не получают присутствия в результатах поиска:

Если мы введем "petar", мы получим следующие результаты:

  1. Petarr (неточное совпадение)
  2. Petaer (неточное совпадение)

... 4. PETAR ( точное совпадение )

То же самое относится к пользовательскому вводу "peter", где первым результатом является "Petero", а вторым «Питер» (второй должен быть первым).

Мне также нужно , чтобы включить только точные совпадения в запросы из нескольких слов - например, если я начну писать " Учетная запись для ...", я получу sh все подходящие результаты, включив фразу" Account for"и, в конечном итоге, ее нечеткие термины, основанные на этой фразе (в основном такие же, как метод containsIgnoreCase (), показанный ранее, просто пытающийся добавить нечеткую поддержку) .

Полагаю, однако, что это противоречит minGramSize из 1 и WhitespaceTokenizerFactory?

1 Ответ

1 голос
/ 16 апреля 2020

Однако случаи точного соответствия не получают присутствия в результатах поиска:

Просто используйте два запроса вместо одного:

РЕДАКТИРОВАТЬ : вам также нужно будет установить два отдельных поля для автозаполнения и «точного» соответствия; см. мои правки внизу.

  org.apache.lucene.search.Query exactSearchByName = qb.keyword().onField("name")
                                                   .matching(userInput).createQuery();
  org.apache.lucene.search.Query fuzzySearchByName = qb.keyword().fuzzy()
                                                   .withEditDistanceUpTo(1).onField("name")
                                                   .matching(userInput).createQuery();
  org.apache.lucene.search.Query searchByName = qb.boolean().should(exactSearchByName).should(fuzzySearchByName).createQuery();
  booleanQuery.add(searchByName, BooleanClause.Occur.MUST);

Это будет соответствовать документам, которые содержат пользовательский ввод точно или приблизительно, так что это будет соответствовать тем же документам, что и ваш пример. Однако документы, которые содержат пользовательский ввод, точно будут соответствовать обоим запросам, в то время как документы, которые содержат только что-то подобное, будут соответствовать только нечеткому запросу. В результате точные совпадения будут иметь более высокий балл и в итоге окажутся выше в списке результатов.

Если точные совпадения недостаточно высоки, попробуйте добавить повышение к запросу exactSearchByName:

  org.apache.lucene.search.Query exactSearchByName = qb.keyword().onField("name")
                                                   .matching(userInput)
                                                   .boostedTo(4.0f)
                                                   .createQuery();

Я полагаю, однако, что это противоречит minGramSize = 1 и WhitespaceTokenizerFactory?

Если вы хотите сопоставить документы, которые содержат любое слово (но не обязательно все слова), появляющиеся в пользовательском вводе и чтобы поместить документы, содержащие больше слов, выше в списке результатов, выполните то, что я объяснил выше.

Если вы хотите сопоставить документы, которые содержат все слова в том же порядке, используйте KeywordTokenizerFactory (т. е. без лексем).

Если вы хотите сопоставить документы, содержащие все слова в любом порядке, ну ... это менее очевидно. В Hibernate Search такой поддержки нет (пока ), поэтому вам, по сути, придется создавать запрос самостоятельно. Один хак, который я уже видел, выглядит примерно так:

Analyzer analyzer = fullTextSession.getSearchFactory().getAnalyzer( "myAnalyzer" );

QueryParser queryParser = new QueryParser( "name", analyzer );
queryParser.setOperator( Operator.AND ); // Match *all* terms
Query luceneQuery = queryParser.parse( userInput );

... но это не будет генерировать нечеткие запросы. Если вам нужны нечеткие запросы, вы можете попробовать переопределить некоторые методы в пользовательском подклассе QueryParser. Я не пробовал это, но это может работать:

public final class FuzzyQueryParser extends QueryParser {
    private final int maxEditDistance;
    private final int prefixLength;

    public FuzzyQueryBuilder(String fieldName, Analyzer analyzer, int maxEditDistance, int prefixLength) {
        super( fieldName, analyzer );
        this.maxEditDistance = maxEditDistance;
        this.prefixLength = prefixLength;
    }

    @Override
    protected Query newTermQuery(Term term) {
        return new FuzzyQuery( term, maxEditDistance, prefixLength );
    }
}

РЕДАКТИРОВАТЬ : с minGramSize 1, вы получите много очень частых терминов: одно- или двухсимвольные термины, извлеченные из начала слов. Скорее всего, это приведет к множеству нежелательных совпадений, которые будут набраны с высокой оценкой (поскольку термины являются частыми) и, вероятно, будут затоплены точные совпадения.

Сначала вы можете попробовать установить сходство (~ формула оценки) на org.apache.lucene.search.similarities.BM25Similarity , который лучше игнорировать очень частые термины. См. здесь для настройки . Это должно улучшить оценку с помощью тех же анализаторов.

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

@Field(name = "name", analyzer = @Analyzer(definition = "text")
@Field(name = "name_autocomplete", analyzer = @Analyzer(definition = "edgeNgram")
private String name;

Анализатор "text" - это просто анализатор "edgeNGram_query" из ответа , который вы связали ; просто переименуйте его.

Продолжайте писать два запроса вместо одного, как описано выше, но убедитесь, что вы указали два разных поля:

  org.apache.lucene.search.Query exactSearchByName = qb.keyword().onField("name")
                                                   .matching(userInput).createQuery();
  org.apache.lucene.search.Query fuzzySearchByName = qb.keyword().fuzzy()
                                                   .withEditDistanceUpTo(1).onField("name_autocomplete")
                                                   .matching(userInput).createQuery();
  org.apache.lucene.search.Query searchByName = qb.boolean().should(exactSearchByName).should(fuzzySearchByName).createQuery();
  booleanQuery.add(searchByName, BooleanClause.Occur.MUST);

Не забудьте переиндексировать после этих изменений конечно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...