feat: myers
This commit is contained in:
parent
6fbbe7ece0
commit
1d90d72978
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user