1
1
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:
Josh Vera 2015-10-15 14:08:23 -04:00
commit 789f879f94
7 changed files with 3570 additions and 651 deletions

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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 {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff