是否有类似diff的算法来处理移动的线条?

时间:2012-04-08 20:14:31

标签: algorithm diff

diff程序,在其各种版本中,相当擅长计算两个文本文件之间的差异,并且比完整地显示两个文件更紧凑地表达它。它将差异显示为插入和删除的行块的序列(或在某些情况下更改的行,但这相当于删除后插入)。 patch和源控制系统使用相同或非常类似的程序或算法来最小化表示同一文件的两个版本之间的差异所需的存储。该算法将在herehere中进行讨论。

但是当文件块在文件中移动时,它就会崩溃。

假设您有以下两个文件a.txtb.txt(想象它们都是数百行而不是6行):

a.txt   b.txt
-----   -----
1       4
2       5
3       6
4       1
5       2
6       3

diff a.txt b.txt显示了这一点:

$ diff a.txt b.txt 
1,3d0
< 1
< 2
< 3
6a4,6
> 1
> 2
> 3

a.txtb.txt的更改可以表示为“取前三行并将其移至结尾”,但diff显示已移动的块的完整内容两次排队,错过了非常简短地描述这一大变化的机会。

请注意,diff -e仅显示一次文本块,但这是因为它不显示已删除行的内容。

是否存在diff算法的变体,(a)保留diff表示插入和删除的能力,(b)有效地表示移动的文本块而无需显示整个内容?

8 个答案:

答案 0 :(得分:20)

由于您要求的是算法而不是应用程序,请查看Walter Tichy的"The String-to-String Correction Problem with Block Moves"。还有其他的,但这是原始的,所以你可以寻找引用它来找到更多的论文。

该论文引用了Paul Heckel的论文“一种隔离文件之间差异的技术”(在this answer中提到这个问题),并提到了它的算法:

  

Heckel [3]指出了LCS技术的类似问题并提出了一个问题   线性 - 石灰算法检测块移动。该算法充分执行   如果字符串中有重复的符号。但是,算法给出了   否则结果不好。例如,给定两个字符串 aabb bbaa ,   Heckel的算法无法发现任何常见的子串。

答案 1 :(得分:10)

以下方法能够检测块移动:

Paul Heckel:一种隔离文件差异的技术
ACM 21(4):264(1978)的通讯 http://doi.acm.org/10.1145/359460.359467(访问限制)
镜像:http://documents.scribd.com/docs/10ro9oowpo1h81pgh1as.pdf(开放存取)

wikEd diff是一个免费的JavaScript差异库,可以实现此算法并对其进行改进。它还包括用于编译文本输出的代码,其中插入,删除,移动块和插入到新文本版本中的原始块位置。有关详细信息,请参阅项目页面或广泛注释的代码。对于测试,您还可以使用online demo

答案 2 :(得分:5)

这是一个可能有用的草图。为了清楚起见,暂时忽略差异插入/删除。

这似乎包括找出最佳阻塞,类似于文本压缩。我们想要找到两个文件的公共子串。一种选择是构建一个通用后缀树并迭代地获取最大公共子字符串,删除它并重复,直到没有某个大小为$ s $的子字符串。这可以使用O(N ^ 2)时间(https://en.wikipedia.org/wiki/Longest_common_substring_problem#Suffix_tree)中的后缀树来完成。贪婪地取最大值似乎是最优的(作为压缩字符的函数),因为从其他子串中获取字符序列意味着在其他地方添加相同数量的字符。

然后每个子字符串将被该块的符号替换,并作为一种“字典”显示一次。

$ diff a.txt b.txt 
1,3d0
< $
6a4,6
> $

 $ = 1,2,3 

现在我们必须重新引入类似差异的行为。简单(可能是非最佳)答案是首先简单地运行diff算法,省略所有不会在原始diff中输出的文本并运行上述算法。

答案 3 :(得分:4)

当使用相同的编程语言计算两个程序的源文本之间的差异时,我们的Smart Differencer工具就是这样做的。根据程序结构(标识符,表达式,语句,块)精确到行/列号以及合理的编辑操作(删除,插入,移动,复制[超出OP的仅仅“复制”请求的报告]来报告差异],重命名标识符在块中。

SmartDifferencers需要结构化工件(例如,编程语言),因此不能对任意文本执行此操作。 (我们可以将结构定义为“只是文本行”,但认为与标准差异相比,它不会特别有价值。)

答案 4 :(得分:4)

Git 2.16(2018年第一季度)将通过忽略一些指定的移动线来引入另一种可能性。

git diff”学习了“--patience”算法的变体,用户可以指定哪个“唯一”行用作锚点。

commit 2477ab2Jonathan Tan (jhowtan)(2017年11月27日) Junio C Hamano -- gitster --合并于commit d7c6c23,2017年12月19日)

  

diff:支持锚定线

     

教授diff新算法,该算法试图阻止用户指定的行在最终结果中显示为删除或添加。
  最终用户可以通过指定一个或多个“--anchored=<text>”来使用此功能   使用Git命令时的时间,例如“diff”和“show”。

documentation for git diff现在显示为:

--anchored=<text>:
     

使用“锚定差异”算法生成差异。

     

可以多次指定此选项。

     

如果源和目标中都存在一条线,只存在一次,并以此文本开头,则此算法会尝试阻止它在输出中显示为删除或添加。
  它在内部使用“耐心差异”算法。

请参阅tests for some examples

pre post
 a   c
 b   a
 c   b
  

通常,移动c以产生最小的差异   但是:

 git diff --no-index --anchored=c pre post

差异为a

答案 5 :(得分:2)

SemanticMerge,&#34;语义scm&#34; this comment中提到的其他答案之一的工具,包括一个&#34;语义差异&#34;处理移动一行(用于支持的编程语言)。我还没有找到关于算法的任何细节,但是diff算法本身并不是特别有趣,因为它依赖于编程语言源代码的单独解析的输出。文件本身。这是SemanticMerge关于实现(外部)语言解析器的文档,它可以解释它的差异是如何工作的:

我刚刚测试过它的差异太棒了。它比我使用this answer中提到的算法演示产生的那个明显更好(并且差异本身比Git的默认差异算法产生的差得多)并且我怀疑仍然比this answer中提到的算法可能产生的效果更好。

答案 6 :(得分:1)

对于我现实生活中编码的这种情况,当我实际上将整个代码块移动到源中的另一个位置时,因为它在逻辑上或在可读性上更有意义,我所做的就是这样:

  • 清理所有现有的差异并提交它们
    • 以便文件只需要我们正在寻找的移动
  • 从源中删除整个代码块
    • 保存文件
    • 和改变的阶段
  • 将代码添加到新位置
    • 保存文件
    • 和改变的阶段
  • 将两个分阶段的补丁提交为一个带有合理消息的提交

答案 7 :(得分:0)

还要检查基于simtexter算法的此在线工具SIM_TEXT。似乎最好。

您还可以查看Javascript实现或C / Java的源代码。

enter image description here