最小差分补丁算法

时间:2012-12-08 18:17:35

标签: algorithm levenshtein-distance delta

我试图传达两个字节流之间的差异。我想最小化补丁中的字节数。

(我不一定希望最小化差异中“变化”的数量,这是levenshtein距离计算中的最佳补丁会给我的。)

理想情况下,补丁的格式应该是,在给定源字节流和差异的情况下,重建目标字节流很容易。

这样做有很好的算法吗?


编辑:为了记录,我尝试发送“在506处的表单的更改,插入以下字节......”,在那里我从levenshtein距离算法创建一个更改列表。

我遇到的问题是levenshtein距离算法给了我很多改变,如:

at spot 506 substitute [some bytes1]
at spot 507 do nothing
at spot 508 substitute [some bytes2]
at spot 509 do nothing
at spot 510 substitute [some bytes3]
...

这是因为lev距离算法试图最小化变化的数量。但是,就我的目的而言,这个指令集是浪费的。如果算法只是说,那可能会更好,

At spot 506 substitute [some bytes1, [byte at spot 507], some bytes2, [byte at spot 509], some bytes3, ...]

可能有一些方法来修改lev距离以支持这些类型的更改,但这似乎有点棘手。我可以在获得更改列表之后合并替换(我将尝试这样做)但是也可能有机会合并删除/插入,并且不太明显如何正确地执行此操作。

只是想知道是否有一个特殊目的算法(或者如果某人已经修改了lev距离以支持这些类型的更改)。

4 个答案:

答案 0 :(得分:2)

你可以使用成对对齐与仿射间隙成本来做到这一点,分别为两个长度为n和m的字符串花费O(nm)时间。

首先要做的事情是:根据使用的位或字节,无法找到可证明最小的补丁。这是因为如果有这样的方法,那么计算它的函数shortest_patch(x, y)可以用于通过用s和Kolmogorov调用它来找到任何给定字符串shortest_patch('', s)的可证明的最小压缩。复杂性告诉我们,给定字符串的最短可能压缩是正式无法计算的。但是如果编辑倾向于聚集在空间中,就像它们似乎在这里一样,那么找到比使用通常的Levenshtein距离算法生成的补丁更小的补丁肯定是可能的。

编辑脚本

补丁通常在CS中称为“编辑脚本”。查找最小值(根据插入次数加上删除次数)编辑脚本以将一个字符串x转换为另一个字符串y等同于找到最佳的成对对齐每对相等字符的值为0,每对不相等的字符的值为-inf,而一个字符串中的字符与-间隙字符对齐的每个位置的值为-1。对齐很容易想象:

st--ing    st-i-ng
stro-ng    str-ong

这些是字符串stingstrong的2个最佳对齐方式,每个字符串在模型下的成本为-3。如果给出不等字符对的值为-1而不是-inf,那么我们得到一个成本等于Levenshtein距离的对齐(插入的数量,加上删除的数量,加上替换的数量):

st-ing    sti-ng
strong    strong

这些是新模型下的2个最佳对齐方式,每个方案的成本为-2。

要了解这些与编辑脚本的对应关系,我们可以将顶部字符串视为“原始”字符串,将底部字符串视为“目标”字符串。包含不相等字符对的列对应于替换,顶行中包含-的列对应于字符的插入,而底行中包含-的列对应于字符的删除。您可以使用“指令”(C)opy,(D)elete,(I)nsert和(S)ubstitute从对齐创建编辑脚本。每条指令后面都有一个数字,表示从对齐中消耗的列数,在I和S的情况下,要插入或替换相应数量的字符。例如,前两个路线的编辑脚本是

C2, I1"r", S1"o", C2     and     C2, S1"r", I1"o", C2

增加聚束

现在,如果我们有mississippitip等字符串,我们会发现两个路线

mississippi
------tip--

mississippi
t---i----p-

两者具有相同的-9分:它们都需要相同的插入,删除和替换总数。但我们更喜欢顶级的,因为它的编辑脚本可以更加简洁地描述:D6, S1"t", C2, D2。第二个编辑脚本为S1"t", D3, C1, D4, C1, D1

为了使对齐算法也“偏好”第一个对齐,我们可以调整间隙成本,以便开始一个间隙块比继续现有的间隙块花费更多。如果我们这样做,当前一列没有间隙时,包含间隙的列成本为-2而不是-1,那么我们实际做的是惩罚连续的间隙块的数量(因为每个连续的间隙块必须明显有第一个位置)。在这个模型下,上面的第一个对齐成本为-11,因为它包含两个连续的间隙块。第二个对齐现在成本为-12,因为它包含三个连续的间隙块。 IOW,算法现在更喜欢第一次对齐。

这个模型,其中每个包含间隙成本g的对齐位置和任何连续的间隙列块中的第一个位置花费g + s,称为仿射间隙成本模型,并给出了O(nm)算法这是由Gotoh在1982年:http://www.genome.ist.i.kyoto-u.ac.jp/~aln_user/archive/JMB82.pdf。增加缺口开放成本将导致对齐的细分市场聚集在一起。您可以使用各种成本参数,直到获得经验上看起来正确并且足够小的对齐(对应于补丁)。

答案 1 :(得分:1)

解决此类问题有两种方法:

1)为X建立一种语言(在这种情况下编辑脚本),并弄清楚如何最小化适用句子的长度;或者,

2)计算Y(字符串差异)的某种最小表示形式,然后想出一种以最短形式表示的方法。

迈尔斯论文表明,对于特定语言,找到最小变化集并找到变化表示的最小长度是同样的问题。

显然,更改语言可能会使该假设无效,并且某些更改可能会非常复杂以便正确应用(例如,假设语言包含原语kP,这意味着删除下一个k个字符对于某些差异,使用该原语可能会成为一个巨大的胜利,但应用程序可能非常罕见。这是一个荒谬的例子,我知道,但它证明了从一种语言开始的难度。

所以我建议从最小更改列表开始,该列表标识插入和删除。我们以简单的方式将其转换为一串命令,其中恰好有三个。这里没有指数。我们的想法是,我们从原始字符串开头的光标开始,然后按顺序执行命令。命令是:

=  Advance the cursor without altering the character it points to
Ic Insert the character `c` before the cursor.
D  Delete the character at the cursor.

虽然我说过有三个命令,但这并不完全正确;实际上A+2其中A是字母表的大小。

这可能会产生如下字符串:

=========================IbIaInIaInIaDD=D=D============================

现在,让我们尝试压缩它。首先,我们运行长度编码(RLE),以便每个命令前面都有一个重复计数,然后我们删除尾随= s

27=1Ib1Ia1In1Ia1In1Ia2D1=1D1=1D

(实际上,RLE会重新创建索引,尽管它们是相对的而不是绝对的。)

最后,我们使用zlib来压缩生成的字符串。我不打算在这里做,只是想知道它可能出现的那种压缩:

27=1Ib1Ia1In||2D1=1D|
      ______+|  ____+
      ___<---+

(试图展示背面参考。这不是很好的ascii艺术,对不起。)

Liv-Zempell非常善于发现和优化意外重复。实际上,我们可以使用它而不是进行中间RLE步骤,但经验表明,在RLE非常有效的情况下,LZ RLE比源更好。但是值得尝试两种方式来看看哪种方式更适合您的应用。

答案 2 :(得分:0)

使用极少数字节(但不一定是理论上的最佳字节数)的常用方法如下:

  1. 用一些字符(可能为零)填充字节,直到它们具有相同的长度。
  2. 将两个流混合在一起。这将导致字节流在字节相同时为零,否则为非零。
  3. 使用任何压缩算法压缩XORed流,可能类似于LZW。
  4. 假设您拥有的补丁是对文件的一小部分的本地化更改集,这将导致非常短的补丁,因为该文件的大部分将为零,这可以被有效压缩。

    要应用补丁,只需解压缩XORed字符串,然后将其与字节流进行异或补丁即可。这计算

    原始异或(原始XOR新)=(原始XOR原稿)XOR新=新

    由于XOR是关联和自反转的。

    希望这有帮助!

答案 3 :(得分:0)

有一种新的改变检测方法。 序列比对问题被认为是协作文本编辑中的变化检测的抽象模型,旨在最小化合并冲突的可能性。新的成本函数定义为检测到的变化与随机字符串之间的交叉概率。 结果应该与其他已知方法的贴片长度最小化更相似。 它避免了LCS和其他方法的已知缺点。 已经提出了立方算法。 http://psta.psiras.ru/read/psta2015_1_3-10.pdf