1
1
mirror of https://github.com/github/semantic.git synced 2024-12-26 08:25:19 +03:00

Merge pull request #120 from github/patch-constructors-on-diff

Patch constructors on diff
This commit is contained in:
Josh Vera 2015-10-15 15:39:14 -04:00
commit e6ec91cd64
4 changed files with 36 additions and 27 deletions

View File

@ -50,7 +50,7 @@ public enum Algorithm<Term: TermType, B> {
: recur($0, $1)
}
let recurOrReplace = {
recur($0, $1) ?? .Pure(.Replace($0, $1))
recur($0, $1) ?? .Replace($0, $1)
}
func cost(diff: Diff) -> Int {
return diff.map { abs(($0.state.before?.size ?? 0) - ($0.state.after?.size ?? 0)) }.iterate { syntax in
@ -83,13 +83,13 @@ public enum Algorithm<Term: TermType, B> {
default:
// This must not call `recur` with `a` and `b`, as that would infinite loop if actually recursive.
return f(Diff.Pure(.Replace(a, b))).evaluate(equals, recur: recur)
return f(.Replace(a, b)).evaluate(equals, recur: recur)
}
case let .Roll(.ByKey(a, b, f)):
// 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 deleted = Set(a.keys).subtract(b.keys).map { ($0, Diff.Delete(a[$0]!)) }
let inserted = Set(b.keys).subtract(a.keys).map { ($0, Diff.Insert(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)

View File

@ -124,6 +124,23 @@ extension Free where B: PatchConvertible, B.Element == Cofree<A, ()> {
}
// MARK: - Patch construction
extension Free where B: PatchConvertible {
public static func Replace(before: B.Element, _ after: B.Element) -> Free {
return .Pure(B(patch: .Replace(before, after)))
}
public static func Insert(after: B.Element) -> Free {
return .Pure(B(patch: .Insert(after)))
}
public static func Delete(before: B.Element) -> Free {
return .Pure(B(patch: .Delete(before)))
}
}
// MARK: - Equality
extension Free {

View File

@ -4,8 +4,8 @@
public func SES<Term, A>(a: [Term], _ b: [Term], cost: Free<A, Patch<Term>> -> Int, 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)) } }
if b.isEmpty { return a.map { Diff.Pure(Patch.Delete($0)) } }
if a.isEmpty { return b.map { .Insert($0) } }
if b.isEmpty { return a.map { .Delete($0) } }
func cons(diff: Diff, rest: Memo<Stream<(Diff, Int)>>) -> Stream<(Diff, Int)> {
return .Cons((diff, cost(diff) + costOfStream(rest)), rest)
@ -33,8 +33,8 @@ public func SES<Term, A>(a: [Term], _ b: [Term], cost: Free<A, Patch<Term>> -> I
let diagonal = matrix[i + 1, j + 1]
if let right = right, down = down, diagonal = diagonal {
let right = (right, Diff.Pure(Patch.Delete(a[i])), costOfStream(right))
let down = (down, Diff.Pure(Patch.Insert(b[j])), costOfStream(down))
let right = (right, Diff.Delete(a[i]), costOfStream(right))
let down = (down, Diff.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
@ -45,12 +45,12 @@ public func SES<Term, A>(a: [Term], _ b: [Term], cost: Free<A, Patch<Term>> -> I
// right extent of the edit graph; can only move down
if let down = down {
return cons(Diff.Pure(Patch.Insert(b[j])), rest: down)
return cons(Diff.Insert(b[j]), rest: down)
}
// bottom extent of the edit graph; can only move right
if let right = right {
return cons(Diff.Pure(Patch.Delete(a[i])), rest: right)
return cons(Diff.Delete(a[i]), rest: right)
}
// bottom-right corner of the edit graph

View File

@ -4,50 +4,42 @@ final class SESTests: XCTestCase {
}
func testSESOverEmptyAndNonEmptyCollectionsIsInsertions() {
assert(SES([], [ a, b ]), ==, [ insert(a), insert(b) ])
assert(SES([], [ a, b ]), ==, [ .Insert(a), .Insert(b) ])
}
func testSESOverNonEmptyAndEmptyCollectionsIsDeletions() {
assert(SES([ a, b ], []), ==, [ delete(a), delete(b) ])
assert(SES([ a, b ], []), ==, [ .Delete(a), .Delete(b) ])
}
func testSESCanInsertAtHead() {
assert(SES([ a, b, c ], [ d, a, b, c ]), ==, [ insert(d), Diff(a), Diff(b), Diff(c) ])
assert(SES([ a, b, c ], [ d, a, b, c ]), ==, [ .Insert(d), Diff(a), Diff(b), Diff(c) ])
}
func testSESCanDeleteAtHead() {
assert(SES([ d, a, b, c ], [ a, b, c ]), ==, [ delete(d), Diff(a), Diff(b), Diff(c) ])
assert(SES([ d, a, b, c ], [ a, b, c ]), ==, [ .Delete(d), Diff(a), Diff(b), Diff(c) ])
}
func testSESCanInsertInMiddle() {
assert(SES([ a, b, c ], [ a, d, b, c ]), ==, [ Diff(a), insert(d), Diff(b), Diff(c) ])
assert(SES([ a, b, c ], [ a, d, b, c ]), ==, [ Diff(a), .Insert(d), Diff(b), Diff(c) ])
}
func testSESCanDeleteInMiddle() {
assert(SES([ a, d, b, c ], [ a, b, c ]), ==, [ Diff(a), delete(d), Diff(b), Diff(c) ])
assert(SES([ a, d, b, c ], [ a, b, c ]), ==, [ Diff(a), .Delete(d), Diff(b), Diff(c) ])
}
func testInsertsAtEnd() {
assert(SES([ a, b, c ], [ a, b, c, d ]), ==, [ Diff(a), Diff(b), Diff(c), insert(d) ])
assert(SES([ a, b, c ], [ a, b, c, d ]), ==, [ Diff(a), Diff(b), Diff(c), .Insert(d) ])
}
func testDeletesAtEnd() {
assert(SES([ a, b, c, d ], [ a, b, c ]), ==, [ Diff(a), Diff(b), Diff(c), delete(d) ])
assert(SES([ a, b, c, d ], [ a, b, c ]), ==, [ Diff(a), Diff(b), Diff(c), .Delete(d) ])
}
func testSESOfLongerSequences() {
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) ])
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) ])
}
}
private func insert(term: Term) -> Diff {
return Diff.Pure(.Insert(term))
}
private func delete(term: Term) -> Diff {
return Diff.Pure(.Delete(term))
}
private typealias Term = Cofree<String, ()>
private typealias Diff = Free<String, Patch<Term>>