1
1
mirror of https://github.com/github/semantic.git synced 2024-12-22 06:11:49 +03:00
semantic/prototype/Doubt/SES.swift

89 lines
3.4 KiB
Swift
Raw Normal View History

2015-10-06 23:50:23 +03:00
// Copyright © 2015 GitHub. All rights reserved.
/// Computes the SES (shortest edit script), i.e. the shortest sequence of diffs (`Free<A, Patch<A>>`) for two arrays of terms (`Fix<A>`) which would suffice to transform `a` into `b`.
///
/// This is computed w.r.t. an `equals` function, which computes the equality of leaf nodes within terms, and a `recur` function, which produces diffs representing matched-up terms.
public func SES<A>(a: [Fix<A>], _ b: [Fix<A>], equals: (A, A) -> Bool, recur: (Fix<A>, Fix<A>) -> Free<A, Patch<A>>) -> [Free<A, Patch<A>>] {
typealias Term = Fix<A>
typealias Diff = Free<A, Patch<A>>
if a.isEmpty { return b.map { Diff.Pure(Patch.Insert($0)) } }
if b.isEmpty { return a.map { Diff.Pure(Patch.Delete($0)) } }
func cost(diff: Diff) -> Int {
2015-10-07 00:08:22 +03:00
return diff.map { $0.cost }.iterate { syntax in
2015-10-06 23:50:23 +03:00
switch syntax {
case .Leaf:
return 0
case let .Indexed(costs):
return costs.reduce(0, combine: +)
case let .Keyed(costs):
return costs.lazy.map { $1 }.reduce(0, combine: +)
}
}
}
func cons(diff: Diff, rest: Memo<Stream<(Diff, Int)>>) -> Stream<(Diff, Int)> {
return .Cons((diff, cost(diff) + costOfStream(rest)), rest)
}
func costOfStream(stream: Memo<Stream<(Diff, Int)>>) -> Int {
return stream.value.first?.1 ?? 0
}
2015-10-07 00:29:33 +03:00
// A matrix whose values are streams representing paths through the edit graph, carrying both the diff & the cost of the remainder of the path.
2015-10-06 23:50:23 +03:00
var matrix: Matrix<Stream<(Diff, Int)>>!
matrix = Matrix(width: a.count + 1, height: b.count + 1) { i, j in
// Some explanation is warranted:
//
// 1. `matrix` captures itself during construction, because each vertex in the edit graph depends on other vertices. This is safe, because a) `Matrix` populates its fields lazily, and b) vertices only depend on those vertices downwards and rightwards of them.
//
// 2. `matrix` is sized bigger than `a.count` x `b.count`. This is safe, because a) we only get a[i]/b[j] when right/down are non-nil (respectively), and b) right/down are found by looking up elements (i + 1, j) & (i, j + 1) in the matrix, which returns `nil` when out of bounds. So we only access a[i] and b[j] when i and j are in bounds.
2015-10-06 23:50:23 +03:00
let right = matrix[i + 1, j]
let down = matrix[i, j + 1]
let diagonal = matrix[i + 1, j + 1]
func copy(term: Term) -> Diff {
return Diff.Roll(term.out.map(copy))
}
2015-10-06 23:59:29 +03:00
let recur = {
Fix.equals(equals)($0, $1)
? copy($1)
: recur($0, $1)
}
2015-10-06 23:50:23 +03:00
if let right = right, down = down, diagonal = diagonal {
let costs = (right: costOfStream(right), down: costOfStream(down), diagonal: costOfStream(diagonal))
// nominate the best edge to continue along
let best: Memo<Stream<(Diff, Int)>>
let diff: Diff
2015-10-06 23:50:23 +03:00
if costs.diagonal < costs.down {
(best, diff) = costs.diagonal < costs.right
? (diagonal, recur(a[i], b[j]))
: (right, Diff.Pure(Patch.Delete(a[i])))
2015-10-06 23:50:23 +03:00
} else {
(best, diff) = costs.down < costs.right
? (down, Diff.Pure(Patch.Insert(b[j])))
: (right, Diff.Pure(Patch.Delete(a[i])))
2015-10-06 23:50:23 +03:00
}
return cons(diff, rest: best)
}
// right extent of the edit graph; can only move down
if let down = down {
return cons(Diff.Pure(Patch.Insert(b[j])), rest: down)
2015-10-06 23:50:23 +03:00
}
// bottom extent of the edit graph; can only move right
if let right = right {
return cons(Diff.Pure(Patch.Delete(a[i])), rest: right)
2015-10-06 23:50:23 +03:00
}
// bottom-right corner of the edit graph
return Stream.Nil
2015-10-06 23:50:23 +03:00
}
return Array(matrix[0, 0]!.value.map { diff, _ in diff })
}