Поиск разницы между строками в Javascript

Я хотел бы сравнить две строки (A до и после) и точно определить, где и что изменилось между ними.

для любых изменений, я хочу знать:

  1. Начальная позиция изменения (включительно, начиная с 0)
  2. конечная позиция изменения (включительно, начиная с 0) относительно предыдущего текста
  3. "изменить"

предположим, что строки будут меняться только в одном месте в одно время (например, не "Bill" -> "Kiln").

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

  • если удаление, начальная и конечная позиции должны быть начальная и конечная позиции удаленного текста, соответственно
  • при замене начальная и конечная позиции должны быть начальными и конечными позициями" удаленного "текста, соответственно (изменение будет" добавлено" текст)
  • если вставка, начальная и конечная позиции должны быть одинаковыми; точка входа текста
  • если нет изменений, пусть начальная и конечная позиции остаются нулевыми, с пустым изменением

например:

"0123456789" -> "03456789"  
Start: 1, End: 2, Change: "" (deletion)

"03456789" -> "0123456789"  
Start: 1, End: 1, Change: "12" (insertion)

"Hello World!" -> "Hello Aliens!"  
Start: 6, End: 10, Change: "Aliens" (replacement)

"Hi" -> "Hi"  
Start: 0, End: 0, Change: "" (no change)

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

var OldText = "My edited string!";
var NewText = "My first string!";

var ChangeStart = 0;
var NewChangeEnd = 0;
var OldChangeEnd = 0;
console.log("Comparing start:");
for (var i = 0; i < NewText.length; i++) {
    console.log(i + ": " + NewText[i] + " -> " + OldText[i]);
    if (NewText[i] != OldText[i]) {
        ChangeStart = i;
        break;
    }
}
console.log("Comparing end:");
// "Addition"?
if (NewText.length > OldText.length) {
    for (var i = 1; i < NewText.length; i++) {
        console.log(i + "(N: " + (NewText.length - i) + " O: " + (OldText.length - i) + ": " + NewText.substring(NewText.length - i, NewText.length - i + 1) + " -> " + OldText.substring(OldText.length - i, OldText.length - i + 1));
        if (NewText.substring(NewText.length - i, NewText.length - i + 1) != OldText.substring(OldText.length - i, OldText.length - i + 1)) {
            NewChangeEnd = NewText.length - i;
            OldChangeEnd = OldText.length - i;
            break;
        }
    }
// "Deletion"?
} else if (NewText.length < OldText.length) {
    for (var i = 1; i < OldText.length; i++) {
        console.log(i + "(N: " + (NewText.length - i) + " O: " + (OldText.length - i) + ": " + NewText.substring(NewText.length - i, NewText.length - i + 1) + " -> " + OldText.substring(OldText.length - i, OldText.length - i + 1));
        if (NewText.substring(NewText.length - i, NewText.length - i + 1) != OldText.substring(OldText.length - i, OldText.length - i + 1)) {
            NewChangeEnd = NewText.length - i;
            OldChangeEnd = OldText.length - i;
            break;
        }
    }
// Same length...
} else {
    // Do something
}
console.log("Change start: " + ChangeStart);
console.log("NChange end : " + NewChangeEnd);
console.log("OChange end : " + OldChangeEnd);
console.log("Change: " + OldText.substring(ChangeStart, OldChangeEnd + 1));

как я скажите, произошла ли вставка, удаление или замена?


Я искал и придумал несколько другое похожие вопросы, но они, похоже, не помогают.

3 ответов


я прошел через ваш код, и ваша логика для сопоставления строки имеет смысл для меня. Он регистрирует ChangeStart, NewChangeEnd и OldChangeEnd правильно и алгоритм течет хорошо. Вы просто хотите знать, если вставка, удаление или замена. Вот как я это сделаю.

прежде всего, вам нужно убедиться, что после того, как вы получили первую точку mis-match т. е. ChangeStart когда вы затем пересекаете строки с конца индекс не должен пересекаться ChangeStart.

я приведу вам пример. Рассмотрим следующие строки:

 var NewText = "Hello Worllolds!";
 var OldText = "Hello Worlds!";

 ChangeStart -> 10 //Makes sense
 OldChangeEnd -> 8
 NewChangeEnd -> 11

 console.log("Change: " + NewText.substring(ChangeStart, NewChangeEnd + 1)); 
 //Ouputs "lo"

проблема в этом случае, когда он начинает соответствовать со спины, поток примерно так:

 Comparing end: 
  1(N: 12 O: 12: ! -> !) 
  2(N: 11 O: 11: s -> s) 
  3(N: 10 O: 10: d -> d)  -> You need to stop here!

 //Although there is not a mismatch, but we have reached ChangeStart and 
 //we have already established that characters from 0 -> ChangeStart-1 match
 //That is why it outputs "lo" instead of "lol"

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

 if (NewText.length > OldText.length) {
 for (var i = 1; i < NewText.length && ((OldText.length-i)>=ChangeStart); i++) {
  ...

    NewChangeEnd = NewText.length - i -1;
    OldChangeEnd = OldText.length - i -1;
  if(//Mismatch condition reached){
         //break..That code is fine.
    }
 }

это состояния -> (OldText.length-i)>=ChangeStart заботится об аномалии, которую я упомянул, и поэтому for цикл автоматически завершается, если это условие будет достигнуто. Однако, как я уже упоминал, могут быть ситуации, когда это условие достигается до того, как обнаруживается несоответствие, как я только что продемонстрировал. Поэтому вам нужно обновить значения NewChangeEnd и OldChangeEnd как 1 меньше, чем соответствуют значение. В случае неправильного соответствия значения сохраняются соответствующим образом.

вместо else -if мы могли бы просто обернуть эти два условия в ситуации, когда мы знаем NewText.length > OldText.length определенно не true т. е. это либо замена или удаление. Опять NewText.length > OldText.length также означает, что это может быть замена или вставка согласно вашим примерам, что имеет смысл. Так что else может быть что-то вроде:

else {
for (var i = 1; i < OldText.length && ((OldText.length-i)>=ChangeStart); i++) { 

    ...
    NewChangeEnd = NewText.length - i -1;
    OldChangeEnd = OldText.length - i -1;
  if(//Mismatch condition reached){
         //break..That code is fine.
    }
 }

если вы поняли незначительные изменения до сих пор, выявление конкретных случаев действительно просто:

  1. удаление условие -> ChangeStart > NewChangeEnd. Удалена строка из ChangeStart -> OldChangeEnd.

удаленный текст -> OldText.substring(ChangeStart, OldChangeEnd + 1);

  1. вставка условие -> ChangeStart > OldChangeEnd. Вставленная строка в ChangeStart.

вставить текст -> NewText.substring(ChangeStart, NewChangeEnd + 1);

  1. замена - если NewText != OldText и эти два условия являются не met, тогда это и замена.

текст в старой строке, которая была заменена -> OldText.substring(ChangeStart, OldChangeEnd + 1);

текст - > замены NewText.substring(ChangeStart, NewChangeEnd + 1);

начальная и конечная позиции в OldText что есть заменить ->ChangeStart -> OldChangeEnd

я создал jsfiddle включение изменений, которые я упомянул в вашем коде. Возможно, вы захотите это проверить. Надеюсь, это поможет тебе начать в правильном направлении.


У меня была аналогичная проблема и я решил ее следующим образом:

function diff(oldText, newText) {

  // Find the index at which the change began
  var s = 0;
  while(s < oldText.length && s < newText.length && oldText[s] == newText[s]) {
    s++;
  }

  // Find the index at which the change ended (relative to the end of the string)
  var e = 0;
  while(e < oldText.length &&
        e < newText.length &&
        oldText.length - e > s &&
        newText.length - e > s &&
        oldText[oldText.length - 1 - e] == newText[newText.length - 1 - e]) {
    e++;
  }

  // The change end of the new string (ne) and old string (oe)
  var ne = newText.length - e;
  var oe = oldText.length - e;

  // The number of chars removed and added
  var removed = oe - s;
  var added = ne - s;

  var type;
  switch(true) {
    case removed == 0 && added > 0:  // It's an 'add' if none were removed and at least 1 added
      type = 'add';
      break;
    case removed > 0 && added == 0:  // It's a 'remove' if none were added and at least one removed
      type = 'remove';
      break;
    case removed > 0 && added > 0:   // It's a replace if there were both added and removed characters
      type = 'replace';
      break;
    default:
      type = 'none';                 // Otherwise there was no change
      s = 0;
  }

  return { type: type, start: s, removed: removed, added: added };
}

обратите внимание,что это не решило мою фактическую проблему. Моя проблема заключалась в том, что у меня был редактор с абзацами, каждый из которых смоделирован с текстом и коллекцией надбавок, определенных с начальным и конечным индексом, например, жирным шрифтом от char 1 до char 5. Я использовал это для обнаружения изменений в строке, чтобы я мог сдвинуть индексы разметки соответственно. Но рассмотрим строка:

xxxxxxxx

подход функции diff не может сказать разницу между символом, добавленным вне полужирного или внутри него.

в конце концов, я взял совершенно другой подход - я просто проанализировал HTML, созданный редактором, и использовал его для определения начальных и конечных индексов наценок.


сделал мою собственную немного более работоспособную версию, вдохновленную той же тактикой, что и выше (ища различия спереди назад и сзади спереди)

function compareText(oldText, newText)
{
    var difStart,difEndOld,difEndNew;

    //from left to right - look up the first index where characters are different
    for(let i=0;i<oldText.length;i++)
    {
        if(oldText.charAt(i) !== newText.charAt(i))
        {
            difStart = i;
            break;
        }
    }

    //from right to left - look up the first index where characters are different
    //first calc the last indices for both strings
    var oldMax = oldText.length - 1;
    var newMax = newText.length - 1;
    for(let i=0;i<oldText.length;i++)
    {
        if(oldText.charAt(oldMax-i) !== newText.charAt(newMax-i))
        {
            //with different string lengths, the index will differ for the old and the new text
            difEndOld = oldMax-i;
            difEndNew = newMax-i;
            break;
        }
    }

    var removed = oldText.substr(difStart,difEndOld-difStart+1);
    var added = newText.substr(difStart,difEndNew-difStart+1);

    return [difStart,added,removed];
}