diff --git a/Sources/Algorithms/Chunked.swift b/Sources/Algorithms/Chunked.swift index bf3365a6..388ee4e3 100644 --- a/Sources/Algorithms/Chunked.swift +++ b/Sources/Algorithms/Chunked.swift @@ -76,8 +76,7 @@ extension LazyChunked: LazyCollectionProtocol { internal func endOfChunk(startingAt start: Base.Index) -> Base.Index { let subject = projection(base[start]) return base[base.index(after: start)...] - .firstIndex(where: { !belongInSameGroup(subject, projection($0)) }) - ?? base.endIndex + .endOfPrefix(while: { belongInSameGroup(subject, projection($0)) }) } @inlinable @@ -119,20 +118,8 @@ extension LazyChunked: BidirectionalCollection // Get the projected value of the last element in the range ending at `end`. let subject = projection(base[indexBeforeEnd]) - - // Search backward from `end` for the first element whose projection isn't - // equal to `subject`. - if let firstMismatch = base[.. Bool ) rethrows -> SubSequence { - let start = startIndex - var result = endIndex - while result != start { - let previous = index(before: result) - guard try predicate(self[previous]) else { break } - result = previous + try self[startOfSuffix(while: predicate)...] + } +} + +//===----------------------------------------------------------------------===// +// endOfPrefix(while:) +//===----------------------------------------------------------------------===// + +extension Collection { + /// Returns the exclusive upper bound of the prefix of elements that satisfy + /// the predicate. + /// + /// - Parameter predicate: A closure that takes an element of the collection + /// as its argument and returns `true` if the element is part of the prefix + /// or `false` if it is not. Once the predicate returns `false` it will not + /// be called again. + /// + /// - Complexity: O(*n*), where *n* is the length of the collection. + @usableFromInline + internal func endOfPrefix( + while predicate: (Element) throws -> Bool + ) rethrows -> Index { + var index = startIndex + while try index != endIndex && predicate(self[index]) { + formIndex(after: &index) + } + return index + } +} + +//===----------------------------------------------------------------------===// +// startOfSuffix(while:) +//===----------------------------------------------------------------------===// + +extension BidirectionalCollection { + /// Returns the inclusive lower bound of the suffix of elements that satisfy + /// the predicate. + /// + /// - Parameter predicate: A closure that takes an element of the collection + /// as its argument and returns `true` if the element is part of the suffix + /// or `false` if it is not. Once the predicate returns `false` it will not + /// be called again. + /// + /// - Complexity: O(*n*), where *n* is the length of the collection. + @usableFromInline + internal func startOfSuffix( + while predicate: (Element) throws -> Bool + ) rethrows -> Index { + var index = endIndex + while index != startIndex { + let after = index + formIndex(before: &index) + if try !predicate(self[index]) { + return after + } } - return self[result...] + return index } } diff --git a/Sources/Algorithms/Trim.swift b/Sources/Algorithms/Trim.swift index bf7ec2d3..3e992afb 100644 --- a/Sources/Algorithms/Trim.swift +++ b/Sources/Algorithms/Trim.swift @@ -29,20 +29,8 @@ extension BidirectionalCollection { public func trimming( while predicate: (Element) throws -> Bool ) rethrows -> SubSequence { - // Consume elements from the front. - let sliceStart = try firstIndex { try predicate($0) == false } ?? endIndex - // sliceEnd is the index _after_ the last index to match the predicate. - var sliceEnd = endIndex - - while sliceStart != sliceEnd { - let idxBeforeSliceEnd = index(before: sliceEnd) - guard try predicate(self[idxBeforeSliceEnd]) else { - return self[sliceStart.. 10 }), []) } + + func testEndOfPrefix() { + let array = Array(0..<10) + XCTAssertEqual(array.endOfPrefix(while: { $0 < 3 }), 3) + XCTAssertEqual(array.endOfPrefix(while: { _ in false }), array.startIndex) + XCTAssertEqual(array.endOfPrefix(while: { _ in true }), array.endIndex) + + let empty = [Int]() + XCTAssertEqual(empty.endOfPrefix(while: { $0 < 3 }), 0) + XCTAssertEqual(empty.endOfPrefix(while: { _ in false }), empty.startIndex) + XCTAssertEqual(empty.endOfPrefix(while: { _ in true }), empty.endIndex) + } + + func testStartOfSuffix() { + let array = Array(0..<10) + XCTAssertEqual(array.startOfSuffix(while: { $0 >= 3 }), 3) + XCTAssertEqual(array.startOfSuffix(while: { _ in false }), array.endIndex) + XCTAssertEqual(array.startOfSuffix(while: { _ in true }), array.startIndex) + + let empty = [Int]() + XCTAssertEqual(empty.startOfSuffix(while: { $0 < 3 }), 0) + XCTAssertEqual(empty.startOfSuffix(while: { _ in false }), empty.endIndex) + XCTAssertEqual(empty.startOfSuffix(while: { _ in true }), empty.startIndex) + } }