mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
333 lines
11 KiB
Swift
333 lines
11 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2015 - 2019 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// A type that represents the difference between two ordered collection states.
|
|
@available(swift, introduced: 5.1)
|
|
public struct CollectionDifference<ChangeElement> {
|
|
/// A type that represents a single change to a collection.
|
|
///
|
|
/// The `offset` of each `insert` refers to the offset of its `element` in
|
|
/// the final state after the difference is fully applied. The `offset` of
|
|
/// each `remove` refers to the offset of its `element` in the original
|
|
/// state. Non-`nil` values of `associatedWith` refer to the offset of the
|
|
/// complementary change.
|
|
public enum Change {
|
|
case insert(offset: Int, element: ChangeElement, associatedWith: Int?)
|
|
case remove(offset: Int, element: ChangeElement, associatedWith: Int?)
|
|
|
|
// Internal common field accessors
|
|
var offset: Int {
|
|
get {
|
|
switch self {
|
|
case .insert(offset: let o, element: _, associatedWith: _):
|
|
return o
|
|
case .remove(offset: let o, element: _, associatedWith: _):
|
|
return o
|
|
}
|
|
}
|
|
}
|
|
var element: ChangeElement {
|
|
get {
|
|
switch self {
|
|
case .insert(offset: _, element: let e, associatedWith: _):
|
|
return e
|
|
case .remove(offset: _, element: let e, associatedWith: _):
|
|
return e
|
|
}
|
|
}
|
|
}
|
|
var associatedOffset: Int? {
|
|
get {
|
|
switch self {
|
|
case .insert(offset: _, element: _, associatedWith: let o):
|
|
return o
|
|
case .remove(offset: _, element: _, associatedWith: let o):
|
|
return o
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The public initializer calls this function to ensure that its parameter
|
|
// meets the conditions set in its documentation.
|
|
private static func validateChanges<C>(_ changes : C) -> Bool where C:Collection, C.Element == Change {
|
|
if changes.count == 0 { return true }
|
|
|
|
var insertAssocToOffset = Dictionary<Int,Int>()
|
|
var removeOffsetToAssoc = Dictionary<Int,Int>()
|
|
var insertOffset = Set<Int>()
|
|
var removeOffset = Set<Int>()
|
|
|
|
for c in changes {
|
|
let offset = c.offset
|
|
if offset < 0 { return false }
|
|
|
|
switch c {
|
|
case .remove(_, _, _):
|
|
if removeOffset.contains(offset) { return false }
|
|
removeOffset.insert(offset)
|
|
case .insert(_, _, _):
|
|
if insertOffset.contains(offset) { return false }
|
|
insertOffset.insert(offset)
|
|
}
|
|
|
|
if let assoc = c.associatedOffset {
|
|
if assoc < 0 { return false }
|
|
switch c {
|
|
case .remove(_, _, _):
|
|
if removeOffsetToAssoc[offset] != nil { return false }
|
|
removeOffsetToAssoc[offset] = assoc
|
|
case .insert(_, _, _):
|
|
if insertAssocToOffset[assoc] != nil { return false }
|
|
insertAssocToOffset[assoc] = offset
|
|
}
|
|
}
|
|
}
|
|
|
|
return removeOffsetToAssoc == insertAssocToOffset
|
|
}
|
|
|
|
/// Creates an instance from a collection of changes.
|
|
///
|
|
/// For clients interested in the difference between two collections, see
|
|
/// `BidirectionalCollection.difference(from:)`.
|
|
///
|
|
/// To guarantee that instances are unambiguous and safe for compatible base
|
|
/// states, this initializer will fail unless its parameter meets to the
|
|
/// following requirements:
|
|
///
|
|
/// 1) All insertion offsets are unique
|
|
/// 2) All removal offsets are unique
|
|
/// 3) All offset associations between insertions and removals are symmetric
|
|
///
|
|
/// - Parameter changes: A collection of changes that represent a transition
|
|
/// between two states.
|
|
///
|
|
/// - Complexity: O(*n* * log(*n*)), where *n* is the length of the
|
|
/// parameter.
|
|
public init?<C>(_ c: C) where C:Collection, C.Element == Change {
|
|
if !CollectionDifference<ChangeElement>.validateChanges(c) {
|
|
return nil
|
|
}
|
|
|
|
self.init(validatedChanges: c)
|
|
}
|
|
|
|
// Internal initializer for use by algorithms that cannot produce invalid
|
|
// collections of changes. These include the Myers' diff algorithm and
|
|
// the move inferencer.
|
|
init<C>(validatedChanges c: C) where C:Collection, C.Element == Change {
|
|
let changes = c.sorted { (a, b) -> Bool in
|
|
switch (a, b) {
|
|
case (.remove(_, _, _), .insert(_, _, _)):
|
|
return true
|
|
case (.insert(_, _, _), .remove(_, _, _)):
|
|
return false
|
|
default:
|
|
return a.offset < b.offset
|
|
}
|
|
}
|
|
|
|
// Find first insertion via binary search
|
|
let firstInsertIndex: Int
|
|
if changes.count == 0 {
|
|
firstInsertIndex = 0
|
|
} else {
|
|
var range = 0...changes.count
|
|
while range.lowerBound != range.upperBound {
|
|
let i = (range.lowerBound + range.upperBound) / 2
|
|
switch changes[i] {
|
|
case .insert(_, _, _):
|
|
range = range.lowerBound...i
|
|
case .remove(_, _, _):
|
|
range = (i + 1)...range.upperBound
|
|
}
|
|
}
|
|
firstInsertIndex = range.lowerBound
|
|
}
|
|
|
|
removals = Array(changes[0..<firstInsertIndex])
|
|
insertions = Array(changes[firstInsertIndex..<changes.count])
|
|
}
|
|
|
|
/// The `.insert` changes contained by this difference, from lowest offset to highest
|
|
public let insertions: [Change]
|
|
|
|
/// The `.remove` changes contained by this difference, from lowest offset to highest
|
|
public let removals: [Change]
|
|
}
|
|
|
|
/// A CollectionDifference is itself a Collection.
|
|
///
|
|
/// The enumeration order of `Change` elements is:
|
|
///
|
|
/// 1. `.remove`s, from highest `offset` to lowest
|
|
/// 2. `.insert`s, from lowest `offset` to highest
|
|
///
|
|
/// This guarantees that applicators on compatible base states are safe when
|
|
/// written in the form:
|
|
///
|
|
/// ```
|
|
/// for c in diff {
|
|
/// switch c {
|
|
/// case .remove(offset: let o, element: _, associatedWith: _):
|
|
/// arr.remove(at: o)
|
|
/// case .insert(offset: let o, element: let e, associatedWith: _):
|
|
/// arr.insert(e, at: o)
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
extension CollectionDifference : Collection {
|
|
public typealias Element = CollectionDifference<ChangeElement>.Change
|
|
|
|
// Opaque index type is isomorphic to Int
|
|
public struct Index: Comparable, Hashable {
|
|
public static func < (lhs: CollectionDifference<ChangeElement>.Index, rhs: CollectionDifference<ChangeElement>.Index) -> Bool {
|
|
return lhs.i < rhs.i
|
|
}
|
|
|
|
let i: Int
|
|
init(_ index: Int) {
|
|
i = index
|
|
}
|
|
}
|
|
|
|
public var startIndex: CollectionDifference<ChangeElement>.Index {
|
|
return Index(0)
|
|
}
|
|
|
|
public var endIndex: CollectionDifference<ChangeElement>.Index {
|
|
return Index(removals.count + insertions.count)
|
|
}
|
|
|
|
public func index(after index: CollectionDifference<ChangeElement>.Index) -> CollectionDifference<ChangeElement>.Index {
|
|
return Index(index.i + 1)
|
|
}
|
|
|
|
public subscript(position: CollectionDifference<ChangeElement>.Index) -> Element {
|
|
return position.i < removals.count ? removals[removals.count - (position.i + 1)] : insertions[position.i - removals.count]
|
|
}
|
|
|
|
public func index(before index: CollectionDifference<ChangeElement>.Index) -> CollectionDifference<ChangeElement>.Index {
|
|
return Index(index.i - 1)
|
|
}
|
|
|
|
public func formIndex(_ index: inout CollectionDifference<ChangeElement>.Index, offsetBy distance: Int) {
|
|
index = Index(index.i + distance)
|
|
}
|
|
|
|
public func distance(from start: CollectionDifference<ChangeElement>.Index, to end: CollectionDifference<ChangeElement>.Index) -> Int {
|
|
return end.i - start.i
|
|
}
|
|
}
|
|
|
|
extension CollectionDifference.Change: Equatable where ChangeElement: Equatable {}
|
|
|
|
extension CollectionDifference: Equatable where ChangeElement: Equatable {}
|
|
|
|
extension CollectionDifference.Change: Hashable where ChangeElement: Hashable {}
|
|
|
|
extension CollectionDifference: Hashable where ChangeElement: Hashable {
|
|
|
|
/// Infers which `ChangeElement`s have been both inserted and removed only
|
|
/// once and returns a new difference with those associations.
|
|
///
|
|
/// - Returns: an instance with all possible moves inferred.
|
|
///
|
|
/// - Complexity: O(*n*) where *n* is `self.count`
|
|
public func inferringMoves() -> CollectionDifference<ChangeElement> {
|
|
let removeDict: [ChangeElement:Int?] = {
|
|
var res = [ChangeElement:Int?](minimumCapacity: Swift.min(removals.count, insertions.count))
|
|
for r in removals {
|
|
let element = r.element
|
|
if res[element] != .none {
|
|
res[element] = .some(.none)
|
|
} else {
|
|
res[element] = .some(r.offset)
|
|
}
|
|
}
|
|
return res.filter { (_, v) -> Bool in v != .none }
|
|
}()
|
|
|
|
let insertDict: [ChangeElement:Int?] = {
|
|
var res = [ChangeElement:Int?](minimumCapacity: Swift.min(removals.count, insertions.count))
|
|
for i in insertions {
|
|
let element = i.element
|
|
if res[element] != .none {
|
|
res[element] = .some(.none)
|
|
} else {
|
|
res[element] = .some(i.offset)
|
|
}
|
|
}
|
|
return res.filter { (_, v) -> Bool in v != .none }
|
|
}()
|
|
|
|
return CollectionDifference.init(validatedChanges:map({ (c: CollectionDifference<ChangeElement>.Change) -> CollectionDifference<ChangeElement>.Change in
|
|
switch c {
|
|
case .remove(offset: let o, element: let e, associatedWith: _):
|
|
if removeDict[e] == nil {
|
|
return c
|
|
}
|
|
if let assoc = insertDict[e] {
|
|
return .remove(offset: o, element: e, associatedWith: assoc)
|
|
}
|
|
case .insert(offset: let o, element: let e, associatedWith: _):
|
|
if insertDict[e] == nil {
|
|
return c
|
|
}
|
|
if let assoc = removeDict[e] {
|
|
return .insert(offset: o, element: e, associatedWith: assoc)
|
|
}
|
|
}
|
|
return c
|
|
}))
|
|
}
|
|
}
|
|
|
|
extension CollectionDifference.Change: Codable where ChangeElement: Codable {
|
|
private enum CodingKeys: String, CodingKey {
|
|
case offset
|
|
case element
|
|
case associatedOffset
|
|
case isRemove
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let values = try decoder.container(keyedBy: CodingKeys.self)
|
|
let offset = try values.decode(Int.self, forKey: .offset)
|
|
let element = try values.decode(ChangeElement.self, forKey: .element)
|
|
let associatedOffset = try values.decode(Int?.self, forKey: .associatedOffset)
|
|
let isRemove = try values.decode(Bool.self, forKey: .isRemove)
|
|
if isRemove {
|
|
self = .remove(offset: offset, element: element, associatedWith: associatedOffset)
|
|
} else {
|
|
self = .insert(offset: offset, element: element, associatedWith: associatedOffset)
|
|
}
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
switch self {
|
|
case .remove(_, _, _):
|
|
try container.encode(true, forKey: .isRemove)
|
|
case .insert(_, _, _):
|
|
try container.encode(false, forKey: .isRemove)
|
|
}
|
|
|
|
try container.encode(offset, forKey: .offset)
|
|
try container.encode(element, forKey: .element)
|
|
try container.encode(associatedOffset, forKey: .associatedOffset)
|
|
}
|
|
}
|
|
|
|
extension CollectionDifference: Codable where ChangeElement: Codable {}
|