mirror of
https://github.com/github/semantic.git
synced 2024-11-25 11:04:00 +03:00
Merge pull request #101 from github/parameterize-algorithm-evaluation-by-comparison-restriction
Parameterize Algorithm evaluation by comparison restriction
This commit is contained in:
commit
6eff8d6f82
@ -43,7 +43,15 @@ public enum Algorithm<A, B> {
|
||||
|
||||
|
||||
/// Evaluates the encoded algorithm, returning its result.
|
||||
public func evaluate(equals: (A, A) -> Bool, recur: (Term, Term) -> Diff) -> B {
|
||||
public func evaluate(equals: (A, A) -> Bool, recur: (Term, Term) -> Diff?) -> B {
|
||||
let recur = {
|
||||
Term.equals(equals)($0, $1)
|
||||
? Diff($1)
|
||||
: recur($0, $1)
|
||||
}
|
||||
let recurOrReplace = {
|
||||
recur($0, $1) ?? .Pure(.Replace($0, $1))
|
||||
}
|
||||
switch self {
|
||||
case let .Pure(b):
|
||||
return b
|
||||
@ -56,10 +64,10 @@ public enum Algorithm<A, B> {
|
||||
|
||||
switch (a.out, b.out) {
|
||||
case let (.Indexed(a), .Indexed(b)) where a.count == b.count:
|
||||
return f(.Indexed(zip(a, b).map(recur))).evaluate(equals, recur: recur)
|
||||
return f(.Indexed(zip(a, b).map(recurOrReplace))).evaluate(equals, recur: recur)
|
||||
|
||||
case let (.Keyed(a), .Keyed(b)) where Array(a.keys) == Array(b.keys):
|
||||
return f(.Keyed(Dictionary(elements: b.keys.map { ($0, recur(a[$0]!, b[$0]!)) }))).evaluate(equals, recur: recur)
|
||||
return f(.Keyed(Dictionary(elements: b.keys.map { ($0, recurOrReplace(a[$0]!, b[$0]!)) }))).evaluate(equals, recur: recur)
|
||||
|
||||
default:
|
||||
// This must not call `recur` with `a` and `b`, as that would infinite loop if actually recursive.
|
||||
@ -67,15 +75,10 @@ public enum Algorithm<A, B> {
|
||||
}
|
||||
|
||||
case let .Roll(.ByKey(a, b, f)):
|
||||
let recur = {
|
||||
Term.equals(equals)($0, $1)
|
||||
? Diff($1)
|
||||
: recur($0, $1)
|
||||
}
|
||||
// Essentially [set reconciliation](https://en.wikipedia.org/wiki/Data_synchronization#Unordered_data) on the keys, followed by recurring into the values of the intersecting keys.
|
||||
let deleted = Set(a.keys).subtract(b.keys).map { ($0, Diff.Pure(Patch.Delete(a[$0]!))) }
|
||||
let inserted = Set(b.keys).subtract(a.keys).map { ($0, Diff.Pure(Patch.Insert(b[$0]!))) }
|
||||
let patched = Set(a.keys).intersect(b.keys).map { ($0, recur(a[$0]!, b[$0]!)) }
|
||||
let patched = Set(a.keys).intersect(b.keys).map { ($0, recurOrReplace(a[$0]!, b[$0]!)) }
|
||||
return f(Dictionary(elements: deleted + inserted + patched)).evaluate(equals, recur: recur)
|
||||
|
||||
case let .Roll(.ByIndex(a, b, f)):
|
||||
@ -85,7 +88,7 @@ public enum Algorithm<A, B> {
|
||||
}
|
||||
|
||||
extension Algorithm where A: Equatable {
|
||||
public func evaluate(recur: (Term, Term) -> Diff) -> B {
|
||||
public func evaluate(recur: (Term, Term) -> Diff?) -> B {
|
||||
return evaluate(==, recur: recur)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/// 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>>] {
|
||||
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>>
|
||||
|
||||
@ -53,11 +53,13 @@ public func SES<A>(a: [Fix<A>], _ b: [Fix<A>], equals: (A, A) -> Bool, recur: (F
|
||||
}
|
||||
|
||||
if let right = right, down = down, diagonal = diagonal {
|
||||
// nominate the best edge to continue along
|
||||
let (best, diff, _) = min(
|
||||
(diagonal, recur(a[i], b[j]), costOfStream(diagonal)),
|
||||
(right, Diff.Pure(Patch.Delete(a[i])), costOfStream(right)),
|
||||
(down, Diff.Pure(Patch.Insert(b[j])), costOfStream(down))) { $0.2 < $1.2 }
|
||||
let right = (right, Diff.Pure(Patch.Delete(a[i])), costOfStream(right))
|
||||
let down = (down, Diff.Pure(Patch.Insert(b[j])), costOfStream(down))
|
||||
let diagonal = recur(a[i], b[j]).map { (diagonal, $0, costOfStream(diagonal)) }
|
||||
// nominate the best edge to continue along, not considering diagonal if `recur` returned `nil`.
|
||||
let (best, diff, _) = diagonal
|
||||
.map { min($0, right, down) { $0.2 < $1.2 } }
|
||||
?? min(right, down) { $0.2 < $1.2 }
|
||||
return cons(diff, rest: best)
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ final class SESTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testSESOfLongerSequences() {
|
||||
assert(SES([ a, b, c, a, b, b, a ], [ c, b, a, b, a, c ]), ==, [ Diff.Pure(.Replace(a, c)), Diff(b), delete(c), Diff(a), delete(b), Diff(b), Diff(a), insert(c) ])
|
||||
assert(SES([ a, b, c, a, b, b, a ], [ c, b, a, b, a, c ]), ==, [ insert(c), delete(a), Diff(b), delete(c), Diff(a), delete(b), Diff(b), Diff(a), insert(c) ])
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ private let c = Term.Leaf(.Literal("c", []))
|
||||
private let d = Term.Leaf(.Literal("d", []))
|
||||
|
||||
private func SES(a: [Term], _ b: [Term]) -> [Diff] {
|
||||
return SES(a, b, equals: ==, recur: { Diff.Pure(Patch.Replace($0, $1)) })
|
||||
return SES(a, b, equals: ==, recur: const(nil))
|
||||
}
|
||||
|
||||
private func == (a: [Diff], b: [Diff]) -> Bool {
|
||||
@ -67,4 +67,5 @@ private func == (a: [Diff], b: [Diff]) -> Bool {
|
||||
|
||||
import Assertions
|
||||
@testable import Doubt
|
||||
import Prelude
|
||||
import XCTest
|
||||
|
Loading…
Reference in New Issue
Block a user