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

Как я могу выполнить нечеткое подстроку в Ruby?

Я нашел много ссылок о нечетком сопоставлении, сравнивая одну строку с другой и видя, что получает самый высокий балл подобия.

У меня есть очень длинная строка, которая является документом и подстрокой. Подстрока была взята из оригинального документа, но была преобразована несколько раз, поэтому могли появиться странные артефакты, такие как пространство здесь, тире там. Подстрока будет соответствовать разделу текста в оригинальном документе 99% или более. Я не согласен, чтобы увидеть, из какого документа эта строка, я пытаюсь найти индекс в документе, где начинается строка.

Если строка была идентичной, потому что случайная ошибка не была введена, я использовал бы document.index(substring), однако это не удается, если есть даже одна разница символов.

Я думал, что разница будет учтена, удалив все символы, кроме az, как в строке, так и подстроке, сравните, а затем используйте индекс I, сгенерированный при сжатии строки, чтобы преобразовать индекс в сжатой строке в индекс в реальный документ. Это работало хорошо, где разница была пробелом и пунктуацией, но как только одна буква отличается, она не удалась.

Документ, как правило, составляет от нескольких страниц до ста страниц, а подстрока - от нескольких предложений до нескольких страниц.

4b9b3361

Ответ 1

Вы можете попробовать amatch. Он доступен как рубиновый камень и, хотя я долгое время не работал с нечеткой логикой, он выглядит так, как вам нужно. Домашняя страница для amatch: http://flori.github.com/amatch/.

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

include 'amatch'

module FuzzyFinder
  def scanner( input )
    out = [] unless block_given?
    pos = 0
    input.scan(/(\w+)(\W*)/) do |word, white|
      startpos = pos
      pos = word.length + white.length
      if block_given?
        yield startpos, word
      else
        out << [startpos, word]
      end
    end
  end

  def find( text, doc )
    index = scanner(doc)
    sstr = text.gsub(/\W/,'')
    levenshtein = Amatch::Levensthtein.new(sstr)
    minlen = sstr.length
    maxndx = index.length
    possibles = []
    minscore = minlen*2
    index.each_with_index do |x, i|
      spos = x[0]
      str = x[1]
      si = i
      while (str.length < minlen)
        i += 1
        break unless i < maxndx
        str += index[i][1]
      end
      str = str.slice(0,minlen) if (str.length > minlen)
      score = levenshtein.search(str)
      if score < minscore
        possibles = [spos]
        minscore = score
      elsif score == minscore
        possibles << spos
      end
    end
    [minscore, possibles]
  end
end

Очевидно, что возможны многочисленные улучшения и, возможно, необходимы! Немного сверху:

  • Обработать документ один раз и сохранить результаты, возможно, в базе данных.
  • Определить используемую длину строки для начальной проверки, процесс против первой начальной подстроки прежде чем пытаться сопоставить весь фрагмент.
  • Следуя предыдущему, предварительно рассчитать начальные фрагменты эта длина.

Ответ 2

Вы должны посмотреть на реализацию StrikeAMatch, описанную здесь: Лучший алгоритм ранжирования сходства для строк переменной длины

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

Также есть жемчужина, которая сочетает в себе StrikeAMatch (реализация Коэффициент кости на персональных биграммах) и расстояние Левенштейна для поиска совпадений: https://github.com/seamusabshere/fuzzy_match

Ответ 3

Простой - fuzzy_match

require 'fuzzy_match'
FuzzyMatch.new(['seamus', 'andy', 'ben']).find('Shamus') #=> seamus

Более разработанный (вы бы не сказали это из этого примера, хотя) levenshein, который вычисляет количество различий.

require 'levenshtein' 
Levenshtein.distance('test', 'test')    # => 0
Levenshtein.distance('test', 'tent')    # => 1

Ответ 4

Это зависит от артефактов, которые могут оказаться в подстроке. В более простом случае, когда они не являются частью [a-z], вы можете использовать синтаксический анализ подстроки, а затем использовать Regexp#match в документе:

document = 'Ulputat non nullandigna tortor dolessi illam sectem laor acipsus.'
substr = "tortor - dolessi _%&#   +illam"

re = Regexp.new(substr.split(/[^a-z]/i).select{|e| !e.empty?}.join(".*"))
md = document.match re
puts document[md.begin(0) ... md.end(0)]
# => tortor dolessi illam

(Здесь, поскольку мы не устанавливаем никаких скобок в Regexp, мы используем begin и end в первом (полном) элементе 0 MatchData.

Если вас интересует только начальная позиция, вы можете использовать оператор =~:

start_pos = document =~ re

Ответ 5

Я не использовал ни одного из них, но я нашел несколько библиотек, просто выполнив поиск "diff" в rubygems.org. Все они могут быть установлены жемчужиной. Возможно, вы захотите попробовать их. Я сам заинтересован, поэтому, если вы уже знаете об этом или если вы попробуете их, было бы полезно, если вы оставите свой комментарий.