diff是我们每天都要使用的一个功能,每次提交时,我都习惯先用git diff --cached看看这次提交更改了些什么,确定没问题,然后再git commit。git生成的diff非常直观,直观到我从来都没有去思考过diff是怎么生成的,觉得这应该是很简单的一件事,两个文件做个对比,不就行了。

什么是直观的diff

我们先简单定义一下什么是diff:diff就是目标文本和源文本之间的区别,也就是将源文本变成目标文本所需要的操作。

git为我们生成的diff是很直观易懂的,一看就知道我们对文件进行了哪些改动。但是,实际上,diff生成是一个非常复杂的问题。

举个简单的例子,源文本为ABCABBA,目标文本为CBABAC,他们之间的diff其实有无穷多种(我们以字符为单位,一般情况下是以行为单位)。比如

1.  - A       2.  - A       3.  + C- B           + C           - AC             B             B- A           - C           - CB             A             A+ A             B             BB           - B           - BA             A             A+ C           + C           + C

上面三种都是有效的diff,都可以将源文本变成目标文本,但是第二种和第三种没有第一种看起来“直观”。

所以,我们需要个算法,生成“直观”的diff,怎么样才叫直观呢?

  • 删除后新增,比新增后删除要好,也就是说,上面的例子2比例子3看起来要直观
  • 当修改一块代码时,整块的删除然后新增,比删除新增交叉在一起要好,例如:

      Good: - one            Bad: - one- two                 + four- three               - two+ four                + five+ five                + six+ six                 - three
    
  • 新增或删除的内容应该和代码结构相呼应,例如下面的例子,左边我们可以很直观地看出新增了一个inspect方法。

      Good: class Foo                   Bad:    class Foodef initialize(name)                def initialize(name)@name = name                        @name = nameend                             +   end+                                   ++   def inspect                     +   def inspect+     @name                         +     @name+   end                                 endend                                 end
    

除了直观以外,diff还需要短,这一点是好理解的,我们希望diff反应的是把源文本变成目标文本需要用的最少的操作。

那么,现在的问题就是:怎样寻找最短的直观的diff?

diff与图搜索

”寻找最短的直观的diff”是一个非常模糊的问题,首先,我们需要把这个问题抽象为一个具体的数学问题,然后再来寻找算法解决。

抽象的过程交给算法科学家了,抽象的结果是:寻找diff的过程可以被表示为图搜索

什么意思呢?还是以两个字符串,src=ABCABBA,dst=CBABAC为例,根据这两个字符串我们可以构造下面一张图,横轴是src内容,纵轴是dst内容。

那么,图中每一条从左上角到右下角的路径,都表示一个diff。向右表示“删除”,向下表示”新增“,对角线则表示“原内容保持不动“。

比如,我们选择这样一条路径:

  1. (0, 0) -> (1, 0)
  2. (1, 0) -> (2, 0) -> (3, 1)
  3. (3, 1) -> (3, 2) -> (4, 3) -> (5, 4)
  4. (5, 4) -> (6, 4) -> (7, 5)
  5. (7, 5) -> (7, 6)

这条路径代表的diff如下。

- A
- BC
+ BAB
- BA
+ C

现在,“寻找diff”这件事,被抽象成了“寻找图的路径”了。那么,“最短的直观的”diff对应的路径有什么特点呢?

  • 路径长度最短(对角线不算长度)
  • 先向右,再向下(先删除,后新增)

Myers算法

Myers算法就是一个能在大部分情况产生”最短的直观的“diff的一个算法,算法原理如下。

首先,定义参数dk,d代表路径的长度,k代表当前坐标x - y的值。定义一个”最优坐标“的概念,最优坐标表示d和k值固定的情况下,x值最大的坐标。x大,表示向右走的多,表示优先删除。

还是用上面那张图为例。我们从坐标(0, 0)开始,此时,d=0k=0,然后逐步增加d,计算每个k值下对应的最优坐标。

因为每一步要么向右(x + 1),要么向下(y + 1),要么就是对角线(x和y都+1),所以,当d=1时,k只可能有两个取值,要么是1,要么是-1

d=1k=1时,最优坐标是(1, 0)

d=1k=-1时,最优坐标是(0, 1)

因为d=1时,k要么是1,要么是-1,当d=2时,表示在d=1的基础上再走一步,k只有三个可能的取值,分别是-202

d=2k=-2时,最优坐标是(2, 4)

d=2k=0时,最优坐标是(2, 2)

d=2k=2时,最优坐标是(3, 1)

以此类推,直到我们找到一个dk值,达到最终的目标坐标(7, 6)

下图横轴代表d,纵轴代表k,中间是最优坐标,从这张图可以清晰的看出,当d=5k=1时,我们到达了目标坐标(7, 6),因此,”最短的直观的“路径就是(0, 0) -> (1, 0) -> (3, 1) -> (5, 4) -> (7, 5) -> (7, 6),对应的diff如下。

- A
- BC
+ BAB
- BA
+ C

现在我们可以知道,其实Myers算法是一个典型的”动态规划“算法,也就是说,父问题的求解归结为子问题的求解。要知道d=5时所有k对应的最优坐标,必须先要知道d=4时所有k对应的最优坐标,要知道d=4时的答案,必须先求解d=3,以此类推,和01背包问题很是相似。

实现

算法原理知道以后,实现便是一件简单的事情了,myers-diff仓库是我使用Go实现的一个版本。基本流程如下:

  1. 迭代d,d的取值范围为0到n+m,其中n和m分别代表源文本和目标文本的长度(这里我们选择以行为单位)
  2. 每个d内部,迭代k,k的取值范围为-d到d,以2为步长,也就是-d,-d + 2,-d + 2 + 2…
  3. 使用一个数组v,以k值为索引,存储最优坐标的x值(这里使用hash也行,但是用数组效率更高一些,因为Go不支持使用负数做索引,所以需要创建一个自定义类型)
  4. 将每个d对应的v数组存储起来,后面回溯的时候需要用
  5. 当我们找到一个d和k,到达目标坐标(n, m)时就跳出循环
  6. 使用上面存储的v数组(每个d对应一个这样的数组),从终点反向得出路径

最后补充一句,Git真正用的是标准Myers算法的一个变体。标准的算法有一个很大的缺点,就是空间消耗很大,因为我们需要存储每一个d对应的v数组。如果输入文件比较大,这样的空间开销是不能接受的。因此Myers在他的论文中,同时提供了一个算法变体,这个变体需要的空间开销要小得多。但是在某些情况下,变体产生的diff会和标准算法有所不同。也就是说,如果你按照上面的算法实现的程序,出来的结果和git diff的结果有所不同是正常的。

参考资料

  • The Myers diff algorithm: part 1

Git是怎样生成diff的:Myers算法相关推荐

  1. Git Diff 算法——Myers算法

    diff 是我们每天都要使用的一个功能,每次提交时,我都习惯先用 git diff --cached 看看这次提交更改了些什么,确定没问题,然后再 git commit.git 生成的 diff 非常 ...

  2. 【转】git生成diff原理:Myers差分算法

    转载:git生成diff原理:Myers差分算法 | 大艺术家_SN 什么是Myers差分算法 举一个最常见的例子,我们使用git进行提交时,通常会使用git diff --cached来查看这次提交 ...

  3. linux的diff工具原理之Myers算法

    linux的diff工具原理之Myers算法 前言 diff diff与图搜索 Myers算法 具体实现 1.为什么d的取值范围是[0,N+M]呢? 2.为什么k的取值范围为[-d,d],并且步长为2 ...

  4. diff git 代码实现_Git diff 算法

    在Git中,有四种diff算法,即Myers,Minimal,Patience和Histogram.默认Myers.Minimal是Myers的改进,Histogram是Patience的改进.Mye ...

  5. git apply、git am打补丁.diff 和 .patch

    前提: 生成patch: git format-patch -M master 生成指定patch,0163bed3bf59ae74c36cc5138b4c24f1556d8304是commit id ...

  6. Python: Git Log自动生成Release Notes,并调用Outlook发送至邮件

    Python: Git Log自动生成Release Notes,并调用Outlook发送至邮件 主要功能 先上图 生成ReleaseNotes 邮件: 生成ReleaseNotes Text: 关键 ...

  7. R语言DALEX包的model_profile函数对caret包生成的多个算法模型的连续变量进行分析、使用Acumulated Local Effects (ALE)方法解释某个连续特征和目标值关系

    R语言使用DALEX包的model_profile函数对caret包生成的多个算法模型的连续变量进行分析.使用Acumulated Local Effects (ALE)方法解释某个连续特征和目标值y ...

  8. R语言使用DALEX包的model_profile函数对caret包生成的多个算法模型的离散变量进行分析、使用偏依赖图(Partial Dependence Plots)解释某个离散特征和目标y的关系

    R语言使用DALEX包的model_profile函数对caret包生成的多个算法模型的离散变量进行分析.使用偏依赖图(Partial Dependence Plots)解释某个离散特征和目标值y的关 ...

  9. R语言使用DALEX包的model_performance函数对caret包生成的多个算法模型进行残差分布分析并使用箱图进行残差分布的可视化

    R语言使用DALEX包的model_performance函数对caret包生成的多个算法模型进行残差分布分析并使用箱图进行残差分布的可视化 目录

最新文章

  1. 一篇文章让你读懂Pivotal的GemFire家族产品
  2. 绿色vmware 安装后看不到虚拟的网卡
  3. 【译】C#9的候选功能
  4. 在安装keepalived出现问题:需要:libmysqlclient.so.18
  5. mysql 数据路由_node-路由操作mysql数据库
  6. Effective C++学习第七天
  7. 使用@Autowired注解警告Field injection is not recommended
  8. mysql kafka binlog_为什么使用kafka处理mysql binlog?
  9. 软能力那点事,你知多少
  10. android 8.0 用户体验优化--day02
  11. js一键批量打印_为什么我推荐你用3D打印技术制造模具?
  12. 剖析 Vue.js 内部运行机制 (1)
  13. 冲动是魔鬼——4.25
  14. 源码生成网站,开发帮助网站
  15. 1.1 半导体基础知识
  16. IEC 60601-2-33:2022 《医疗诊断用磁共振设备基本安全和基本性能的特殊要求》。
  17. 计算机的串口波特率,串口常用参数
  18. 阿里云域名配置和https证书(ssl证书)配置内容
  19. Xgboost实践 | 第一名天池o2o优惠券的使用预测思路完整版
  20. 谷歌学术得到论文被引用信息

热门文章

  1. 计算机学院军训特色标语,军训标语
  2. android悬浮窗口 关闭,Android悬浮对话框(即点即关对话框)实现代码
  3. python列表导出excel_python list格式数据excel导出方法
  4. MySQL数据库锁表
  5. RedHat Linux6.7系统安装JDK1.8
  6. 未来计算机行业发展的趋势
  7. 计算机无法关闭怎么办,电脑进程无法关闭怎么办
  8. 职高机电专业和计算机专业哪个好,职高有哪些专业?读职校什么专业好
  9. 关于兔子生兔子,牛生牛,鸡生鸡。。。若干年后一共多少只的问题
  10. TouchDesigner案例(三)萤火虫