1
1
mirror of https://github.com/github/semantic.git synced 2024-11-29 02:44:36 +03:00

Merge branch 'parameterize-algorithm-evaluation-by-comparison-restriction' into generalize-patch

Conflicts:
	prototype/Doubt/SES.swift
	prototype/DoubtTests/SESTests.swift
This commit is contained in:
Rob Rix 2015-10-14 12:24:04 -04:00
commit 8d4a91d3a9
3 changed files with 24 additions and 18 deletions

View File

@ -46,7 +46,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
@ -59,10 +67,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.
@ -70,15 +78,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)):
@ -88,7 +91,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)
}
}

View File

@ -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<Term, A>(a: [Term], _ b: [Term], recur: (Term, Term) -> Free<A, Patch<Term>>) -> [Free<A, Patch<Term>>] {
public func SES<Term, A>(a: [Term], _ b: [Term], recur: (Term, Term) -> Free<A, Patch<Term>>?) -> [Free<A, Patch<Term>>] {
typealias Diff = Free<A, Patch<Term>>
if a.isEmpty { return b.map { Diff.Pure(Patch.Insert($0)) } }
@ -46,11 +46,13 @@ public func SES<Term, A>(a: [Term], _ b: [Term], recur: (Term, Term) -> Free<A,
let diagonal = matrix[i + 1, j + 1]
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)
}

View File

@ -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("c")
private let d = Term.Leaf("d")
private func SES(a: [Term], _ b: [Term]) -> [Diff] {
return SES(a, b, recur: { Diff.Pure(Patch.Replace($0, $1)) })
return SES(a, b, 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