// Copyright 2022 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package lcs import ( "log" "sort" ) // lcs is a longest common sequence type lcs []diag // A diag is a piece of the edit graph where A[X+i] == B[Y+i], for 0<=i l[j].Len }) return l } // validate that the elements of the lcs do not overlap // (can only happen when the two-sided algorithm ends early) // expects the lcs to be sorted func (l lcs) valid() bool { for i := 1; i < len(l); i++ { if l[i-1].X+l[i-1].Len > l[i].X { return false } if l[i-1].Y+l[i-1].Len > l[i].Y { return false } } return true } // repair overlapping lcs // only called if two-sided stops early func (l lcs) fix() lcs { // from the set of diagonals in l, find a maximal non-conflicting set // this problem may be NP-complete, but we use a greedy heuristic, // which is quadratic, but with a better data structure, could be D log D. // indepedent is not enough: {0,3,1} and {3,0,2} can't both occur in an lcs // which has to have monotone x and y if len(l) == 0 { return nil } sort.Slice(l, func(i, j int) bool { return l[i].Len > l[j].Len }) tmp := make(lcs, 0, len(l)) tmp = append(tmp, l[0]) for i := 1; i < len(l); i++ { var dir direction nxt := l[i] for _, in := range tmp { if dir, nxt = overlap(in, nxt); dir == empty || dir == bad { break } } if nxt.Len > 0 && dir != bad { tmp = append(tmp, nxt) } } tmp.sort() if false && !tmp.valid() { // debug checking log.Fatalf("here %d", len(tmp)) } return tmp } type direction int const ( empty direction = iota // diag is empty (so not in lcs) leftdown // proposed acceptably to the left and below rightup // proposed diag is acceptably to the right and above bad // proposed diag is inconsistent with the lcs so far ) // overlap trims the proposed diag prop so it doesn't overlap with // the existing diag that has already been added to the lcs. func overlap(exist, prop diag) (direction, diag) { if prop.X <= exist.X && exist.X < prop.X+prop.Len { // remove the end of prop where it overlaps with the X end of exist delta := prop.X + prop.Len - exist.X prop.Len -= delta if prop.Len <= 0 { return empty, prop } } if exist.X <= prop.X && prop.X < exist.X+exist.Len { // remove the beginning of prop where overlaps with exist delta := exist.X + exist.Len - prop.X prop.Len -= delta if prop.Len <= 0 { return empty, prop } prop.X += delta prop.Y += delta } if prop.Y <= exist.Y && exist.Y < prop.Y+prop.Len { // remove the end of prop that overlaps (in Y) with exist delta := prop.Y + prop.Len - exist.Y prop.Len -= delta if prop.Len <= 0 { return empty, prop } } if exist.Y <= prop.Y && prop.Y < exist.Y+exist.Len { // remove the beginning of peop that overlaps with exist delta := exist.Y + exist.Len - prop.Y prop.Len -= delta if prop.Len <= 0 { return empty, prop } prop.X += delta // no test reaches this code prop.Y += delta } if prop.X+prop.Len <= exist.X && prop.Y+prop.Len <= exist.Y { return leftdown, prop } if exist.X+exist.Len <= prop.X && exist.Y+exist.Len <= prop.Y { return rightup, prop } // prop can't be in an lcs that contains exist return bad, prop } // manipulating Diag and lcs // prepend a diagonal (x,y)-(x+1,y+1) segment either to an empty lcs // or to its first Diag. prepend is only called to extend diagonals // the backward direction. func (lcs lcs) prepend(x, y int) lcs { if len(lcs) > 0 { d := &lcs[0] if int(d.X) == x+1 && int(d.Y) == y+1 { // extend the diagonal down and to the left d.X, d.Y = int(x), int(y) d.Len++ return lcs } } r := diag{X: int(x), Y: int(y), Len: 1} lcs = append([]diag{r}, lcs...) return lcs } // append appends a diagonal, or extends the existing one. // by adding the edge (x,y)-(x+1.y+1). append is only called // to extend diagonals in the forward direction. func (lcs lcs) append(x, y int) lcs { if len(lcs) > 0 { last := &lcs[len(lcs)-1] // Expand last element if adjoining. if last.X+last.Len == x && last.Y+last.Len == y { last.Len++ return lcs } } return append(lcs, diag{X: x, Y: y, Len: 1}) } // enforce constraint on d, k func ok(d, k int) bool { return d >= 0 && -d <= k && k <= d }