diff --git a/next/tutorial/example/myers-diff/index.md b/next/tutorial/example/myers-diff/index.md new file mode 100644 index 00000000..fda435d9 --- /dev/null +++ b/next/tutorial/example/myers-diff/index.md @@ -0,0 +1,8 @@ +# Myers diff + +```{toctree} +:maxdepth: 2 +:caption: Contents: +myers-diff +myers-diff2 +myers-diff3 \ No newline at end of file diff --git a/next/tutorial/example/myers-diff/myers-diff.md b/next/tutorial/example/myers-diff/myers-diff.md new file mode 100644 index 00000000..6204fc97 --- /dev/null +++ b/next/tutorial/example/myers-diff/myers-diff.md @@ -0,0 +1,390 @@ +# Myers diff + +Have you ever used the Unix tool `diff`? In short, it is a tool for comparing the differences between two text files. What's more, Unix has a tool called `patch`. + +Nowadays, few people manually apply patches to software packages, but `diff` still retains its usefulness in another area: version control systems. It's quite handy to have the function of being able to see what changes have been made to source code files after a particular commit (and highlighted with different colors). Take the most popular version control system today, git, as an example: + +```diff +diff --git a/main/main.mbt b/main/main.mbt +index 99f4c4c..52b1388 100644 +--- a/main/main.mbt ++++ b/main/main.mbt +@@ -3,7 +3,7 @@ + + fn main { + let a = lines("A\nB\nC\nA\nB\nB\nA") +- let b = lines("C\nB\nA\nB\nA\nC") ++ let b = lines("C\nB\nA\nB\nA\nA") + let r = shortst_edit(a, b) + println(r) + } +``` + +But how exactly do we calculate the differences between two text files? + +git's default diff algorithm was proposed by _Eugene W. Myers_ in his paper **An O(ND) Difference Algorithm and Its Variations**. This paper mainly focuses on proving the correctness of the algorithm. In the following text, we will understand the basic framework of this algorithm in a less rigorous way and use MoonBit to write a simple implementation. + +## Defining "Difference" and Its Measurement Criteria + +When we talk about the "difference" between two text files, what we are actually referring to is a series of editing steps that can transform text a into text b. + +Assume the content of text a is + +``` +A +B +C +A +B +B +A +``` + +Assume the content of text b is + +``` +C +B +A +B +A +C +``` + +To transform text a into text b, the simplest edit sequence is to delete each character in a (indicated with a minus sign) and then insert each character in b (indicated with a plus sign). + +```diff +- A +- B +- C +- A +- B +- B +- A ++ C ++ B ++ A ++ B ++ A ++ C +``` + +But such a result might not be very helpful for programmers reading the code. The following edit sequence is much better, at least it is shorter. + +```diff +- A +- B + C ++ B + A + B +- B + A ++ C +``` + +In fact, it is one of the shortest edit sequences that can transform text a into text b, with 5 operations. If we only measure the length of the edit sequence, this result is satisfactory. But when we look at the various programming languages, we find that there are other metrics that are equally important for user experience. Let's look at the following examples: + +```diff +// good quality + struct RHSet[T] { + set : RHTable[T, Unit] + } ++ ++ fn RHSet::new[T](capacity : Int) -> RHSet[T] { ++ let set : RHTable[T, Unit]= RHTable::new(capacity) ++ { set : set } ++ } + + +// bad quality + struct RHSet[T] { + set : RHTable[T, Unit] ++ } ++ ++ fn RHSet::new[T](capacity : Int) -> RHSet[T] { ++ let set : RHTable[T, Unit]= RHTable::new(capacity) ++ { set : set } + } +``` + +When we insert a new function definition at the end of a file, the calculated edit sequence should ideally locate the changes at the end. In similar cases, when there are both deletions and insertions, it is best not to calculate an edit sequence that interleaves these two operations. Here's another example. + +``` +Good: - one Bad: - one + - two + four + - three - two + + four + five + + five + six + + six - three +``` + +Myers' diff algorithm can fulfill all those requirements. It is a greedy algorithm that skips over matching lines whenever possible (avoiding inserting text before `{`), and it also tries to place deletions before insertions, avoiding the latter situation. + +## Algorithm Overview + +The basic idea in Myers' paper is to construct a grid graph of edit sequences and then search for the shortest path on this graph. Using the previous example `a = ABCABBA` and `b = CBABAC`, we create an `(x, y)` coordinate grid. + +``` + 0 1 2 3 4 5 6 7 + +0 o-----o-----o-----o-----o-----o-----o-----o + | | | \ | | | | | + | | | \ | | | | | C + | | | \ | | | | | +1 o-----o-----o-----o-----o-----o-----o-----o + | | \ | | | \ | \ | | + | | \ | | | \ | \ | | B + | | \ | | | \ | \ | | +2 o-----o-----o-----o-----o-----o-----o-----o + | \ | | | \ | | | \ | + | \ | | | \ | | | \ | A + | \ | | | \ | | | \ | +3 o-----o-----o-----o-----o-----o-----o-----o + | | \ | | | \ | \ | | + | | \ | | | \ | \ | | B + | | \ | | | \ | \ | | +4 o-----o-----o-----o-----o-----o-----o-----o + | \ | | | \ | | | \ | + | \ | | | \ | | | \ | A + | \ | | | \ | | | \ | +5 o-----o-----o-----o-----o-----o-----o-----o + | | | \ | | | | | + | | | \ | | | | | C + | | | \ | | | | | +6 o-----o-----o-----o-----o-----o-----o-----o + + + A B C A B B A +``` + +The upper left of this grid is the starting point `(0, 0)`, and the lower right is the endpoint `(7, 6)`. Moving one step right along the x-axis deletes the corresponding character in a, moving one step down along the y-axis inserts the corresponding character in b, and diagonal lines indicate matching characters that can be skipped without triggering any edits. + +Before writing the actual search code, let's manually perform two rounds of searching: + +- The first round starts at `(0, 0)` and moves one step to reach `(0,1)` and `(1,0)`. + +- The second round starts at `(0,1)` and `(1,0)`. From `(0,1)`, moving down reaches `(0,2)`, but there is a diagonal line leading to `(1,3)`, so the final point is `(1,3)`. + +The entire Myers algorithm is based on this kind of breadth-first search. + +## Implementation + +We have outlined the basic idea, now it's time to consider the design in detail. The input to the algorithm is two strings, but the search needs to be conducted on a graph. It's a waste of both memory and time to construct the graph and then search it. + +The implementation of the Myers algorithm adopts a clever approach by defining a new coordinate `k = x - y`. + +- Moving right increases `k` by one. + +- Moving left decreases `k` by one. + +- Moving diagonally down-left keeps `k` unchanged. + +Let's define another coordinate `d` to represent the depth of the search. Using `d` as the horizontal axis and `k` as the vertical axis, we can draw a tree diagram of the search process. + +``` + | 0 1 2 3 4 5 +----+-------------------------------------- + | + 4 | 7,3 + | / + 3 | 5,2 + | / + 2 | 3,1 7,5 + | / \ / \ + 1 | 1,0 5,4 7,6 + | / \ \ + 0 | 0,0 2,2 5,5 + | \ \ +-1 | 0,1 4,5 5,6 + | \ / \ +-2 | 2,4 4,6 + | \ +-3 | 3,6 +``` + +You can see that in each round of searching, `k` is strictly within the range `[-d, d]` (because in one move, it can at most increase or decrease by one from the previous round), and the `k` values between points have an interval of 2. The basic idea of Myers' algorithm comes from this idea: searching by iterating over `d` and `k`. Of course, it also needs to save the `x` coordinates of each round for use in the next round of searching. + +Let's first define the `Line` struct, which represents a line in the text. + +```rust +struct Line { + number : Int // Line number + text : String // Does not include newline +} derive(Debug, Show) + +fn Line::new(number : Int, text : String) -> Line { + Line::{ number : number, text : text } +} +``` + +Then, define a helper function that splits a string into `Array[Line]` based on newline characters. Note that line numbers start from 1. + +```rust +fn lines(str : String) -> Array[Line] { + let mut line_number = 0 + let buf = Buffer::make(50) + let vec = [] + for i = 0; i < str.length(); i = i + 1 { + let ch = str[i] + buf.write_char(ch) + if ch == '\n' { + let text = buf.to_string() + buf.reset() + line_number = line_number + 1 + vec.push(Line::new(line_number, text)) + } + } else { + // The text may not end with a newline + let text = buf.to_string() + if text != "" { + line_number = line_number + 1 + vec.push(Line::new(line_number, text)) + } + vec + } +} +``` + +Next, we need to wrap the array so that it supports negative indexing because we will use the value of `k` as an index. + +```rust +type BPArray[T] Array[T] // BiPolar Array + +fn BPArray::make[T](capacity : Int, default : T) -> BPArray[T] { + let arr = Array::make(capacity, default) + BPArray(arr) +} + +fn op_get[T](self : BPArray[T], idx : Int) -> T { + let BPArray(arr) = self + if idx < 0 { + arr[arr.length() + idx] + } else { + arr[idx] + } +} + +fn op_set[T](self : BPArray[T], idx : Int, elem : T) -> Unit { + let BPArray(arr) = self + if idx < 0 { + arr[arr.length() + idx] = elem + } else { + arr[idx] = elem + } +} +``` + +Now we can start writing the search function. Before searching for the complete path, let's start with our first goal to find the length of the shortest path (equal to the search depth). Here is the basic framework: + +```rust +fn shortst_edit(a : Array[Line], b : Array[Line]) -> Int { + let n = a.length() + let m = b.length() + let max = n + m + let v = BPArray::make(2 * max + 1, 0) + for d = 0; d < max + 1; d = d + 1 { + for k = -d; k < d + 1; k = k + 2 { + ...... + } + } +} +``` + +In the most extreme case (the two texts have no matching lines), it can be inferred that the maximum number of steps needed is `n + m`, and the minimum is 0. Therefore, set the variable `max = n + m`. The array `v` uses `k` as an index to store the historical record of `x` values. Since `k` ranges from `[-d, d]`, the size of this array is set to `2 * max + 1`. + +But even at this point, it is still difficult to figure out what to do next, so let's only consider the case `d = 0; k = 0` for now. At this point, it must be at `(0, 0)`. Also, if the beginnings of the two texts are the same, they can be skipped directly. We write the final coordinates of this round into the array `v`. + +```rust +if d == 0 { // When d equals 0, k must also equal 0 + x = 0 + y = x - k + while x < n && y < m && a[x].text == b[y].text { + // Skip all matching lines + x = x + 1 + y = y + 1 + } + v[k] = x +} +``` + +When `d > 0`, the coordinate information stored from the previous round is required. When we know the `k` value of a point and the coordinates of the points from the previous round of searching, the value of `v[k]` is easy to deduce. Because with each step k can only increase or decrease by one, `v[k]` in the search tree must extend from either `v[k - 1]` or `v[k + 1]`. The next question is: how to choose between the two paths ending at `v[k - 1]` and `v[k + 1]`? + +There are two boundary cases: `k == -d` and `k == d`. + +- When `k == -d`, you can only choose `v[k + 1]`. + +- When `k == -d`, you can only choose `v[k - 1]`. + +Recalling the requirement mentioned earlier: arranging deletions before insertions as much as possible, this essentially means choosing the position with the largest `x` value from the previous position. + +```rust +if k == -d { + x = v[k + 1] +} else if k == d { + x = v[k - 1] + 1 // add 1 to move horizontally +} else if v[k - 1] < v[k + 1] { + x = v[k + 1] +} else { + x = v[k - 1] + 1 +} +``` + +Merging these four branches, we get the following code: + +```rust +if k == -d || (k != d && v[k - 1] < v[k + 1]) { + x = v[k + 1] +} else { + x = v[k - 1] + 1 +} +``` + +Combining all the steps above, we get the following code: + +```rust +fn shortst_edit(a : Array[Line], b : Array[Line]) -> Int { + let n = a.length() + let m = b.length() + let max = n + m + let v = BPArray::make(2 * max + 1, 0) + // v[1] = 0 + for d = 0; d < max + 1; d = d + 1 { + for k = -d; k < d + 1; k = k + 2 { + let mut x = 0 + let mut y = 0 + // if d == 0 { + // x = 0 + // } + if k == -d || (k != d && v[k - 1] < v[k + 1]) { + x = v[k + 1] + } else { + x = v[k - 1] + 1 + } + y = x - k + while x < n && y < m && a[x].text == b[y].text { + x = x + 1 + y = y + 1 + } + v[k] = x + if x >= n && y >= m { + return d + } + } + } else { + abort("impossible") + } +} +``` + +Since the initial value of the array is 0, we can omit the branch for `d == 0`. + +## Epilogue + +We have implemented an incomplete version of Myers' algorithm, which completes the forward path search. In the next article, we will implement the backtracking to restore the complete edit path and write a function to output a colored diff. + +This article references: + +- [https://blog.jcoglan.com/2017/02/15/the-myers-diff-algorithm-part-2/](https://blog.jcoglan.com/2017/02/15/the-myers-diff-algorithm-part-2/) + +Thanks to the author James Coglan. diff --git a/next/tutorial/example/myers-diff/myers-diff2.md b/next/tutorial/example/myers-diff/myers-diff2.md new file mode 100644 index 00000000..10fe761b --- /dev/null +++ b/next/tutorial/example/myers-diff/myers-diff2.md @@ -0,0 +1,188 @@ +# Myers diff 2 + +This is the second post in the diff series. In the [previous one](https://www.moonbitlang.com/docs/examples/myers-diff), we learned how to transform the process of computing diffs into a graph search problem and how to search for the shortest edit distance. In this article, we will learn how to extend the search process from the previous post to obtain the complete edit sequence. + +## Recording the Search Process + +The first step to obtaining the complete edit sequence is to save the entire editing process. This step is relatively simple; we just need to save the current search depth `d` and the graph node with depth `d` at the beginning of each search round. + +```rust +fn shortest_edit(a : Array[Line], b : Array[Line]) -> Array[(BPArray[Int], Int)] { + let n = a.length() + let m = b.length() + let max = n + m + let v = BPArray::make(2 * max + 1, 0) + let trace = [] + fn push(v : BPArray[Int], d : Int) -> Unit { + trace.push((v, d)) + } + // v[1] = 0 + for d = 0; d < max + 1; d = d + 1 { + push(v.copy(), d) // Save search depth and node + for k = -d; k < d + 1; k = k + 2 { + let mut x = 0 + let mut y = 0 + // if d == 0 { + // x = 0 + // } + if k == -d || (k != d && v[k - 1] < v[k + 1]) { + x = v[k + 1] + } else { + x = v[k - 1] + 1 + } + y = x - k + while x < n && y < m && a[x].text == b[y].text { + x = x + 1 + y = y + 1 + } + + v[k] = x + if x >= n && y >= m { + return trace + } + } + } else { + abort("impossible") + } +} +``` + +## Backtracking the Edit Path + +After recording the entire search process, the next step is to walk back from the endpoint to find the path taken. But before we do that, let's first define the `Edit` type. + +```rust +enum Edit { + Insert(~new : Line) + Delete(~old : Line) + Equal(~old : Line, ~new : Line) // old, new +} derive(Debug, Show) +``` + +Next, let's perform the backtracking. + +```rust +fn backtrack(a : Array[Line], b : Array[Line], trace : Array[(BPArray[Int], Int)]) -> Array[Edit] { + let mut x = a.length() + let mut y = b.length() + let edits = [] + fn push(edit : Edit) -> Unit { + edits.push(edit) + } + ...... +``` + +The method of backtracking is essentially the same as forward search, just in reverse. + +- Calculate the current `k` value using `x` and `y`. + +- Access the historical records and use the same judgment criteria as in forward search to find the `k` value at the previous search round. + +- Restore the coordinates of the previous search round. + +- Try free movement and record the corresponding edit actions. + +- Determine the type of edit that caused the change in `k` value. + +- Continue iterating. + +```rust + for i = trace.length() - 1; i >= 0; i = i - 1 { + let (v, d) = trace[i] + let k = x - y + let prev_k = if k == -d || (k != d && v[k - 1] < v[k + 1]) { + k + 1 + } else { + k - 1 + } + let prev_x = v[prev_k] + let prev_y = prev_x - prev_k + while x > prev_x && y > prev_y { + x = x - 1 + y = y - 1 + push(Equal(old = a[x], new = b[y])) + } + if d > 0 { + if x == prev_x { + push(Insert(new = b[prev_y])) + } else if y == prev_y { + push(Delete(old = a[prev_x])) + } + x = prev_x + y = prev_y + } + } +``` + +Combining the two functions, we get a complete `diff` implementation. + +```rust +fn diff(a : Array[Line], b : Array[Line]) -> Array[Edit] { + let trace = shortest_edit(a, b) + backtrack(a, b, trace) +} +``` + +## Printing the Diff + +To print a neat diff, we need to left-align the text. Also, due to the order issue during backtracking, we need to print from back to front. + +```rust +let line_width = 4 + +fn pad_right(s : String, width : Int) -> String { + String::make(width - s.length(), ' ') + s +} + +fn print_edit(edit : Edit) -> String { + match edit { + Insert(_) as edit => { + let tag = "+" + let old_line = pad_right("", line_width) + let new_line = pad_right(edit.new.number.to_string(), line_width) + let text = edit.new.text + "\{tag} \{old_line} \{new_line} \{text}" + } + Delete(_) as edit => { + let tag = "-" + let old_line = pad_right(edit.old.number.to_string(), line_width) + let new_line = pad_right("", line_width) + let text = edit.old.text + "\{tag} \{old_line} \{new_line} \{text}" + } + Equal(_) as edit => { + let tag = " " + let old_line = pad_right(edit.old.number.to_string(), line_width) + let new_line = pad_right(edit.new.number.to_string(), line_width) + let text = edit.old.text + "\{tag} \{old_line} \{new_line} \{text}" + } + } +} + +fn print_diff(diff : Array[Edit]) -> Unit { + for i = diff.length() - 1; i >= 0; i = i - 1 { + diff[i] + |> print_edit + |> println + } +} +``` + +The result is as follows: + +```diff +- 1 A +- 2 B + 3 1 C ++ 2 B + 4 3 A + 5 4 B +- 6 B + 7 5 A ++ 6 C +``` + +## Conclusion + +The Myers algorithm demonstrated above is complete, but due to the frequent copying of arrays, it has a very large space overhead. Therefore, most software like Git uses a linear variant of the diff algorithm (found in the appendix of the original paper). This variant may sometimes produce diffs of lower quality (harder for humans to read) than the standard Myers algorithm but can still ensure the shortest edit sequence. diff --git a/next/tutorial/example/myers-diff/myers-diff3.md b/next/tutorial/example/myers-diff/myers-diff3.md new file mode 100644 index 00000000..c18e039e --- /dev/null +++ b/next/tutorial/example/myers-diff/myers-diff3.md @@ -0,0 +1,216 @@ +# Myers diff 3 + +This article is the third in the [diff series](https://docs.moonbitlang.com/examples/myers-diff). In the [previous part](https://docs.moonbitlang.com/examples/myers-diff2), we explored the full Myers algorithm and its limitations. In this post, we'll learn how to implement a variant of the Myers algorithm that operates with linear space complexity. + +## Divide and Conquer + +The linear variant of Myers' diff algorithm used by Git employs a concept called the _Snake_ (sometimes referred to as the _Middle Snake_) to break down the entire search process. A Snake in the edit graph represents a diagonal movement of 0 to N steps after a single left or down move. The linear Myers algorithm finds the middle Snake on the optimal edit path and uses it to divide the entire edit graph into two parts. The subsequent steps apply the same technique to the resulting subgraphs, eventually producing a complete edit path. + +```bash + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 + 0 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 +``` + +> A quick recap: The optimal edit path is the one that has the shortest distance to the endpoint (a diagonal distance of zero), and there can be more than one such path. + +Attentive readers may have noticed a chicken-and-egg problem: to find a Snake, you need an optimal edit path, but to get an optimal edit path, it seems like you need to run the original Myers algorithm first. + +In fact, the idea behind the linear Myers algorithm is somewhat unconventional: it alternates the original Myers algorithm from both the top-left and bottom-right corners, but without storing the history. Instead, it simply checks if the searches from both sides overlap. When they do, the overlapping portion is returned as the Middle Snake. + +This approach seems straightforward, but there are still some details to sort out. + +When searching from the bottom-right, the diagonal coordinate can no longer be referred to as _k_. We need to define a new diagonal coordinate **c = k - delta**. This coordinate is the mirror image of _k_, perfectly suited for reverse direction search. + +```bash + x k + 0 1 2 3 + 0 1 2 3 \ \ \ \ + y 0 o-----o-----o-----o o-----o-----o-----o + | | | | -1 | | | | \ + | | | | \ | | | | 2 + 1 o-----o-----o-----o o-----o-----o-----o + | | \ | | -2 | | \ | | \ + | | \ | | \ | | \ | | 1 + 2 o-----o-----o-----o o-----o-----o-----o + \ \ \ \ + -3 -2 -1 0 + c +``` + +How do we determine if the searches overlap? Simply check if the position on a diagonal line in the forward search has an _x_ value greater than that in the reverse search. However, since the _k_ and _c_ coordinates differ for the same diagonal, the conversion can be a bit tricky. + +### Code Implementation + +We'll start by defining `Snake` and `Box` types, representing the middle snake and the sub-edit graphs (since they're square, we call them `Box`). + +```rust +struct Box { + left : Int, + right : Int, + top : Int, + bottom : 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() +} +``` + +To avoid getting bogged down in details too early, let's assume we already have a function `midpoint : (Box, Array[Line], Array[Line]) -> Snake?` to find the middle snake. Then, we can build the function `find_path` to search for the complete path. + +```rust +fn find_path(box : Box, a : Array[Line], b : Array[Line]) -> Iter[(Int, Int)]? { + let snake = midpoint(box, a, b)?; + let start = snake.start; + let end = snake.end; + let 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)) +} +``` + +The implementation of `find_path` is straightforward, but `midpoint` is a bit more complex: + +- For a `Box` of size 0, return `None`. +- Calculate the search boundaries. Since forward and backward searches each cover half the distance, divide by two. However, if the size of the `Box` is odd, add one more to the forward search boundary. +- Store the results of the forward and backward searches in two arrays. +- Alternate between forward and backward searches, returning `None` if no result is found. + +```rust +fn midpoint(self : Box, a : Array[Line], b : Array[Line]) -> Snake? { + if self.size() == 0 { + return None; + } + let max = { + let half = self.size() / 2; + if is_odd(self.size()) { + half + 1 + } else { + half + } + }; + let vf = BPArray::make(2 * max + 1, 0); + vf[1] = self.left; + let vb = BPArray::make(2 * max + 1, 0); + vb[1] = self.bottom; + for d in 0..max + 1 { + match forward(self, vf, vb, d, a, b) { + None => + match backward(self, vf, vb, d, a, b) { + None => continue, + res => return res, + }, + res => return res, + } + } + None +} +``` + +The forward and backward searches have some modifications compared to the original Myers algorithm, which need a bit of explanation: + +- Since we need to return the snake, the search process must calculate the previous coordinate (`px` stands for previous x). +- The search now works within a `Box` (not the global edit graph), so calculating `y` from `x` (or vice versa) requires conversion. +- The backward search minimizes `y` as a heuristic strategy, but minimizing `x` would also work. + +```rust +fn forward(self : Box, vf : BPArray, vb : BPArray, d : Int, a : Array[Line], b : Array[Line]) -> Snake? { + for k in (0..=d).rev() { + let c = k - self.delta(); + let (mut x, mut px) = if k == -d || (k != d && vf[k - 1] < vf[k + + + 1]) { + (vf[k + 1], vf[k + 1]) + } else { + (vf[k - 1] + 1, vf[k - 1]) + }; + let mut y = self.top + (x - self.left) - k; + let py = if d == 0 || x != px { y } else { y - 1 }; + while x < self.right && y < self.bottom && a[x].text == b[y].text { + x += 1; + y += 1; + } + vf[k] = x; + if is_odd(self.delta()) && (c >= -(d - 1) && c <= d - 1) && y >= vb[c] { + return Some(Snake { start: (px, py), end: (x, y) }); + } + } + None +} + +fn backward(self : Box, vf : BPArray, vb : BPArray, d : Int, a : Array[Line], b : Array[Line]) -> Snake? { + for c in (0..=d).rev() { + let k = c + self.delta(); + let (mut y, mut py) = if c == -d || (c != d && vb[c - 1] > vb[c + 1]) { + (vb[c + 1], vb[c + 1]) + } else { + (vb[c - 1] - 1, vb[c - 1]) + }; + let mut x = self.left + (y - self.top) + k; + let 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 -= 1; + y -= 1; + } + vb[c] = y; + if is_even(self.delta()) && (k >= -d && k <= d) && x <= vf[k] { + return Some(Snake { start: (x, y), end: (px, py) }); + } + } + None +} +``` + +## Conclusion + +In addition to the default diff algorithm, Git also offers another diff algorithm called patience diff. It differs significantly from Myers diff in approach and sometimes produces more readable diff results.