Подтвердить что ты не робот

Нечеткий поиск Javascript, который имеет смысл

Я ищу библиотеку JavaScript нечеткого поиска для фильтрации массива. Я пытался использовать fuzzyset.js и fuse.js, но результаты ужасны (есть демоверсии, которые вы можете попробовать на связанных страницах).

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

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

Я хочу использовать это в контексте завершения текста, поэтому, если у меня есть массив ['international', 'splint', 'tinder'], и мой запрос int, я думаю, что международный должен иметь более высокий рейтинг, чем splint, даже хотя первый имеет оценку (выше = хуже) 10 против последних 3.

Так что я ищу (и буду создавать, если он не существует), это библиотека, которая делает следующее:

  • Весит различные текстовые манипуляции
  • Вес каждой манипуляции различается в зависимости от того, где они встречаются в слове (ранние манипуляции обходятся дороже, чем поздние манипуляции)
  • Возвращает список результатов, отсортированных по релевантности

Кто-нибудь сталкивался с чем-нибудь подобным? Я понимаю, что StackOverflow - это не то место, где нужно спрашивать рекомендации по программному обеспечению, но подразумевается (не больше!) В приведенном выше: я думаю об этом правильно?


редактировать

Я нашел хорошую статью (pdf) на эту тему. Некоторые заметки и выдержки:

Аффинные функции редактирования расстояния назначают относительно более низкую стоимость последовательности вставок или удалений

функция расстояния Монгера-Элкана (Monge & Elkan 1996), которая является аффинным вариантом функции расстояния Смита-Уотермана (Дурбан и др. 1998) с конкретными параметрами стоимости

Для расстояния Смита-Уотермана (википедия): "Вместо того, чтобы рассматривать общую последовательность, алгоритм Смита-Уотермана сравнивает сегменты всех возможных длин и оптимизирует меру подобия". Это н-граммовый подход.

Схожей метрикой, которая не основана на модели расстояния редактирования, является метрика Jaro (Jaro 1995; 1989; Winkler 1999). В литературе о связях записей хорошие результаты были получены с использованием вариантов этого метода, который основан на количестве и порядке общих символов между двумя строками.

Вариант этого из-за Winkler (1999) также использует длину P самого длинного общего префикса

(кажется, предназначен в первую очередь для коротких строк)

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

Заключение:

ранжирование TFIDF показало лучший результат среди нескольких метрик расстояния на основе токенов, а настроенная метрика аффинного промежутка редактирования, предложенная Монге и Элканом, показала наилучший результат среди нескольких метрик расстояния редактирования строки. Удивительно хорошая метрика расстояния - это быстрая эвристическая схема, предложенная Джаро и позже расширенная Винклером. Это работает почти так же, как схема Монжа-Элкана, но на порядок быстрее. Один простой способ объединения метода TFIDF и винклера Jaro- состоит в замене точных совпадений токенов, используемых в TFIDF, на приближенные совпадения токенов на основе схемы Винклера Jaro-. Эта комбинация работает немного лучше, чем Jaro- Winkler или TFIDF в среднем, и иногда работает намного лучше. По производительности он также близок к изученной комбинации нескольких лучших метрик, рассмотренных в этой статье.

4b9b3361

Ответ 1

Хороший вопрос! Но я думаю, что вместо того, чтобы пытаться изменить Левенштейна-Демерау, вам лучше попробовать другой алгоритм или объединить/взвесить результаты двух алгоритмов.

Меня поражает, что точное или близкое совпадение с "стартовым префиксом" - это то, что Левенштейн-Демерау не придает особого значения - но ваши очевидные пользовательские ожидания могли бы это сделать.

Я искал "лучше, чем Левенштейн" и, среди прочего, нашел это:

http://www.joyofdata.de/blog/comparison-of-string-distance-algorithms/

Здесь упоминается ряд "расстояний между строками". Три, которые выглядели особенно актуальными для вашего требования, будут:

  1. Longest Common Substring distance: минимальное количество символов, которое необходимо удалить в обеих строках, пока результирующие подстроки не будут идентичны.

  2. Расстояние q-граммы: сумма абсолютных разностей между векторами N-грамм обеих строк.

  3. Расстояние по Джакарте : 1 мин. Отношение общих N-грамм и всех наблюдаемых N-грамм.

Может быть, вы могли бы использовать взвешенную комбинацию (или минимум) этих метрик, с Левенштейном - общая подстрока, общая N-грамм или Jaccard все решительно предпочтут аналогичные строки - или, возможно, попробуйте просто использовать Jaccard?

В зависимости от размера вашего списка/базы данных, эти алгоритмы могут быть умеренно дорогими. Для нечеткого поиска, который я реализовал, я использовал настраиваемое количество N-грамм в качестве "ключей поиска" из БД, а затем запустил дорогостоящее измерение расстояния до строки, чтобы отсортировать их в порядке предпочтения.

Я написал несколько заметок о нечетком поиске строк в SQL. Увидеть:

Ответ 2

Я попытался использовать существующие нечеткие библиотеки, такие как fuse.js, и обнаружил, что они ужасны, поэтому я написал такую, которая в основном ведет себя как возвышенный поиск. https://github.com/farzher/fuzzysort

Единственная опечатка, которую это позволяет, является транспонированием. Он довольно солидный (1 тыс. Звезд, 0 выпусков), очень быстрый и легко обрабатывает ваше дело:

fuzzysort.go('int', ['international', 'splint', 'tinder'])
// [{highlighted: '*int*ernational', score: 10}, {highlighted: 'spl*int*', socre: 3003}]

Ответ 3

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

get_bigrams = (string) ->
    s = string.toLowerCase()
    v = new Array(s.length - 1)
    for i in [0..v.length] by 1
        v[i] = s.slice(i, i + 2)
    return v

string_similarity = (str1, str2) ->
    if str1.length > 0 and str2.length > 0
        pairs1 = get_bigrams(str1)
        pairs2 = get_bigrams(str2)
        union = pairs1.length + pairs2.length
        hit_count = 0
        for x in pairs1
            for y in pairs2
                if x is y
                    hit_count++
        if hit_count > 0
            return ((2.0 * hit_count) / union)
    return 0.0

Передайте две строки в string_similarity, которая вернет число между 0 и 1.0 в зависимости от того, насколько они похожи. В этом примере используется Lo-Dash

Пример использования....

query = 'jenny Jackson'
names = ['John Jackson', 'Jack Johnson', 'Jerry Smith', 'Jenny Smith']

results = []
for name in names
    relevance = string_similarity(query, name)
    obj = {name: name, relevance: relevance}
    results.push(obj)

results = _.first(_.sortBy(results, 'relevance').reverse(), 10)

console.log results

Также есть fiddle

Убедитесь, что ваша консоль открыта или вы ничего не увидите:)

Ответ 4

вы можете взглянуть на Atom https://github.com/atom/fuzzaldrin/ lib.

он доступен на npm, имеет простой API и работает нормально для меня.

> fuzzaldrin.filter(['international', 'splint', 'tinder'], 'int');
< ["international", "splint"]

Ответ 5

(function (int) {
    $("input[id=input]")
        .on("input", {
        sort: int
    }, function (e) {
        $.each(e.data.sort, function (index, value) {
          if ( value.indexOf($(e.target).val()) != -1 
              && value.charAt(0) === $(e.target).val().charAt(0) 
              && $(e.target).val().length === 3 ) {
                $("output[for=input]").val(value);
          };
          return false
        });
        return false
    });
}(["international", "splint", "tinder"]))

jsfiddle http://jsfiddle.net/guest271314/QP7z5/

Ответ 6

это моя короткая и компактная функция для нечеткого соответствия:

function fuzzyMatch(pattern, str) {
  pattern = '.*' + pattern.split('').join('.*') + '.*';
  const re = new RegExp(pattern);
  return re.test(str);
}