You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// (5,3) :下一步往 `leftOver` 方向走到 (4,2)
// from (5,3)
satur - day
sun - day
// to (4,2)
satu - nday
su - nday
// 可见将 r 改为 n 即可
// (3,1):下一步往 `left` 方向走到 (2,1)
// from (3,1)
sat - unday
s - unday
// to (2,1)
sa - unday
s - unday
// 可见删掉 t 即可
// (2,1):下一步往 `left` 方向走到 (1,1)
// from (2,1)
sa - unday
s - unday
// to (1,1)
s - unday
s - unday
// 可见删掉 a 即可
基于 Levenshtein distance 算法的字符串 diff 算法
之前的博文介绍了如何利用 Levenshtein distance 算法求解从一个字符串到另外一个字符串的编辑距离。在博文的结尾提到了一种通过回溯 Levenshtein distance 算法生成的表格,求得从一个字符串到另外一个字符串变化步骤的算法,算法有如下效果。
注释部分表示算法输出的结果,输出的数组表示:
saturday
中 index 为 4 的字符替换为 n。(即 r 替换为 n)saturday
中 index 为 2 的字符删除。(即删除 t)saturday
中 index 为 1 的字符删除。(即删除 a)回溯法求解编辑路径
之前的博文通过自底向上法完成了一个表格,如下:
回溯的路径
既然是回溯,那么就要从这个表格的最后一个单元格出发,按照一定的路径向第一个单元格移动,即从右下角向左上角移动。在这里,如何选择回溯的路径将是这个算法成功的关键。Levenshtein distance 的数学定义描述了每个单元格的值都是根据其左边的单元格,左上方的单元格,上方的单元格的值的关系确定的,具体的确定方式可以看之前的博文。同样的我们在回溯的时候也要考虑当前单元格和其左边的单元格,左上方的单元格,上方的单元格的值的关系。图例如下:
起点
origin
可以往left
方向,leftOver
方向,over
方向出发。但我们的目的既然是求解从一个字符串到另外一个字符串最少编辑的步骤,那么在选择方向的时候得满足两点。left
,leftOver
和over
中编辑距离(也就是值)最小的方向出发。leftOver
方向出发。根据这两条规则,很快就可以确定回溯的路径,如下,加粗字体表示回溯时会走的路径:
回溯的方向和相应的编辑操作
在回溯的路径上,单元格里边的数字每发生一次变化,就代表发生了一次编辑操作,比如。
可以看到,路径上数字的变化次数是 3 次,这和我们执行 levenshteinDistance 的结果是一致的。那么每一次变化分别都代表了什么操作呢。可以通过观察数字变化前的单元格([(5,3),(3,1),(2,1)])得出些头绪,如下:
总结一下,就是:
left
方向走是删除操作。over
方向走是插入操作。leftOver
方向走是更新操作或是不操作,由当前字符决定,比如从 (1,1) 到 (0,0) 就是不操作。diff 算法的 Javascript 实现
我认为是时候贴代码了
小结
这个 diff 函数,除了可以用来比较以外还可以用来比较列表,像下面一样。
另外,通过自定义的
match
函数(见之前的博文),它可以用来比较更多的东西,比如嵌套的数组。如果你乐意,这个 diff 函数也可以扩写为比较 DOM 树的函数,就像 React 的那个一样。当然,这个 diff 方法还有很多不完善的地方,比如:
这样的接口,实在算不上优美,另外,它也没经过太多的优化。
The text was updated successfully, but these errors were encountered: