feat: upload audio, get a private Swedish transcript, discuss it (#33, ADR-033) #39
@@ -0,0 +1,187 @@
|
||||
package reconcile
|
||||
|
||||
// OpKind is the kind of one run in an edit script.
|
||||
type OpKind int
|
||||
|
||||
const (
|
||||
// Equal: A[AStart:AEnd] and B[BStart:BEnd] are the same words.
|
||||
Equal OpKind = iota
|
||||
// Replace: different words on both sides.
|
||||
Replace
|
||||
// Delete: words only in A.
|
||||
Delete
|
||||
// Insert: words only in B.
|
||||
Insert
|
||||
)
|
||||
|
||||
// Op is one run of the edit script. The ops of a Diff tile both inputs: each op starts
|
||||
// where the previous one ended, in A and in B.
|
||||
type Op struct {
|
||||
Kind OpKind
|
||||
AStart, AEnd int
|
||||
BStart, BEnd int
|
||||
}
|
||||
|
||||
// maxEdits bounds the Myers search. Memory grows with the square of the edit
|
||||
// distance, so two texts that share almost nothing (one engine produced garbage)
|
||||
// are reported as one Replace instead of spending gigabytes proving it. A real
|
||||
// hour disagrees in roughly 400 places, i.e. about 800 edits, far below this.
|
||||
const maxEdits = 3000
|
||||
|
||||
// Diff returns a minimal edit script from a to b (Myers, O(ND)). Adjacent deletions
|
||||
// and insertions are reported as one Replace.
|
||||
func Diff(a, b []string) []Op {
|
||||
pre := 0
|
||||
for pre < len(a) && pre < len(b) && a[pre] == b[pre] {
|
||||
pre++
|
||||
}
|
||||
suf := 0
|
||||
for suf < len(a)-pre && suf < len(b)-pre && a[len(a)-1-suf] == b[len(b)-1-suf] {
|
||||
suf++
|
||||
}
|
||||
midA, midB := a[pre:len(a)-suf], b[pre:len(b)-suf]
|
||||
|
||||
steps, ok := myers(midA, midB)
|
||||
if !ok {
|
||||
steps = steps[:0]
|
||||
for range midA {
|
||||
steps = append(steps, stepDel)
|
||||
}
|
||||
for range midB {
|
||||
steps = append(steps, stepIns)
|
||||
}
|
||||
}
|
||||
|
||||
var ops []Op
|
||||
ai, bi := 0, 0
|
||||
emit := func(k OpKind, da, db int) {
|
||||
if da == 0 && db == 0 {
|
||||
return
|
||||
}
|
||||
if k == Equal && len(ops) > 0 && ops[len(ops)-1].Kind == Equal {
|
||||
ops[len(ops)-1].AEnd += da
|
||||
ops[len(ops)-1].BEnd += db
|
||||
} else {
|
||||
ops = append(ops, Op{Kind: k, AStart: ai, AEnd: ai + da, BStart: bi, BEnd: bi + db})
|
||||
}
|
||||
ai += da
|
||||
bi += db
|
||||
}
|
||||
emit(Equal, pre, pre)
|
||||
for i := 0; i < len(steps); {
|
||||
if steps[i] == stepEq {
|
||||
j := i
|
||||
for j < len(steps) && steps[j] == stepEq {
|
||||
j++
|
||||
}
|
||||
emit(Equal, j-i, j-i)
|
||||
i = j
|
||||
continue
|
||||
}
|
||||
dels, inss := 0, 0
|
||||
for i < len(steps) && steps[i] != stepEq {
|
||||
if steps[i] == stepDel {
|
||||
dels++
|
||||
} else {
|
||||
inss++
|
||||
}
|
||||
i++
|
||||
}
|
||||
switch {
|
||||
case dels > 0 && inss > 0:
|
||||
emit(Replace, dels, inss)
|
||||
case dels > 0:
|
||||
emit(Delete, dels, 0)
|
||||
default:
|
||||
emit(Insert, 0, inss)
|
||||
}
|
||||
}
|
||||
emit(Equal, suf, suf)
|
||||
return ops
|
||||
}
|
||||
|
||||
type step int8
|
||||
|
||||
const (
|
||||
stepEq step = iota
|
||||
stepDel
|
||||
stepIns
|
||||
)
|
||||
|
||||
// myers returns one step per element of the edit script, in order. ok is false when
|
||||
// the edit distance exceeds maxEdits.
|
||||
func myers(a, b []string) ([]step, bool) {
|
||||
n, m := len(a), len(b)
|
||||
if n == 0 && m == 0 {
|
||||
return nil, true
|
||||
}
|
||||
limit := min(n+m, maxEdits)
|
||||
off := limit + 1
|
||||
v := make([]int32, 2*limit+3)
|
||||
var trace [][]int32 // trace[d] is v as it stood at the start of round d, for k in [-d, d]
|
||||
|
||||
for d := 0; d <= limit; d++ {
|
||||
snap := make([]int32, 2*d+1)
|
||||
for k := -d; k <= d; k++ {
|
||||
snap[k+d] = v[off+k]
|
||||
}
|
||||
trace = append(trace, snap)
|
||||
for k := -d; k <= d; k += 2 {
|
||||
var x int
|
||||
if k == -d || (k != d && v[off+k-1] < v[off+k+1]) {
|
||||
x = int(v[off+k+1])
|
||||
} else {
|
||||
x = int(v[off+k-1]) + 1
|
||||
}
|
||||
y := x - k
|
||||
for x < n && y < m && a[x] == b[y] {
|
||||
x++
|
||||
y++
|
||||
}
|
||||
v[off+k] = int32(x)
|
||||
if x >= n && y >= m {
|
||||
return backtrack(trace, n, m, d), true
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func backtrack(trace [][]int32, n, m, d int) []step {
|
||||
var rev []step
|
||||
x, y := n, m
|
||||
for ; d >= 0; d-- {
|
||||
snap := trace[d]
|
||||
at := func(k int) int { return int(snap[k+d]) }
|
||||
k := x - y
|
||||
var prevK int
|
||||
if k == -d || (k != d && at(k-1) < at(k+1)) {
|
||||
prevK = k + 1
|
||||
} else {
|
||||
prevK = k - 1
|
||||
}
|
||||
prevX := 0
|
||||
if d > 0 {
|
||||
prevX = at(prevK)
|
||||
}
|
||||
prevY := prevX - prevK
|
||||
for x > prevX && y > prevY {
|
||||
rev = append(rev, stepEq)
|
||||
x--
|
||||
y--
|
||||
}
|
||||
if d > 0 {
|
||||
if x == prevX {
|
||||
rev = append(rev, stepIns)
|
||||
} else {
|
||||
rev = append(rev, stepDel)
|
||||
}
|
||||
}
|
||||
x, y = prevX, prevY
|
||||
}
|
||||
out := make([]step, len(rev))
|
||||
for i, s := range rev {
|
||||
out[len(rev)-1-i] = s
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user