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