| // 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<Len. |
| // All computed diagonals are parts of a longest common subsequence. |
| type diag struct { |
| X, Y int |
| Len int |
| } |
| |
| // sort sorts in place, by lowest X, and if tied, inversely by Len |
| func (l lcs) sort() lcs { |
| sort.Slice(l, func(i, j int) bool { |
| if l[i].X != l[j].X { |
| return l[i].X < l[j].X |
| } |
| return l[i].Len > 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 |
| } |