Levenshtein Distance:从矩阵推断编辑操作

问题描述

|| 我用C ++编写了Levenshtein算法 如果我输入: 字符串s:民主人士 字符串t:共和党 我得到矩阵D填满的内容,并且可以在D [10] [8] = 8中读取操作次数(Levenshtein距离) 除了填充的矩阵外,我还想构建最佳解决方案。如何看待这个解决方案?我没主意 请只写信给我这个例子。     

解决方法

问题是 给定由Levenshtein算法生成的矩阵,如何找到“最佳解”? 即,我们如何找到字符串操作的精确顺序:插入,删除和替换[单个字母],这是将\'的字符串\'转换为\'t字符串\'所必需的? 首先,应该指出的是,在许多情况下,存在几种最优解。尽管Levenshtein算法提供的操作次数最少(在民主人士/共和党的例子中为8个),但有很多序列(8个操作)可以产生这种转换。 通过“解码” Levenshtein矩阵,可以枚举所有这样的最优序列。 总体思路是,最优解都从左上角到右下角(或沿另一个方向)遵循“路径”,从而该路径上的矩阵像元值保持不变或增加一个(或从相反方向减少1),从0开始,到针对所讨论的字符串的最佳操作次数结束(0到8个民主人士/共和党的情况)。当需要进行操作时,数字增加,当字符串中相应位置的字母相同时,数字保持不变。 产生这样的路径的算法很容易产生(产生所有可能的路径要稍微复杂一些),并且从这样的路径推论出运算的顺序。 此路径查找算法应从右下角开始,然后向后进行。之所以采用这种方法,是因为我们知道,要成为最佳解决方案,它必须在此角处结束,并且要在此角处结束,它必须来自三个单元格之一,即紧靠其左侧,紧靠上方它或立即对角线。通过在这三个像元中选择一个像元,该一个像元满足“相同值或减少一个”的要求,我们可以在最佳路径之一上有效地选择一个像元。通过重复该操作直到我们到达左上角(或者实际上直到到达值为0的单元格),我们才能有效地在最佳路径上回溯。 与民主党的插图-共和党的例子 还应注意,可以用以下两种方式之一构建矩阵:水平或垂直使用“民主人士”。这既不会改变Levenshtein距离的计算,也不会改变所需的操作列表。它只会改变我们解释矩阵的方式,例如在\“ path \”上水平移动要么意味着插入一个字符(从t字符串),要么删除一个字符(从s字符串),这取决于是否是'string s \'在矩阵中为“水平”或“垂直”。 我将使用以下矩阵。因此,约定是(仅沿从左到右和/或从上到下的方向) 横向移动是插入来自“不是字符串”的字母 垂直移动是从\'的字符串\'中删除字母 对角移动是: 无操作(两个位置的字母都相同);数字不变 替补(在各个位置的字母是不同的);这个数字增加一。 s = \“民主\”,t = \“共和党\”的Levenshtein矩阵
      r  e  p  u  b  l  i  c  a  n
   0  1  2  3  4  5  6  7  8  9  10
d  1  1  2  3  4  5  6  7  8  9  10
e  2  2  1  2  3  4  5  6  7  8  9
m  3  3  2  2  3  4  5  6  7  8  9
o  4  4  3  3  3  4  5  6  7  8  9
c  5  5  4  4  4  4  5  6  6  7  8
r  6  5  5  5  5  5  5  6  7  7  8
a  7  6  6  6  6  6  6  6  7  7  8
t  8  7  7  7  7  7  7  7  7  8  8
下面粗略介绍了我用来在几种可能的最佳路径中选择一条路径的任意方法:
Starting at the bottom-rightmost cell,and working our way backward toward 
the top left.

For each \"backward\" step,consider the 3 cells directly adjacent to the current
cell   (in the left,top or left+top directions)

   if the value in the diagonal cell (going up+left) is smaller or equal to the
      values found in the other two cells
   AND 
      if this is same or 1 minus the value of the current cell 
   then  \"take the diagonal cell\"
         if the value of the diagonal cell is one less than the current cell:
            Add a SUBSTITUTION operation (from the letters corresponding to
            the _current_ cell)
         otherwise: do not add an operation this was a no-operation.

   elseif the value in the cell to the left is smaller of equal to the value of
       the of the cell above current cell
   AND 
       if this value is same or 1 minus the value of the current cell 
   then \"take the cell to left\",and
        add an INSERTION of the letter corresponding to the cell
   else
       take the cell above,add
       Add a DELETION operation of the letter in \'s string\'
遵循此非正式的伪代码,我们得到以下信息: 从右下角的“ n”,“ t”单元格开始。 选择[对角线] \“ a \”,\“ a \”单元格作为下一个目标,因为它小于其他两个(并满足相同或-1条件)。 请注意,新单元格比当前单元格少一个 因此,第8步将\“ t \”替换为\“ n \”:democra N 继续\“ a \”,\“ a \”单元格,     选择[对角线] \“ c \”,\“ r \”单元格作为下一个目的地...     请注意,新单元格的值与当前单元格==>的值相同,无需进行任何操作。 继续使用\“ c \”,\“ r \”单元格,    选择[对角线] \“ i \”,\“ c \”单元格作为下一个目的地...    请注意,新单元格比当前单元格少一个      因此,第7步将\“ r \”替换为\“ c \”:democ C an 继续\“ i \”,\“ c \”单元格,    选择[对角线] \“ l \”,\“ o \”单元格作为下一个目的地...    请注意,新单元格比当前单元格少一个      因此第6步将\“ c \”替换为\“ i \”:演示我可以 继续\“ l \”,\“ o \”单元格,    选择[对角线] \“ b \”,\“ m \”单元格作为下一个目的地...    请注意,新单元格比当前单元格少一个      因此第5步将\“ o \”替换为\“ l \”:dem L ican 继续\“ b \”,\“ m \”单元格,    选择[对角线] \“ u \”,\“ e \”单元格作为下一个目的地...    请注意,新单元格比当前单元格少一个      因此第4步将\“ m \”替换为\“ b \”:de B lican 继续使用\“ u \”,\“ e \”单元格,    请注意,\“对角\”单元格不合格,因为\“ left \”单元格小于它。    选择[left] \“ p \”,\“ e \”单元格作为下一个目的地...         因此第3步在\“ e \”之后插入\“ u \”:de U blican 继续使用\“ p \”,\“ e \”单元格,    同样,“对角线”单元格不符合条件    选择[left] \“ e \”,\“ e \”单元格作为下一个目的地...         因此,步骤2是在\“ e \”之后插入\“ p \”:de Publican 继续使用\“ e \”,\“ e \”单元格,    选择[对角线] \“ r \”,\“ d \”单元格作为下一个目的地...    请注意,新单元格的值与当前单元格==>的值相同,无需进行任何操作。 继续\“ r \”,\“ d \”单元格,    选择[对角线] \“开始\”单元格作为下一个目标...    请注意,新单元格比当前单元格少一个      因此第1步将\“ d \”替换为\“ r \”:R epublican 您已经到达一个值为0的单元格:您的工作已经完成!     ,自从我玩了一段时间以来,但是在我看来矩阵应该看起来像这样:
. . r e p u b l i c a n
. 0 1 2 3 4 5 6 7 8 9 10
d 1 1 2 3 4 5 6 7 8 9 10
e 2 2 1 2 3 4 5 6 7 8 9
m 3 3 2 2 3 4 5 6 7 8 9
o 4 4 3 3 3 4 5 6 7 8 9
c 5 5 4 4 4 4 5 6 7 8 9
r 6 5 5 5 5 5 5 6 7 8 9
a 7 6 6 6 6 6 6 6 7 7 8
t 8 7 7 7 7 7 7 7 7 7 8
不过不要认为这是理所当然的。     ,这是基于mjv的答案的VBA算法。 (解释得很好,但缺少一些案例)。
    Sub TU_Levenshtein()

        Call Levenshtein(\"democrat\",\"republican\")

        Call Levenshtein(\"ooo\",\"u\") 

        Call Levenshtein(\"ceci est un test\",\"ceci n\'est pas un test\")

    End Sub

    Sub Levenshtein(ByVal string1 As String,ByVal string2 As String)

        \' Fill Matrix Levenshtein (-> array \'Distance\')

        Dim i As Long,j As Long
        Dim string1_length As Long
        Dim string2_length As Long
        Dim distance() As Long

        string1_length = Len(string1)
        string2_length = Len(string2)
        ReDim distance(string1_length,string2_length)

        For i = 0 To string1_length
            distance(i,0) = i
        Next

        For j = 0 To string2_length
            distance(0,j) = j
        Next

        For i = 1 To string1_length
            For j = 1 To string2_length
                If Asc(Mid$(string1,i,1)) = Asc(Mid$(string2,j,1)) Then
                    distance(i,j) = distance(i - 1,j - 1)
                Else
                    distance(i,j) = Application.WorksheetFunction.min _
                    (distance(i - 1,j) + 1,_
                     distance(i,j - 1) + 1,_
                     distance(i - 1,j - 1) + 1)
                End If
            Next
        Next

        LevenshteinDistance = distance(string1_length,string2_length) \' for information only

        \' Write Matrix on VBA sheets (only for visuation,not used in calculus)

        Cells.Clear

        For i = 1 To UBound(distance,1)
            Cells(i + 2,1).Value = Mid(string1,1)
        Next i

        For i = 1 To UBound(distance,2)
            Cells(1,i + 2).Value = Mid(string2,1)
        Next i

        For i = 0 To UBound(distance,1)

            For j = 0 To UBound(distance,2)

                Cells(i + 2,j + 2) = distance(i,j)

            Next j

        Next i

        \' One solution

        current_posx = UBound(distance,1)
        current_posy = UBound(distance,2)

        Do

            cc = distance(current_posx,current_posy)

            Cells(current_posx + 1,current_posy + 1).Interior.Color = vbYellow \' visualisation again

            \' Manage border case

            If current_posy - 1 < 0 Then

                MsgBox (\"deletion. \" & Mid(string1,current_posx,1))

                current_posx = current_posx - 1
                current_posy = current_posy

                GoTo suivant

            End If

            If current_posx - 1 < 0 Then

                MsgBox (\"insertion. \" & Mid(string2,current_posy,1))

                current_posx = current_posx
                current_posy = current_posy - 1

                GoTo suivant

            End If

            \' Middle cases

            cc_L = distance(current_posx,current_posy - 1)
            cc_U = distance(current_posx - 1,current_posy)
            cc_D = distance(current_posx - 1,current_posy - 1)

            If (cc_D <= cc_L And cc_D <= cc_U) And (cc_D = cc - 1 Or cc_D = cc) Then

                If (cc_D = cc - 1) Then

                    MsgBox \"substitution. \" & Mid(string1,1) & \" by \" & Mid(string2,1)

                    current_posx = current_posx - 1
                    current_posy = current_posy - 1

                    GoTo suivant

                Else

                    MsgBox \"no operation\"

                    current_posx = current_posx - 1
                    current_posy = current_posy - 1

                    GoTo suivant

                End If

            ElseIf cc_L <= cc_D And cc_L = cc - 1 Then

                MsgBox (\"insertion. \" & Mid(string2,1))

                current_posx = current_posx
                current_posy = current_posy - 1

                GoTo suivant

            Else

                MsgBox (\"deletion.\" & Mid(string1,1))

                current_posx = current_posx
                current_posy = current_posy - 1

                GoTo suivant

            End If

    suivant:

        Loop While Not (current_posx = 0 And current_posy = 0)

    End Sub
    ,我最近使用Levenshtein距离算法的矩阵做了一些工作。我需要产生将一个列表转换为另一个列表的操作。 (这也适用于字符串。) 以下(vows)测试是否显示您正在寻找的功能?
,\"lev - complex 2\"
  : { topic
    : lev.diff([13,6,5,1,8,9,2,15,12,7,11],[9,13,11]),\"check actions\"
    : function(topic) { assert.deepEqual(topic,[{ op: \'delete\',pos: 9,val: 7 },{ op: \'delete\',pos: 5,val: 9 },{ op: \'insert\',pos: 0,]); }
    },\"lev - complex 3\"
  : { topic
    : lev.diff([9,[13,val: 7 }
                                                ]); }
    },\"lev - complex 4\"
  : { topic
    : lev.diff([9,11,16],17]),{ op: \'replace\',pos: 11,val: 17 }
                                                ]); }
    }
    ,这是一些Matlab代码,您认为这正确吗?似乎给出正确的结果:)
clear all

s = char(\'democrat\');
t = char(\'republican\');

% Edit Matrix
m=length(s);
n=length(t);
mat=zeros(m+1,n+1);
for i=1:1:m
    mat(i+1,1)=i;
end
for j=1:1:n
    mat(1,j+1)=j;
end
for i=1:m
    for j=1:n
        if (s(i) == t(j))
            mat(i+1,j+1)=mat(i,j);
        else
            mat(i+1,j+1)=1+min(min(mat(i+1,j),mat(i,j+1)),j));
        end
    end
end

% Edit Sequence
s = char(\'democrat\');
t = char(\'republican\');
i = m+1;
j = n+1;
display([s \' --> \' t])
while(i ~= 1 && j ~= 1)
    temp = min(min(mat(i-1,j-1),j-1)),mat(i-1,j));
    if(mat(i-1,j) == temp)
        i = i - 1;
        t = [t(1:j-1) s(i) t(j:end)];
        disp(strcat([\'iinsertion: i=\' int2str(i) \',j=\' int2str(j) \' ; \' s \' --> \' t]))
    elseif(mat(i-1,j-1) == temp)
        if(mat(i-1,j-1) == mat(i,j))
            i = i - 1;
            j = j - 1;
            disp(strcat([\'uunchanged: i=\' int2str(i) \',j=\' int2str(j) \' ; \' s \' --> \' t]))
        else
            i = i - 1;
            j = j - 1;
            t(j) = s(i);
            disp(strcat([\'substition: i=\' int2str(i) \',j=\' int2str(j) \' ; \' s \' --> \' t]))
        end
    elseif(mat(i,j-1) == temp)
        j = j - 1;
        t(j) = [];
        disp(strcat([\'dddeletion: i=\' int2str(i) \',j=\' int2str(j) \' ; \' s \' --> \' t]))
    end
end
    ,JackIsJack的C#实现会进行一些更改: 操作以“前进”顺序输出(JackIsJack输出以相反顺序); 原始答案中的最后一个'else \'子句工作不正确(看起来像复制粘贴错误)。 控制台应用程序代码:
class Program
{
    static void Main(string[] args)
    {
        Levenshtein(\"1\",\"1234567890\");
        Levenshtein( \"1234567890\",\"1\");

        Levenshtein(\"kitten\",\"mittens\");
        Levenshtein(\"mittens\",\"kitten\");
        Levenshtein(\"kitten\",\"sitting\");
        Levenshtein(\"sitting\",\"kitten\");
        Levenshtein(\"1234567890\",\"12356790\");
        Levenshtein(\"12356790\",\"1234567890\");
        Levenshtein(\"ceci est un test\",\"ceci n\'est pas un test\");
        Levenshtein(\"ceci n\'est pas un test\",\"ceci est un test\");
    }

    static void Levenshtein(string string1,string string2)
    {
        Console.WriteLine(\"Levenstein \'\" + string1 + \"\' => \'\" + string2 + \"\'\");

        var string1_length = string1.Length;
        var string2_length = string2.Length;

        int[,] distance = new int[string1_length + 1,string2_length + 1];

        for (int i = 0; i <= string1_length; i++)
        {
            distance[i,0] = i;
        }


        for (int j = 0; j <= string2_length; j++)
        {
            distance[0,j] = j;
        }


        for (int i = 1; i <= string1_length; i++)
        {
            for (int j = 1; j <= string2_length; j++)
            {
                if (string1[i - 1] == string2[j - 1])
                {
                    distance[i,j] = distance[i - 1,j - 1];
                }
                else
                {
                    distance[i,j] = Math.Min(distance[i - 1,j] + 1,Math.Min(
                       distance[i,j - 1] + 1,distance[i - 1,j - 1] + 1));
                }

            }
        }


        var LevenshteinDistance = distance[string1_length,string2_length];// for information only
        Console.WriteLine($\"Levernstein distance: {LevenshteinDistance}\");

        // List of operations
        var current_posx = string1_length;
        var current_posy = string2_length;

        var stack = new Stack<string>(); // for outputting messages in forward direction

        while (current_posx != 0 || current_posy != 0)
        {
            var cc = distance[current_posx,current_posy];
            // edge cases
            if (current_posy - 1 < 0)
            {
                stack.Push(\"Delete \'\" + string1[current_posx - 1] + \"\'\");
                current_posx--;
                continue;
            }

            if (current_posx - 1 < 0)
            {
                stack.Push(\"Insert \'\" + string2[current_posy - 1] + \"\'\");
                current_posy--;
                continue;
            }

            // Middle cases
            var cc_L = distance[current_posx,current_posy - 1];
            var cc_U = distance[current_posx - 1,current_posy];
            var cc_D = distance[current_posx - 1,current_posy - 1];

            if ((cc_D <= cc_L && cc_D <= cc_U) && (cc_D == cc - 1 || cc_D == cc))
            {
                if (cc_D == cc - 1)
                {
                    stack.Push(\"Substitute \'\" + string1[current_posx - 1] + \"\' by \'\" + string2[current_posy - 1] + \"\'\");
                    current_posx--;
                    current_posy--;
                }
                else
                {
                    stack.Push(\"Keep \'\" + string1[current_posx - 1] + \"\'\");
                    current_posx--;
                    current_posy--;
                }
            }
            else if (cc_L <= cc_D && cc_L == cc - 1)
            {
                stack.Push(\"Insert \'\" + string2[current_posy - 1] + \"\'\");
                current_posy--;                   
            }
            else
            {
                stack.Push(\"Delete \'\" + string1[current_posx - 1]+\"\'\");
                current_posx--;                   
            }
        }

        while(stack.Count > 0)
        {
            Console.WriteLine(stack.Pop());
        }
    }
}
    

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...