当前位置: 首页 > news >正文

编程实践|如何用 MoonBit 实现 diff(三)

本篇文章为diff系列的第三篇。在上一篇中,我们了解了完整的myers算法及其不足之处。在本文中,我们将了解如何实现线性空间复杂度的myers算法变种。

分而治之

Git所使用的Myers diff线性变种采用一种叫做Snake(有时也叫Middle Snake)的概念分解整个搜索过程,一条Snake在编辑图中意味着一步左移/下移后跟0~N步对角线移动。 Myers diff的线性变种会在一条最优的编辑路径上寻找居于中间位置中间的Snake, 并通过它将整个编辑图分割为两个部分。接下来的步骤则如法炮制,分别对分割出的两块子图运用相同的技术进行分割,最终得到一条完整的编辑路径。

    0   1   2   3   4   5   6   7   8   9  10  11  12  13  140  o---o---o---o---o---o---o|   |   |   |   |   |   |1  o---o---o---o---o---o---o|   | \ |   |   |   |   |2  o---o---o---o---o---o---o|   |   |   |   |   |   |3  o---o---o---o---o---o---o|   |   |   |   | \ |   |4  o---o---o---o---o---o---o|   |   |   |   |   |   |5  o---o---o---o---o---o---o\6                              @\7                                  @---o---o---o---o---o---o|   |   |   |   |   |8                                      o---o---o---o---o---o| \ |   |   |   |   |9                                      o---o---o---o---o---o|   |   |   |   |   |
10                                      o---o---o---o---o---o|   |   |   |   |   |
11                                      o---o---o---o---o---o|   |   | \ |   |   |
12                                      o---o---o---o---o---o|   |   |   |   |   |
13                                      o---o---o---o---o---o|   |   |   |   | \ |
14                                      o---o---o---o---o---o

稍微回顾一下前文,最优编辑路径指的是到终点距离最短(对角线距离为零),这样的编辑路径不止一条。

细心的读者想必已经发现了,以上论述存在先有鸡还是先有蛋的问题:要得到一条Snake, 必须先有一条最优的编辑路径,但是要得到一条最优的编辑路径,目前看来唯一的办法是跑一遍原版myers算法。

实际上,线性myers算法的思路基本就是这样,但它采取了一种不大寻常的思路:同时从左上角和右下角使用原版myers算法交替进行搜索,但是不保存历史记录,只检查两边的搜索过程是否重叠,一旦重叠,就将重叠部分作为Middle Snake返回。

听起来思路很清晰,但还有些细节需要搞清楚。

从后往前搜索时,对角线坐标就不能再用k了,我们需要定义一个新的对角线坐标c = k - delta。它和k是互为镜像的,这样正好满足从反方向向起点搜索的需求。

        x                       k0     1     2     30     1     2     3         \     \     \     \y  0  o-----o-----o-----o           o-----o-----o-----o|     |     |     |      -1   |     |     |     | \|     |     |     |         \ |     |     |     |   21  o-----o-----o-----o           o-----o-----o-----o|     | \   |     |      -2   |     | \   |     | \|     |   \ |     |         \ |     |   \ |     |   12  o-----o-----o-----o           o-----o-----o-----o\     \     \     \-3    -2    -1      0c

如何判断搜索过程是否重叠?只要发现正向搜索在某一条对角线上的位置其x正好比反向的位置要大就行,但是由于同一条对角线的k和c坐标不同,换算会稍微有点麻烦。

代码实现

我们首先定义SnakeBox类型,分别对应middle snake以及被分割出的子编辑图(因为是方形的,所以直接以Box称呼了)

struct Box {left : Intright : Inttop : Intbottom : Int
} derive(Debug, Show)struct Snake {start : (Int, Int)end : (Int, Int)
} derive(Debug, Show)fn width(self : Box) -> Int {self.right - self.left
}fn height(self : Box) -> Int {self.bottom - self.top
}fn size(self : Box) -> Int {self.width() + self.height()
}fn delta(self : Box) -> Int {self.width() - self.height()
}

为了避免太早陷入细节,我们先假设已经有了能找到middle snake的函数midpoint : (Box, Array[Line], Array[Line]) -> Snake?, 然后在此基础上编写能搜索出完整path的函数find_path

fn find_path(box : Box, a : Array[Line], b : Array[Line]) -> Iter[(Int, Int)]? {let snake = midpoint(box, a, b)?let start = snake.startlet end = snake.endlet headbox = Box::{ left : box.left, top : box.top, right : start.0, bottom : start.1 }let tailbox = Box::{ left : end.0, top : end.1, right : box.right, bottom : box.bottom }// println("snake = \(snake)")// println("headbox = \(headbox)")// println("tailbox = \(tailbox)")let head = find_path(headbox, a, b).or(Iter::singleton(start))let tail = find_path(tailbox, a, b).or(Iter::singleton(end))Some(head.concat(tail))
}

find_path的实现非常简单直接,而midpoint就要复杂一些

  • 对于大小为0的Box, 直接返回None

  • 计算搜索范围的边界,由于前向和后向搜索各搜一半故除以二,但在Box大小为奇数时因为前向搜索的范围要大一点,所以结果加一。

  • 前向和后向搜索的记录分两个数组保存

  • 正反交替搜索,若没找到结果便返回None

fn midpoint(self : Box, a : Array[Line], b : Array[Line]) -> Snake? {if self.size() == 0 {return None}let max = {let half = self.size() / 2if is_odd(self.size()) {half + 1} else {half}}let vf = BPArray::make(2 * max + 1, 0)vf[1] = self.leftlet vb = BPArray::make(2 * max + 1, 0)vb[1] = self.bottomfor d = 0; d < max + 1; d = d + 1 {match forward(self, vf, vb, d, a, b) {None => match backward(self, vf, vb, d, a, b) {None => continueres => return res}res => return res}} else {None}
}

前向/后向搜索的过程相比原本的myers算法做出了一些需要略作解释的改动

  • 由于需要返回snake,搜索过程需要算出上一个坐标(px在这里指previous x)

  • 由于现在的搜索过程在一个Box中工作(不是全局的编辑图),从x计算y(或者反过来)要考虑换算

  • 后向搜索过程选择最小化y只是一种启发策略,改成x也行

fn forward(self : Box, vf : BPArray[Int], vb : BPArray[Int], d : Int, a : Array[Line], b : Array[Line]) -> Snake? {for k = d; k >= -d; k = k - 2 {let c = k - self.delta()let mut x = 0let mut px = 0if k == -d || (k != d && vf[k - 1] < vf[k + 1]) {x = vf[k + 1]px = x} else {px = vf[k - 1]x = px + 1}let mut y = self.top + (x - self.left) - klet py = if (d == 0 || x != px) { y } else { y - 1 }while x < self.right && y < self.bottom && a[x].text == b[y].text {x = x + 1y = y + 1}vf[k] = xif is_odd(self.delta()) && (c >= -(d - 1) && c <= d - 1) && y >= vb[c] {return Some(Snake::{ start : (px, py), end : (x, y) })}}return None
}fn backward(self : Box, vf : BPArray[Int], vb : BPArray[Int], d : Int, a : Array[Line], b : Array[Line]) -> Snake? {for c = d; c >= -d; c = c - 2 {let k = c + self.delta()let mut y = 0let mut py = 0if c == -d || (c != d && vb[c - 1] > vb[c + 1]) {y = vb[c + 1]py = y} else {py = vb[c - 1]y = py - 1}let mut x = self.left + (y - self.top) + klet px = if (d == 0 || y != py) { x } else { x + 1 }while x > self.left && y > self.top && a[x - 1].text == b[y - 1].text {x = x - 1y = y - 1}vb[c] = yif is_even(self.delta()) && (k >= -d && k <= d) && x <= vf[k] {return Some(Snake::{ start : (x, y), end : (px, py) })}}return None
}

尾声

实际上,Git在默认的diff算法之外还提供了另一种diff算法可以选用:patience diff,它和myers diff的思路截然不同,有时能产出可读性更高的diff结果。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 使用css在照片右上角设置缎带效果
  • 请以零基础学Python 之 第二十讲 分组和贪婪匹配
  • 从新手到高手:Scala函数式编程完全指南,Scala 文件 I/O(27)
  • 【LLM】-10-部署llama-3-chinese-8b-instruct-v3 大模型
  • 类似redmine的项目管理系统有哪些?10款软件测评
  • 基础跟张宇,强化换武忠祥可行吗?会不会漏什么?
  • Flask目录结构路由重定向简单实例讲解——轻量级的 Python Web 框架
  • 【数据结构】——堆的实现与算法
  • ElasticSearch父子索引实战
  • 怎么用U盘重装系统
  • ansible笔记
  • go 语言踏出第一步
  • 【Stable Diffusion】(基础篇七)—— lora
  • AI-WEB-1.0 靶机
  • 2024年8月1日 十二生肖 今日运势
  • [LeetCode] Wiggle Sort
  • 【跃迁之路】【641天】程序员高效学习方法论探索系列(实验阶段398-2018.11.14)...
  • 【跃迁之路】【735天】程序员高效学习方法论探索系列(实验阶段492-2019.2.25)...
  • C++入门教程(10):for 语句
  • HTTP传输编码增加了传输量,只为解决这一个问题 | 实用 HTTP
  • JWT究竟是什么呢?
  • macOS 中 shell 创建文件夹及文件并 VS Code 打开
  • miniui datagrid 的客户端分页解决方案 - CS结合
  • MySQL几个简单SQL的优化
  • Object.assign方法不能实现深复制
  • PAT A1050
  • Python学习之路13-记分
  • TCP拥塞控制
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • vuex 笔记整理
  • 创建一种深思熟虑的文化
  • 回流、重绘及其优化
  • 试着探索高并发下的系统架构面貌
  • 我的zsh配置, 2019最新方案
  • AI算硅基生命吗,为什么?
  • 关于Android全面屏虚拟导航栏的适配总结
  • ​Redis 实现计数器和限速器的
  • ​软考-高级-信息系统项目管理师教程 第四版【第14章-项目沟通管理-思维导图】​
  • # 日期待t_最值得等的SUV奥迪Q9:空间比MPV还大,或搭4.0T,香
  • ###51单片机学习(2)-----如何通过C语言运用延时函数设计LED流水灯
  • #QT(QCharts绘制曲线)
  • %@ page import=%的用法
  • %3cscript放入php,跟bWAPP学WEB安全(PHP代码)--XSS跨站脚本攻击
  • (10)工业界推荐系统-小红书推荐场景及内部实践【排序模型的特征】
  • (NSDate) 时间 (time )比较
  • (Python第六天)文件处理
  • (Redis使用系列) Springboot 使用redis实现接口Api限流 十
  • (超详细)语音信号处理之特征提取
  • (简单) HDU 2612 Find a way,BFS。
  • (利用IDEA+Maven)定制属于自己的jar包
  • (四)React组件、useState、组件样式
  • (算法二)滑动窗口
  • *算法训练(leetcode)第三十九天 | 115. 不同的子序列、583. 两个字符串的删除操作、72. 编辑距离
  • .bat批处理(四):路径相关%cd%和%~dp0的区别
  • .Mobi域名介绍