通过模糊匹配,我不是指Levenshtein距离或类似的相似字符串,而是它在TextMate / Ido / Icicles中使用的方式:给定一个字符串列表,找到那些包含搜索字符串中所有字符的字符串,但是可能还有其他角色,更喜欢最合适。
答案 0 :(得分:30)
我终于明白了你在寻找什么。这个问题很有意思,但是看看你发现的2种算法,似乎人们对规范有不同的看法;)
我认为更清楚地陈述问题和要求会很有用。
<强>问题强>:
我们正在寻找一种加快输入速度的方法,允许用户只键入他们实际想要的关键字的几个字母,并为他们提供一个可供选择的列表。
<强>分析强>:
前两个要求可以这样总结:对于输入axg
,我们正在寻找与此正则表达式匹配的单词[^a]*a[^x]*x[^g]*g.*
第三个要求是故意松散的。单词应该出现在列表中的顺序需要保持一致......但是很难猜测评分方法是否优于字母顺序。如果列表很长,那么评分方法可能会更好,但是对于简短列表,眼睛更容易在列表中查找以明显方式排序的特定项目。
此外,字母顺序在打字过程中具有一致性的优点:即添加字母不会完全重新排序列表(对眼睛和大脑感到痛苦),它只是过滤掉不再匹配的项目。
处理unicode字符没有精确性,例如à
类似于a
或其他字符?由于我不知道目前在他们的关键字中使用这些字符的语言,我现在就让它滑倒。
我的解决方案:
对于任何输入,我将构建前面表达的正则表达式。它适用于Python,因为该语言已经具有不区分大小写的匹配。
然后我会匹配我的(按字母顺序排序的)关键字列表,并将其输出以进行过滤。
在伪代码中:
WORDS = ['Bar', 'Foo', 'FooBar', 'Other']
def GetList(input, words = WORDS):
expr = ['[^' + i + ']*' + i for i in input]
return [w for w in words if re.match(expr, w, re.IGNORECASE)]
我本可以使用单行,但认为它会使代码模糊不清;)
此解决方案适用于增量情况(即,当您匹配用户类型并因此保持重建时),因为当用户添加角色时,您可以简单地重新过滤刚刚计算的结果。因此:
我还应该注意,这个正则表达式不涉及反向跟踪,因此非常有效。它也可以建模为一个简单的状态机。
答案 1 :(得分:9)
Levenshtein的“编辑距离”算法肯定适用于您要做的事情:它们会测量两个单词或地址或电话号码,诗篇,独白和学术文章之间的匹配程度,您对结果进行排名并选择最佳匹配。
更轻量级的appproach是计算公共子串:它不如Levenshtein好,但它提供了可用的结果,并且可以快速运行,可以访问快速的“InString”函数。
几年前我在Excellerando发布了一个Excel'模糊查找',使用'FuzzyMatchScore'功能,据我所知,确切地说你需要什么:
http://excellerando.blogspot.com/2010/03/vlookup-with-fuzzy-matching-to-get.html
当然,它是在Visual Basic for Applications中。小心谨慎,十字架和大蒜:
Public Function SumOfCommonStrings( _ ByVal s1 As String, _ ByVal s2 As String, _ Optional Compare As VBA.VbCompareMethod = vbTextCompare, _ Optional iScore As Integer = 0 _ ) As Integer Application.Volatile False ' N.Heffernan 06 June 2006 ' THIS CODE IS IN THE PUBLIC DOMAIN ' Function to measure how much of String 1 is made up of substrings found in String 2 ' This function uses a modified Longest Common String algorithm. ' Simple LCS algorithms are unduly sensitive to single-letter ' deletions/changes near the midpoint of the test words, eg: ' Wednesday is obviously closer to WedXesday on an edit-distance ' basis than it is to WednesXXX. So it would be better to score ' the 'Wed' as well as the 'esday' and add up the total matched ' Watch out for strings of differing lengths: ' ' SumOfCommonStrings("Wednesday", "WednesXXXday") ' ' This scores the same as: ' ' SumOfCommonStrings("Wednesday", "Wednesday") ' ' So make sure the calling function uses the length of the longest ' string when calculating the degree of similarity from this score. ' This is coded for clarity, not for performance. Dim arr() As Integer ' Scoring matrix Dim n As Integer ' length of s1 Dim m As Integer ' length of s2 Dim i As Integer ' start position in s1 Dim j As Integer ' start position in s2 Dim subs1 As String ' a substring of s1 Dim len1 As Integer ' length of subs1 Dim sBefore1 ' documented in the code Dim sBefore2 Dim sAfter1 Dim sAfter2 Dim s3 As String SumOfCommonStrings = iScore n = Len(s1) m = Len(s2) If s1 = s2 Then SumOfCommonStrings = n Exit Function End If If n = 0 Or m = 0 Then Exit Function End If 's1 should always be the shorter of the two strings: If n > m Then s3 = s2 s2 = s1 s1 = s3 n = Len(s1) m = Len(s2) End If n = Len(s1) m = Len(s2) ' Special case: s1 is n exact substring of s2 If InStr(1, s2, s1, Compare) Then SumOfCommonStrings = n Exit Function End If For len1 = n To 1 Step -1 For i = 1 To n - len1 + 1 subs1 = Mid(s1, i, len1) j = 0 j = InStr(1, s2, subs1, Compare) If j > 0 Then ' We've found a matching substring... iScore = iScore + len1 ' Now clip out this substring from s1 and s2... ' And search the fragments before and after this excision: If i > 1 And j > 1 Then sBefore1 = left(s1, i - 1) sBefore2 = left(s2, j - 1) iScore = SumOfCommonStrings(sBefore1, _ sBefore2, _ Compare, _ iScore) End If If i + len1 < n And j + len1 < m Then sAfter1 = right(s1, n + 1 - i - len1) sAfter2 = right(s2, m + 1 - j - len1) iScore = SumOfCommonStrings(sAfter1, _ sAfter2, _ Compare, _ iScore) End If SumOfCommonStrings = iScore Exit Function End If Next Next End Function Private Function Minimum(ByVal a As Integer, _ ByVal b As Integer, _ ByVal c As Integer) As Integer Dim min As Integer min = a If b < min Then min = b End If If c < min Then min = c End If Minimum = min End Function
答案 2 :(得分:3)
到目前为止我发现了两种算法:
答案 3 :(得分:3)
我实际上正在为Emacs构建类似于Vim的Command-T和ctrlp插件的东西,只是为了好玩。我和一些聪明的同事讨论了如何最有效地完成这项工作。目标是减少消除不匹配文件所需的操作数量。因此,我们创建一个嵌套映射,其中顶层的每个键都是一个出现在搜索集某处的字符,映射到搜索集中所有字符串的索引。然后,每个索引都映射到字符偏移列表,特定字符出现在搜索字符串中。
在伪代码中,对于字符串:
我们会建立一个这样的地图:
{
"c" => {
0 => [0]
},
"o" => {
0 => [1, 5],
1 => [1]
},
"n" => {
0 => [2]
},
"t" => {
0 => [3]
},
"r" => {
0 => [4, 9]
},
"l" => {
0 => [6, 7],
1 => [4]
},
"e" => {
0 => [9],
1 => [3],
2 => [2]
},
"m" => {
1 => [0]
},
"d" => {
1 => [2]
},
"v" => {
2 => [0]
},
"i" => {
2 => [1]
},
"w" => {
2 => [3]
}
}
所以现在你有这样的映射:
{
character-1 => {
word-index-1 => [occurrence-1, occurrence-2, occurrence-n, ...],
word-index-n => [ ... ],
...
},
character-n => {
...
},
...
}
现在搜索字符串“oe”:
{0 => 1, 1 => 1}
。{0 => 9, 1 => 3}
。现在通过查看我们累积的地图中的键,我们知道哪些字符串与模糊搜索匹配。
理想情况下,如果在用户输入时执行搜索,您将跟踪累积的结果哈希并将其传回搜索功能。我认为这比迭代所有搜索字符串并在每个搜索字符串上执行完整的通配符搜索要快得多。
有趣的是,你可以有效地存储Levenstein距离以及每场比赛,假设你只关心插入,而不是替换或删除。虽然也许并不难加入这种逻辑。
答案 4 :(得分:2)
我最近不得不解决同样的问题。我的解决方案涉及对具有连续匹配字母的字符串进行高度评分,并排除不包含键入字母的字符串。
我在这里详细记录了算法:http://blog.kazade.co.uk/2014/10/a-fuzzy-filename-matching-algorithm.html
答案 5 :(得分:0)
如果您的文字主要是英文,那么您可以尝试使用各种Soundex算法 1. Classic soundex 2. Metafone
这些算法可让您选择听起来彼此相似的单词,并且是查找拼写错误的单词的好方法。