diff
程序,在其各种版本中,相当擅长计算两个文本文件之间的差异,并且比完整地显示两个文件更紧凑地表达它。它将差异显示为插入和删除的行块的序列(或在某些情况下更改的行,但这相当于删除后插入)。 patch
和源控制系统使用相同或非常类似的程序或算法来最小化表示同一文件的两个版本之间的差异所需的存储。该算法将在here和here中进行讨论。
但是当文件块在文件中移动时,它就会崩溃。
假设您有以下两个文件a.txt
和b.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.txt
到b.txt
的更改可以表示为“取前三行并将其移至结尾”,但diff
显示已移动的块的完整内容两次排队,错过了非常简短地描述这一大变化的机会。
请注意,diff -e
仅显示一次文本块,但这是因为它不显示已删除行的内容。
是否存在diff
算法的变体,(a)保留diff
表示插入和删除的能力,(b)有效地表示移动的文本块而无需显示整个内容?
答案 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 2477ab2见Jonathan 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>:
使用“锚定差异”算法生成差异。
可以多次指定此选项。
如果源和目标中都存在一条线,只存在一次,并以此文本开头,则此算法会尝试阻止它在输出中显示为删除或添加。
它在内部使用“耐心差异”算法。
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)