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"
)
// operation represents the type of edit operation.
type operation uint
const (
INSERT operation = iota + 1
DELETE
MOVE
)
type Conf struct {
Cases []struct {
Score int
@ -54,18 +63,16 @@ func (*Diff) Run(results []stage.ExecutorResult, confAny any) (
stdoutLines := strings.Split(string(stdout), "\n")
resultLines := strings.Split(result.Files["stdout"], "\n")
// Find the first difference
diffIndex := findFirstDifferenceIndex(stdoutLines, resultLines, caseConf.IgnoreWhitespace)
if diffIndex != -1 {
// Generate Myers diff
diffOps := myersDiff(stdoutLines, resultLines)
// Generate diff block with surrounding context
diffOutput := generateDiffWithContext(stdoutLines, resultLines, diffIndex, 10)
diffOutput := generateDiffWithContext(stdoutLines, resultLines, diffOps)
comment += fmt.Sprintf(
"difference found at line %d:\n```diff\n%s```",
diffIndex+1,
"difference found:\n```diff\n%s```",
diffOutput,
)
}
}
res = append(res, stage.ParserResult{
Score: score,
@ -96,81 +103,133 @@ func removeWhitespace(s string) string {
return b.String()
}
// findFirstDifferenceIndex finds the index of the first line where stdout and result differ.
func findFirstDifferenceIndex(stdoutLines, resultLines []string, ignoreWhitespace bool) int {
maxLines := len(stdoutLines)
if len(resultLines) > maxLines {
maxLines = len(resultLines)
// myersDiff computes the Myers' diff between two slices of strings.
func myersDiff(src, dst []string) []operation {
n := len(src)
m := len(dst)
max := n + m
var trace []map[int]int
var x, y int
loop:
for d := 0; d <= max; d++ {
v := make(map[int]int, d+2)
trace = append(trace, v)
if d == 0 {
t := 0
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
}
for i := 0; i < maxLines; i++ {
stdoutLine := stdoutLines[i]
resultLine := resultLines[i]
lastV := trace[d-1]
if ignoreWhitespace {
stdoutLine = removeWhitespace(stdoutLine)
resultLine = removeWhitespace(resultLine)
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
}
if stdoutLine != resultLine {
return i
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.
func generateDiffWithContext(stdoutLines, resultLines []string, index, contextSize int) string {
func generateDiffWithContext(stdoutLines, resultLines []string, ops []operation) string {
var diffBuilder strings.Builder
start := index - contextSize
if start < 0 {
start = 0
}
end := index + contextSize + 1
if end > len(stdoutLines) {
end = len(stdoutLines)
srcIndex, dstIndex := 0, 0
for _, op := range ops {
switch op {
case INSERT:
if dstIndex < len(resultLines) {
diffBuilder.WriteString(fmt.Sprintf("+ %s\n", resultLines[dstIndex]))
dstIndex++
}
// Adding context before the diff
for i := start; i < index; i++ {
stdoutLine, resultLine := getLine(stdoutLines, resultLines, i)
if stdoutLine != resultLine {
diffBuilder.WriteString(fmt.Sprintf("- %s\n", stdoutLine))
diffBuilder.WriteString(fmt.Sprintf("+ %s\n", resultLine))
} else {
diffBuilder.WriteString(fmt.Sprintf(" %s\n", stdoutLine))
}
case MOVE:
if srcIndex < len(stdoutLines) {
diffBuilder.WriteString(fmt.Sprintf(" %s\n", stdoutLines[srcIndex]))
srcIndex++
dstIndex++
}
// Adding the diff line
stdoutLine, resultLine := getLine(stdoutLines, resultLines, index)
if stdoutLine != resultLine {
diffBuilder.WriteString(fmt.Sprintf("- %s\n", stdoutLine))
diffBuilder.WriteString(fmt.Sprintf("+ %s\n", resultLine))
case DELETE:
if srcIndex < len(stdoutLines) {
diffBuilder.WriteString(fmt.Sprintf("- %s\n", stdoutLines[srcIndex]))
srcIndex++
}
// Adding context after the diff
for i := index + 1; i < end; i++ {
stdoutLine, resultLine := getLine(stdoutLines, resultLines, i)
if stdoutLine != resultLine {
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()
}
// 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
}