xref: /aosp_15_r20/external/go-cmp/cmp/internal/diff/diff.go (revision 88d15eac089d7f20c739ff1001d56b91872b21a1)
1*88d15eacSSasha Smundak// Copyright 2017, The Go Authors. All rights reserved.
2*88d15eacSSasha Smundak// Use of this source code is governed by a BSD-style
3*88d15eacSSasha Smundak// license that can be found in the LICENSE file.
4*88d15eacSSasha Smundak
5*88d15eacSSasha Smundak// Package diff implements an algorithm for producing edit-scripts.
6*88d15eacSSasha Smundak// The edit-script is a sequence of operations needed to transform one list
7*88d15eacSSasha Smundak// of symbols into another (or vice-versa). The edits allowed are insertions,
8*88d15eacSSasha Smundak// deletions, and modifications. The summation of all edits is called the
9*88d15eacSSasha Smundak// Levenshtein distance as this problem is well-known in computer science.
10*88d15eacSSasha Smundak//
11*88d15eacSSasha Smundak// This package prioritizes performance over accuracy. That is, the run time
12*88d15eacSSasha Smundak// is more important than obtaining a minimal Levenshtein distance.
13*88d15eacSSasha Smundakpackage diff
14*88d15eacSSasha Smundak
15*88d15eacSSasha Smundakimport (
16*88d15eacSSasha Smundak	"math/rand"
17*88d15eacSSasha Smundak	"time"
18*88d15eacSSasha Smundak
19*88d15eacSSasha Smundak	"github.com/google/go-cmp/cmp/internal/flags"
20*88d15eacSSasha Smundak)
21*88d15eacSSasha Smundak
22*88d15eacSSasha Smundak// EditType represents a single operation within an edit-script.
23*88d15eacSSasha Smundaktype EditType uint8
24*88d15eacSSasha Smundak
25*88d15eacSSasha Smundakconst (
26*88d15eacSSasha Smundak	// Identity indicates that a symbol pair is identical in both list X and Y.
27*88d15eacSSasha Smundak	Identity EditType = iota
28*88d15eacSSasha Smundak	// UniqueX indicates that a symbol only exists in X and not Y.
29*88d15eacSSasha Smundak	UniqueX
30*88d15eacSSasha Smundak	// UniqueY indicates that a symbol only exists in Y and not X.
31*88d15eacSSasha Smundak	UniqueY
32*88d15eacSSasha Smundak	// Modified indicates that a symbol pair is a modification of each other.
33*88d15eacSSasha Smundak	Modified
34*88d15eacSSasha Smundak)
35*88d15eacSSasha Smundak
36*88d15eacSSasha Smundak// EditScript represents the series of differences between two lists.
37*88d15eacSSasha Smundaktype EditScript []EditType
38*88d15eacSSasha Smundak
39*88d15eacSSasha Smundak// String returns a human-readable string representing the edit-script where
40*88d15eacSSasha Smundak// Identity, UniqueX, UniqueY, and Modified are represented by the
41*88d15eacSSasha Smundak// '.', 'X', 'Y', and 'M' characters, respectively.
42*88d15eacSSasha Smundakfunc (es EditScript) String() string {
43*88d15eacSSasha Smundak	b := make([]byte, len(es))
44*88d15eacSSasha Smundak	for i, e := range es {
45*88d15eacSSasha Smundak		switch e {
46*88d15eacSSasha Smundak		case Identity:
47*88d15eacSSasha Smundak			b[i] = '.'
48*88d15eacSSasha Smundak		case UniqueX:
49*88d15eacSSasha Smundak			b[i] = 'X'
50*88d15eacSSasha Smundak		case UniqueY:
51*88d15eacSSasha Smundak			b[i] = 'Y'
52*88d15eacSSasha Smundak		case Modified:
53*88d15eacSSasha Smundak			b[i] = 'M'
54*88d15eacSSasha Smundak		default:
55*88d15eacSSasha Smundak			panic("invalid edit-type")
56*88d15eacSSasha Smundak		}
57*88d15eacSSasha Smundak	}
58*88d15eacSSasha Smundak	return string(b)
59*88d15eacSSasha Smundak}
60*88d15eacSSasha Smundak
61*88d15eacSSasha Smundak// stats returns a histogram of the number of each type of edit operation.
62*88d15eacSSasha Smundakfunc (es EditScript) stats() (s struct{ NI, NX, NY, NM int }) {
63*88d15eacSSasha Smundak	for _, e := range es {
64*88d15eacSSasha Smundak		switch e {
65*88d15eacSSasha Smundak		case Identity:
66*88d15eacSSasha Smundak			s.NI++
67*88d15eacSSasha Smundak		case UniqueX:
68*88d15eacSSasha Smundak			s.NX++
69*88d15eacSSasha Smundak		case UniqueY:
70*88d15eacSSasha Smundak			s.NY++
71*88d15eacSSasha Smundak		case Modified:
72*88d15eacSSasha Smundak			s.NM++
73*88d15eacSSasha Smundak		default:
74*88d15eacSSasha Smundak			panic("invalid edit-type")
75*88d15eacSSasha Smundak		}
76*88d15eacSSasha Smundak	}
77*88d15eacSSasha Smundak	return
78*88d15eacSSasha Smundak}
79*88d15eacSSasha Smundak
80*88d15eacSSasha Smundak// Dist is the Levenshtein distance and is guaranteed to be 0 if and only if
81*88d15eacSSasha Smundak// lists X and Y are equal.
82*88d15eacSSasha Smundakfunc (es EditScript) Dist() int { return len(es) - es.stats().NI }
83*88d15eacSSasha Smundak
84*88d15eacSSasha Smundak// LenX is the length of the X list.
85*88d15eacSSasha Smundakfunc (es EditScript) LenX() int { return len(es) - es.stats().NY }
86*88d15eacSSasha Smundak
87*88d15eacSSasha Smundak// LenY is the length of the Y list.
88*88d15eacSSasha Smundakfunc (es EditScript) LenY() int { return len(es) - es.stats().NX }
89*88d15eacSSasha Smundak
90*88d15eacSSasha Smundak// EqualFunc reports whether the symbols at indexes ix and iy are equal.
91*88d15eacSSasha Smundak// When called by Difference, the index is guaranteed to be within nx and ny.
92*88d15eacSSasha Smundaktype EqualFunc func(ix int, iy int) Result
93*88d15eacSSasha Smundak
94*88d15eacSSasha Smundak// Result is the result of comparison.
95*88d15eacSSasha Smundak// NumSame is the number of sub-elements that are equal.
96*88d15eacSSasha Smundak// NumDiff is the number of sub-elements that are not equal.
97*88d15eacSSasha Smundaktype Result struct{ NumSame, NumDiff int }
98*88d15eacSSasha Smundak
99*88d15eacSSasha Smundak// BoolResult returns a Result that is either Equal or not Equal.
100*88d15eacSSasha Smundakfunc BoolResult(b bool) Result {
101*88d15eacSSasha Smundak	if b {
102*88d15eacSSasha Smundak		return Result{NumSame: 1} // Equal, Similar
103*88d15eacSSasha Smundak	} else {
104*88d15eacSSasha Smundak		return Result{NumDiff: 2} // Not Equal, not Similar
105*88d15eacSSasha Smundak	}
106*88d15eacSSasha Smundak}
107*88d15eacSSasha Smundak
108*88d15eacSSasha Smundak// Equal indicates whether the symbols are equal. Two symbols are equal
109*88d15eacSSasha Smundak// if and only if NumDiff == 0. If Equal, then they are also Similar.
110*88d15eacSSasha Smundakfunc (r Result) Equal() bool { return r.NumDiff == 0 }
111*88d15eacSSasha Smundak
112*88d15eacSSasha Smundak// Similar indicates whether two symbols are similar and may be represented
113*88d15eacSSasha Smundak// by using the Modified type. As a special case, we consider binary comparisons
114*88d15eacSSasha Smundak// (i.e., those that return Result{1, 0} or Result{0, 1}) to be similar.
115*88d15eacSSasha Smundak//
116*88d15eacSSasha Smundak// The exact ratio of NumSame to NumDiff to determine similarity may change.
117*88d15eacSSasha Smundakfunc (r Result) Similar() bool {
118*88d15eacSSasha Smundak	// Use NumSame+1 to offset NumSame so that binary comparisons are similar.
119*88d15eacSSasha Smundak	return r.NumSame+1 >= r.NumDiff
120*88d15eacSSasha Smundak}
121*88d15eacSSasha Smundak
122*88d15eacSSasha Smundakvar randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
123*88d15eacSSasha Smundak
124*88d15eacSSasha Smundak// Difference reports whether two lists of lengths nx and ny are equal
125*88d15eacSSasha Smundak// given the definition of equality provided as f.
126*88d15eacSSasha Smundak//
127*88d15eacSSasha Smundak// This function returns an edit-script, which is a sequence of operations
128*88d15eacSSasha Smundak// needed to convert one list into the other. The following invariants for
129*88d15eacSSasha Smundak// the edit-script are maintained:
130*88d15eacSSasha Smundak//   - eq == (es.Dist()==0)
131*88d15eacSSasha Smundak//   - nx == es.LenX()
132*88d15eacSSasha Smundak//   - ny == es.LenY()
133*88d15eacSSasha Smundak//
134*88d15eacSSasha Smundak// This algorithm is not guaranteed to be an optimal solution (i.e., one that
135*88d15eacSSasha Smundak// produces an edit-script with a minimal Levenshtein distance). This algorithm
136*88d15eacSSasha Smundak// favors performance over optimality. The exact output is not guaranteed to
137*88d15eacSSasha Smundak// be stable and may change over time.
138*88d15eacSSasha Smundakfunc Difference(nx, ny int, f EqualFunc) (es EditScript) {
139*88d15eacSSasha Smundak	// This algorithm is based on traversing what is known as an "edit-graph".
140*88d15eacSSasha Smundak	// See Figure 1 from "An O(ND) Difference Algorithm and Its Variations"
141*88d15eacSSasha Smundak	// by Eugene W. Myers. Since D can be as large as N itself, this is
142*88d15eacSSasha Smundak	// effectively O(N^2). Unlike the algorithm from that paper, we are not
143*88d15eacSSasha Smundak	// interested in the optimal path, but at least some "decent" path.
144*88d15eacSSasha Smundak	//
145*88d15eacSSasha Smundak	// For example, let X and Y be lists of symbols:
146*88d15eacSSasha Smundak	//	X = [A B C A B B A]
147*88d15eacSSasha Smundak	//	Y = [C B A B A C]
148*88d15eacSSasha Smundak	//
149*88d15eacSSasha Smundak	// The edit-graph can be drawn as the following:
150*88d15eacSSasha Smundak	//	   A B C A B B A
151*88d15eacSSasha Smundak	//	  ┌─────────────┐
152*88d15eacSSasha Smundak	//	C │_|_|\|_|_|_|_│ 0
153*88d15eacSSasha Smundak	//	B │_|\|_|_|\|\|_│ 1
154*88d15eacSSasha Smundak	//	A │\|_|_|\|_|_|\│ 2
155*88d15eacSSasha Smundak	//	B │_|\|_|_|\|\|_│ 3
156*88d15eacSSasha Smundak	//	A │\|_|_|\|_|_|\│ 4
157*88d15eacSSasha Smundak	//	C │ | |\| | | | │ 5
158*88d15eacSSasha Smundak	//	  └─────────────┘ 6
159*88d15eacSSasha Smundak	//	   0 1 2 3 4 5 6 7
160*88d15eacSSasha Smundak	//
161*88d15eacSSasha Smundak	// List X is written along the horizontal axis, while list Y is written
162*88d15eacSSasha Smundak	// along the vertical axis. At any point on this grid, if the symbol in
163*88d15eacSSasha Smundak	// list X matches the corresponding symbol in list Y, then a '\' is drawn.
164*88d15eacSSasha Smundak	// The goal of any minimal edit-script algorithm is to find a path from the
165*88d15eacSSasha Smundak	// top-left corner to the bottom-right corner, while traveling through the
166*88d15eacSSasha Smundak	// fewest horizontal or vertical edges.
167*88d15eacSSasha Smundak	// A horizontal edge is equivalent to inserting a symbol from list X.
168*88d15eacSSasha Smundak	// A vertical edge is equivalent to inserting a symbol from list Y.
169*88d15eacSSasha Smundak	// A diagonal edge is equivalent to a matching symbol between both X and Y.
170*88d15eacSSasha Smundak
171*88d15eacSSasha Smundak	// Invariants:
172*88d15eacSSasha Smundak	//   - 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
173*88d15eacSSasha Smundak	//   - 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
174*88d15eacSSasha Smundak	//
175*88d15eacSSasha Smundak	// In general:
176*88d15eacSSasha Smundak	//   - fwdFrontier.X < revFrontier.X
177*88d15eacSSasha Smundak	//   - fwdFrontier.Y < revFrontier.Y
178*88d15eacSSasha Smundak	//
179*88d15eacSSasha Smundak	// Unless, it is time for the algorithm to terminate.
180*88d15eacSSasha Smundak	fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)}
181*88d15eacSSasha Smundak	revPath := path{-1, point{nx, ny}, make(EditScript, 0)}
182*88d15eacSSasha Smundak	fwdFrontier := fwdPath.point // Forward search frontier
183*88d15eacSSasha Smundak	revFrontier := revPath.point // Reverse search frontier
184*88d15eacSSasha Smundak
185*88d15eacSSasha Smundak	// Search budget bounds the cost of searching for better paths.
186*88d15eacSSasha Smundak	// The longest sequence of non-matching symbols that can be tolerated is
187*88d15eacSSasha Smundak	// approximately the square-root of the search budget.
188*88d15eacSSasha Smundak	searchBudget := 4 * (nx + ny) // O(n)
189*88d15eacSSasha Smundak
190*88d15eacSSasha Smundak	// Running the tests with the "cmp_debug" build tag prints a visualization
191*88d15eacSSasha Smundak	// of the algorithm running in real-time. This is educational for
192*88d15eacSSasha Smundak	// understanding how the algorithm works. See debug_enable.go.
193*88d15eacSSasha Smundak	f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es)
194*88d15eacSSasha Smundak
195*88d15eacSSasha Smundak	// The algorithm below is a greedy, meet-in-the-middle algorithm for
196*88d15eacSSasha Smundak	// computing sub-optimal edit-scripts between two lists.
197*88d15eacSSasha Smundak	//
198*88d15eacSSasha Smundak	// The algorithm is approximately as follows:
199*88d15eacSSasha Smundak	//   - Searching for differences switches back-and-forth between
200*88d15eacSSasha Smundak	//     a search that starts at the beginning (the top-left corner), and
201*88d15eacSSasha Smundak	//     a search that starts at the end (the bottom-right corner).
202*88d15eacSSasha Smundak	//     The goal of the search is connect with the search
203*88d15eacSSasha Smundak	//     from the opposite corner.
204*88d15eacSSasha Smundak	//   - As we search, we build a path in a greedy manner,
205*88d15eacSSasha Smundak	//     where the first match seen is added to the path (this is sub-optimal,
206*88d15eacSSasha Smundak	//     but provides a decent result in practice). When matches are found,
207*88d15eacSSasha Smundak	//     we try the next pair of symbols in the lists and follow all matches
208*88d15eacSSasha Smundak	//     as far as possible.
209*88d15eacSSasha Smundak	//   - When searching for matches, we search along a diagonal going through
210*88d15eacSSasha Smundak	//     through the "frontier" point. If no matches are found,
211*88d15eacSSasha Smundak	//     we advance the frontier towards the opposite corner.
212*88d15eacSSasha Smundak	//   - This algorithm terminates when either the X coordinates or the
213*88d15eacSSasha Smundak	//     Y coordinates of the forward and reverse frontier points ever intersect.
214*88d15eacSSasha Smundak
215*88d15eacSSasha Smundak	// This algorithm is correct even if searching only in the forward direction
216*88d15eacSSasha Smundak	// or in the reverse direction. We do both because it is commonly observed
217*88d15eacSSasha Smundak	// that two lists commonly differ because elements were added to the front
218*88d15eacSSasha Smundak	// or end of the other list.
219*88d15eacSSasha Smundak	//
220*88d15eacSSasha Smundak	// Non-deterministically start with either the forward or reverse direction
221*88d15eacSSasha Smundak	// to introduce some deliberate instability so that we have the flexibility
222*88d15eacSSasha Smundak	// to change this algorithm in the future.
223*88d15eacSSasha Smundak	if flags.Deterministic || randBool {
224*88d15eacSSasha Smundak		goto forwardSearch
225*88d15eacSSasha Smundak	} else {
226*88d15eacSSasha Smundak		goto reverseSearch
227*88d15eacSSasha Smundak	}
228*88d15eacSSasha Smundak
229*88d15eacSSasha SmundakforwardSearch:
230*88d15eacSSasha Smundak	{
231*88d15eacSSasha Smundak		// Forward search from the beginning.
232*88d15eacSSasha Smundak		if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
233*88d15eacSSasha Smundak			goto finishSearch
234*88d15eacSSasha Smundak		}
235*88d15eacSSasha Smundak		for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
236*88d15eacSSasha Smundak			// Search in a diagonal pattern for a match.
237*88d15eacSSasha Smundak			z := zigzag(i)
238*88d15eacSSasha Smundak			p := point{fwdFrontier.X + z, fwdFrontier.Y - z}
239*88d15eacSSasha Smundak			switch {
240*88d15eacSSasha Smundak			case p.X >= revPath.X || p.Y < fwdPath.Y:
241*88d15eacSSasha Smundak				stop1 = true // Hit top-right corner
242*88d15eacSSasha Smundak			case p.Y >= revPath.Y || p.X < fwdPath.X:
243*88d15eacSSasha Smundak				stop2 = true // Hit bottom-left corner
244*88d15eacSSasha Smundak			case f(p.X, p.Y).Equal():
245*88d15eacSSasha Smundak				// Match found, so connect the path to this point.
246*88d15eacSSasha Smundak				fwdPath.connect(p, f)
247*88d15eacSSasha Smundak				fwdPath.append(Identity)
248*88d15eacSSasha Smundak				// Follow sequence of matches as far as possible.
249*88d15eacSSasha Smundak				for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
250*88d15eacSSasha Smundak					if !f(fwdPath.X, fwdPath.Y).Equal() {
251*88d15eacSSasha Smundak						break
252*88d15eacSSasha Smundak					}
253*88d15eacSSasha Smundak					fwdPath.append(Identity)
254*88d15eacSSasha Smundak				}
255*88d15eacSSasha Smundak				fwdFrontier = fwdPath.point
256*88d15eacSSasha Smundak				stop1, stop2 = true, true
257*88d15eacSSasha Smundak			default:
258*88d15eacSSasha Smundak				searchBudget-- // Match not found
259*88d15eacSSasha Smundak			}
260*88d15eacSSasha Smundak			debug.Update()
261*88d15eacSSasha Smundak		}
262*88d15eacSSasha Smundak		// Advance the frontier towards reverse point.
263*88d15eacSSasha Smundak		if revPath.X-fwdFrontier.X >= revPath.Y-fwdFrontier.Y {
264*88d15eacSSasha Smundak			fwdFrontier.X++
265*88d15eacSSasha Smundak		} else {
266*88d15eacSSasha Smundak			fwdFrontier.Y++
267*88d15eacSSasha Smundak		}
268*88d15eacSSasha Smundak		goto reverseSearch
269*88d15eacSSasha Smundak	}
270*88d15eacSSasha Smundak
271*88d15eacSSasha SmundakreverseSearch:
272*88d15eacSSasha Smundak	{
273*88d15eacSSasha Smundak		// Reverse search from the end.
274*88d15eacSSasha Smundak		if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
275*88d15eacSSasha Smundak			goto finishSearch
276*88d15eacSSasha Smundak		}
277*88d15eacSSasha Smundak		for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
278*88d15eacSSasha Smundak			// Search in a diagonal pattern for a match.
279*88d15eacSSasha Smundak			z := zigzag(i)
280*88d15eacSSasha Smundak			p := point{revFrontier.X - z, revFrontier.Y + z}
281*88d15eacSSasha Smundak			switch {
282*88d15eacSSasha Smundak			case fwdPath.X >= p.X || revPath.Y < p.Y:
283*88d15eacSSasha Smundak				stop1 = true // Hit bottom-left corner
284*88d15eacSSasha Smundak			case fwdPath.Y >= p.Y || revPath.X < p.X:
285*88d15eacSSasha Smundak				stop2 = true // Hit top-right corner
286*88d15eacSSasha Smundak			case f(p.X-1, p.Y-1).Equal():
287*88d15eacSSasha Smundak				// Match found, so connect the path to this point.
288*88d15eacSSasha Smundak				revPath.connect(p, f)
289*88d15eacSSasha Smundak				revPath.append(Identity)
290*88d15eacSSasha Smundak				// Follow sequence of matches as far as possible.
291*88d15eacSSasha Smundak				for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
292*88d15eacSSasha Smundak					if !f(revPath.X-1, revPath.Y-1).Equal() {
293*88d15eacSSasha Smundak						break
294*88d15eacSSasha Smundak					}
295*88d15eacSSasha Smundak					revPath.append(Identity)
296*88d15eacSSasha Smundak				}
297*88d15eacSSasha Smundak				revFrontier = revPath.point
298*88d15eacSSasha Smundak				stop1, stop2 = true, true
299*88d15eacSSasha Smundak			default:
300*88d15eacSSasha Smundak				searchBudget-- // Match not found
301*88d15eacSSasha Smundak			}
302*88d15eacSSasha Smundak			debug.Update()
303*88d15eacSSasha Smundak		}
304*88d15eacSSasha Smundak		// Advance the frontier towards forward point.
305*88d15eacSSasha Smundak		if revFrontier.X-fwdPath.X >= revFrontier.Y-fwdPath.Y {
306*88d15eacSSasha Smundak			revFrontier.X--
307*88d15eacSSasha Smundak		} else {
308*88d15eacSSasha Smundak			revFrontier.Y--
309*88d15eacSSasha Smundak		}
310*88d15eacSSasha Smundak		goto forwardSearch
311*88d15eacSSasha Smundak	}
312*88d15eacSSasha Smundak
313*88d15eacSSasha SmundakfinishSearch:
314*88d15eacSSasha Smundak	// Join the forward and reverse paths and then append the reverse path.
315*88d15eacSSasha Smundak	fwdPath.connect(revPath.point, f)
316*88d15eacSSasha Smundak	for i := len(revPath.es) - 1; i >= 0; i-- {
317*88d15eacSSasha Smundak		t := revPath.es[i]
318*88d15eacSSasha Smundak		revPath.es = revPath.es[:i]
319*88d15eacSSasha Smundak		fwdPath.append(t)
320*88d15eacSSasha Smundak	}
321*88d15eacSSasha Smundak	debug.Finish()
322*88d15eacSSasha Smundak	return fwdPath.es
323*88d15eacSSasha Smundak}
324*88d15eacSSasha Smundak
325*88d15eacSSasha Smundaktype path struct {
326*88d15eacSSasha Smundak	dir   int // +1 if forward, -1 if reverse
327*88d15eacSSasha Smundak	point     // Leading point of the EditScript path
328*88d15eacSSasha Smundak	es    EditScript
329*88d15eacSSasha Smundak}
330*88d15eacSSasha Smundak
331*88d15eacSSasha Smundak// connect appends any necessary Identity, Modified, UniqueX, or UniqueY types
332*88d15eacSSasha Smundak// to the edit-script to connect p.point to dst.
333*88d15eacSSasha Smundakfunc (p *path) connect(dst point, f EqualFunc) {
334*88d15eacSSasha Smundak	if p.dir > 0 {
335*88d15eacSSasha Smundak		// Connect in forward direction.
336*88d15eacSSasha Smundak		for dst.X > p.X && dst.Y > p.Y {
337*88d15eacSSasha Smundak			switch r := f(p.X, p.Y); {
338*88d15eacSSasha Smundak			case r.Equal():
339*88d15eacSSasha Smundak				p.append(Identity)
340*88d15eacSSasha Smundak			case r.Similar():
341*88d15eacSSasha Smundak				p.append(Modified)
342*88d15eacSSasha Smundak			case dst.X-p.X >= dst.Y-p.Y:
343*88d15eacSSasha Smundak				p.append(UniqueX)
344*88d15eacSSasha Smundak			default:
345*88d15eacSSasha Smundak				p.append(UniqueY)
346*88d15eacSSasha Smundak			}
347*88d15eacSSasha Smundak		}
348*88d15eacSSasha Smundak		for dst.X > p.X {
349*88d15eacSSasha Smundak			p.append(UniqueX)
350*88d15eacSSasha Smundak		}
351*88d15eacSSasha Smundak		for dst.Y > p.Y {
352*88d15eacSSasha Smundak			p.append(UniqueY)
353*88d15eacSSasha Smundak		}
354*88d15eacSSasha Smundak	} else {
355*88d15eacSSasha Smundak		// Connect in reverse direction.
356*88d15eacSSasha Smundak		for p.X > dst.X && p.Y > dst.Y {
357*88d15eacSSasha Smundak			switch r := f(p.X-1, p.Y-1); {
358*88d15eacSSasha Smundak			case r.Equal():
359*88d15eacSSasha Smundak				p.append(Identity)
360*88d15eacSSasha Smundak			case r.Similar():
361*88d15eacSSasha Smundak				p.append(Modified)
362*88d15eacSSasha Smundak			case p.Y-dst.Y >= p.X-dst.X:
363*88d15eacSSasha Smundak				p.append(UniqueY)
364*88d15eacSSasha Smundak			default:
365*88d15eacSSasha Smundak				p.append(UniqueX)
366*88d15eacSSasha Smundak			}
367*88d15eacSSasha Smundak		}
368*88d15eacSSasha Smundak		for p.X > dst.X {
369*88d15eacSSasha Smundak			p.append(UniqueX)
370*88d15eacSSasha Smundak		}
371*88d15eacSSasha Smundak		for p.Y > dst.Y {
372*88d15eacSSasha Smundak			p.append(UniqueY)
373*88d15eacSSasha Smundak		}
374*88d15eacSSasha Smundak	}
375*88d15eacSSasha Smundak}
376*88d15eacSSasha Smundak
377*88d15eacSSasha Smundakfunc (p *path) append(t EditType) {
378*88d15eacSSasha Smundak	p.es = append(p.es, t)
379*88d15eacSSasha Smundak	switch t {
380*88d15eacSSasha Smundak	case Identity, Modified:
381*88d15eacSSasha Smundak		p.add(p.dir, p.dir)
382*88d15eacSSasha Smundak	case UniqueX:
383*88d15eacSSasha Smundak		p.add(p.dir, 0)
384*88d15eacSSasha Smundak	case UniqueY:
385*88d15eacSSasha Smundak		p.add(0, p.dir)
386*88d15eacSSasha Smundak	}
387*88d15eacSSasha Smundak	debug.Update()
388*88d15eacSSasha Smundak}
389*88d15eacSSasha Smundak
390*88d15eacSSasha Smundaktype point struct{ X, Y int }
391*88d15eacSSasha Smundak
392*88d15eacSSasha Smundakfunc (p *point) add(dx, dy int) { p.X += dx; p.Y += dy }
393*88d15eacSSasha Smundak
394*88d15eacSSasha Smundak// zigzag maps a consecutive sequence of integers to a zig-zag sequence.
395*88d15eacSSasha Smundak//
396*88d15eacSSasha Smundak//	[0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...]
397*88d15eacSSasha Smundakfunc zigzag(x int) int {
398*88d15eacSSasha Smundak	if x&1 != 0 {
399*88d15eacSSasha Smundak		x = ^x
400*88d15eacSSasha Smundak	}
401*88d15eacSSasha Smundak	return x >> 1
402*88d15eacSSasha Smundak}
403