feat: myers

This commit is contained in:
zzjc1234 2024-09-10 16:21:20 +08:00 committed by Boming Zhang
parent 6fbbe7ece0
commit 1d90d72978
GPG Key ID: D47306D7062CDA9D

View File

@ -9,6 +9,15 @@ import (
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage" "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
) )
// operation represents the type of edit operation.
type operation uint
const (
INSERT operation = iota + 1
DELETE
MOVE
)
type Conf struct { type Conf struct {
Cases []struct { Cases []struct {
Score int Score int
@ -54,17 +63,15 @@ func (*Diff) Run(results []stage.ExecutorResult, confAny any) (
stdoutLines := strings.Split(string(stdout), "\n") stdoutLines := strings.Split(string(stdout), "\n")
resultLines := strings.Split(result.Files["stdout"], "\n") resultLines := strings.Split(result.Files["stdout"], "\n")
// Find the first difference // Generate Myers diff
diffIndex := findFirstDifferenceIndex(stdoutLines, resultLines, caseConf.IgnoreWhitespace) diffOps := myersDiff(stdoutLines, resultLines)
if diffIndex != -1 {
// Generate diff block with surrounding context // Generate diff block with surrounding context
diffOutput := generateDiffWithContext(stdoutLines, resultLines, diffIndex, 10) diffOutput := generateDiffWithContext(stdoutLines, resultLines, diffOps)
comment += fmt.Sprintf( comment += fmt.Sprintf(
"difference found at line %d:\n```diff\n%s```", "difference found:\n```diff\n%s```",
diffIndex+1, diffOutput,
diffOutput, )
)
}
} }
res = append(res, stage.ParserResult{ res = append(res, stage.ParserResult{
@ -96,81 +103,133 @@ func removeWhitespace(s string) string {
return b.String() return b.String()
} }
// findFirstDifferenceIndex finds the index of the first line where stdout and result differ. // myersDiff computes the Myers' diff between two slices of strings.
func findFirstDifferenceIndex(stdoutLines, resultLines []string, ignoreWhitespace bool) int { func myersDiff(src, dst []string) []operation {
maxLines := len(stdoutLines) n := len(src)
if len(resultLines) > maxLines { m := len(dst)
maxLines = len(resultLines) max := n + m
} var trace []map[int]int
var x, y int
for i := 0; i < maxLines; i++ { loop:
stdoutLine := stdoutLines[i] for d := 0; d <= max; d++ {
resultLine := resultLines[i] v := make(map[int]int, d+2)
trace = append(trace, v)
if ignoreWhitespace { if d == 0 {
stdoutLine = removeWhitespace(stdoutLine) t := 0
resultLine = removeWhitespace(resultLine) for len(src) > t && len(dst) > t && src[t] == dst[t] {
t++
}
v[0] = t
if t == len(src) && t == len(dst) {
break loop
}
continue
} }
if stdoutLine != resultLine { lastV := trace[d-1]
return i
for k := -d; k <= d; k += 2 {
if k == -d || (k != d && lastV[k-1] < lastV[k+1]) {
x = lastV[k+1]
} else {
x = lastV[k-1] + 1
}
y = x - k
for x < n && y < m && src[x] == dst[y] {
x, y = x+1, y+1
}
v[k] = x
if x == n && y == m {
break loop
}
} }
} }
return -1
var script []operation
x = n
y = m
var k, prevK, prevX, prevY int
for d := len(trace) - 1; d > 0; d-- {
k = x - y
lastV := trace[d-1]
if k == -d || (k != d && lastV[k-1] < lastV[k+1]) {
prevK = k + 1
} else {
prevK = k - 1
}
prevX = lastV[prevK]
prevY = prevX - prevK
for x > prevX && y > prevY {
script = append(script, MOVE)
x -= 1
y -= 1
}
if x == prevX {
script = append(script, INSERT)
} else {
script = append(script, DELETE)
}
x, y = prevX, prevY
}
if trace[0][0] != 0 {
for i := 0; i < trace[0][0]; i++ {
script = append(script, MOVE)
}
}
return reverse(script)
}
// reverse reverses a slice of operations.
func reverse(s []operation) []operation {
result := make([]operation, len(s))
for i, v := range s {
result[len(s)-1-i] = v
}
return result
} }
// generateDiffWithContext creates a diff block with surrounding context from stdout and result. // generateDiffWithContext creates a diff block with surrounding context from stdout and result.
func generateDiffWithContext(stdoutLines, resultLines []string, index, contextSize int) string { func generateDiffWithContext(stdoutLines, resultLines []string, ops []operation) string {
var diffBuilder strings.Builder var diffBuilder strings.Builder
start := index - contextSize srcIndex, dstIndex := 0, 0
if start < 0 {
start = 0
}
end := index + contextSize + 1
if end > len(stdoutLines) {
end = len(stdoutLines)
}
// Adding context before the diff for _, op := range ops {
for i := start; i < index; i++ { switch op {
stdoutLine, resultLine := getLine(stdoutLines, resultLines, i) case INSERT:
if stdoutLine != resultLine { if dstIndex < len(resultLines) {
diffBuilder.WriteString(fmt.Sprintf("- %s\n", stdoutLine)) diffBuilder.WriteString(fmt.Sprintf("+ %s\n", resultLines[dstIndex]))
diffBuilder.WriteString(fmt.Sprintf("+ %s\n", resultLine)) dstIndex++
} else { }
diffBuilder.WriteString(fmt.Sprintf(" %s\n", stdoutLine))
}
}
// Adding the diff line case MOVE:
stdoutLine, resultLine := getLine(stdoutLines, resultLines, index) if srcIndex < len(stdoutLines) {
if stdoutLine != resultLine { diffBuilder.WriteString(fmt.Sprintf(" %s\n", stdoutLines[srcIndex]))
diffBuilder.WriteString(fmt.Sprintf("- %s\n", stdoutLine)) srcIndex++
diffBuilder.WriteString(fmt.Sprintf("+ %s\n", resultLine)) dstIndex++
} }
// Adding context after the diff case DELETE:
for i := index + 1; i < end; i++ { if srcIndex < len(stdoutLines) {
stdoutLine, resultLine := getLine(stdoutLines, resultLines, i) diffBuilder.WriteString(fmt.Sprintf("- %s\n", stdoutLines[srcIndex]))
if stdoutLine != resultLine { srcIndex++
diffBuilder.WriteString(fmt.Sprintf("- %s\n", stdoutLine)) }
diffBuilder.WriteString(fmt.Sprintf("+ %s\n", resultLine))
} else {
diffBuilder.WriteString(fmt.Sprintf(" %s\n", stdoutLine))
} }
} }
return diffBuilder.String() return diffBuilder.String()
} }
// getLine safely retrieves lines from both stdout and result
func getLine(stdoutLines, resultLines []string, i int) (stdoutLine, resultLine string) {
if i < len(stdoutLines) {
stdoutLine = stdoutLines[i]
}
if i < len(resultLines) {
resultLine = resultLines[i]
}
return
}