GoPLS Viewer

Home|gopls/internal/diff/lcs/doc.go
1// Copyright 2022 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// package lcs contains code to find longest-common-subsequences
6// (and diffs)
7package lcs
8
9/*
10Compute longest-common-subsequences of two slices A, B using
11algorithms from Myers' paper. A longest-common-subsequence
12(LCS from now on) of A and B is a maximal set of lexically increasing
13pairs of subscripts (x,y) with A[x]==B[y]. There may be many LCS, but
14they all have the same length. An LCS determines a sequence of edits
15that changes A into B.
16
17The key concept is the edit graph of A and B.
18If A has length N and B has length M, then the edit graph has
19vertices v[i][j] for 0 <= i <= N, 0 <= j <= M. There is a
20horizontal edge from v[i][j] to v[i+1][j] whenever both are in
21the graph, and a vertical edge from v[i][j] to f[i][j+1] similarly.
22When A[i] == B[j] there is a diagonal edge from v[i][j] to v[i+1][j+1].
23
24A path between in the graph between (0,0) and (N,M) determines a sequence
25of edits converting A into B: each horizontal edge corresponds to removing
26an element of A, and each vertical edge corresponds to inserting an
27element of B.
28
29A vertex (x,y) is on (forward) diagonal k if x-y=k. A path in the graph
30is of length D if it has D non-diagonal edges. The algorithms generate
31forward paths (in which at least one of x,y increases at each edge),
32or backward paths (in which at least one of x,y decreases at each edge),
33or a combination. (Note that the orientation is the traditional mathematical one,
34with the origin in the lower-left corner.)
35
36Here is the edit graph for A:"aabbaa", B:"aacaba". (I know the diagonals look weird.)
37          ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙
38   a      |   ___/‾‾‾   |   ___/‾‾‾   |             |             |   ___/‾‾‾   |   ___/‾‾‾   |
39          ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙
40   b      |             |             |   ___/‾‾‾   |   ___/‾‾‾   |             |             |
41          ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙
42   a      |   ___/‾‾‾   |   ___/‾‾‾   |             |             |   ___/‾‾‾   |   ___/‾‾‾   |
43          ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙
44   c      |             |             |             |             |             |             |
45          ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙
46   a      |   ___/‾‾‾   |   ___/‾‾‾   |             |             |   ___/‾‾‾   |   ___/‾‾‾   |
47          ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙
48   a      |   ___/‾‾‾   |   ___/‾‾‾   |             |             |   ___/‾‾‾   |   ___/‾‾‾   |
49          ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙
50                 a             a             b             b             a             a
51
52
53The algorithm labels a vertex (x,y) with D,k if it is on diagonal k and at
54the end of a maximal path of length D. (Because x-y=k it suffices to remember
55only the x coordinate of the vertex.)
56
57The forward algorithm: Find the longest diagonal starting at (0,0) and
58label its end with D=0,k=0. From that vertex take a vertical step and
59then follow the longest diagonal (up and to the right), and label that vertex
60with D=1,k=-1. From the D=0,k=0 point take a horizontal step and the follow
61the longest diagonal (up and to the right) and label that vertex
62D=1,k=1. In the same way, having labelled all the D vertices,
63from a vertex labelled D,k find two vertices
64tentatively labelled D+1,k-1 and D+1,k+1. There may be two on the same
65diagonal, in which case take the one with the larger x.
66
67Eventually the path gets to (N,M), and the diagonals on it are the LCS.
68
69Here is the edit graph with the ends of D-paths labelled. (So, for instance,
700/2,2 indicates that x=2,y=2 is labelled with 0, as it should be, since the first
71step is to go up the longest diagonal from (0,0).)
72A:"aabbaa", B:"aacaba"
73          ⊙   -------   ⊙   -------   ⊙   -------(3/3,6)-------   ⊙   -------(3/5,6)-------(4/6,6)
74   a      |   ___/‾‾‾   |   ___/‾‾‾   |             |             |   ___/‾‾‾   |   ___/‾‾‾   |
75          ⊙   -------   ⊙   -------   ⊙   -------(2/3,5)-------   ⊙   -------   ⊙   -------   ⊙
76   b      |             |             |   ___/‾‾‾   |   ___/‾‾‾   |             |             |
77          ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------(3/5,4)-------   ⊙
78   a      |   ___/‾‾‾   |   ___/‾‾‾   |             |             |   ___/‾‾‾   |   ___/‾‾‾   |
79          ⊙   -------   ⊙   -------(1/2,3)-------(2/3,3)-------   ⊙   -------   ⊙   -------   ⊙
80   c      |             |             |             |             |             |             |
81          ⊙   -------   ⊙   -------(0/2,2)-------(1/3,2)-------(2/4,2)-------(3/5,2)-------(4/6,2)
82   a      |   ___/‾‾‾   |   ___/‾‾‾   |             |             |   ___/‾‾‾   |   ___/‾‾‾   |
83          ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙
84   a      |   ___/‾‾‾   |   ___/‾‾‾   |             |             |   ___/‾‾‾   |   ___/‾‾‾   |
85          ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙   -------   ⊙
86                 a             a             b             b             a             a
87
88The 4-path is reconstructed starting at (4/6,6), horizontal to (3/5,6), diagonal to (3,4), vertical
89to (2/3,3), horizontal to (1/2,3), vertical to (0/2,2), and diagonal to (0,0). As expected,
90there are 4 non-diagonal steps, and the diagonals form an LCS.
91
92There is a symmetric backward algorithm, which gives (backwards labels are prefixed with a colon):
93A:"aabbaa", B:"aacaba"
94            ⊙   --------    ⊙   --------    ⊙   --------    ⊙   --------    ⊙   --------    ⊙   --------    ⊙
95    a       |   ____/‾‾‾    |   ____/‾‾‾    |               |               |   ____/‾‾‾    |   ____/‾‾‾    |
96            ⊙   --------    ⊙   --------    ⊙   --------    ⊙   --------    ⊙   --------(:0/5,5)--------    ⊙
97    b       |               |               |   ____/‾‾‾    |   ____/‾‾‾    |               |               |
98            ⊙   --------    ⊙   --------    ⊙   --------(:1/3,4)--------    ⊙   --------    ⊙   --------    ⊙
99    a       |   ____/‾‾‾    |   ____/‾‾‾    |               |               |   ____/‾‾‾    |   ____/‾‾‾    |
100        (:3/0,3)--------(:2/1,3)--------    ⊙   --------(:2/3,3)--------(:1/4,3)--------    ⊙   --------    ⊙
101    c       |               |               |               |               |               |               |
102            ⊙   --------    ⊙   --------    ⊙   --------(:3/3,2)--------(:2/4,2)--------    ⊙   --------    ⊙
103    a       |   ____/‾‾‾    |   ____/‾‾‾    |               |               |   ____/‾‾‾    |   ____/‾‾‾    |
104        (:3/0,1)--------    ⊙   --------    ⊙   --------    ⊙   --------(:3/4,1)--------    ⊙   --------    ⊙
105    a       |   ____/‾‾‾    |   ____/‾‾‾    |               |               |   ____/‾‾‾    |   ____/‾‾‾    |
106        (:4/0,0)--------    ⊙   --------    ⊙   --------    ⊙   --------(:4/4,0)--------    ⊙   --------    ⊙
107                    a               a               b               b               a               a
108
109Neither of these is ideal for use in an editor, where it is undesirable to send very long diffs to the
110front end. It's tricky to decide exactly what 'very long diffs' means, as "replace A by B" is very short.
111We want to control how big D can be, by stopping when it gets too large. The forward algorithm then
112privileges common prefixes, and the backward algorithm privileges common suffixes. Either is an undesirable
113asymmetry.
114
115Fortunately there is a two-sided algorithm, implied by results in Myers' paper. Here's what the labels in
116the edit graph look like.
117A:"aabbaa", B:"aacaba"
118             ⊙    ---------    ⊙    ---------    ⊙    ---------    ⊙    ---------    ⊙    ---------    ⊙    ---------    ⊙
119    a        |    ____/‾‾‾‾    |    ____/‾‾‾‾    |                 |                 |    ____/‾‾‾‾    |    ____/‾‾‾‾    |
120             ⊙    ---------    ⊙    ---------    ⊙    --------- (2/3,5) ---------    ⊙    --------- (:0/5,5)---------    ⊙
121    b        |                 |                 |    ____/‾‾‾‾    |    ____/‾‾‾‾    |                 |                 |
122             ⊙    ---------    ⊙    ---------    ⊙    --------- (:1/3,4)---------    ⊙    ---------    ⊙    ---------    ⊙
123    a        |    ____/‾‾‾‾    |    ____/‾‾‾‾    |                 |                 |    ____/‾‾‾‾    |    ____/‾‾‾‾    |
124             ⊙    --------- (:2/1,3)--------- (1/2,3) ---------(2:2/3,3)--------- (:1/4,3)---------    ⊙    ---------    ⊙
125    c        |                 |                 |                 |                 |                 |                 |
126             ⊙    ---------    ⊙    --------- (0/2,2) --------- (1/3,2) ---------(2:2/4,2)---------    ⊙    ---------    ⊙
127    a        |    ____/‾‾‾‾    |    ____/‾‾‾‾    |                 |                 |    ____/‾‾‾‾    |    ____/‾‾‾‾    |
128             ⊙    ---------    ⊙    ---------    ⊙    ---------    ⊙    ---------    ⊙    ---------    ⊙    ---------    ⊙
129    a        |    ____/‾‾‾‾    |    ____/‾‾‾‾    |                 |                 |    ____/‾‾‾‾    |    ____/‾‾‾‾    |
130             ⊙    ---------    ⊙    ---------    ⊙    ---------    ⊙    ---------    ⊙    ---------    ⊙    ---------    ⊙
131                      a                 a                 b                 b                 a                 a
132
133The algorithm stopped when it saw the backwards 2-path ending at (1,3) and the forwards 2-path ending at (3,5). The criterion
134is a backwards path ending at (u,v) and a forward path ending at (x,y), where u <= x and the two points are on the same
135diagonal. (Here the edgegraph has a diagonal, but the criterion is x-y=u-v.) Myers proves there is a forward
1362-path from (0,0) to (1,3), and that together with the backwards 2-path ending at (1,3) gives the expected 4-path.
137Unfortunately the forward path has to be constructed by another run of the forward algorithm; it can't be found from the
138computed labels. That is the worst case. Had the code noticed (x,y)=(u,v)=(3,3) the whole path could be reconstructed
139from the edgegraph. The implementation looks for a number of special cases to try to avoid computing an extra forward path.
140
141If the two-sided algorithm has stop early (because D has become too large) it will have found a forward LCS and a
142backwards LCS. Ideally these go with disjoint prefixes and suffixes of A and B, but disjointness may fail and the two
143computed LCS may conflict. (An easy example is where A is a suffix of B, and shares a short prefix. The backwards LCS
144is all of A, and the forward LCS is a prefix of A.) The algorithm combines the two
145to form a best-effort LCS. In the worst case the forward partial LCS may have to
146be recomputed.
147*/
148
149/* Eugene Myers paper is titled
150"An O(ND) Difference Algorithm and Its Variations"
151and can be found at
152http://www.xmailserver.org/diff2.pdf
153
154(There is a generic implementation of the algorithm the the repository with git hash
155b9ad7e4ade3a686d608e44475390ad428e60e7fc)
156*/
157
MembersX
Members
X