Skip to content

Commit db64440

Browse files
committed
Change the definition of endIndex.
It's now defined as an index with `sequenceLength == Int.max`. This frees up the range `base.endIndex..<base.endIndex` to represent a final empty subsequence, if necessary. Also fixes a bug when splitting an empty collection and not omitting empty subsequences. Test added for this case.
1 parent fe115ed commit db64440

File tree

2 files changed

+46
-14
lines changed

2 files changed

+46
-14
lines changed

Sources/Algorithms/LazySplitCollection.swift

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public struct LazySplitCollection<Base: Collection> {
2424
internal let maxSplits: Int
2525
internal let omittingEmptySubsequences: Bool
2626
internal var _startIndex: Index
27+
internal var _endIndex: Index
2728

2829
internal init(
2930
base: Base,
@@ -35,10 +36,28 @@ public struct LazySplitCollection<Base: Collection> {
3536
self.isSeparator = isSeparator
3637
self.maxSplits = maxSplits
3738
self.omittingEmptySubsequences = omittingEmptySubsequences
38-
self._startIndex = Index(baseRange: base.startIndex..<base.startIndex)
39+
self._endIndex = Index(
40+
baseRange: base.endIndex..<base.endIndex,
41+
sequenceLength: Int.max,
42+
separatorCount: Int.max
43+
)
3944

40-
if !base.isEmpty {
41-
// Precompute the start index.
45+
/// We precalculate `startIndex`. There are three possibilities:
46+
/// 1. `base` is empty and we're _not_ omitting empty subsequences, in which
47+
/// case the following index describes the sole element of this collection;
48+
self._startIndex = Index(
49+
baseRange: base.startIndex..<base.startIndex,
50+
sequenceLength: 1,
51+
separatorCount: 0
52+
)
53+
if base.isEmpty {
54+
if omittingEmptySubsequences {
55+
/// 2. `base` is empty and we _are_ omitting empty subsequences, so this
56+
/// collection has no elements;
57+
_startIndex = _endIndex
58+
}
59+
} else {
60+
/// 3. `base` isn't empty, so we must iterate it to determine the start index.
4261
_startIndex = indexForSubsequence(atOrAfter: base.startIndex)
4362
}
4463
}
@@ -56,23 +75,22 @@ extension LazySplitCollection: LazyCollectionProtocol {
5675

5776
internal init(
5877
baseRange: Range<Base.Index>,
59-
sequenceLength: Int = 0,
60-
separatorCount: Int = 0
78+
sequenceLength: Int,
79+
separatorCount: Int
6180
) {
6281
self.baseRange = baseRange
6382
self.sequenceLength = sequenceLength
6483
self.separatorCount = separatorCount
6584
}
6685

6786
public static func == (lhs: Index, rhs: Index) -> Bool {
68-
// Since each index represents the range of a disparate subsequence, no
69-
// two unique indices will have the same lower bound.
70-
lhs.baseRange.lowerBound == rhs.baseRange.lowerBound
87+
// `sequenceLength` is equivalent to the index's 1-based position in the
88+
// collection of indices.
89+
lhs.sequenceLength == rhs.sequenceLength
7190
}
7291

7392
public static func < (lhs: Index, rhs: Index) -> Bool {
74-
// Only use the lower bound to test for ordering, as above.
75-
lhs.baseRange.lowerBound < rhs.baseRange.lowerBound
93+
lhs.sequenceLength < rhs.sequenceLength
7694
}
7795
}
7896

@@ -128,7 +146,7 @@ extension LazySplitCollection: LazyCollectionProtocol {
128146
}
129147

130148
public var endIndex: Index {
131-
Index(baseRange: base.endIndex..<base.endIndex)
149+
_endIndex
132150
}
133151

134152
public func index(after i: Index) -> Index {
@@ -147,10 +165,11 @@ extension LazySplitCollection: LazyCollectionProtocol {
147165
&& i.sequenceLength < i.separatorCount + 1
148166
{
149167
/// The base collection ended with a separator, so we need to emit one
150-
/// more empty subsequence. Its range can't be equal to that of
151-
/// `endIndex`, else we'll terminate iteration prematurely.
168+
/// more empty subsequence. This one differs from `endIndex` in its
169+
/// `sequenceLength` (except in an extreme edge case!), which is the
170+
/// sole property tested for equality and comparison.
152171
return Index(
153-
baseRange: i.baseRange.upperBound..<i.baseRange.upperBound,
172+
baseRange: base.endIndex..<base.endIndex,
154173
sequenceLength: i.sequenceLength + 1,
155174
separatorCount: i.separatorCount
156175
)

Tests/SwiftAlgorithmsTests/LazySplitCollectionTests.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,19 @@ final class LazySplitCollectionTests: XCTestCase {
174174
XCTAssertEqualSequences(expectedResult, testResult)
175175
}
176176

177+
func testIntsEmptyEquatableCollectionNotOmittingEmpty() {
178+
let empty: [Int] = []
179+
let expectedResult = empty.split(
180+
separator: 42,
181+
omittingEmptySubsequences: false
182+
)
183+
let testResult = empty.lazy.split(
184+
separator: 42,
185+
omittingEmptySubsequences: false
186+
)
187+
XCTAssertEqualSequences(expectedResult, testResult)
188+
}
189+
177190
func testIntsSepCountEqualOrMoreThanElemCount() {
178191
let nums = [0, 1, 0, 0, 2]
179192
let expectedResult = nums.split(separator: 0)

0 commit comments

Comments
 (0)