Поиск разницы между строками в Javascript
Я хотел бы сравнить две строки (A до и после) и точно определить, где и что изменилось между ними.
для любых изменений, я хочу знать:
- Начальная позиция изменения (включительно, начиная с 0)
- конечная позиция изменения (включительно, начиная с 0) относительно предыдущего текста
- "изменить"
предположим, что строки будут меняться только в одном месте в одно время (например, не "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.
}
}
если вы поняли незначительные изменения до сих пор, выявление конкретных случаев действительно просто:
-
удаление условие ->
ChangeStart > NewChangeEnd
. Удалена строка изChangeStart -> OldChangeEnd
.
удаленный текст -> OldText.substring(ChangeStart, OldChangeEnd + 1);
-
вставка условие ->
ChangeStart > OldChangeEnd
. Вставленная строка вChangeStart
.
вставить текст -> NewText.substring(ChangeStart, NewChangeEnd + 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];
}