mirror of
https://github.com/github/semantic.git
synced 2024-12-27 00:44:57 +03:00
Merge pull request #120 from github/patch-constructors-on-diff
Patch constructors on diff
This commit is contained in:
commit
e6ec91cd64
@ -50,7 +50,7 @@ public enum Algorithm<Term: TermType, B> {
|
|||||||
: recur($0, $1)
|
: recur($0, $1)
|
||||||
}
|
}
|
||||||
let recurOrReplace = {
|
let recurOrReplace = {
|
||||||
recur($0, $1) ?? .Pure(.Replace($0, $1))
|
recur($0, $1) ?? .Replace($0, $1)
|
||||||
}
|
}
|
||||||
func cost(diff: Diff) -> Int {
|
func cost(diff: Diff) -> Int {
|
||||||
return diff.map { abs(($0.state.before?.size ?? 0) - ($0.state.after?.size ?? 0)) }.iterate { syntax in
|
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:
|
default:
|
||||||
// This must not call `recur` with `a` and `b`, as that would infinite loop if actually recursive.
|
// 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)):
|
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.
|
// 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 deleted = Set(a.keys).subtract(b.keys).map { ($0, Diff.Delete(a[$0]!)) }
|
||||||
let inserted = Set(b.keys).subtract(a.keys).map { ($0, Diff.Pure(Patch.Insert(b[$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]!)) }
|
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)
|
return f(Dictionary(elements: deleted + inserted + patched)).evaluate(equals, recur: recur)
|
||||||
|
|
||||||
|
@ -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
|
// MARK: - Equality
|
||||||
|
|
||||||
extension Free {
|
extension Free {
|
||||||
|
@ -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>>] {
|
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>>
|
typealias Diff = Free<A, Patch<Term>>
|
||||||
|
|
||||||
if a.isEmpty { return b.map { Diff.Pure(Patch.Insert($0)) } }
|
if a.isEmpty { return b.map { .Insert($0) } }
|
||||||
if b.isEmpty { return a.map { Diff.Pure(Patch.Delete($0)) } }
|
if b.isEmpty { return a.map { .Delete($0) } }
|
||||||
|
|
||||||
func cons(diff: Diff, rest: Memo<Stream<(Diff, Int)>>) -> Stream<(Diff, Int)> {
|
func cons(diff: Diff, rest: Memo<Stream<(Diff, Int)>>) -> Stream<(Diff, Int)> {
|
||||||
return .Cons((diff, cost(diff) + costOfStream(rest)), rest)
|
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]
|
let diagonal = matrix[i + 1, j + 1]
|
||||||
|
|
||||||
if let right = right, down = down, diagonal = diagonal {
|
if let right = right, down = down, diagonal = diagonal {
|
||||||
let right = (right, Diff.Pure(Patch.Delete(a[i])), costOfStream(right))
|
let right = (right, Diff.Delete(a[i]), costOfStream(right))
|
||||||
let down = (down, Diff.Pure(Patch.Insert(b[j])), costOfStream(down))
|
let down = (down, Diff.Insert(b[j]), costOfStream(down))
|
||||||
let diagonal = recur(a[i], b[j]).map { (diagonal, $0, costOfStream(diagonal)) }
|
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`.
|
// nominate the best edge to continue along, not considering diagonal if `recur` returned `nil`.
|
||||||
let (best, diff, _) = diagonal
|
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
|
// right extent of the edit graph; can only move down
|
||||||
if let down = 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
|
// bottom extent of the edit graph; can only move right
|
||||||
if let right = 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
|
// bottom-right corner of the edit graph
|
||||||
|
@ -4,50 +4,42 @@ final class SESTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testSESOverEmptyAndNonEmptyCollectionsIsInsertions() {
|
func testSESOverEmptyAndNonEmptyCollectionsIsInsertions() {
|
||||||
assert(SES([], [ a, b ]), ==, [ insert(a), insert(b) ])
|
assert(SES([], [ a, b ]), ==, [ .Insert(a), .Insert(b) ])
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSESOverNonEmptyAndEmptyCollectionsIsDeletions() {
|
func testSESOverNonEmptyAndEmptyCollectionsIsDeletions() {
|
||||||
assert(SES([ a, b ], []), ==, [ delete(a), delete(b) ])
|
assert(SES([ a, b ], []), ==, [ .Delete(a), .Delete(b) ])
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSESCanInsertAtHead() {
|
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() {
|
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() {
|
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() {
|
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() {
|
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() {
|
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() {
|
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 Term = Cofree<String, ()>
|
||||||
private typealias Diff = Free<String, Patch<Term>>
|
private typealias Diff = Free<String, Patch<Term>>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user