获取最接近的字符串匹配


397

我需要一种将多个字符串与一个测试字符串进行比较并返回与其非常相似的字符串的方法:

TEST STRING: THE BROWN FOX JUMPED OVER THE RED COW

CHOICE A   : THE RED COW JUMPED OVER THE GREEN CHICKEN
CHOICE B   : THE RED COW JUMPED OVER THE RED COW
CHOICE C   : THE RED FOX JUMPED OVER THE BROWN COW

(如果我正确地做到了)与“ TEST STRING”最接近的字符串应该是“ CHOICE C”。最简单的方法是什么?

我计划将其实现为多种语言,包括VB.net,Lua和JavaScript。在这一点上,伪代码是可以接受的。如果您可以提供特定语言的示例,也将不胜感激!


3
通常执行此类操作的算法会确定将已检查的字符串转换为目标字符串需要进行多少更改。这些类型的算法在这种情况下根本无法正常工作。我认为拥有一台计算机来实现这一目标将非常困难。
马特·格里尔

3
Levenshtein距离源代码有多种语言:Java,Ruby,Python,PHP等。en.wikibooks.org
wiki/

9
通常,什么算作“最接近的字符串”将取决于所使用的相似性度量以及用于在对齐方式中引入间隙的惩罚。例如,您是否认为“牛”和“鸡”比“牛”和“红色”更相似(因为它们是相关概念),或者相反(因为“鸡”的字母比“牛”更多) )?但是,考虑到相似性度量和空位罚分,可以证明下面的Levenshtein算法可以保证找到最接近的字符串。Needleman-Wunsch和Smith-Waterman(下文进一步介绍)也是如此。
Sten L 2012年

进行字符分组或单词分组。给它分数。
凯西

Answers:


952

大约一年前,我遇到了这个问题,它涉及在杂项信息数据库中查找用户输入的有关石油钻井平台的信息。目的是进行某种模糊字符串搜索,以识别具有最常见元素的数据库条目。

研究的一部分涉及实施Levenshtein距离算法,该算法确定必须对字符串或短语进行多少更改才能将其转换为另一个字符串或短语。

我想出的实现方式相对简单,涉及两个词组的长度,每个词组之间的更改次数以及是否可以在目标条目中找到每个词的加权比较。

该文章位于私人站点上,因此我将尽力在此处附加相关内容:


模糊字符串匹配是对两个单词或短语进行相似度估算的过程。在许多情况下,它涉及识别彼此最相似的单词或短语。本文介绍了模糊字符串匹配问题的内部解决方案及其在解决各种问题中的实用性,这些问题可以使我们能够自动化以前需要乏味用户参与的任务。

介绍

在开发墨西哥湾验证程序工具时,最初就需要进行模糊字符串匹配。那里存在着一个已知的墨西哥石油钻井平台和平台的数据库,购买保险的人会给我们一些关于他们资产的错误键入信息,我们必须将其与已知平台的数据库进行匹配。当给出的信息很少时,我们所能做的最好的就是依靠承销商“认出”他们所指的信息,并调出适当的信息。这是该自动化解决方案派上用场的地方。

我花了一天的时间研究模糊字符串匹配的方法,最终偶然发现了Wikipedia上非常有用的Levenshtein距离算法。

实作

在了解了其背后的理论之后,我实现并找到了对其进行优化的方法。这是我的代码在VBA中的样子:

'Calculate the Levenshtein Distance between two strings (the number of insertions,
'deletions, and substitutions needed to transform the first string into the second)
Public Function LevenshteinDistance(ByRef S1 As String, ByVal S2 As String) As Long
    Dim L1 As Long, L2 As Long, D() As Long 'Length of input strings and distance matrix
    Dim i As Long, j As Long, cost As Long 'loop counters and cost of substitution for current letter
    Dim cI As Long, cD As Long, cS As Long 'cost of next Insertion, Deletion and Substitution
    L1 = Len(S1): L2 = Len(S2)
    ReDim D(0 To L1, 0 To L2)
    For i = 0 To L1: D(i, 0) = i: Next i
    For j = 0 To L2: D(0, j) = j: Next j

    For j = 1 To L2
        For i = 1 To L1
            cost = Abs(StrComp(Mid$(S1, i, 1), Mid$(S2, j, 1), vbTextCompare))
            cI = D(i - 1, j) + 1
            cD = D(i, j - 1) + 1
            cS = D(i - 1, j - 1) + cost
            If cI <= cD Then 'Insertion or Substitution
                If cI <= cS Then D(i, j) = cI Else D(i, j) = cS
            Else 'Deletion or Substitution
                If cD <= cS Then D(i, j) = cD Else D(i, j) = cS
            End If
        Next i
    Next j
    LevenshteinDistance = D(L1, L2)
End Function

简单,快速且非常有用的指标。使用此方法,我创建了两个单独的指标来评估两个字符串的相似性。一个我称为“ valuePhrase”,另一个我称为“ valueWords”。valuePhrase只是两个短语之间的Levenshtein距离,valueWords根据空格,破折号和其他所需的分隔符将字符串分成单个单词,并将每个单词与另一个单词进行比较,得出最短的单词Levenshtein距离连接任意两个词。本质上,它衡量一个词组中的信息是否真的包含在另一个词组中,就像逐字排列一样。我花了几天时间作为一个辅助项目,提出了基于定界符分割字符串的最有效方法。

valueWords,valuePhrase和Split函数:

Public Function valuePhrase#(ByRef S1$, ByRef S2$)
    valuePhrase = LevenshteinDistance(S1, S2)
End Function

Public Function valueWords#(ByRef S1$, ByRef S2$)
    Dim wordsS1$(), wordsS2$()
    wordsS1 = SplitMultiDelims(S1, " _-")
    wordsS2 = SplitMultiDelims(S2, " _-")
    Dim word1%, word2%, thisD#, wordbest#
    Dim wordsTotal#
    For word1 = LBound(wordsS1) To UBound(wordsS1)
        wordbest = Len(S2)
        For word2 = LBound(wordsS2) To UBound(wordsS2)
            thisD = LevenshteinDistance(wordsS1(word1), wordsS2(word2))
            If thisD < wordbest Then wordbest = thisD
            If thisD = 0 Then GoTo foundbest
        Next word2
foundbest:
        wordsTotal = wordsTotal + wordbest
    Next word1
    valueWords = wordsTotal
End Function

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' SplitMultiDelims
' This function splits Text into an array of substrings, each substring
' delimited by any character in DelimChars. Only a single character
' may be a delimiter between two substrings, but DelimChars may
' contain any number of delimiter characters. It returns a single element
' array containing all of text if DelimChars is empty, or a 1 or greater
' element array if the Text is successfully split into substrings.
' If IgnoreConsecutiveDelimiters is true, empty array elements will not occur.
' If Limit greater than 0, the function will only split Text into 'Limit'
' array elements or less. The last element will contain the rest of Text.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function SplitMultiDelims(ByRef Text As String, ByRef DelimChars As String, _
        Optional ByVal IgnoreConsecutiveDelimiters As Boolean = False, _
        Optional ByVal Limit As Long = -1) As String()
    Dim ElemStart As Long, N As Long, M As Long, Elements As Long
    Dim lDelims As Long, lText As Long
    Dim Arr() As String

    lText = Len(Text)
    lDelims = Len(DelimChars)
    If lDelims = 0 Or lText = 0 Or Limit = 1 Then
        ReDim Arr(0 To 0)
        Arr(0) = Text
        SplitMultiDelims = Arr
        Exit Function
    End If
    ReDim Arr(0 To IIf(Limit = -1, lText - 1, Limit))

    Elements = 0: ElemStart = 1
    For N = 1 To lText
        If InStr(DelimChars, Mid(Text, N, 1)) Then
            Arr(Elements) = Mid(Text, ElemStart, N - ElemStart)
            If IgnoreConsecutiveDelimiters Then
                If Len(Arr(Elements)) > 0 Then Elements = Elements + 1
            Else
                Elements = Elements + 1
            End If
            ElemStart = N + 1
            If Elements + 1 = Limit Then Exit For
        End If
    Next N
    'Get the last token terminated by the end of the string into the array
    If ElemStart <= lText Then Arr(Elements) = Mid(Text, ElemStart)
    'Since the end of string counts as the terminating delimiter, if the last character
    'was also a delimiter, we treat the two as consecutive, and so ignore the last elemnent
    If IgnoreConsecutiveDelimiters Then If Len(Arr(Elements)) = 0 Then Elements = Elements - 1

    ReDim Preserve Arr(0 To Elements) 'Chop off unused array elements
    SplitMultiDelims = Arr
End Function

相似度

使用这两个指标,第三个简单地计算两个字符串之间的距离,我有一系列变量,可以运行优化算法以实现最大数量的匹配。模糊字符串匹配本身就是一门模糊科学,因此,通过创建线性独立的度量来测量字符串相似度,并拥有一组我们希望相互匹配的已知字符串,我们可以找到针对我们特定样式的参数字符串,给出最佳的模糊匹配结果。

最初,度量标准的目标是为精确匹配提供较低的搜索值,并为日益排列的度量提高搜索值。在不切实际的情况下,使用一组定义明确的排列来定义这是相当容易的,并设计最终公式以使它们具有所需的增加的搜索值结果。

模糊字符串匹配排列

在上面的屏幕截图中,我调整了试探法,提出了一些可以很好地扩展到搜索字词和结果之间的差异的方法。我Value Phrase在上述电子表格中使用的启发式方法是=valuePhrase(A2,B2)-0.8*ABS(LEN(B2)-LEN(A2))。我有效地将Levenstein距离的损失减少了两个“短语”长度差的80%。这样,具有相同长度的“短语”将遭受全部罚款,但是包含“附加信息”(较长)但除此以外仍共享大多数相同字符的“短语”将受到较少的惩罚。我按Value Words原样使用该函数,然后将我的最终SearchVal启发式定义为=MIN(D2,E2)*0.8+MAX(D2,E2)*0.2-加权平均值。两个分数中较低的一个加权为80%,较高的加权为20%。这只是一种启发式方法,适合我的用例以获得良好的匹配率。然后可以调整这些权重以使其测试数据获得最佳匹配率。

模糊字符串匹配值短语

模糊字符串匹配值词

如您所见,最后两个度量标准(即模糊字符串匹配度量标准)已经自然地倾向于为要匹配的字符串(对角线下方)赋予较低的分数。这是非常好的。

应用程序 为了优化模糊匹配,我对每个指标进行加权。这样,模糊字符串匹配的每个应用程序都可以对参数进行不同的加权。定义最终分数的公式是指标及其权重的简单组合:

value = Min(phraseWeight*phraseValue, wordsWeight*wordsValue)*minWeight
      + Max(phraseWeight*phraseValue, wordsWeight*wordsValue)*maxWeight
      + lengthWeight*lengthValue

使用优化算法(神经网络在这里是最好的,因为它是一个离散的多维问题),现在的目标是最大化匹配数。我创建了一个函数,用于检测每个集合彼此之间正确匹配的次数,如最终屏幕截图所示。如果将最低分数分配给了要匹配的字符串,则列或行将获得一个点;如果最低分数存在并列,则正确的匹配是在已绑定的匹配字符串中,则列或行将得到部分分数。然后,我对其进行了优化。您会看到绿色的单元格是与当前行最匹配的列,而单元格周围的蓝色正方形是与当前列最匹配的行。底角的得分大约是成功匹配的次数,这就是我们告诉优化问题最大化的原因。

模糊字符串匹配优化指标

该算法取得了巨大的成功,解决方案参数说明了这类问题。您会注意到优化得分是44,最佳得分是48。最后5列是诱饵,与行值完全不匹配。诱饵越多,自然就越难找到最佳匹配。

在这种特殊的匹配情况下,字符串的长度是无关紧要的,因为我们期望表示较长单词的缩写,所以长度的最佳权重是-0.3,这意味着我们不会惩罚长度变化的字符串。我们在预期这些缩写时会降低分数,为部分单词匹配提供更多空间,以取代由于字符串较短而只需要较少替换的非单词匹配。

单词权重为1.0,而短语权重仅为0.5,这意味着我们对一个字符串中缺少的整个单词进行惩罚,并对整个短语保持原样进行评估。这很有用,因为许多这样的字符串有一个共同的词(危险),真正重要的是是否保持组合(区域和危险)。

最后,最小权重被优化为10,最大权重被优化为1。这意味着,如果两个分数(值短语和值单词)中的最佳分数不是很好,那么匹配将受到极大的惩罚,但是我们不这样做不会大大惩罚两个分数中最差的一个。从本质上讲,这使得重视,要求无论在valueWord或valuePhrase有一个不错的成绩,但不能同时使用。一种“竭尽所能”的心态。

这5个权重的优化值对发生的模糊字符串匹配的确令人着迷。对于完全不同的模糊字符串匹配实际案例,这些参数是非常不同的。到目前为止,我已经将它用于3个单独的应用程序。

在最终优化中未使用时,建立了一个基准测试表,该基准表将列与自身匹配以实现对角线上的所有理想结果,并允许用户更改参数以控制分数偏离0的速度,并注意搜索词组之间的先天相似性(理论上可以用来抵消结果中的误报)

模糊字符串匹配基准

进一步的应用

该解决方案有可能在用户希望计算机系统在没有完美匹配的一组字符串中标识一个字符串的任何地方使用。(就像字符串的近似匹配vlookup一样)。


因此,您应该从中得到的是,您可能希望结合使用高级启发式方法(从另一个短语中的一个短语中查找单词,两个短语的长度等)以及Levenshtein距离算法的实现。因为确定哪个是“最佳”匹配是一种启发式(模糊)确定,所以您必须为要得出的任何度量标准提供一组权重才能确定相似性。

借助适当的试探法和权重,您将使您的比较程序快速做出您将要做出的决定。


13
奖励:如果有人想在加权启发式算法中加入其他指标,(因为我只提供了3个线性独立的指标),这是Wikipedia上的完整列表:en.wikipedia.org/wiki/String_metric
Alain

1
如果S2包含很多单词(在您选择的语言中创建许多小对象并不是很慢),则Trie可以加快处理速度。使用Trie进行快速便捷的Levenshtein距离是有关尝试的出色文章。
2012年

1
@Alain这是一个有趣的方法!我只是在谈谈您的想法(在C ++中),但一点都不明白的值valuePhrase。如果我在您的代码中正确看到,则为Levenshtein距离函数的返回值。'abcd efgh'搜索表中的double / float值是怎么来的?Levenshtein距离是一个整数值,在您的代码中我看不到进一步的计算,该计算使其成为浮点数。我想念什么?
Andreas W. Wylach's

1
@ AndreasW.Wylach很棒的观察。我展示的VBA只是为了计算Levenshtein距离,但是我在电子表格中使用的启发式方法是,=valuePhrase(A2,B2)-0.8*ABS(LEN(B2)-LEN(A2))因此我将Levenstein距离的损失减少了两个“短语”长度之差的80%。这样,具有相同长度的“短语”将遭受全部罚款,但是包含“附加信息”(较长)但除此以外仍共享大多数相同字符的“短语”将受到较少的惩罚。
阿兰(Alain)

1
@Alain感谢您回到我的问题,对此我表示感谢。您的解释现在使事情变得更清楚了。同时,我实现了一个value_phrase方法,该方法可以更深入地分析短语的标记,即短语标记的顺序/位置,非查询标记序列,并且在涉及到某些内容时,它会更加模糊比起“ abcd”,例如“ acbd”。短语值值得分的倾向与您相同,但在这里和那里会降低一些。再次进行出色的锻炼,这为我提供了模糊搜索算法的灵感!
Andreas W. Wylach'4

88

这个问题一直在生物信息学中出现。上面公认的答案(顺便说一句很棒)在生物信息学中称为Needleman-Wunsch(比较两个字符串)和Smith-Waterman(在较长的字符串中找到近似子字符串)算法。他们工作出色,几十年来一直是他们的主力军。

但是,如果要比较一百万个字符串怎么办?这是一万亿个成对的比较,每个比较都是O(n * m)!现代DNA测序仪可以轻松生成十亿个短DNA序列,每个序列长约200个DNA“字母”。通常,我们希望为每个这样的字符串找到与人类基因组最匹配的字符串(30亿个字母)。显然,Needleman-Wunsch算法及其亲属不会这样做。

所谓的“对准问题”是积极研究的领域。目前,最流行的算法能够在合理的硬件(例如8个内核和32 GB RAM)上花费数小时的时间,找到10亿个短字符串与人类基因组之间的不精确匹配。

这些算法大多数通过快速查找短精确匹配(种子),然后使用较慢的算法(例如Smith-Waterman)将其扩展到完整字符串来工作。之所以起作用,是因为我们实际上只对几场近距离比赛感兴趣,因此摆脱不存在共同点的99.9 ...%的对是有回报的。

查找精确匹配如何帮助查找不精确的匹配?好吧,假设我们只允许查询和目标之间存在一个差异。显而易见,这种差异必须出现在查询的右半部分或左半部分,因此另一半必须完全匹配。这个想法可以扩展到多个错配,并且是Illumina DNA测序仪通常使用的ELAND算法的基础。

有很多非常好的算法可以执行精确的字符串匹配。给定长度为200的查询字符串和长度为30亿的目标字符串(人类基因组),我们希望找到目标中任何地方,其中存在长度为k的子字符串与查询的子字符串完全匹配的子字符串。一种简单的方法是从索引目标开始:获取所有k长的子字符串,将它们放入数组中并对其进行排序。然后获取查询的每个k长子串并搜索排序的索引。排序和搜索可以在O(log n)时间完成。

但是存储可能是个问题。30亿个字母目标的索引将需要包含30亿个指针和30亿个k长的单词。似乎很难将其容纳在少于几十GB的RAM中。但是令人惊讶的是,我们可以使用Burrows-Wheeler变换极大地压缩索引,并且仍然可以有效地查询它。人类基因组的索引可以容纳少于4 GB的RAM。这个想法是BowtieBWA等流行序列比对器的基础。

或者,我们可以使用后缀数组,该后缀数组仅存储指针,但表示目标字符串中所有后缀的同时索引(本质上,是所有k可能值的同时索引; Burrows-Wheeler变换也是如此)。如果使用32位指针,则人类基因组的后缀数组索引将占用12 GB RAM。

上面的链接包含大量信息,并链接到主要研究论文。ELAND链接转到PDF,其中包含有用的数字,阐明了所涉及的概念,并显示了如何处理插入和删除。

最后,虽然这些算法已基本解决了对单个人类基因组(十亿个短字符串)进行(重新)测序的问题,但DNA测序技术的改进速度甚至超过了摩尔定律,而且我们正在快速接近万亿个字母的数据集。例如,目前正在进行对10,000种脊椎动物物种的基因组进行测序的项目,每种物种大约10亿个字母左右。自然,我们将要对数据进行成对的不精确字符串匹配...


3
真的不错。一些更正:对中缀进行排序至少需要O(n),而不是O(log n)。而且由于O(log n)搜索实际上在实践中太慢,因此通常需要构建一个附加表来进行O(1)查找(q-gram索引)。此外,我不确定您为什么将其与后缀数组区别对待–只是对后缀数组的一种优化,不是(不对后缀进行排序(因为我们实际上不需要的是固定长度的前缀,所以将定长的后缀替换为后缀))。
康拉德·鲁道夫2012年

1
此外,这些算法对于从头测序仍然不切实际。他们仅在我们具有可用于作图的参考序列的情况下才解决了人类基因组的测序问题。但是对于从头组装,则需​​要其他算法(当然,有些对齐器基于映射,但是将重叠群缝合在一起是一个整体的“另一个问题”)。最后,无耻的插件:我的学士论文包含对ELAND算法的详细描述。
康拉德·鲁道夫2012年

1
谢谢。我排除了错误。我之所以开始描述固定长度数组是因为它很容易理解。后缀数组和BWT较难掌握,但实际上我们有时确实希望使用具有不同k值的索引。例如,STAR使用后缀数组来有效地找到拼接的路线。当然这对于使RNA与基因组比对有用。
Sten L 2012年

30

我认为选项B更接近测试字符串,因为它离原始字符串只有4个字符(删除了2个字符)。而您认为C更接近,因为它同时包含棕色和红色。但是,它将具有更大的编辑距离。

有一种称为Levenshtein距离的算法,用于测量两个输入之间的编辑距离。

是该算法的工具。

  1. 将选项A的等级评定为15。
  2. 将选项B的等级评定为6。
  3. 将选项C的距离定为9。

编辑:对不起,我一直在levenshtein工具中混合字符串。更新以更正答案。


2
好吧,我想那是真的。我来看一下。我个人不在乎它与目标有多近,只要它很近就可以了。无需完美;)分给你,直到我可以验证你的答案:)结果
Freesnöw

18

Lua实现,后代:

function levenshtein_distance(str1, str2)
    local len1, len2 = #str1, #str2
    local char1, char2, distance = {}, {}, {}
    str1:gsub('.', function (c) table.insert(char1, c) end)
    str2:gsub('.', function (c) table.insert(char2, c) end)
    for i = 0, len1 do distance[i] = {} end
    for i = 0, len1 do distance[i][0] = i end
    for i = 0, len2 do distance[0][i] = i end
    for i = 1, len1 do
        for j = 1, len2 do
            distance[i][j] = math.min(
                distance[i-1][j  ] + 1,
                distance[i  ][j-1] + 1,
                distance[i-1][j-1] + (char1[i] == char2[j] and 0 or 1)
                )
        end
    end
    return distance[len1][len2]
end

14

您可能对此博客帖子感兴趣。

http://seatgeek.com/blog/dev/fuzzywuzzy-fuzzy-string-matching-in-python

Fuzzywuzzy是一个Python库,可提供简单的距离度量(例如Levenshtein距离)进行字符串匹配。它建立在标准库中的difflib之上,并将在可用的情况下利用C实现Python-levenshtein。

http://pypi.python.org/pypi/python-Levenshtein/


对于其他阅读本文的人,Fuzzywuzzy实际上在Alain的精彩文章中实现了许多想法。如果您实际上想使用其中一些想法,那么它是一个很好的起点。
格雷戈里·阿雷纽斯


2

如果您是在搜索引擎或针对数据库的前端环境中执行此操作,则可以考虑使用带有ComplexPhraseQueryParser插件的Apache Solr之类的工具。通过这种组合,您可以根据由Levenshtein距离确定的相关性对字符串索引进行搜索。

当传入的查询可能有一个或多个错别字时,我们一直在将它用于大量的艺术家和歌曲名称,并且效果很好(考虑到成千上万个字符串中的集合,效果非常好)。

此外,借助Solr,您可以通过JSON根据需要搜索索引,因此您无需在正在寻找的不同语言之间重新发明解决方案。


1

对于这类算法,非常非常好的资源是Simmetrics:http : //sourceforge.net/projects/simmetrics/

不幸的是,包含很多文档的令人敬畏的网站已经消失了:(如果再次出现,它的先前地址是这样的:http : //www.dcs.shef.ac.uk/~sam/simmetrics.html

Voila(由“ Wayback Machine”提供):http ://web.archive.org/web/20081230184321/http: //www.dcs.shef.ac.uk/~sam/simmetrics.html

您可以研究代码源,有数十种用于这些比较的算法,每种算法都有不同的权衡。这些实现使用Java。


1

要以高效的方式查询大量文本,可以使用“编辑距离/前缀编辑距离”的概念。

编辑距离ED(x,y):从项x到项y的最小转换数

但是在每个术语和查询文本之间计算ED会占用大量资源和时间。因此,我们可以使用称为Qgram Index的技术提取可能的匹配项,而不是先为每个项计算ED。然后对这些选定的术语应用ED计算。

Qgram索引技术的一个优点是它支持模糊搜索。

适应QGram索引的一种可能方法是使用Qgrams建立反向索引。我们在其中存储了由特定Qgram组成的所有单词,位于该Qgram之下(您可以为每个字符串使用唯一的ID,而不是存储完整的字符串)。您可以为此使用Java中的Tree Map数据结构。以下是有关术语存储的一个小例子

上校:col mbia,col ombo,gan col a,ta col ama

然后在查询时,我们计算查询文本和可用术语之间的常见Qgram数。

Example: x = HILLARY, y = HILARI(query term)
Qgrams
$$HILLARY$$ -> $$H, $HI, HIL, ILL, LLA, LAR, ARY, RY$, Y$$
$$HILARI$$ -> $$H, $HI, HIL, ILA, LAR, ARI, RI$, I$$
number of q-grams in common = 4

共同的q克数= 4。

对于具有大量常见Qgram的术语,我们根据查询术语计算ED / PED,然后将该术语建议给最终用户。

您可以在以下项目中找到此理论的实现(请参见“ QGramIndex.java”)。随意问任何问题。https://github.com/Bhashitha-Gamage/City_Search

要了解有关“编辑距离”和“前缀编辑距离Qgram索引”的更多信息,请观看Hannah Bast博士的以下视频https://www.youtube.com/embed/6pUg2wmGJRo(课程从20:06开始)


1

如果输入数据太大(例如数百万个字符串),则很难实现此问题。我使用弹性搜索来解决这个问题。

快速入门:https//www.elastic.co/guide/zh-CN/elasticsearch/client/net-api/6.x/elasticsearch-net.html

只需将所有输入数据插入数据库,您便可以基于任何编辑距离快速搜索任何字符串。这是一个C#代码段,它将为您提供按编辑距离(从小到大)排序的结果列表

var res = client.Search<ClassName>(s => s
    .Query(q => q
    .Match(m => m
        .Field(f => f.VariableName)
        .Query("SAMPLE QUERY")
        .Fuzziness(Fuzziness.EditDistance(5))
    )
));

您正在使用哪个库?需要更多信息才能对此有所帮助。
投注

0

在这里,您可以使用golang POC来计算给定单词之间的距离。您可以调整minDistancedifference用于其他示波器。

游乐场:https : //play.golang.org/p/NtrBzLdC3rE

package main

import (
    "errors"
    "fmt"
    "log"
    "math"
    "strings"
)

var data string = `THE RED COW JUMPED OVER THE GREEN CHICKEN-THE RED COW JUMPED OVER THE RED COW-THE RED FOX JUMPED OVER THE BROWN COW`

const minDistance float64 = 2
const difference float64 = 1

type word struct {
    data    string
    letters map[rune]int
}

type words struct {
    words []word
}

// Print prettify the data present in word
func (w word) Print() {
    var (
        lenght int
        c      int
        i      int
        key    rune
    )
    fmt.Printf("Data: %s\n", w.data)
    lenght = len(w.letters) - 1
    c = 0
    for key, i = range w.letters {
        fmt.Printf("%s:%d", string(key), i)
        if c != lenght {
            fmt.Printf(" | ")
        }
        c++
    }
    fmt.Printf("\n")
}

func (ws words) fuzzySearch(data string) ([]word, error) {
    var (
        w      word
        err    error
        founds []word
    )
    w, err = initWord(data)
    if err != nil {
        log.Printf("Errors: %s\n", err.Error())
        return nil, err
    }
    // Iterating all the words
    for i := range ws.words {
        letters := ws.words[i].letters
        //
        var similar float64 = 0
        // Iterating the letters of the input data
        for key := range w.letters {
            if val, ok := letters[key]; ok {
                if math.Abs(float64(val-w.letters[key])) <= minDistance {
                    similar += float64(val)
                }
            }
        }

        lenSimilarity := math.Abs(similar - float64(len(data)-strings.Count(data, " ")))
        log.Printf("Comparing %s with %s i've found %f similar letter, with weight %f", data, ws.words[i].data, similar, lenSimilarity)
        if lenSimilarity <= difference {
            founds = append(founds, ws.words[i])
        }
    }

    if len(founds) == 0 {
        return nil, errors.New("no similar found for data: " + data)
    }

    return founds, nil
}

func initWords(data []string) []word {
    var (
        err   error
        words []word
        word  word
    )
    for i := range data {
        word, err = initWord(data[i])
        if err != nil {
            log.Printf("Error in index [%d] for data: %s", i, data[i])
        } else {
            words = append(words, word)
        }
    }
    return words

}

func initWord(data string) (word, error) {
    var word word

    word.data = data
    word.letters = make(map[rune]int)
    for _, r := range data {
        if r != 32 { // avoid to save the whitespace
            word.letters[r]++
        }

    }
    return word, nil
}
func main() {
    var ws words
    words := initWords(strings.Split(data, "-"))
    for i := range words {
        words[i].Print()
    }
    ws.words = words

    solution, _ := ws.fuzzySearch("THE BROWN FOX JUMPED OVER THE RED COW")
    fmt.Println("Possible solutions: ", solution)

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.