Skip to content

Commit 7ca12be

Browse files
author
Tim Vermeulen
authored
Precompute LazyChunked's startIndex (#68)
1 parent 4f7cd34 commit 7ca12be

File tree

1 file changed

+27
-37
lines changed

1 file changed

+27
-37
lines changed

Sources/Algorithms/Chunked.swift

Lines changed: 27 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ public struct LazyChunked<Base: Collection, Subject> {
2323
@usableFromInline
2424
internal let belongInSameGroup: (Subject, Subject) -> Bool
2525

26+
/// The upper bound of the first chunk.
27+
@usableFromInline
28+
internal var firstUpperBound: Base.Index
29+
2630
@usableFromInline
2731
internal init(
2832
base: Base,
@@ -32,46 +36,37 @@ public struct LazyChunked<Base: Collection, Subject> {
3236
self.base = base
3337
self.projection = projection
3438
self.belongInSameGroup = belongInSameGroup
39+
self.firstUpperBound = base.startIndex
40+
41+
if !base.isEmpty {
42+
firstUpperBound = endOfChunk(startingAt: base.startIndex)
43+
}
3544
}
3645
}
3746

3847
extension LazyChunked: LazyCollectionProtocol {
3948
/// A position in a chunked collection.
4049
public struct Index: Comparable {
41-
/// The lower bound of the chunk at this position.
50+
/// The range corresponding to the chunk at this position.
4251
@usableFromInline
43-
internal var lowerBound: Base.Index
52+
internal var baseRange: Range<Base.Index>
4453

45-
/// The upper bound of the chunk at this position.
46-
///
47-
/// `upperBound` is optional so that computing `startIndex` can be an O(1)
48-
/// operation. When `upperBound` is `nil`, the actual upper bound is found
49-
/// when subscripting or calling `index(after:)`.
5054
@usableFromInline
51-
internal var upperBound: Base.Index?
52-
53-
@usableFromInline
54-
internal init(lowerBound: Base.Index, upperBound: Base.Index? = nil) {
55-
self.lowerBound = lowerBound
56-
self.upperBound = upperBound
55+
internal init(_ baseRange: Range<Base.Index>) {
56+
self.baseRange = baseRange
5757
}
5858

5959
@inlinable
6060
public static func == (lhs: Index, rhs: Index) -> Bool {
61-
// Only use the lower bound to test for equality, since sometimes the
62-
// `startIndex` will have an upper bound of `nil` and sometimes it won't,
63-
// such as when retrieved by:
64-
// `c.index(before: c.index(after: c.startIndex))`.
65-
//
6661
// Since each index represents the range of a disparate chunk, no two
6762
// unique indices will have the same lower bound.
68-
lhs.lowerBound == rhs.lowerBound
63+
lhs.baseRange.lowerBound == rhs.baseRange.lowerBound
6964
}
7065

7166
@inlinable
7267
public static func < (lhs: Index, rhs: Index) -> Bool {
7368
// Only use the lower bound to test for ordering, as above.
74-
lhs.lowerBound < rhs.lowerBound
69+
lhs.baseRange.lowerBound < rhs.baseRange.lowerBound
7570
}
7671
}
7772

@@ -87,28 +82,27 @@ extension LazyChunked: LazyCollectionProtocol {
8782

8883
@inlinable
8984
public var startIndex: Index {
90-
Index(lowerBound: base.startIndex)
85+
Index(base.startIndex..<firstUpperBound)
9186
}
9287

9388
@inlinable
9489
public var endIndex: Index {
95-
Index(lowerBound: base.endIndex)
90+
Index(base.endIndex..<base.endIndex)
9691
}
9792

9893
@inlinable
9994
public func index(after i: Index) -> Index {
10095
precondition(i != endIndex, "Can't advance past endIndex")
101-
let upperBound = i.upperBound ?? endOfChunk(startingAt: i.lowerBound)
96+
let upperBound = i.baseRange.upperBound
10297
guard upperBound != base.endIndex else { return endIndex }
10398
let end = endOfChunk(startingAt: upperBound)
104-
return Index(lowerBound: upperBound, upperBound: end)
99+
return Index(upperBound..<end)
105100
}
106101

107102
@inlinable
108103
public subscript(position: Index) -> Base.SubSequence {
109-
let upperBound = position.upperBound
110-
?? endOfChunk(startingAt: position.lowerBound)
111-
return base[position.lowerBound..<upperBound]
104+
precondition(position != endIndex, "Can't subscript using endIndex")
105+
return base[position.baseRange]
112106
}
113107
}
114108

@@ -144,8 +138,8 @@ extension LazyChunked: BidirectionalCollection
144138
@inlinable
145139
public func index(before i: Index) -> Index {
146140
precondition(i != startIndex, "Can't advance before startIndex")
147-
let start = startOfChunk(endingAt: i.lowerBound)
148-
return Index(lowerBound: start, upperBound: i.lowerBound)
141+
let start = startOfChunk(endingAt: i.baseRange.lowerBound)
142+
return Index(start..<i.baseRange.lowerBound)
149143
}
150144
}
151145

@@ -157,9 +151,7 @@ extension LazyCollectionProtocol {
157151
/// Returns a lazy collection of subsequences of this collection, chunked by
158152
/// the given predicate.
159153
///
160-
/// - Complexity: O(1). When iterating over the resulting collection,
161-
/// accessing each successive chunk has a complexity of O(*m*), where *m*
162-
/// is the length of the chunk.
154+
/// - Complexity: O(*n*), because the start index is pre-computed.
163155
@inlinable
164156
public func chunked(
165157
by belongInSameGroup: @escaping (Element, Element) -> Bool
@@ -173,9 +165,7 @@ extension LazyCollectionProtocol {
173165
/// Returns a lazy collection of subsequences of this collection, chunked by
174166
/// grouping elements that project to the same value.
175167
///
176-
/// - Complexity: O(1). When iterating over the resulting collection,
177-
/// accessing each successive chunk has a complexity of O(*m*), where *m*
178-
/// is the length of the chunk.
168+
/// - Complexity: O(*n*), because the start index is pre-computed.
179169
@inlinable
180170
public func chunked<Subject: Equatable>(
181171
on projection: @escaping (Element) -> Subject
@@ -276,7 +266,7 @@ public struct ChunkedByCount<Base: Collection> {
276266
/// Creates a view instance that presents the elements of `base`
277267
/// in `SubSequence` chunks of the given count.
278268
///
279-
/// - Complexity: O(n)
269+
/// - Complexity: O(*n*), because the start index is pre-computed.
280270
@inlinable
281271
internal init(_base: Base, _chunkCount: Int) {
282272
self.base = _base
@@ -522,7 +512,7 @@ extension Collection {
522512
/// print(c.chunks(ofCount: 3).map(Array.init))
523513
/// // [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
524514
///
525-
/// - Complexity: O(1)
515+
/// - Complexity: O(*n*), because the start index is pre-computed.
526516
@inlinable
527517
public func chunks(ofCount count: Int) -> ChunkedByCount<Self> {
528518
precondition(count > 0, "Cannot chunk with count <= 0!")

0 commit comments

Comments
 (0)