【问题标题】:Finding difference between strings in Javascript在Javascript中查找字符串之间的差异
【发布时间】:2014-11-11 04:23:19
【问题描述】:

我想比较两个字符串(之前和之后)并准确检测它们之间的变化和变化。

如有任何更改,我想知道:

  1. 变化的起始位置(含,从0开始)
  2. 相对于前一个文本的更改的结束位置(包括,从 0 开始)
  3. “变化”

假设字符串一次只在一个地方发生变化(例如,永远不会“Bill” -> “K iln”)。

另外,我需要开始和结束位置来反映变化的类型:

  • 如果删除,开始和结束位置应该分别是被删除文本的开始和结束位置
  • 如果替换,开始和结束位置应该分别是“删除”文本的开始和结束位置(更改将是“添加”文本)
  • 如果插入,开始和结束位置应该相同;文本的入口点
  • 如果没有变化,让开始和结束位置保持为零,变化为空

例如:

"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));

如何判断是否发生了插入、删除或替换?


我搜索并提出了fewother 类似的问题,但它们似乎没有帮助。

【问题讨论】:

  • 我建议使用库而不是自己重新创建轮子。有很多差异库,例如:code.google.com/p/google-diff-match-patch
  • 将两个字符串分成两半并查看每一半是否匹配可能会更快。如果一样的话,很酷。如果不是,则取第一个不匹配并不断重复,直到找到第一个更改的位置。

标签: javascript string difference


【解决方案1】:

我已经浏览了你的代码,你匹配字符串的逻辑对我来说很有意义。它正确记录了ChangeStartNewChangeEndOldChangeEnd,并且算法运行正常。您只想知道是否发生了插入删除替换。以下是我的处理方式。

首先,您需要确保在获得第一个不匹配点(即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)&gt;=ChangeStart 负责处理我提到的异常,因此如果达到此条件,for 循环将自动终止。但是,正如我所提到的,在某些情况下,可能会在遇到不匹配之前达到此条件,就像我刚才演示的那样。因此,您需要将 NewChangeEndOldChangeEnd 的值更新为比 ma​​tched 值小 1。如果出现不匹配的情况,您可以适当地存储这些值。

代替else -if,我们可以将这两个条件包装在我们知道NewText.length &gt; OldText.length绝对为真的情况下,即它是替换删除。同样NewText.length &gt; OldText.length 也意味着根据您的示例,它可以是 replacementinsertion,这是有道理的。所以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 &gt; NewChangeEnd。从ChangeStart -&gt; OldChangeEnd 中删除的字符串。

已删除文本 -> OldText.substring(ChangeStart, OldChangeEnd + 1);

  1. 插入 - 条件 -> ChangeStart &gt; OldChangeEnd。在ChangeStart 处插入字符串。

插入文本 -> NewText.substring(ChangeStart, NewChangeEnd + 1);

  1. 替换 - 如果NewText != OldText 和以上两个条件满足,那么它替换。

被替换的旧字符串中的文本 -> OldText.substring(ChangeStart, OldChangeEnd + 1);

替换文本 -> NewText.substring(ChangeStart, NewChangeEnd + 1);

OldText 中的开始和结束位置被替换 -> ChangeStart -&gt; OldChangeEnd

我创建了一个jsfiddle,其中包含我在您的代码中提到的更改。你可能想检查一下。希望它能让你朝着正确的方向开始。

【讨论】:

  • 我查看了 jsfiddle,按照您所说的进行了一些调整,我想我已经成功了。
【解决方案2】:

根据与上述相同的策略(寻找从前到后和从后到前的差异)启发,制作了我自己的稍微性能更高的版本

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];
}

【讨论】:

    【解决方案3】:

    我遇到了类似的问题,用以下方法解决了:

    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,并用它来确定标记的开始和结束索引。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-04-16
      • 2018-10-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多