我正在编写差异文本工具来比较两个类似的源代码文件。
周围有很多这样的“差异”工具,但我的工作会有所改善:
如果它发现两侧的一组线不匹配(即在两个文件中),它不仅要突出显示这些线,还要突出显示这些线的各个变化(我在这里称之为线间比较)。
我有点工作的解决方案的一个例子:
alt text http://files.tempel.org/tmp/diff_example.png
它目前所做的是采取一组不匹配的线条并再次通过差异运行单个字符,产生粉红色突出显示。
然而,第二组不匹配,包含“原始2”,需要更多工作:这里,前两条右线(“添加线a / b”)被添加,而第三条线是更改版本的左边。我希望我的软件能够检测到可能的更改和可能的新行之间的这种差异。
在看这个简单的例子时,我可以很容易地发现这种情况:
使用像Levenshtein这样的算法,我可以在3到5的集合中找到所有正确的行,5行最好匹配左行3,因此我可以推断右边的行3和4被添加,并且在左边3行和右边5行进行行间比较。
到目前为止,这么好。但是我仍然坚持如何将其转化为更通用的算法。
在更复杂的情况下,一组不同的线可以在两侧添加线,其间有几条紧密匹配的线。这变得非常复杂:
我不仅要将左边的第一行与右边的最佳行匹配,反之亦然,依此类推所有其他行。基本上,我必须匹配左边的每一行与右边的每一行。在最坏的情况下,这可能会产生偶数交叉,因此不再容易清楚哪些线路是新插入的,哪些是刚改变的(注意:我不想处理这种块中可能移动的线路,除非这实际上会简化算法)。
当然,这永远不会是完美的,但我试图让它变得比现在更好。任何不太过理论但相当实用的建议(我不太了解抽象算法)都会受到赞赏。
更新
我必须承认,我甚至不了解LCS算法是如何工作的。我只是给它提供了两个字符串数组,然后列出了哪些序列不匹配。我基本上使用的是此处的代码:http://www.incava.org/projects/java/java-diff
查看代码我发现一个函数equal()负责告诉算法两行是否匹配。根据帕维尔的建议,我想知道这是否是我做出改变的地方。但是怎么样?此函数仅返回布尔值 - 而不是可以识别匹配质量的相对值。我不能简单地使用一个固定的Levenshtein比率来决定一条相似的线是否仍然被认为是相同的 - 我需要一些自我采用的东西来处理所有相关的线。
所以,我基本上说的是,我仍然不明白我在哪里应用与没有(完全)匹配的线的相对相似性相关的模糊值。
答案 0 :(得分:3)
Levenshtein距离基于将一个字符串转换为另一个字符串的“编辑脚本”的概念。它与用于通过插入间隙字符来对齐DNA序列的Needleman-Wunsch algorithm密切相关,其中我们使用动态编程搜索最大化O( nm )时间分数的对齐。字符之间的精确匹配会增加分数,而不匹配或插入的间隙字符会降低分数。 AACTTGCCA
和AATGCGAT
的示例对齐:
AACTTGCCA-
AA-T-GCGAT
(6 matches, 1 mismatch, 3 gap characters, 3 gap regions)
我们可以认为顶部字符串是我们正在转换为底部“最终”序列的“起始”序列。底部包含-
间隙字符的每列都是删除,顶部带有-
的每列都是插入,每个具有不同(非间隙)字符的列都是替换。在上述对齐中有2个删除,1个插入和1个替换,因此Levenshtein距离为4.
这是相同字符串的另一个对齐,具有相同的Levenshtein距离:
AACTTGCCA-
AA--TGCGAT
(6 matches, 1 mismatch, 3 gap characters, 2 gap regions)
但请注意,虽然差距相同,但差距区域会少一个。由于生物过程比多个单独的间隙更容易造成广泛的差距,因此生物学家更喜欢这种对齐 - ,您的程序用户也是如此。这是通过来惩罚我们计算得分的差距区域数来实现的。 Gotoh于1982年在一篇名为“改进的论文”中给出了一个O( nm )算法来完成长度为 n 和 m 的字符串。匹配生物序列的算法“。不幸的是,我找不到任何链接到论文的免费全文 - 但是你可以通过谷歌搜索“序列比对”和“仿射空位惩罚”来找到许多有用的教程。
一般来说,匹配,不匹配,间隙和间隙区域权重的不同选择会给出不同的对齐方式,但间隙区域的任何负分数都会更喜欢上方的底部对齐到顶部对齐。
这一切与您的问题有什么关系?如果您对具有合适空位罚分的个别角色使用Gotoh算法(通过一些经验测试得出),您应该会发现显着减少在你给出的例子中看起来很糟糕的对齐数量。
理想情况下,您可以对字符执行此操作并完全忽略行,因为仿射惩罚可以将更改聚类到跨越多行的块中,只要它可以。但是由于运行时间较长,在线路上进行第一次传递然后在字符上重新运行算法可能更为现实,使用不相同的所有线路作为输入。在这种方案下,任何相同行的共享块都可以通过将其压缩为具有膨胀匹配权重的单个“字符”来处理,这有助于确保不出现“交叉”。
答案 1 :(得分:0)
使用像Levenshtein这样的算法,我可以在3到5的集合中找到所有正确的行,5行最好匹配左行3,因此我可以推断右边的行3和4被添加,并且在左边3行和右边5行进行行间比较。
确定后,使用相同的算法确定这两个chink中的哪些行相互匹配。但是你需要做一些修改。当您使用算法匹配相等的行时,行可以匹配或不匹配,因此将0或1添加到您使用的表的单元格中。
当比较一个块中的字符串时,其中一些字符串比其他字符串“更平等”(对奥威尔来说)。因此,在考虑到目前为止哪种序列最匹配时,他们可以将0到1的实数添加到单元格中。
要计算这个指标(从0到1),你可以应用你遇到的每对字符串...对,再次使用相同的算法(实际上,当你第一次执行时,你已经这样做了/ em> Levenstein算法的传递)。这将计算LCS的长度,其与两个字符串的平均长度的比率将是度量值。
或者,您可以从diff工具中借用算法。例如,vimdiff
可以突出显示您需要的匹配项。
答案 2 :(得分:0)
这是其他人让我意识到的一种可能的解决方案:
我原来的方法是这样的:
虽然这可以在比较源代码修订时更好地直观地显示更改,但我现在发现更简单的方法通常就足够了。它的工作原理如下:
也许那些回复我原来问题的人认为我一直都知道这样做,但是我的重点是如此强烈地对每行进行比较,以至于我没有想到在行集上应用LCS通过连接它们,而不是逐行处理它们。
所以,虽然这种方法不会像我原来的意图那样提供详细的变更信息,但它仍然可以改善我昨天开始写这个问题时的结果。
我会把这个问题保持开放一段时间 - 也许其他人,阅读所有这些,仍然可以提供完整的答案(Pavel和random_hacker提供了一些建议,但它还不是一个完整的解决方案 - 无论如何,谢谢你有用的评论)。