diff --git a/Guides/Trim.md b/Guides/Trim.md index 37f083d9..e5b098af 100644 --- a/Guides/Trim.md +++ b/Guides/Trim.md @@ -48,6 +48,25 @@ func myAlgorithm2(input: Input) where Input: BidirectionalCollection { Swift provides the `BidirectionalCollection` protocol for marking types which support reverse traversal, and generic types and algorithms which want to make use of that should add it to their constraints. +### >= 0.3.0 + +In `v0.3.0` new methods are added to allow discarding all the elements matching the predicate at the beginning (prefix) or at the ending (suffix) of the collection. +- `trimmingSuffix(while:)` can only be run on collections conforming to the `BidirectionalCollection` protocol. +- `trimmingPrefix(while:)` can be run also on collections conforming to the `Collection` protocol. + +```swift +let myString = " hello, world " +print(myString.trimmingPrefix(while: \.isWhitespace)) // "hello, world " + +print(myString.trimmingSuffix(while: \.isWhitespace)) // " hello, world" +``` +Also mutating variants for all the methods already existing and the new ones are added. +```swift +var myString = " hello, world " +myString.trim(while: \.isWhitespace) +print(myString) // "hello, world" +``` + ### Complexity Calling this method is O(_n_). @@ -111,4 +130,4 @@ let result = input.dropFromBothEnds(while: { ... }) // No such ambiguity here. let result = input.trimming(while: { ... }) -``` +``` \ No newline at end of file diff --git a/Sources/Algorithms/Trim.swift b/Sources/Algorithms/Trim.swift index 3e992afb..9771f91e 100644 --- a/Sources/Algorithms/Trim.swift +++ b/Sources/Algorithms/Trim.swift @@ -7,6 +7,94 @@ // // See https://swift.org/LICENSE.txt for license information // + +//===----------------------------------------------------------------------===// +// trimmingPrefix(while:) +//===----------------------------------------------------------------------===// + +extension Collection { + /// Returns a `SubSequence` formed by discarding all elements at the start + /// of the collection which satisfy the given predicate. + /// + /// This example uses `trimmingPrefix(while:)` to get a substring without the white + /// space at the beginning of the string: + /// + /// let myString = " hello, world " + /// print(myString.trimmingPrefix(while: \.isWhitespace)) // "hello, world " + /// + /// - Parameters: + /// - predicate: A closure which determines if the element should be + /// omitted from the resulting slice. + /// + /// - Complexity: O(*n*), where *n* is the length of this collection. + /// + @inlinable + public func trimmingPrefix( + while predicate: (Element) throws -> Bool + ) rethrows -> SubSequence { + let start = try endOfPrefix(while: predicate) + return self[start.. Bool + ) rethrows { + let end = try endOfPrefix(while: predicate) + removeSubrange(startIndex.. Bool + ) rethrows { + self = try trimmingPrefix(while: predicate) + } +} + +//===----------------------------------------------------------------------===// +// trimming(while:) / trimmingSuffix(while:) //===----------------------------------------------------------------------===// extension BidirectionalCollection { @@ -29,8 +117,132 @@ extension BidirectionalCollection { public func trimming( while predicate: (Element) throws -> Bool ) rethrows -> SubSequence { - let start = try endOfPrefix(while: predicate) - let end = try self[start...].startOfSuffix(while: predicate) - return self[start.. Bool + ) rethrows -> SubSequence { + let end = try startOfSuffix(while: predicate) + return self[startIndex.. Bool + ) rethrows { + replaceSubrange(startIndex.. Bool + ) rethrows { + let start = try startOfSuffix(while: predicate) + removeSubrange(start.. Bool + ) rethrows { + self = try trimming(while: predicate) + } + + /// Mutates a `BidirectionalCollection` by discarding all elements at the end + /// of it which satisfy the given predicate. + /// + /// This example uses `trimSuffix(while:)` to remove the white + /// space at the beginning of the string: + /// + /// let myString = " hello, world " + /// myString.trimSuffix(while: \.isWhitespace) + /// print(myString) // " hello, world" + /// + /// - Parameters: + /// - predicate: A closure which determines if the element should be + /// removed from the string. + /// + /// - Complexity: O(*n*), where *n* is the length of this collection. + /// + @inlinable + public mutating func trimSuffix( + while predicate: (Element) throws -> Bool + ) rethrows { + self = try trimmingSuffix(while: predicate) } } diff --git a/Tests/SwiftAlgorithmsTests/TrimTests.swift b/Tests/SwiftAlgorithmsTests/TrimTests.swift index 41c237ef..46d38ce9 100644 --- a/Tests/SwiftAlgorithmsTests/TrimTests.swift +++ b/Tests/SwiftAlgorithmsTests/TrimTests.swift @@ -13,12 +13,12 @@ import Algorithms import XCTest final class TrimTests: XCTestCase { - + func testEmpty() { let results_empty = ([] as [Int]).trimming { $0.isMultiple(of: 2) } XCTAssertEqual(results_empty, []) } - + func testNoMatch() { // No match (nothing trimmed). let results_nomatch = [1, 3, 5, 7, 9, 11, 13, 15].trimming { @@ -26,34 +26,83 @@ final class TrimTests: XCTestCase { } XCTAssertEqual(results_nomatch, [1, 3, 5, 7, 9, 11, 13, 15]) } - + func testNoTailMatch() { // No tail match (only trim head). let results_notailmatch = [1, 3, 5, 7, 9, 11, 13, 15].trimming { $0 < 10 } XCTAssertEqual(results_notailmatch, [11, 13, 15]) } - + func testNoHeadMatch() { // No head match (only trim tail). let results_noheadmatch = [1, 3, 5, 7, 9, 11, 13, 15].trimming { $0 > 10 } XCTAssertEqual(results_noheadmatch, [1, 3, 5, 7, 9]) } - + func testBothEndsMatch() { // Both ends match, some string of >1 elements do not (return that string). let results = [2, 10, 11, 15, 20, 21, 100].trimming { $0.isMultiple(of: 2) } XCTAssertEqual(results, [11, 15, 20, 21]) } - + func testEverythingMatches() { // Everything matches (trim everything). let results_allmatch = [1, 3, 5, 7, 9, 11, 13, 15].trimming { _ in true } XCTAssertEqual(results_allmatch, []) } - + func testEverythingButOneMatches() { // Both ends match, one element does not (trim all except that element). let results_one = [2, 10, 12, 15, 20, 100].trimming { $0.isMultiple(of: 2) } XCTAssertEqual(results_one, [15]) } + + func testTrimmingPrefix() { + let results = [2, 10, 12, 15, 20, 100].trimmingPrefix { $0.isMultiple(of: 2) } + XCTAssertEqual(results, [15, 20, 100]) + } + + func testTrimmingSuffix() { + let results = [2, 10, 12, 15, 20, 100].trimmingSuffix { $0.isMultiple(of: 2) } + XCTAssertEqual(results, [2, 10, 12, 15]) + } + + // Self == Self.Subsequence + func testTrimNoAmbiguity() { + var values = [2, 10, 12, 15, 20, 100] as ArraySlice + values.trim { $0.isMultiple(of: 2) } + XCTAssertEqual(values, [15]) + } + + // Self == Self.Subsequence + func testTrimPrefixNoAmbiguity() { + var values = [2, 10, 12, 15, 20, 100] as ArraySlice + values.trimPrefix { $0.isMultiple(of: 2) } + XCTAssertEqual(values, [15, 20, 100]) + } + + // Self == Self.Subsequence + func testTrimSuffixNoAmbiguity() { + var values = [2, 10, 12, 15, 20, 100] as ArraySlice + values.trimSuffix { $0.isMultiple(of: 2) } + XCTAssertEqual(values, [2, 10, 12, 15]) + } + + func testTrimRangeReplaceable() { + var values = [2, 10, 12, 15, 20, 100] + values.trim { $0.isMultiple(of: 2) } + XCTAssertEqual(values, [15]) + } + + func testTrimPrefixRangeReplaceable() { + var values = [2, 10, 12, 15, 20, 100] + values.trimPrefix { $0.isMultiple(of: 2) } + XCTAssertEqual(values, [15, 20, 100]) + } + + func testTrimSuffixRangeReplaceable() { + var values = [2, 10, 12, 15, 20, 100] + values.trimSuffix { $0.isMultiple(of: 2) } + XCTAssertEqual(values, [2, 10, 12, 15]) + } }