Расстояние Левенштейна с обратной трассировкой в PHP
Я пытаюсь выровнять строки в PHP с использованием алгоритма расстояния Левенштейна. Проблема в том, что мой обратный код отслеживания не работает должным образом для всех случаев. Например, когда второй массив вставил строки в начале. Тогда обратная трассировка будет идти только до я = 0.
Как правильно выполнить обратную трассировку для расстояния Левенштейна?
Расстояние Левенштейна, $s и $t - массивы строк (строк)
function match_rows($s, $t)
{
$m = count($s);
$n = count($t);
for($i = 0; $i <= $m; $i++) $d[$i][0] = $i;
for($j = 0; $j <= $n; $j++) $d[0][$j] = $j;
for($i = 1; $i <= $m; $i++)
{
for($j = 1; $j <= $n; $j++)
{
if($s[$i-1] == $t[$j-1])
{
$d[$i][$j] = $d[$i-1][$j-1];
}
else
{
$d[$i][$j] = min($d[$i-1][$j], $d[$i][$j-1], $d[$i-1][$j-1]) + 1;
}
}
}
// backtrace
$i = $m;
$j = $n;
while($i > 0 && $j > 0)
{
$min = min($d[$i-1][$j], $d[$i][$j-1], $d[$i-1][$j-1]);
switch($min)
{
// equal or substitution
case($d[$i-1][$j-1]):
if($d[$i][$j] != $d[$i-1][$j-1])
{
// substitution
$sub['i'][] = $i;
$sub['j'][] = $j;
}
$i = $i - 1;
$j = $j - 1;
break;
// insertion
case($d[$i][$j-1]):
$ins[] = $j;
$j = $j - 1;
break;
// deletion
case($d[$i-1][$j]):
$del[] = $i;
$i = $i - 1;
break;
}
}
Ответы
Ответ 1
Это не должно быть ничтожным, но чтобы помочь вам найти нужные ответы (и улучшить вашу реализацию).
Алгоритм, который вы используете, - это алгоритм Вагнера-Фишера, а не алгоритм Левенштейна. Кроме того, расстояние Левенштейна не используется для выравнивания строк. Это строго измерение расстояния.
Существует два типа выравнивания: глобальный и локальный. Глобальное выравнивание используется для минимизации расстояния между двумя целыми строками. Пример: глобальное выравнивание "RACE" на "REACH", вы получаете "RxACx". X - промежутки.
В общем, это алгоритм Needleman-Wunsch, который очень похож на алгоритм Вагнера-Фишера. Локальное выравнивание находит подстроку в длинной строке и минимизирует разницу между короткой строкой и подстрокой длинной строки.
Пример: локальный выровнять "BELL" на "UMBRELLA", и вы получите "BxELL", выровненный по "BRELL". Это алгоритм Смита-Уотермана, который, опять же, очень похож на алгоритм Вагнера-Фишера.
Я надеюсь, что это поможет вам лучше определить точный тип выравнивания, который вы хотите.
Ответ 2
Я думаю, что ваша ошибка - это именно то, что вы говорите в своем вопросе, что это: вы останавливаетесь, как только i==0
, вместо того, чтобы идти до i==0 && j==0
. Просто замените это условие:
while($i > 0 && $j > 0)
с
while ($i > 0 || $j > 0)
и вы на полпути к вашему решению. Сложный бит заключается в том, что если $i==0
, то неверно использовать индекс массива $i-1
в теле цикла. Таким образом, вам также придется изменить тело цикла на нечто большее, чем
while ($i || $j) {
$min = $d[$i][$j]; // or INT_MAX or something
if ($i && $j && $min > $d[$i-1][$j-1]) {
$newi = $i-1;
$newj = $j-1;
$min = $d[$newi][$newj];
}
if ($i && $min > $d[$i-1][$j]) {
$newi = $i-1;
$newj = $j;
$min = $d[$newi][$newj];
}
if ($j && $min > $d[$i][$j-1]) {
$newi = $i;
$newj = $j-1;
$min = $d[$newi][$newj];
}
// What sort of transformation is this?
if ($newi == $i && $newj == $j) {
assert(false); // should never happen
} else if ($newi == $i) {
// insertion
$ins[] = $j;
} else if ($newj == $j) {
// deletion
$del[] = $i;
} else if ($d[$i][$j] != $d[$newi][$newj]) {
// substitution
$sub['i'][] = $i;
$sub['j'][] = $j;
} else {
// identity
}
assert($newi >= 0); assert($newj >= 0);
$i = $newi;
$j = $newj;
}