如何在Ruby中进行模糊子串匹配?

时间:2011-05-23 06:12:29

标签: ruby string fuzzy-search

我找到了许多关于模糊匹配的链接,将一个字符串与另一个字符串进行比较,并查看哪个字符串得到最高的相

我有一个非常长的字符串,它是一个文档和一个子字符串。子字符串来自原始文档,但已被多次转换,因此可能引入了奇怪的工件,例如此处的空格,字符串。子字符串将匹配原始文档中文本的一部分99%或更多。我不匹配以查看此字符串是哪个文档,我试图在文档中找到字符串开头的索引。

如果字符串相同,因为没有引入随机错误,我会使用document.index(substring),但是如果有一个字符差异则会失败。

我认为通过删除字符串和子字符串中除az以外的所有字符来比较差异,然后使用压缩字符串时生成的索引将压缩字符串中的索引转换为索引真实的文件。这种情况很好用,其中差异是空格和标点符号,但只要一个字母不同就失败了。

文档通常是几页到一百页,而子字符串从几个句子到几页。

5 个答案:

答案 0 :(得分:5)

你可以试试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

显然,可能有许多改进,可能是必要的!顶部的几个:

  1. 处理文档一次并存储 结果,可能在数据库中。
  2. 确定字符串的可用长度 进行初步检查 首先反对那个初始子串 在尝试匹配整个之前 片段。
  3. 跟进前一个, 预先计算出的起始片段 那个长度。

答案 1 :(得分: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

答案 2 :(得分:2)

你应该看看这里详述的StrikeAMatch实现: A better similarity ranking algorithm for variable length strings

不依赖某种字符串距离(即两个字符串之间的变化次数),而是查看字符对模式。每个字符串中出现的字符对越多,匹配就越好。它在我们的应用程序中运行得非常好,我们在纯文本文件中搜索错误类型/可变长度标题。

还有一个宝石,它结合了StrikeAMatch(在角色级别的双子座上实现Dice's coefficient)和Levenshtein距离来查找匹配:https://github.com/seamusabshere/fuzzy_match

答案 3 :(得分:1)

这取决于最终可能在子字符串中的工件。在更简单的情况下,它们不是[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上使用0MatchData

如果您只对起始位置感兴趣,可以使用=~运算符:

start_pos = document =~ re

答案 4 :(得分:0)

我没有使用它们,但我在rubygems.org中搜索'diff'时发现了一些库。所有这些都可以通过gem安装。你可能想尝试一下。我自己很感兴趣,所以如果你已经知道这些,或者如果你试试这些,那么如果你发表评论会有所帮助。