Расстояние Левенштейна: вывод операций редактирования из матрицы

Я написал алгоритм Левенштейна на C++.

Если я ввожу:
строка s: демократ
строка t: республиканец

Я получаю заполненную матрицу D, и количество операций (расстояние Левенштейна) можно прочитать в D[10][8] = 8
Помимо заполненной матрицы я хочу построить оптимальное решение. Как должно выглядеть это решение? У меня нет идеи.
Пожалуйста, только напишите мне, КАК ДОЛЖЕН ВЫГЛЯДИТЬ этот пример.


person borebardha    schedule 01.05.2011    source источник
comment
На странице Википедии есть пример (en.wikipedia.org/wiki/Levenshtein_distance). Это помогает?   -  person Oliver Charlesworth    schedule 01.05.2011
comment
Везде только заполненная матрица. НО мне нужно для решения, которое можно построить из этой матрицы. Но как будет выглядеть решение???   -  person borebardha    schedule 01.05.2011
comment
@borebardha: Вы читали статью? Или псевдокод? В нем говорится, что ответом является нижний правый элемент матрицы.   -  person Oliver Charlesworth    schedule 01.05.2011
comment
@borebardha: Что ты имеешь в виду, что печатать? Расстояние Левенштейна задается правым нижним элементом матрицы. Если у @Matthieu все правильно, то ответ 8.   -  person Oliver Charlesworth    schedule 01.05.2011
comment
Оли, 8 означает количество операций (ins, del, subs). Что это за 8 операций. Как вывести?   -  person borebardha    schedule 01.05.2011


Ответы (7)


Вопрос в следующем
Как можно найти "оптимальное решение", учитывая матрицу, созданную алгоритмом Левенштейна?
т.е. как мы можем найти точную последовательность строковых операций: вставки, удаления и замены [одной буквы], необходимые для преобразования «строки» в «t-строку»?

Во-первых, следует отметить, что во многих случаях существует НЕСКОЛЬКО оптимальных решений.
В то время как алгоритм Левенштейна обеспечивает минимальное количество операций (8 в демократическом/республиканском примере ) есть много последовательностей (из 8 операций), которые могут произвести это преобразование.

"Расшифровав" матрицу Левенштейна, можно перечислить ВСЕ такие оптимальные последовательности.
Общая идея состоит в том, что все оптимальные решения следуют "пути", из верхнего левого угла в нижний правый угол (или в другую сторону), при этом значения ячеек матрицы на этом пути либо остаются прежними, либо увеличиваются на единицу (или уменьшаются на единицу в обратном направлении), начиная с 0 и заканчивая оптимальным числом операций для рассматриваемых строк (от 0 до 8 в демократическом/республиканском случае). Число увеличивается, когда операция необходима, оно остается прежним, когда буквы в соответствующих позициях в строках совпадают.

Легко составить алгоритм, который выдает такой путь (немного сложнее создать все возможные пути), и из такого пути вывести последовательность операций.

Этот алгоритм поиска пути должен начинаться в правом нижнем углу и работать в обратном направлении. Причина такого подхода в том, что мы точно знаем, что для того, чтобы быть оптимальным решением, оно должно заканчиваться в этом углу, а чтобы заканчиваться в этом углу, оно должно исходить из одной из 3-х ячеек либо сразу слева от нее, либо сразу над ней. это или сразу по диагонали. Выбирая из этих трех ячеек ячейку, которая удовлетворяет нашему требованию «одинаковое значение или уменьшение на единицу», мы фактически выбираем ячейку на одном из оптимальных путей. Повторяя операцию до тех пор, пока мы не попадем в верхний левый угол (или даже пока мы не достигнем ячейки со значением 0), мы эффективно возвращаемся назад по оптимальному пути.

Иллюстрация с демократом - республиканский пример

Следует также отметить, что построить матрицу можно одним из двух способов: с «демократом» по горизонтали или по вертикали. Это не меняет ни вычисления расстояния Левенштейна, ни списка необходимых операций; это только меняет то, как мы интерпретируем матрицу, например, горизонтальное перемещение по «пути» означает либо вставку символа [из строки t], либо удаление символа [из строки s] в зависимости от того, является ли «строка s» «горизонтальной» или "вертикаль" в матрице.

Я буду использовать следующую матрицу. Таким образом, соглашения (только в направлении слева направо и/или сверху вниз)

  • перемещение по горизонтали — это ВСТАВКА буквы из 't string'
  • перемещение по вертикали - это УДАЛЕНИЕ буквы из "строки"
  • a diagonal move is either:
    • a no-operation (both letters at respective positions are the same); the number doesn't change
    • ЗАМЕНА (буквы на соответствующих позициях различны); число увеличивается на единицу.

Матрица Левенштейна для s = "демократ", t="республиканец"

      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": демо I может

Продолжайте с ячейкой "l", "o",
Выберите ячейку [диагональ] "b", "m" в качестве следующего пункта назначения...
Обратите внимание, что новая ячейка на единицу меньше, чем текущая ячейка
поэтому на шаге 5 замените "o" на "l": dem L ican

Продолжите с ячейкой "b", "m",
Выберите ячейку [диагональ] "u", "e" в качестве следующего пункта назначения...
Обратите внимание, что новая ячейка на единицу меньше, чем текущая ячейка
поэтому на шаге 4 замените "m" на "b": de B lican

Продолжайте с ячейкой "u", "e".
Обратите внимание, что "диагональная" ячейка не подходит, потому что "левая" ячейка меньше ее. Выберите ячейку [слева] "p", "e" в качестве следующего пункта назначения...
поэтому шаг 3 вставляет "u" после "e": de U blican

Продолжайте с ячейкой "p", "e",
снова ячейка "диагональ" не подходит Выберите [левую] ячейку "e", "e" в качестве следующего пункта назначения...
поэтому шаг 2 вставляется "p" после "e": de P ublican

Продолжайте с ячейкой "e", "e",
Выберите ячейку [диагональ] "r", "d" в качестве следующего пункта назначения...
Обратите внимание, что новая ячейка имеет то же значение, что и текущая ячейка ==> операция не требуется.

Продолжайте с ячейкой "r", "d",
Выберите [диагональную] "начальную" ячейку в качестве следующего пункта назначения...
Обратите внимание, что новая ячейка на единицу меньше, чем текущая ячейка
, поэтому шаг 1 заменяет "d" на "r": R epublican

Вы попали в ячейку со значением 0: ваша работа выполнена!

person mjv    schedule 02.05.2011
comment
Это видео мне очень помогло. youtube.com/watch?v=We3YDTzNXEk - person MKatleast3; 18.10.2016
comment
Но как сделать все пути? - person Matías Guzmán Naranjo; 09.11.2019

Я уже несколько раз играл с ним, но мне кажется, что матрица должна выглядеть примерно так:

. . 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

Однако не принимайте это как должное.

person Matthieu M.    schedule 01.05.2011
comment
Спасибо. НО, кроме этой матрицы мне нужно построить решение. Как должно выглядеть решение? - person borebardha; 01.05.2011
comment
Это не имеет ничего общего с поиском результирующих операций. - person jhpratt; 16.11.2018

Вот алгоритм VBA, основанный на ответе mjv. (очень хорошо объяснено, но некоторые дела отсутствовали).

    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, i, 1)
        Next i

        For i = 1 To UBound(distance, 2)
            Cells(1, i + 2).Value = Mid(string2, i, 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, current_posx, 1) & " by " & Mid(string2, current_posy, 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, current_posy, 1))

                current_posx = current_posx
                current_posy = current_posy - 1

                GoTo suivant

            Else

                MsgBox ("deletion." & Mid(string1, current_posy, 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
person JackIsJack    schedule 18.05.2016

Алгоритм возврата для вывода ходов из матрицы, реализованный в python:

    def _backtrack_string(matrix, output_word):
    '''
    Iteratively backtrack DP matrix to get optimal set of moves

    Inputs: DP matrix (list:list:int),
            Input word (str),
            Output word (str),
            Start x position in DP matrix (int),
            Start y position in DP matrix (int)
    Output: Optimal path (list)
    '''

    i = len(matrix) - 1
    j = len(matrix[0]) - 1
    optimal_path = []
    while i > 0 and j > 0:
        diagonal = matrix[i-1][j-1]
        vertical = matrix[i-1][j]
        horizontal = matrix[i][j-1]
        current = matrix[i][j]
        if diagonal <= vertical and diagonal <= horizontal and (diagonal <= current):
            i = i - 1
            j = j - 1
            if diagonal == current - 1:
                optimal_path.append("Replace " + str(j) + ", " + str(output_word[j]) )
            elif horizontal <= vertical and horizontal <= current:
                j = j - 1
                optimal_path.append("Insert " + str(j) + ", " + str(output_word[j]))
            elif vertical <= horizontal and vertical <= current:
                i = i - 1
                optimal_path.append("Delete " + str(i))
        elif horizontal <= vertical and horizontal <= current:
            j = j - 1
            optimal_path.append("Insert " + str(j) + ", " + str(output_word[j]))
        else:
            i = i - 1
            optimal_path.append("Delete " + str(i))

    return reversed(optimal_path)

Вывод, который я получаю, когда запускаю алгоритм с исходным словом «РАБОТА» и желаемым словом «КОНСТАНТИН», следующий:

    Insert 0, C
    Replace 2, N
    Replace 3, S
    Replace 4, T
    Insert 6, N
    Replace 10, E

       ""  C  O  N  S  T  A  N  T  I  N   E

    "" [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] 
              <--                              Insert 0, C
    O  [1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9,  10]
                \                              Replace 2, N
    P  [2, 2, 2, 2, 3, 4, 5, 6, 7, 8, 9,  10]
                   \                           Replace 3, S
    E  [3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9,  9]
                      \                        Replace 4, T
    R  [4, 4, 4, 4, 4, 4, 5, 6, 7, 8, 9,  10]  No move
                         \ <--                 Insert 6, N
    A  [5, 5, 5, 5, 5, 5, 4, 5, 6, 7, 8,  9]
                               \               No move
    T  [6, 6, 6, 6, 6, 5, 5, 5, 5, 6, 7,  8]
                                  \            No move
    I  [7, 7, 7, 7, 7, 6, 6, 6, 6, 5, 6,  7]
                                     \         No move
    N  [8, 8, 8, 7, 8, 7, 7, 6, 7, 6, 5,  6]
                                        \      Replace 10, E
    G  [9, 9, 9, 8, 8, 8, 8, 7, 7, 7, 6,  6]

Обратите внимание, что мне пришлось добавить дополнительные условия, если элемент по диагонали совпадает с текущим элементом. В зависимости от значений в вертикальной (вверху) и горизонтальной (влево) позициях может быть удаление или вставка. Мы получаем только операцию «нет операции» или «заменить», когда происходит следующее

# assume bottom right of a 2x2 matrix is the reference position 
# and has value v
# the following is the situation where we get a replace operation
    [v + 1 , v<]
    [  v<  , v]
# the following is the situation where we get a "no operation"
    [v , v<]
    [v<, v ] 

Я думаю, что именно здесь алгоритм, описанный в первом ответе, может сломаться. В приведенной выше матрице 2x2 могут быть другие схемы, когда ни одна из операций не является правильной. Пример, показанный с вводом «РАБОТАЕТ» и выводом «КОНСТАНТИН», нарушает алгоритм, если это не принято во внимание.

person ll--Tetrode--ll    schedule 28.01.2020

Недавно я немного поработал с матрицей алгоритма расстояния Левенштейна. Мне нужно было произвести операции, которые преобразовали бы один список в другой. (Это будет работать и для строк.)

Показывают ли следующие (обещания) тесты ту функциональность, которую вы ищете?

  , "lev - complex 2"
  : { topic
    : lev.diff([13, 6, 5, 1, 8, 9, 2, 15, 12, 7, 11], [9, 13, 6, 5, 1, 8, 2, 15, 12, 11])
    , "check actions"
    : function(topic) { assert.deepEqual(topic, [{ op: 'delete', pos: 9, val: 7 },
                                                 { op: 'delete', pos: 5, val: 9 },
                                                 { op: 'insert', pos: 0, val: 9 },
                                                ]); }
    }
  , "lev - complex 3"
  : { topic
    : lev.diff([9, 13, 6, 5, 1, 8, 2, 15, 12, 11], [13, 6, 5, 1, 8, 9, 2, 15, 12, 7, 11])
    , "check actions"
    : function(topic) { assert.deepEqual(topic, [{ op: 'delete', pos: 0, val: 9 },
                                                 { op: 'insert', pos: 5, val: 9 },
                                                 { op: 'insert', pos: 9, val: 7 }
                                                ]); }
    }
  , "lev - complex 4"
  : { topic
    : lev.diff([9, 13, 6, 5, 1, 8, 2, 15, 12, 11, 16], [13, 6, 5, 1, 8, 9, 2, 15, 12, 7, 11, 17])
    , "check actions"
    : function(topic) { assert.deepEqual(topic, [{ op: 'delete', pos: 0, val: 9 },
                                                 { op: 'insert', pos: 5, val: 9 },
                                                 { op: 'insert', pos: 9, val: 7 },
                                                 { op: 'replace', pos: 11, val: 17 }
                                                ]); }
    }
person fadedbee    schedule 21.06.2011

Вот какой-то код 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)),mat(i,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), mat(i,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
person user3083171    schedule 05.09.2017

С# реализация ответа JackIsJack с некоторыми изменениями:

  • Операции выводятся в прямом порядке (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());
        }
    }
}
person Artemix    schedule 14.06.2018