From 6d18566fa561292e33c98553f51c74be140cb8ae Mon Sep 17 00:00:00 2001 From: Tim Vermeulen Date: Mon, 2 Nov 2020 23:52:48 +0100 Subject: [PATCH 1/3] Add endOfPrefix(while:) and startOfSuffix(while:) internally --- Sources/Algorithms/Suffix.swift | 56 +++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/Sources/Algorithms/Suffix.swift b/Sources/Algorithms/Suffix.swift index 2310a956..553339bc 100644 --- a/Sources/Algorithms/Suffix.swift +++ b/Sources/Algorithms/Suffix.swift @@ -37,3 +37,59 @@ extension BidirectionalCollection { return self[result...] } } + +//===----------------------------------------------------------------------===// +// 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 index + } +} From 8ac0221cccfd2a9d6713ee33705d364a4b2fe270 Mon Sep 17 00:00:00 2001 From: Tim Vermeulen Date: Tue, 9 Mar 2021 12:22:04 +0100 Subject: [PATCH 2/3] Add tests --- Tests/SwiftAlgorithmsTests/SuffixTests.swift | 26 +++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Tests/SwiftAlgorithmsTests/SuffixTests.swift b/Tests/SwiftAlgorithmsTests/SuffixTests.swift index c2a03693..03ead353 100644 --- a/Tests/SwiftAlgorithmsTests/SuffixTests.swift +++ b/Tests/SwiftAlgorithmsTests/SuffixTests.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// import XCTest -import Algorithms +@testable import Algorithms final class SuffixTests: XCTestCase { func testSuffix() { @@ -23,4 +23,28 @@ final class SuffixTests: XCTestCase { let empty: [Int] = [] XCTAssertEqualSequences(empty.suffix(while: { $0 > 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) + } } From e0d9fb95f8754ebdffd1d54210707f322b6f676e Mon Sep 17 00:00:00 2001 From: Tim Vermeulen Date: Tue, 9 Mar 2021 12:08:17 +0100 Subject: [PATCH 3/3] Use these methods throughout the package --- Sources/Algorithms/Chunked.swift | 19 +++---------------- Sources/Algorithms/Suffix.swift | 9 +-------- Sources/Algorithms/Trim.swift | 18 +++--------------- 3 files changed, 7 insertions(+), 39 deletions(-) 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 - } - return self[result...] + try self[startOfSuffix(while: predicate)...] } } 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..