mirror of
https://github.com/github/semantic.git
synced 2024-12-01 17:59:10 +03:00
Merge branch 'shortest-edit-script-in-algorithm' into update-swift-target
This commit is contained in:
commit
2a8a444dbe
@ -1,9 +1,9 @@
|
||||
/// An operation of diffing over terms or collections of terms.
|
||||
public enum Algorithm<Recur, A> {
|
||||
/// The type of `Term`s over which `Algorithm`s operate.
|
||||
public enum Operation<Recur, A> {
|
||||
/// The type of `Term`s over which `Operation`s operate.
|
||||
public typealias Term = Fix<A>
|
||||
|
||||
/// The type of `Diff`s which `Algorithm`s produce.
|
||||
/// The type of `Diff`s which `Operation`s produce.
|
||||
public typealias Diff = Free<A, Patch<A>>
|
||||
|
||||
/// Indicates that diffing should compare the enclosed `Term`s.
|
||||
@ -20,7 +20,7 @@ public enum Algorithm<Recur, A> {
|
||||
|
||||
// MARK: Functor
|
||||
|
||||
public func map<Other>(transform: Recur -> Other) -> Algorithm<Other, A> {
|
||||
public func map<Other>(transform: Recur -> Other) -> Operation<Other, A> {
|
||||
switch self {
|
||||
case let .Recursive(a, b, f):
|
||||
return .Recursive(a, b, f >>> transform)
|
||||
@ -33,27 +33,27 @@ public enum Algorithm<Recur, A> {
|
||||
}
|
||||
|
||||
|
||||
/// The free monad over `Algorithm`, implementing the language of diffing.
|
||||
/// The free monad over `Operation`, implementing the language of diffing.
|
||||
///
|
||||
/// As with `Free`, this is “free” in the sense of “unconstrained,” i.e. “the monad induced by `Algorithm` without extra assumptions.”
|
||||
/// As with `Free`, this is “free” in the sense of “unconstrained,” i.e. “the monad induced by `Operation` without extra assumptions.”
|
||||
///
|
||||
/// Where `Algorithm` models a single diffing strategy, `FreeAlgorithm` models the recursive selection of diffing strategies at each node. Thus, a value in `FreeAlgorithm` models an algorithm for constructing a value in the type `B` from the resulting diffs. By this means, diffing can be adapted not just to the specific grammar, but to specific trees produced by that grammar, and even the values of type `A` encapsulated at each node.
|
||||
public enum FreeAlgorithm<A, B> {
|
||||
/// The type of `Term`s over which `FreeAlgorithm`s operate.
|
||||
public typealias Term = Algorithm<FreeAlgorithm, A>.Term
|
||||
/// Where `Operation` models a single diffing strategy, `Algorithm` models the recursive selection of diffing strategies at each node. Thus, a value in `Algorithm` models an algorithm for constructing a value in the type `B` from the resulting diffs. By this means, diffing can be adapted not just to the specific grammar, but to specific trees produced by that grammar, and even the values of type `A` encapsulated at each node.
|
||||
public enum Algorithm<A, B> {
|
||||
/// The type of `Term`s over which `Algorithm`s operate.
|
||||
public typealias Term = Operation<Algorithm, A>.Term
|
||||
|
||||
/// The type of `Diff`s which `FreeAlgorithm`s produce.
|
||||
public typealias Diff = Algorithm<FreeAlgorithm, A>.Diff
|
||||
/// The type of `Diff`s which `Algorithm`s produce.
|
||||
public typealias Diff = Operation<Algorithm, A>.Diff
|
||||
|
||||
/// The injection of a value of type `B` into an `Algorithm`.
|
||||
/// The injection of a value of type `B` into an `Operation`.
|
||||
///
|
||||
/// Equally, a way to return a result or throw an error during computation, as determined by the type which `B` is instantiated to, and the specific context in which it is being evaluated.
|
||||
case Pure(B)
|
||||
|
||||
/// A recursive instantiation of `Algorithm`, unrolling another iteration of the recursive type.
|
||||
case Roll(Algorithm<FreeAlgorithm, A>)
|
||||
/// A recursive instantiation of `Operation`, unrolling another iteration of the recursive type.
|
||||
case Roll(Operation<Algorithm, A>)
|
||||
|
||||
public func analysis<C>(@noescape ifPure ifPure: B -> C, @noescape ifRoll: Algorithm<FreeAlgorithm, A> -> C) -> C {
|
||||
public func analysis<C>(@noescape ifPure ifPure: B -> C, @noescape ifRoll: Operation<Algorithm, A> -> C) -> C {
|
||||
switch self {
|
||||
case let .Pure(b):
|
||||
return ifPure(b)
|
||||
@ -65,14 +65,14 @@ public enum FreeAlgorithm<A, B> {
|
||||
|
||||
// MARK: Functor
|
||||
|
||||
public func map<Other>(transform: B -> Other) -> FreeAlgorithm<A, Other> {
|
||||
return analysis(ifPure: transform >>> FreeAlgorithm<A, Other>.Pure, ifRoll: { .Roll($0.map { $0.map(transform) }) })
|
||||
public func map<Other>(transform: B -> Other) -> Algorithm<A, Other> {
|
||||
return analysis(ifPure: transform >>> Algorithm<A, Other>.Pure, ifRoll: { .Roll($0.map { $0.map(transform) }) })
|
||||
}
|
||||
|
||||
|
||||
// MARK: Monad
|
||||
|
||||
public func flatMap<C>(transform: B -> FreeAlgorithm<A, C>) -> FreeAlgorithm<A, C> {
|
||||
public func flatMap<C>(transform: B -> Algorithm<A, C>) -> Algorithm<A, C> {
|
||||
return analysis(ifPure: transform, ifRoll: { .Roll($0.map { $0.flatMap(transform) }) })
|
||||
}
|
||||
|
||||
@ -84,12 +84,21 @@ public enum FreeAlgorithm<A, B> {
|
||||
return b
|
||||
|
||||
case let .Roll(.Recursive(a, b, f)):
|
||||
// Recur structurally into both terms, if compatible, patching paired sub-terms. This is akin to the shape of unification, except that it computes a patched tree instead of a substitution. It’s also a little like a structural zip on the pair of terms.
|
||||
//
|
||||
// At the moment, there are no restrictions on whether terms are compatible, and there is no structure to exploit in terms; therefore, this simplifies to copying if equal, and replacing otherwise.
|
||||
return f(Term.equals(equals)(a, b)
|
||||
? Diff(b)
|
||||
// This must not call `recur` with `a` and `b`, as that would infinite loop if actually recursive.
|
||||
: Diff.Pure(.Replace(a, b))).evaluate(equals, recur: recur)
|
||||
|
||||
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]!)) }
|
||||
@ -101,7 +110,7 @@ public enum FreeAlgorithm<A, B> {
|
||||
}
|
||||
}
|
||||
|
||||
extension FreeAlgorithm where A: Equatable {
|
||||
extension Algorithm where A: Equatable {
|
||||
public func evaluate(recur: (Term, Term) -> Diff) -> B {
|
||||
return evaluate(==, recur: recur)
|
||||
}
|
||||
@ -121,8 +130,8 @@ extension Free: FreeConvertible {
|
||||
public var free: Free { return self }
|
||||
}
|
||||
|
||||
extension FreeAlgorithm where B: FreeConvertible, B.RollType == A, B.PureType == Patch<A> {
|
||||
/// `FreeAlgorithm<A, Diff>`s can be constructed from a pair of `Term`s using `ByKey` when `Keyed`, `ByIndex` when `Indexed`, and `Recursive` otherwise.
|
||||
extension Algorithm where B: FreeConvertible, B.RollType == A, B.PureType == Patch<A> {
|
||||
/// `Algorithm<A, Diff>`s can be constructed from a pair of `Term`s using `ByKey` when `Keyed`, `ByIndex` when `Indexed`, and `Recursive` otherwise.
|
||||
public init(_ a: Term, _ b: Term) {
|
||||
switch (a.out, b.out) {
|
||||
case let (.Keyed(a), .Keyed(b)):
|
||||
@ -130,16 +139,16 @@ extension FreeAlgorithm where B: FreeConvertible, B.RollType == A, B.PureType ==
|
||||
case let (.Indexed(a), .Indexed(b)):
|
||||
self = .Roll(.ByIndex(a, b, Syntax.Indexed >>> Free.Roll >>> B.init >>> Pure))
|
||||
default:
|
||||
self = .Roll(.Recursive(a, b, B.init >>> FreeAlgorithm.Pure))
|
||||
self = .Roll(.Recursive(a, b, B.init >>> Algorithm.Pure))
|
||||
}
|
||||
}
|
||||
|
||||
public func evaluate(equals: (A, A) -> Bool) -> B {
|
||||
return evaluate(equals, recur: { FreeAlgorithm($0, $1).evaluate(equals).free })
|
||||
return evaluate(equals, recur: { Algorithm($0, $1).evaluate(equals).free })
|
||||
}
|
||||
}
|
||||
|
||||
extension FreeAlgorithm where A: Equatable, B: FreeConvertible, B.RollType == A, B.PureType == Patch<A> {
|
||||
extension Algorithm where A: Equatable, B: FreeConvertible, B.RollType == A, B.PureType == Patch<A> {
|
||||
public func evaluate() -> B {
|
||||
return evaluate(==)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
public enum Category: AlgebraicHashable, CustomDebugStringConvertible {
|
||||
public enum Category: AlgebraicHashable, Comparable, CustomDebugStringConvertible {
|
||||
case Tag(String)
|
||||
|
||||
|
||||
@ -25,8 +25,9 @@ public enum Category: AlgebraicHashable, CustomDebugStringConvertible {
|
||||
}
|
||||
|
||||
public func == (left: Category, right: Category) -> Bool {
|
||||
switch (left, right) {
|
||||
case let (.Tag(a), .Tag(b)):
|
||||
return a == b
|
||||
}
|
||||
return left.tag == right.tag
|
||||
}
|
||||
|
||||
public func < (left: Category, right: Category) -> Bool {
|
||||
return left.tag < right.tag
|
||||
}
|
||||
|
@ -18,10 +18,10 @@ public enum Info: AlgebraicHashable, CustomDebugStringConvertible {
|
||||
|
||||
public var debugDescription: String {
|
||||
switch self {
|
||||
case let .Literal(s, c) where c.isEmpty:
|
||||
return s
|
||||
case let .Literal(s, c):
|
||||
return c.isEmpty
|
||||
? s
|
||||
: s + " (" + c.map { String(reflecting: $0) }.joinWithSeparator(", ") + ")"
|
||||
return s + " (" + c.sort().map { String(reflecting: $0) }.joinWithSeparator(", ") + ")"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
// Copyright © 2015 GitHub. All rights reserved.
|
||||
|
||||
/// 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.
|
||||
@ -18,7 +16,7 @@ public func SES<A>(a: [Fix<A>], _ b: [Fix<A>], equals: (A, A) -> Bool, recur: (F
|
||||
case let .Indexed(costs):
|
||||
return costs.reduce(0, combine: +)
|
||||
case let .Keyed(costs):
|
||||
return costs.lazy.map { $1 }.reduce(0, combine: +)
|
||||
return costs.values.reduce(0, combine: +)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -31,6 +29,10 @@ public func SES<A>(a: [Fix<A>], _ b: [Fix<A>], equals: (A, A) -> Bool, recur: (F
|
||||
return stream.value.first?.1 ?? 0
|
||||
}
|
||||
|
||||
func min<A>(a: A, _ rest: A..., _ isLessThan: (A, A) -> Bool) -> A {
|
||||
return rest.reduce(a, combine: { isLessThan($0, $1) ? $0 : $1 })
|
||||
}
|
||||
|
||||
// A matrix whose values are streams representing paths through the edit graph, carrying both the diff & the cost of the remainder of the path.
|
||||
var matrix: Matrix<Stream<(Diff, Int)>>!
|
||||
matrix = Matrix(width: a.count + 1, height: b.count + 1) { i, j in
|
||||
@ -51,19 +53,11 @@ public func SES<A>(a: [Fix<A>], _ b: [Fix<A>], equals: (A, A) -> Bool, recur: (F
|
||||
}
|
||||
|
||||
if let right = right, down = down, diagonal = diagonal {
|
||||
let costs = (right: costOfStream(right), down: costOfStream(down), diagonal: costOfStream(diagonal))
|
||||
// nominate the best edge to continue along
|
||||
let best: Memo<Stream<(Diff, Int)>>
|
||||
let diff: Diff
|
||||
if costs.diagonal < costs.down {
|
||||
(best, diff) = costs.diagonal < costs.right
|
||||
? (diagonal, recur(a[i], b[j]))
|
||||
: (right, Diff.Pure(Patch.Delete(a[i])))
|
||||
} else {
|
||||
(best, diff) = costs.down < costs.right
|
||||
? (down, Diff.Pure(Patch.Insert(b[j])))
|
||||
: (right, Diff.Pure(Patch.Delete(a[i])))
|
||||
}
|
||||
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 }
|
||||
return cons(diff, rest: best)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user