Skip to content

Commit 65ef6cb

Browse files
natecook1000Tim Vermeulen
and
Tim Vermeulen
authored
Update AdjacentPairs implementations and tests (#139)
* Update AdjacentPairs implementations and tests * Add conditional lazy conformances * Apply suggestions from code review Co-authored-by: Tim Vermeulen <[email protected]> Co-authored-by: Tim Vermeulen <[email protected]>
1 parent 0f27d99 commit 65ef6cb

File tree

2 files changed

+181
-151
lines changed

2 files changed

+181
-151
lines changed

Sources/Algorithms/AdjacentPairs.swift

Lines changed: 136 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -18,35 +18,35 @@ extension Sequence {
1818
/// The following example uses the `adjacentPairs()` method to iterate over
1919
/// adjacent pairs of integers:
2020
///
21-
/// for pair in (1...5).adjacentPairs() {
22-
/// print(pair)
23-
/// }
24-
/// // Prints "(1, 2)"
25-
/// // Prints "(2, 3)"
26-
/// // Prints "(3, 4)"
27-
/// // Prints "(4, 5)"
21+
/// for pair in (1...).prefix(5).adjacentPairs() {
22+
/// print(pair)
23+
/// }
24+
/// // Prints "(1, 2)"
25+
/// // Prints "(2, 3)"
26+
/// // Prints "(3, 4)"
27+
/// // Prints "(4, 5)"
2828
@inlinable
2929
public func adjacentPairs() -> AdjacentPairsSequence<Self> {
3030
AdjacentPairsSequence(base: self)
3131
}
3232
}
3333

3434
extension Collection {
35-
/// A collection of adjacent pairs of elements built from an underlying collection.
35+
/// A collection of adjacent pairs of elements built from an underlying
36+
/// collection.
3637
///
37-
/// In an `AdjacentPairsCollection`, the elements of the *i*th pair are the *i*th
38-
/// and *(i+1)*th elements of the underlying sequence. The following example
39-
/// uses the `adjacentPairs()` method to iterate over adjacent pairs of
40-
/// integers:
41-
/// ```
42-
/// for pair in (1...5).adjacentPairs() {
43-
/// print(pair)
44-
/// }
45-
/// // Prints "(1, 2)"
46-
/// // Prints "(2, 3)"
47-
/// // Prints "(3, 4)"
48-
/// // Prints "(4, 5)"
49-
/// ```
38+
/// In an `AdjacentPairsCollection`, the elements of the *i*th pair are the
39+
/// *i*th and *(i+1)*th elements of the underlying sequence. The following
40+
/// example uses the `adjacentPairs()` method to iterate over adjacent pairs
41+
/// of integers:
42+
///
43+
/// for pair in (1...5).adjacentPairs() {
44+
/// print(pair)
45+
/// }
46+
/// // Prints "(1, 2)"
47+
/// // Prints "(2, 3)"
48+
/// // Prints "(3, 4)"
49+
/// // Prints "(4, 5)"
5050
@inlinable
5151
public func adjacentPairs() -> AdjacentPairsCollection<Self> {
5252
AdjacentPairsCollection(base: self)
@@ -55,19 +55,8 @@ extension Collection {
5555

5656
/// A sequence of adjacent pairs of elements built from an underlying sequence.
5757
///
58-
/// In an `AdjacentPairsSequence`, the elements of the *i*th pair are the *i*th
59-
/// and *(i+1)*th elements of the underlying sequence. The following example
60-
/// uses the `adjacentPairs()` method to iterate over adjacent pairs of
61-
/// integers:
62-
/// ```
63-
/// for pair in (1...5).adjacentPairs() {
64-
/// print(pair)
65-
/// }
66-
/// // Prints "(1, 2)"
67-
/// // Prints "(2, 3)"
68-
/// // Prints "(3, 4)"
69-
/// // Prints "(4, 5)"
70-
/// ```
58+
/// Use the `adjacentPairs()` method on a sequence to create an
59+
/// `AdjacentPairsSequence` instance.
7160
public struct AdjacentPairsSequence<Base: Sequence> {
7261
@usableFromInline
7362
internal let base: Base
@@ -80,6 +69,7 @@ public struct AdjacentPairsSequence<Base: Sequence> {
8069
}
8170

8271
extension AdjacentPairsSequence {
72+
/// The iterator for an `AdjacentPairsSequence` or `AdjacentPairsCollection`.
8373
public struct Iterator {
8474
@usableFromInline
8575
internal var base: Base.Iterator
@@ -124,21 +114,14 @@ extension AdjacentPairsSequence: Sequence {
124114
}
125115
}
126116

127-
/// A collection of adjacent pairs of elements built from an underlying collection.
117+
extension AdjacentPairsSequence: LazySequenceProtocol
118+
where Base: LazySequenceProtocol {}
119+
120+
/// A collection of adjacent pairs of elements built from an underlying
121+
/// collection.
128122
///
129-
/// In an `AdjacentPairsCollection`, the elements of the *i*th pair are the *i*th
130-
/// and *(i+1)*th elements of the underlying sequence. The following example
131-
/// uses the `adjacentPairs()` method to iterate over adjacent pairs of
132-
/// integers:
133-
/// ```
134-
/// for pair in (1...5).adjacentPairs() {
135-
/// print(pair)
136-
/// }
137-
/// // Prints "(1, 2)"
138-
/// // Prints "(2, 3)"
139-
/// // Prints "(3, 4)"
140-
/// // Prints "(4, 5)"
141-
/// ```
123+
/// Use the `adjacentPairs()` method on a collection to create an
124+
/// `AdjacentPairsCollection` instance.
142125
public struct AdjacentPairsCollection<Base: Collection> {
143126
@usableFromInline
144127
internal let base: Base
@@ -148,13 +131,25 @@ public struct AdjacentPairsCollection<Base: Collection> {
148131
@inlinable
149132
internal init(base: Base) {
150133
self.base = base
134+
135+
// Lazily build the end index, since we can't use the instance
136+
// property pre-initialization
137+
var endIndex: Index {
138+
Index(first: base.endIndex, second: base.endIndex)
139+
}
151140

152-
// Precompute `startIndex` to ensure O(1) behavior,
153-
// avoiding indexing past `endIndex`
154-
let start = base.startIndex
155-
let end = base.endIndex
156-
let second = start == end ? start : base.index(after: start)
157-
self.startIndex = Index(first: start, second: second)
141+
// Precompute `startIndex` to ensure O(1) behavior.
142+
guard !base.isEmpty else {
143+
self.startIndex = endIndex
144+
return
145+
}
146+
147+
// If there's only one element (i.e. the second index of base == endIndex)
148+
// then this collection should be empty.
149+
let secondIndex = base.index(after: base.startIndex)
150+
self.startIndex = secondIndex == base.endIndex
151+
? endIndex
152+
: Index(first: base.startIndex, second: secondIndex)
158153
}
159154
}
160155

@@ -168,6 +163,7 @@ extension AdjacentPairsCollection {
168163
}
169164

170165
extension AdjacentPairsCollection {
166+
/// A position in an `AdjacentPairsCollection`.
171167
public struct Index: Comparable {
172168
@usableFromInline
173169
internal var first: Base.Index
@@ -181,22 +177,22 @@ extension AdjacentPairsCollection {
181177
self.second = second
182178
}
183179

180+
@inlinable
181+
public static func ==(lhs: Index, rhs: Index) -> Bool {
182+
lhs.first == rhs.first
183+
}
184+
184185
@inlinable
185186
public static func < (lhs: Index, rhs: Index) -> Bool {
186-
(lhs.first, lhs.second) < (rhs.first, rhs.second)
187+
lhs.first < rhs.first
187188
}
188189
}
189190
}
190191

191192
extension AdjacentPairsCollection: Collection {
192193
@inlinable
193194
public var endIndex: Index {
194-
switch base.endIndex {
195-
case startIndex.first, startIndex.second:
196-
return startIndex
197-
case let end:
198-
return Index(first: end, second: end)
199-
}
195+
Index(first: base.endIndex, second: base.endIndex)
200196
}
201197

202198
@inlinable
@@ -206,6 +202,7 @@ extension AdjacentPairsCollection: Collection {
206202

207203
@inlinable
208204
public func index(after i: Index) -> Index {
205+
precondition(i != endIndex, "Can't advance beyond endIndex")
209206
let next = base.index(after: i.second)
210207
return next == base.endIndex
211208
? endIndex
@@ -214,38 +211,74 @@ extension AdjacentPairsCollection: Collection {
214211

215212
@inlinable
216213
public func index(_ i: Index, offsetBy distance: Int) -> Index {
217-
if distance == 0 {
218-
return i
219-
} else if distance > 0 {
220-
let firstOffsetIndex = base.index(i.first, offsetBy: distance)
221-
let secondOffsetIndex = base.index(after: firstOffsetIndex)
222-
return secondOffsetIndex == base.endIndex
223-
? endIndex
224-
: Index(first: firstOffsetIndex, second: secondOffsetIndex)
214+
guard distance != 0 else { return i }
215+
216+
guard let result = distance > 0
217+
? offsetForward(i, by: distance, limitedBy: endIndex)
218+
: offsetBackward(i, by: -distance, limitedBy: startIndex)
219+
else { fatalError("Index out of bounds") }
220+
return result
221+
}
222+
223+
@inlinable
224+
public func index(
225+
_ i: Index, offsetBy distance: Int, limitedBy limit: Index
226+
) -> Index? {
227+
guard distance != 0 else { return i }
228+
guard limit != i else { return nil }
229+
230+
if distance > 0 {
231+
let limit = limit > i ? limit : endIndex
232+
return offsetForward(i, by: distance, limitedBy: limit)
225233
} else {
226-
return i == endIndex
227-
? Index(first: base.index(i.first, offsetBy: distance - 1),
228-
second: base.index(i.first, offsetBy: distance))
229-
: Index(first: base.index(i.first, offsetBy: distance),
230-
second: i.first)
234+
let limit = limit < i ? limit : startIndex
235+
return offsetBackward(i, by: -distance, limitedBy: limit)
231236
}
232237
}
238+
239+
@inlinable
240+
internal func offsetForward(
241+
_ i: Index, by distance: Int, limitedBy limit: Index
242+
) -> Index? {
243+
assert(distance > 0)
244+
assert(limit > i)
245+
246+
guard let newFirst = base.index(i.second, offsetBy: distance - 1, limitedBy: limit.first),
247+
newFirst != base.endIndex
248+
else { return nil }
249+
let newSecond = base.index(after: newFirst)
250+
251+
precondition(newSecond <= base.endIndex, "Can't advance beyond endIndex")
252+
return newSecond == base.endIndex
253+
? endIndex
254+
: Index(first: newFirst, second: newSecond)
255+
}
256+
257+
@inlinable
258+
internal func offsetBackward(
259+
_ i: Index, by distance: Int, limitedBy limit: Index
260+
) -> Index? {
261+
assert(distance > 0)
262+
assert(limit < i)
263+
264+
let offset = i == endIndex ? 0 : 1
265+
guard let newSecond = base.index(
266+
i.first,
267+
offsetBy: -(distance - offset),
268+
limitedBy: limit.second)
269+
else { return nil }
270+
let newFirst = base.index(newSecond, offsetBy: -1)
271+
precondition(newFirst >= base.startIndex, "Can't move before startIndex")
272+
return Index(first: newFirst, second: newSecond)
273+
}
233274

234275
@inlinable
235276
public func distance(from start: Index, to end: Index) -> Int {
236-
let offset: Int
237-
switch (start.first, end.first) {
238-
case (base.endIndex, base.endIndex):
239-
return 0
240-
case (base.endIndex, _):
241-
offset = +1
242-
case (_, base.endIndex):
243-
offset = -1
244-
default:
245-
offset = 0
246-
}
247-
248-
return base.distance(from: start.first, to: end.first) + offset
277+
// While there's a 2-step gap between the `first` base index values in
278+
// `endIndex` and the penultimate index of this collection, the `second`
279+
// base index values are consistently one step apart throughout the
280+
// entire collection.
281+
base.distance(from: start.second, to: end.second)
249282
}
250283

251284
@inlinable
@@ -259,13 +292,24 @@ extension AdjacentPairsCollection: BidirectionalCollection
259292
{
260293
@inlinable
261294
public func index(before i: Index) -> Index {
262-
i == endIndex
263-
? Index(first: base.index(i.first, offsetBy: -2),
264-
second: base.index(before: i.first))
265-
: Index(first: base.index(before: i.first),
266-
second: i.first)
295+
precondition(i != startIndex, "Can't offset before startIndex")
296+
let second = i == endIndex
297+
? base.index(before: base.endIndex)
298+
: i.first
299+
let first = base.index(before: second)
300+
return Index(first: first, second: second)
267301
}
268302
}
269303

270304
extension AdjacentPairsCollection: RandomAccessCollection
271305
where Base: RandomAccessCollection {}
306+
307+
extension AdjacentPairsCollection: LazySequenceProtocol, LazyCollectionProtocol
308+
where Base: LazyCollectionProtocol {}
309+
310+
extension AdjacentPairsCollection.Index: Hashable where Base.Index: Hashable {
311+
@inlinable
312+
public func hash(into hasher: inout Hasher) {
313+
hasher.combine(first)
314+
}
315+
}

0 commit comments

Comments
 (0)