Skip to content

Commit ab0ec2a

Browse files
author
Tim Vermeulen
authored
Internally add Collection.endOfPrefix(while:) and BidirectionalCollection.startOfSuffix(while:) (#92)
* Add endOfPrefix(while:) and startOfSuffix(while:) internally * Add tests * Use these methods throughout the package
1 parent e7c6716 commit ab0ec2a

File tree

4 files changed

+87
-39
lines changed

4 files changed

+87
-39
lines changed

Sources/Algorithms/Chunked.swift

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,7 @@ extension LazyChunked: LazyCollectionProtocol {
7676
internal func endOfChunk(startingAt start: Base.Index) -> Base.Index {
7777
let subject = projection(base[start])
7878
return base[base.index(after: start)...]
79-
.firstIndex(where: { !belongInSameGroup(subject, projection($0)) })
80-
?? base.endIndex
79+
.endOfPrefix(while: { belongInSameGroup(subject, projection($0)) })
8180
}
8281

8382
@inlinable
@@ -119,20 +118,8 @@ extension LazyChunked: BidirectionalCollection
119118

120119
// Get the projected value of the last element in the range ending at `end`.
121120
let subject = projection(base[indexBeforeEnd])
122-
123-
// Search backward from `end` for the first element whose projection isn't
124-
// equal to `subject`.
125-
if let firstMismatch = base[..<indexBeforeEnd]
126-
.lastIndex(where: { !belongInSameGroup(projection($0), subject) })
127-
{
128-
// If we found one, that's the last element of the _next_ previous chunk,
129-
// and therefore one position _before_ the start of this chunk.
130-
return base.index(after: firstMismatch)
131-
} else {
132-
// If we didn't find such an element, this chunk extends back to the start
133-
// of the collection.
134-
return base.startIndex
135-
}
121+
return base[..<indexBeforeEnd]
122+
.startOfSuffix(while: { belongInSameGroup(projection($0), subject) })
136123
}
137124

138125
@inlinable

Sources/Algorithms/Suffix.swift

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,62 @@ extension BidirectionalCollection {
2727
public func suffix(
2828
while predicate: (Element) throws -> Bool
2929
) rethrows -> SubSequence {
30-
let start = startIndex
31-
var result = endIndex
32-
while result != start {
33-
let previous = index(before: result)
34-
guard try predicate(self[previous]) else { break }
35-
result = previous
30+
try self[startOfSuffix(while: predicate)...]
31+
}
32+
}
33+
34+
//===----------------------------------------------------------------------===//
35+
// endOfPrefix(while:)
36+
//===----------------------------------------------------------------------===//
37+
38+
extension Collection {
39+
/// Returns the exclusive upper bound of the prefix of elements that satisfy
40+
/// the predicate.
41+
///
42+
/// - Parameter predicate: A closure that takes an element of the collection
43+
/// as its argument and returns `true` if the element is part of the prefix
44+
/// or `false` if it is not. Once the predicate returns `false` it will not
45+
/// be called again.
46+
///
47+
/// - Complexity: O(*n*), where *n* is the length of the collection.
48+
@usableFromInline
49+
internal func endOfPrefix(
50+
while predicate: (Element) throws -> Bool
51+
) rethrows -> Index {
52+
var index = startIndex
53+
while try index != endIndex && predicate(self[index]) {
54+
formIndex(after: &index)
55+
}
56+
return index
57+
}
58+
}
59+
60+
//===----------------------------------------------------------------------===//
61+
// startOfSuffix(while:)
62+
//===----------------------------------------------------------------------===//
63+
64+
extension BidirectionalCollection {
65+
/// Returns the inclusive lower bound of the suffix of elements that satisfy
66+
/// the predicate.
67+
///
68+
/// - Parameter predicate: A closure that takes an element of the collection
69+
/// as its argument and returns `true` if the element is part of the suffix
70+
/// or `false` if it is not. Once the predicate returns `false` it will not
71+
/// be called again.
72+
///
73+
/// - Complexity: O(*n*), where *n* is the length of the collection.
74+
@usableFromInline
75+
internal func startOfSuffix(
76+
while predicate: (Element) throws -> Bool
77+
) rethrows -> Index {
78+
var index = endIndex
79+
while index != startIndex {
80+
let after = index
81+
formIndex(before: &index)
82+
if try !predicate(self[index]) {
83+
return after
84+
}
3685
}
37-
return self[result...]
86+
return index
3887
}
3988
}

Sources/Algorithms/Trim.swift

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,8 @@ extension BidirectionalCollection {
2929
public func trimming(
3030
while predicate: (Element) throws -> Bool
3131
) rethrows -> SubSequence {
32-
// Consume elements from the front.
33-
let sliceStart = try firstIndex { try predicate($0) == false } ?? endIndex
34-
// sliceEnd is the index _after_ the last index to match the predicate.
35-
var sliceEnd = endIndex
36-
37-
while sliceStart != sliceEnd {
38-
let idxBeforeSliceEnd = index(before: sliceEnd)
39-
guard try predicate(self[idxBeforeSliceEnd]) else {
40-
return self[sliceStart..<sliceEnd]
41-
}
42-
sliceEnd = idxBeforeSliceEnd
43-
}
44-
45-
// Trimmed everything.
46-
return self[Range(uncheckedBounds: (sliceStart, sliceStart))]
32+
let start = try endOfPrefix(while: predicate)
33+
let end = try self[start...].startOfSuffix(while: predicate)
34+
return self[start..<end]
4735
}
4836
}

Tests/SwiftAlgorithmsTests/SuffixTests.swift

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//===----------------------------------------------------------------------===//
1111

1212
import XCTest
13-
import Algorithms
13+
@testable import Algorithms
1414

1515
final class SuffixTests: XCTestCase {
1616
func testSuffix() {
@@ -23,4 +23,28 @@ final class SuffixTests: XCTestCase {
2323
let empty: [Int] = []
2424
XCTAssertEqualSequences(empty.suffix(while: { $0 > 10 }), [])
2525
}
26+
27+
func testEndOfPrefix() {
28+
let array = Array(0..<10)
29+
XCTAssertEqual(array.endOfPrefix(while: { $0 < 3 }), 3)
30+
XCTAssertEqual(array.endOfPrefix(while: { _ in false }), array.startIndex)
31+
XCTAssertEqual(array.endOfPrefix(while: { _ in true }), array.endIndex)
32+
33+
let empty = [Int]()
34+
XCTAssertEqual(empty.endOfPrefix(while: { $0 < 3 }), 0)
35+
XCTAssertEqual(empty.endOfPrefix(while: { _ in false }), empty.startIndex)
36+
XCTAssertEqual(empty.endOfPrefix(while: { _ in true }), empty.endIndex)
37+
}
38+
39+
func testStartOfSuffix() {
40+
let array = Array(0..<10)
41+
XCTAssertEqual(array.startOfSuffix(while: { $0 >= 3 }), 3)
42+
XCTAssertEqual(array.startOfSuffix(while: { _ in false }), array.endIndex)
43+
XCTAssertEqual(array.startOfSuffix(while: { _ in true }), array.startIndex)
44+
45+
let empty = [Int]()
46+
XCTAssertEqual(empty.startOfSuffix(while: { $0 < 3 }), 0)
47+
XCTAssertEqual(empty.startOfSuffix(while: { _ in false }), empty.endIndex)
48+
XCTAssertEqual(empty.startOfSuffix(while: { _ in true }), empty.startIndex)
49+
}
2650
}

0 commit comments

Comments
 (0)