JOJ3/internal/parser/diff/patience.go
张泊明518370910136 37c0d76bf3
All checks were successful
submodules sync / sync (push) Successful in 58s
build / build (push) Successful in 2m26s
build / trigger-build-image (push) Successful in 11s
refactor(parser/diff): modify code from peter-evans/patience
2025-03-28 08:08:44 -04:00

189 lines
4.3 KiB
Go

package diff
// modified from https://github.com/peter-evans/patience
import (
"fmt"
"strings"
)
// DiffType defines the type of a diff element.
type DiffType int8
const (
// Delete represents a diff delete operation.
Delete DiffType = -1
// Insert represents a diff insert operation.
Insert DiffType = 1
// Equal represents no diff.
Equal DiffType = 0
)
// DiffLine represents a single line and its diff type.
type DiffLine struct {
Text string
Type DiffType
}
// typeSymbol returns the associated symbol of a DiffType.
func typeSymbol(t DiffType) string {
switch t {
case Equal:
return " "
case Insert:
return "+ "
case Delete:
return "- "
default:
panic("unknown DiffType")
}
}
// DiffText returns the source and destination texts (all equalities, insertions and deletions).
func DiffText(diffs []DiffLine) string {
s := make([]string, len(diffs))
for i, l := range diffs {
s[i] = fmt.Sprintf("%s%s", typeSymbol(l.Type), l.Text)
}
return strings.Join(s, "\n")
}
// LCS computes the longest common subsequence of two string
// slices and returns the index pairs of the LCS.
func LCS(a, b []string, equal func(a, b string) bool) [][2]int {
// Initialize the LCS table.
lcs := make([][]int, len(a)+1)
for i := 0; i <= len(a); i++ {
lcs[i] = make([]int, len(b)+1)
}
// Populate the LCS table.
for i := 1; i < len(lcs); i++ {
for j := 1; j < len(lcs[i]); j++ {
if equal(a[i-1], b[j-1]) {
lcs[i][j] = lcs[i-1][j-1] + 1
} else {
lcs[i][j] = max(lcs[i-1][j], lcs[i][j-1])
}
}
}
// Backtrack to find the LCS.
i, j := len(a), len(b)
s := make([][2]int, 0, lcs[i][j])
for i > 0 && j > 0 {
switch {
case equal(a[i-1], b[j-1]):
s = append(s, [2]int{i - 1, j - 1})
i--
j--
case lcs[i-1][j] > lcs[i][j-1]:
i--
default:
j--
}
}
// Reverse the backtracked LCS.
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
return s
}
// toDiffLines is a convenience function to convert a slice of strings
// to a slice of DiffLines with the specified diff type.
func toDiffLines(a []string, t DiffType) []DiffLine {
diffs := make([]DiffLine, len(a))
for i, l := range a {
diffs[i] = DiffLine{l, t}
}
return diffs
}
// uniqueElements returns a slice of unique elements from a slice of
// strings, and a slice of the original indices of each element.
func uniqueElements(a []string) ([]string, []int) {
m := make(map[string]int)
for _, e := range a {
m[e]++
}
elements := []string{}
indices := []int{}
for i, e := range a {
if m[e] == 1 {
elements = append(elements, e)
indices = append(indices, i)
}
}
return elements, indices
}
// PatienceDiff returns the patience diff of two slices of strings.
func PatienceDiff(a, b []string, equal func(a, b string) bool) []DiffLine {
switch {
case len(a) == 0 && len(b) == 0:
return nil
case len(a) == 0:
return toDiffLines(b, Insert)
case len(b) == 0:
return toDiffLines(a, Delete)
}
// Find equal elements at the head of slices a and b.
i := 0
for i < len(a) && i < len(b) && equal(a[i], b[i]) {
i++
}
if i > 0 {
return append(
toDiffLines(a[:i], Equal),
PatienceDiff(a[i:], b[i:], equal)...,
)
}
// Find equal elements at the tail of slices a and b.
j := 0
for j < len(a) && j < len(b) && equal(a[len(a)-1-j], b[len(b)-1-j]) {
j++
}
if j > 0 {
return append(
PatienceDiff(a[:len(a)-j], b[:len(b)-j], equal),
toDiffLines(a[len(a)-j:], Equal)...,
)
}
// Find the longest common subsequence of unique elements in a and b.
ua, idxa := uniqueElements(a)
ub, idxb := uniqueElements(b)
lcs := LCS(ua, ub, equal)
// If the LCS is empty, the diff is all deletions and insertions.
if len(lcs) == 0 {
return append(toDiffLines(a, Delete), toDiffLines(b, Insert)...)
}
// Lookup the original indices of slices a and b.
for i, x := range lcs {
lcs[i][0] = idxa[x[0]]
lcs[i][1] = idxb[x[1]]
}
diffs := []DiffLine{}
ga, gb := 0, 0
for _, ip := range lcs {
// PatienceDiff the gaps between the lcs elements.
diffs = append(diffs, PatienceDiff(a[ga:ip[0]], b[gb:ip[1]], equal)...)
// Append the LCS elements to the diff.
diffs = append(diffs, DiffLine{Type: Equal, Text: a[ip[0]]})
ga = ip[0] + 1
gb = ip[1] + 1
}
// PatienceDiff the remaining elements of a and b after the final LCS element.
diffs = append(diffs, PatienceDiff(a[ga:], b[gb:], equal)...)
return diffs
}