mirror of
https://github.com/github/semantic.git
synced 2024-11-29 02:44:36 +03:00
Merge pull request #110 from github/patch-costs-are-proportional-to-the-size-of-their-trees
Patch costs are proportional to the size of their trees
This commit is contained in:
commit
789f879f94
@ -52,6 +52,18 @@ public enum Algorithm<Term: TermType, B> {
|
||||
let recurOrReplace = {
|
||||
recur($0, $1) ?? .Pure(.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
|
||||
switch syntax {
|
||||
case .Leaf:
|
||||
return 0
|
||||
case let .Indexed(costs):
|
||||
return costs.reduce(0, combine: +)
|
||||
case let .Keyed(costs):
|
||||
return costs.values.reduce(0, combine: +)
|
||||
}
|
||||
}
|
||||
}
|
||||
switch self {
|
||||
case let .Pure(b):
|
||||
return b
|
||||
@ -82,7 +94,7 @@ public enum Algorithm<Term: TermType, B> {
|
||||
return f(Dictionary(elements: deleted + inserted + patched)).evaluate(equals, recur: recur)
|
||||
|
||||
case let .Roll(.ByIndex(a, b, f)):
|
||||
return f(SES(a, b, recur: recur)).evaluate(equals, recur: recur)
|
||||
return f(SES(a, b, cost: cost, recur: recur)).evaluate(equals, recur: recur)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ public enum Patch<A>: CustomDebugStringConvertible {
|
||||
return ".Insert(\(String(reflecting: b)))"
|
||||
case let .Delete(a):
|
||||
return ".Delete(\(String(reflecting: a)))"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,6 +85,8 @@ extension Patch where A: CustomJSONConvertible {
|
||||
}
|
||||
|
||||
|
||||
// MARK: - PatchConvertible
|
||||
|
||||
/// A hack to enable constrained extensions on `Free<A, Patch<Term: TermType where LeafType == A>`.
|
||||
public protocol PatchConvertible {
|
||||
typealias Element
|
||||
|
@ -1,25 +1,12 @@
|
||||
/// Computes the SES (shortest edit script), i.e. the shortest sequence of diffs (`Free<A, Patch<Term>>`) for two arrays of `Term`s 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], 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)) } }
|
||||
|
||||
func cost(diff: Diff) -> Int {
|
||||
return diff.map(const(1)).iterate { syntax in
|
||||
switch syntax {
|
||||
case .Leaf:
|
||||
return 0
|
||||
case let .Indexed(costs):
|
||||
return costs.reduce(0, combine: +)
|
||||
case let .Keyed(costs):
|
||||
return costs.values.reduce(0, combine: +)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cons(diff: Diff, rest: Memo<Stream<(Diff, Int)>>) -> Stream<(Diff, Int)> {
|
||||
return .Cons((diff, cost(diff) + costOfStream(rest)), rest)
|
||||
}
|
||||
|
@ -6,6 +6,33 @@ public protocol TermType {
|
||||
}
|
||||
|
||||
|
||||
extension TermType {
|
||||
/// Catamorphism over `TermType`s.
|
||||
///
|
||||
/// Folds the tree encoded by the receiver into a single value by recurring top-down through the tree, applying `transform` to leaves, then to branches, and so forth.
|
||||
public func cata<Result>(transform: Syntax<Result, LeafType> -> Result) -> Result {
|
||||
return self |> ({ $0.unwrap } >>> { $0.map { $0.cata(transform) } } >>> transform)
|
||||
}
|
||||
|
||||
|
||||
/// The count of nodes in the receiver.
|
||||
///
|
||||
/// This is used to compute the cost of patches, such that a patch inserting a very large tree will be charged approximately the same as a very large tree consisting of many small patches.
|
||||
public var size: Int {
|
||||
return cata {
|
||||
switch $0 {
|
||||
case .Leaf:
|
||||
return 1
|
||||
case let .Indexed(i):
|
||||
return i.reduce(1, combine: +)
|
||||
case let .Keyed(k):
|
||||
return k.values.reduce(1, combine: +)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Cofree: TermType {}
|
||||
|
||||
|
||||
@ -16,3 +43,6 @@ extension TermType {
|
||||
return Syntax.equals(ifLeaf: leaf, ifRecur: equals(leaf))(a.unwrap, b.unwrap)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
import Prelude
|
||||
|
@ -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) { Cofree.equals(annotation: const(true), leaf: ==)($0, $1) ? Diff($1) : nil }
|
||||
return SES(a, b, cost: const(1)) { Cofree.equals(annotation: const(true), leaf: ==)($0, $1) ? Diff($1) : nil }
|
||||
}
|
||||
|
||||
private func == (a: [Diff], b: [Diff]) -> Bool {
|
||||
|
2497
prototype/doubt-json/Fixtures/a.json
vendored
2497
prototype/doubt-json/Fixtures/a.json
vendored
File diff suppressed because it is too large
Load Diff
1659
prototype/doubt-json/Fixtures/b.json
vendored
1659
prototype/doubt-json/Fixtures/b.json
vendored
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user