Skip to content

Commit b7f36c3

Browse files
committed
Include renamed CollectionDifference.swift
1 parent b467e24 commit b7f36c3

File tree

1 file changed

+332
-0
lines changed

1 file changed

+332
-0
lines changed
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2015 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
/// A type that represents the difference between two ordered collection states.
14+
@available(swift, introduced: 5.1)
15+
public struct CollectionDifference<ChangeElement> {
16+
/// A type that represents a single change to a collection.
17+
///
18+
/// The `offset` of each `insert` refers to the offset of its `element` in
19+
/// the final state after the difference is fully applied. The `offset` of
20+
/// each `remove` refers to the offset of its `element` in the original
21+
/// state. Non-`nil` values of `associatedWith` refer to the offset of the
22+
/// complementary change.
23+
public enum Change {
24+
case insert(offset: Int, element: ChangeElement, associatedWith: Int?)
25+
case remove(offset: Int, element: ChangeElement, associatedWith: Int?)
26+
27+
// Internal common field accessors
28+
var offset: Int {
29+
get {
30+
switch self {
31+
case .insert(offset: let o, element: _, associatedWith: _):
32+
return o
33+
case .remove(offset: let o, element: _, associatedWith: _):
34+
return o
35+
}
36+
}
37+
}
38+
var element: ChangeElement {
39+
get {
40+
switch self {
41+
case .insert(offset: _, element: let e, associatedWith: _):
42+
return e
43+
case .remove(offset: _, element: let e, associatedWith: _):
44+
return e
45+
}
46+
}
47+
}
48+
var associatedOffset: Int? {
49+
get {
50+
switch self {
51+
case .insert(offset: _, element: _, associatedWith: let o):
52+
return o
53+
case .remove(offset: _, element: _, associatedWith: let o):
54+
return o
55+
}
56+
}
57+
}
58+
}
59+
60+
// The public initializer calls this function to ensure that its parameter
61+
// meets the conditions set in its documentation.
62+
private static func validateChanges<C>(_ changes : C) -> Bool where C:Collection, C.Element == Change {
63+
if changes.count == 0 { return true }
64+
65+
var insertAssocToOffset = Dictionary<Int,Int>()
66+
var removeOffsetToAssoc = Dictionary<Int,Int>()
67+
var insertOffset = Set<Int>()
68+
var removeOffset = Set<Int>()
69+
70+
for c in changes {
71+
let offset = c.offset
72+
if offset < 0 { return false }
73+
74+
switch c {
75+
case .remove(_, _, _):
76+
if removeOffset.contains(offset) { return false }
77+
removeOffset.insert(offset)
78+
case .insert(_, _, _):
79+
if insertOffset.contains(offset) { return false }
80+
insertOffset.insert(offset)
81+
}
82+
83+
if let assoc = c.associatedOffset {
84+
if assoc < 0 { return false }
85+
switch c {
86+
case .remove(_, _, _):
87+
if removeOffsetToAssoc[offset] != nil { return false }
88+
removeOffsetToAssoc[offset] = assoc
89+
case .insert(_, _, _):
90+
if insertAssocToOffset[assoc] != nil { return false }
91+
insertAssocToOffset[assoc] = offset
92+
}
93+
}
94+
}
95+
96+
return removeOffsetToAssoc == insertAssocToOffset
97+
}
98+
99+
/// Creates an instance from a collection of changes.
100+
///
101+
/// For clients interested in the difference between two collections, see
102+
/// `BidirectionalCollection.difference(from:)`.
103+
///
104+
/// To guarantee that instances are unambiguous and safe for compatible base
105+
/// states, this initializer will fail unless its parameter meets to the
106+
/// following requirements:
107+
///
108+
/// 1) All insertion offsets are unique
109+
/// 2) All removal offsets are unique
110+
/// 3) All offset associations between insertions and removals are symmetric
111+
///
112+
/// - Parameter changes: A collection of changes that represent a transition
113+
/// between two states.
114+
///
115+
/// - Complexity: O(*n* * log(*n*)), where *n* is the length of the
116+
/// parameter.
117+
public init?<C>(_ c: C) where C:Collection, C.Element == Change {
118+
if !CollectionDifference<ChangeElement>.validateChanges(c) {
119+
return nil
120+
}
121+
122+
self.init(validatedChanges: c)
123+
}
124+
125+
// Internal initializer for use by algorithms that cannot produce invalid
126+
// collections of changes. These include the Myers' diff algorithm and
127+
// the move inferencer.
128+
init<C>(validatedChanges c: C) where C:Collection, C.Element == Change {
129+
let changes = c.sorted { (a, b) -> Bool in
130+
switch (a, b) {
131+
case (.remove(_, _, _), .insert(_, _, _)):
132+
return true
133+
case (.insert(_, _, _), .remove(_, _, _)):
134+
return false
135+
default:
136+
return a.offset < b.offset
137+
}
138+
}
139+
140+
// Find first insertion via binary search
141+
let firstInsertIndex: Int
142+
if changes.count == 0 {
143+
firstInsertIndex = 0
144+
} else {
145+
var range = 0...changes.count
146+
while range.lowerBound != range.upperBound {
147+
let i = (range.lowerBound + range.upperBound) / 2
148+
switch changes[i] {
149+
case .insert(_, _, _):
150+
range = range.lowerBound...i
151+
case .remove(_, _, _):
152+
range = (i + 1)...range.upperBound
153+
}
154+
}
155+
firstInsertIndex = range.lowerBound
156+
}
157+
158+
removals = Array(changes[0..<firstInsertIndex])
159+
insertions = Array(changes[firstInsertIndex..<changes.count])
160+
}
161+
162+
/// The `.insert` changes contained by this difference, from lowest offset to highest
163+
public let insertions: [Change]
164+
165+
/// The `.remove` changes contained by this difference, from lowest offset to highest
166+
public let removals: [Change]
167+
}
168+
169+
/// A CollectionDifference is itself a Collection.
170+
///
171+
/// The enumeration order of `Change` elements is:
172+
///
173+
/// 1. `.remove`s, from highest `offset` to lowest
174+
/// 2. `.insert`s, from lowest `offset` to highest
175+
///
176+
/// This guarantees that applicators on compatible base states are safe when
177+
/// written in the form:
178+
///
179+
/// ```
180+
/// for c in diff {
181+
/// switch c {
182+
/// case .remove(offset: let o, element: _, associatedWith: _):
183+
/// arr.remove(at: o)
184+
/// case .insert(offset: let o, element: let e, associatedWith: _):
185+
/// arr.insert(e, at: o)
186+
/// }
187+
/// }
188+
/// ```
189+
extension CollectionDifference : Collection {
190+
public typealias Element = CollectionDifference<ChangeElement>.Change
191+
192+
// Opaque index type is isomorphic to Int
193+
public struct Index: Comparable, Hashable {
194+
public static func < (lhs: CollectionDifference<ChangeElement>.Index, rhs: CollectionDifference<ChangeElement>.Index) -> Bool {
195+
return lhs.i < rhs.i
196+
}
197+
198+
let i: Int
199+
init(_ index: Int) {
200+
i = index
201+
}
202+
}
203+
204+
public var startIndex: CollectionDifference<ChangeElement>.Index {
205+
return Index(0)
206+
}
207+
208+
public var endIndex: CollectionDifference<ChangeElement>.Index {
209+
return Index(removals.count + insertions.count)
210+
}
211+
212+
public func index(after index: CollectionDifference<ChangeElement>.Index) -> CollectionDifference<ChangeElement>.Index {
213+
return Index(index.i + 1)
214+
}
215+
216+
public subscript(position: CollectionDifference<ChangeElement>.Index) -> Element {
217+
return position.i < removals.count ? removals[removals.count - (position.i + 1)] : insertions[position.i - removals.count]
218+
}
219+
220+
public func index(before index: CollectionDifference<ChangeElement>.Index) -> CollectionDifference<ChangeElement>.Index {
221+
return Index(index.i - 1)
222+
}
223+
224+
public func formIndex(_ index: inout CollectionDifference<ChangeElement>.Index, offsetBy distance: Int) {
225+
index = Index(index.i + distance)
226+
}
227+
228+
public func distance(from start: CollectionDifference<ChangeElement>.Index, to end: CollectionDifference<ChangeElement>.Index) -> Int {
229+
return end.i - start.i
230+
}
231+
}
232+
233+
extension CollectionDifference.Change: Equatable where ChangeElement: Equatable {}
234+
235+
extension CollectionDifference: Equatable where ChangeElement: Equatable {}
236+
237+
extension CollectionDifference.Change: Hashable where ChangeElement: Hashable {}
238+
239+
extension CollectionDifference: Hashable where ChangeElement: Hashable {
240+
241+
/// Infers which `ChangeElement`s have been both inserted and removed only
242+
/// once and returns a new difference with those associations.
243+
///
244+
/// - Returns: an instance with all possible moves inferred.
245+
///
246+
/// - Complexity: O(*n*) where *n* is `self.count`
247+
public func inferringMoves() -> CollectionDifference<ChangeElement> {
248+
let removeDict: [ChangeElement:Int?] = {
249+
var res = [ChangeElement:Int?](minimumCapacity: Swift.min(removals.count, insertions.count))
250+
for r in removals {
251+
let element = r.element
252+
if res[element] != .none {
253+
res[element] = .some(.none)
254+
} else {
255+
res[element] = .some(r.offset)
256+
}
257+
}
258+
return res.filter { (_, v) -> Bool in v != .none }
259+
}()
260+
261+
let insertDict: [ChangeElement:Int?] = {
262+
var res = [ChangeElement:Int?](minimumCapacity: Swift.min(removals.count, insertions.count))
263+
for i in insertions {
264+
let element = i.element
265+
if res[element] != .none {
266+
res[element] = .some(.none)
267+
} else {
268+
res[element] = .some(i.offset)
269+
}
270+
}
271+
return res.filter { (_, v) -> Bool in v != .none }
272+
}()
273+
274+
return CollectionDifference.init(validatedChanges:map({ (c: CollectionDifference<ChangeElement>.Change) -> CollectionDifference<ChangeElement>.Change in
275+
switch c {
276+
case .remove(offset: let o, element: let e, associatedWith: _):
277+
if removeDict[e] == nil {
278+
return c
279+
}
280+
if let assoc = insertDict[e] {
281+
return .remove(offset: o, element: e, associatedWith: assoc)
282+
}
283+
case .insert(offset: let o, element: let e, associatedWith: _):
284+
if insertDict[e] == nil {
285+
return c
286+
}
287+
if let assoc = removeDict[e] {
288+
return .insert(offset: o, element: e, associatedWith: assoc)
289+
}
290+
}
291+
return c
292+
}))
293+
}
294+
}
295+
296+
extension CollectionDifference.Change: Codable where ChangeElement: Codable {
297+
private enum CodingKeys: String, CodingKey {
298+
case offset
299+
case element
300+
case associatedOffset
301+
case isRemove
302+
}
303+
304+
public init(from decoder: Decoder) throws {
305+
let values = try decoder.container(keyedBy: CodingKeys.self)
306+
let offset = try values.decode(Int.self, forKey: .offset)
307+
let element = try values.decode(ChangeElement.self, forKey: .element)
308+
let associatedOffset = try values.decode(Int?.self, forKey: .associatedOffset)
309+
let isRemove = try values.decode(Bool.self, forKey: .isRemove)
310+
if isRemove {
311+
self = .remove(offset: offset, element: element, associatedWith: associatedOffset)
312+
} else {
313+
self = .insert(offset: offset, element: element, associatedWith: associatedOffset)
314+
}
315+
}
316+
317+
public func encode(to encoder: Encoder) throws {
318+
var container = encoder.container(keyedBy: CodingKeys.self)
319+
switch self {
320+
case .remove(_, _, _):
321+
try container.encode(true, forKey: .isRemove)
322+
case .insert(_, _, _):
323+
try container.encode(false, forKey: .isRemove)
324+
}
325+
326+
try container.encode(offset, forKey: .offset)
327+
try container.encode(element, forKey: .element)
328+
try container.encode(associatedOffset, forKey: .associatedOffset)
329+
}
330+
}
331+
332+
extension CollectionDifference: Codable where ChangeElement: Codable {}

0 commit comments

Comments
 (0)