From e4218b121ade5908e18f88117c2ef4c713739234 Mon Sep 17 00:00:00 2001 From: Federico Ciardi Date: Fri, 12 Mar 2021 17:50:03 +0100 Subject: [PATCH 1/8] feat: add `trimmingSuffix(while)` and `trimmingPrefix(while)` --- Sources/Algorithms/Trim.swift | 52 ++++++++++++++++++++-- Tests/SwiftAlgorithmsTests/TrimTests.swift | 10 +++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/Sources/Algorithms/Trim.swift b/Sources/Algorithms/Trim.swift index 3e992afb..f7ad41b6 100644 --- a/Sources/Algorithms/Trim.swift +++ b/Sources/Algorithms/Trim.swift @@ -9,6 +9,31 @@ // //===----------------------------------------------------------------------===// +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 -> SubSequence { - let start = try endOfPrefix(while: predicate) - let end = try self[start...].startOfSuffix(while: predicate) - return self[start.. Bool + ) rethrows -> SubSequence { + let end = try self[startIndex...].startOfSuffix(while: predicate) + return self[startIndex.. Date: Sat, 13 Mar 2021 08:58:37 +0100 Subject: [PATCH 2/8] feat: add mutating variants --- Sources/Algorithms/Trim.swift | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Sources/Algorithms/Trim.swift b/Sources/Algorithms/Trim.swift index f7ad41b6..0417e191 100644 --- a/Sources/Algorithms/Trim.swift +++ b/Sources/Algorithms/Trim.swift @@ -34,6 +34,15 @@ extension Collection { } } +extension Collection where Self == SubSequence { + @inlinable + public mutating func trimPrefix( + while predicate: (Element) throws -> Bool + ) rethrows { + self = try trimmingPrefix(while: predicate) + } +} + extension BidirectionalCollection { /// Returns a `SubSequence` formed by discarding all elements at the start and /// end of the collection which satisfy the given predicate. @@ -80,3 +89,19 @@ extension BidirectionalCollection { return self[startIndex.. Bool + ) rethrows { + self = try trimming(while: predicate) + } + + @inlinable + public mutating func trimSuffix( + while predicate: (Element) throws -> Bool + ) rethrows { + self = try trimmingPrefix(while: predicate) + } +} From c5bf93755da245f80e934490654721327786b624 Mon Sep 17 00:00:00 2001 From: Federico Ciardi Date: Tue, 23 Mar 2021 08:50:01 +0100 Subject: [PATCH 3/8] Update mutating variants --- Sources/Algorithms/Trim.swift | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/Sources/Algorithms/Trim.swift b/Sources/Algorithms/Trim.swift index 0417e191..0c4e2f27 100644 --- a/Sources/Algorithms/Trim.swift +++ b/Sources/Algorithms/Trim.swift @@ -7,6 +7,9 @@ // // See https://swift.org/LICENSE.txt for license information // + +//===----------------------------------------------------------------------===// +// trimmingPrefix(while:) //===----------------------------------------------------------------------===// extension Collection { @@ -34,15 +37,23 @@ extension Collection { } } -extension Collection where Self == SubSequence { +//===----------------------------------------------------------------------===// +// trimPrefix(toStartAt:) +//===----------------------------------------------------------------------===// + +extension Collection where Self: RangeReplaceableCollection { @inlinable public mutating func trimPrefix( while predicate: (Element) throws -> Bool ) rethrows { - self = try trimmingPrefix(while: predicate) + replaceSubrange(startIndex.. Bool ) rethrows { - self = try trimming(while: predicate) + replaceSubrange(startIndex.. Bool ) rethrows { - self = try trimmingPrefix(while: predicate) + replaceSubrange(startIndex.. Date: Fri, 2 Apr 2021 12:13:59 +0200 Subject: [PATCH 4/8] Address code review --- Sources/Algorithms/Trim.swift | 16 +++++++++------- Tests/SwiftAlgorithmsTests/TrimTests.swift | 8 ++++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Sources/Algorithms/Trim.swift b/Sources/Algorithms/Trim.swift index 0c4e2f27..28597c6d 100644 --- a/Sources/Algorithms/Trim.swift +++ b/Sources/Algorithms/Trim.swift @@ -38,7 +38,7 @@ extension Collection { } //===----------------------------------------------------------------------===// -// trimPrefix(toStartAt:) +// trimPrefix(while:) //===----------------------------------------------------------------------===// extension Collection where Self: RangeReplaceableCollection { @@ -46,12 +46,13 @@ extension Collection where Self: RangeReplaceableCollection { public mutating func trimPrefix( while predicate: (Element) throws -> Bool ) rethrows { - replaceSubrange(startIndex.. SubSequence { return try trimmingPrefix(while: predicate).trimmingSuffix(while: predicate) } - + /// Returns a `SubSequence` formed by discarding all elements at the end /// of the collection which satisfy the given predicate. /// @@ -96,13 +97,13 @@ extension BidirectionalCollection { public func trimmingSuffix( while predicate: (Element) throws -> Bool ) rethrows -> SubSequence { - let end = try self[startIndex...].startOfSuffix(while: predicate) + let end = try startOfSuffix(while: predicate) return self[startIndex.. Bool ) rethrows { - replaceSubrange(startIndex.. Date: Fri, 2 Apr 2021 13:06:43 +0200 Subject: [PATCH 5/8] Add overloads --- Sources/Algorithms/Trim.swift | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Sources/Algorithms/Trim.swift b/Sources/Algorithms/Trim.swift index 28597c6d..b6f4ee31 100644 --- a/Sources/Algorithms/Trim.swift +++ b/Sources/Algorithms/Trim.swift @@ -51,6 +51,15 @@ extension Collection where Self: RangeReplaceableCollection { } } +extension Collection where Self == Self.SubSequence { + @inlinable + public mutating func trimPrefix( + while predicate: (Element) throws -> Bool + ) rethrows { + self = try trimmingPrefix(while: predicate) + } +} + //===----------------------------------------------------------------------===// // trimming(while:) / trimmingSuffix(while:) //===----------------------------------------------------------------------===// @@ -122,3 +131,19 @@ extension BidirectionalCollection where Self: RangeReplaceableCollection { removeSubrange(start.. Bool + ) rethrows { + self = try trimming(while: predicate) + } + + @inlinable + public mutating func trimSuffix( + while predicate: (Element) throws -> Bool + ) rethrows { + self = try trimmingSuffix(while: predicate) + } +} From 9c2fb045ca502785eb7e9a5efdfb4cb7df0fb436 Mon Sep 17 00:00:00 2001 From: Federico Ciardi Date: Tue, 13 Apr 2021 09:58:51 +0200 Subject: [PATCH 6/8] Add tests --- Sources/Algorithms/Trim.swift | 3 ++ Tests/SwiftAlgorithmsTests/TrimTests.swift | 57 ++++++++++++++++++---- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/Sources/Algorithms/Trim.swift b/Sources/Algorithms/Trim.swift index b6f4ee31..84a80414 100644 --- a/Sources/Algorithms/Trim.swift +++ b/Sources/Algorithms/Trim.swift @@ -43,6 +43,7 @@ extension Collection { extension Collection where Self: RangeReplaceableCollection { @inlinable + @_disfavoredOverload public mutating func trimPrefix( while predicate: (Element) throws -> Bool ) rethrows { @@ -117,6 +118,7 @@ extension BidirectionalCollection { extension BidirectionalCollection where Self: RangeReplaceableCollection { @inlinable + @_disfavoredOverload public mutating func trim( while predicate: (Element) throws -> Bool ) rethrows { @@ -124,6 +126,7 @@ extension BidirectionalCollection where Self: RangeReplaceableCollection { } @inlinable + @_disfavoredOverload public mutating func trimSuffix( while predicate: (Element) throws -> Bool ) rethrows { diff --git a/Tests/SwiftAlgorithmsTests/TrimTests.swift b/Tests/SwiftAlgorithmsTests/TrimTests.swift index 2d1dcc3d..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,44 +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]) + } } From 18406a23d10c35246b2d3b2765bd7fb854c170ee Mon Sep 17 00:00:00 2001 From: Federico Ciardi Date: Tue, 13 Apr 2021 10:57:35 +0200 Subject: [PATCH 7/8] Add docs --- Sources/Algorithms/Trim.swift | 96 +++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/Sources/Algorithms/Trim.swift b/Sources/Algorithms/Trim.swift index 84a80414..9771f91e 100644 --- a/Sources/Algorithms/Trim.swift +++ b/Sources/Algorithms/Trim.swift @@ -42,6 +42,22 @@ extension Collection { //===----------------------------------------------------------------------===// extension Collection where Self: RangeReplaceableCollection { + /// Mutates a `Collection` by discarding all elements at the start + /// of it which satisfy the given predicate. + /// + /// This example uses `trimPrefix(while:)` to remove the white + /// space at the beginning of the string: + /// + /// let myString = " hello, world " + /// myString.trimPrefix(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 @_disfavoredOverload public mutating func trimPrefix( @@ -53,6 +69,22 @@ extension Collection where Self: RangeReplaceableCollection { } extension Collection where Self == Self.SubSequence { + /// Mutates a `Collection` by discarding all elements at the start + /// of it which satisfy the given predicate. + /// + /// This example uses `trimPrefix(while:)` to remove the white + /// space at the beginning of the string: + /// + /// let myString = " hello, world " + /// myString.trimPrefix(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 trimPrefix( while predicate: (Element) throws -> Bool @@ -117,6 +149,22 @@ extension BidirectionalCollection { //===----------------------------------------------------------------------===// extension BidirectionalCollection where Self: RangeReplaceableCollection { + /// Mutates a `BidirectionalCollection` by discarding all elements at the start + /// and at the end of it which satisfy the given predicate. + /// + /// This example uses `trim(while:)` to remove the white + /// space at the beginning of the string: + /// + /// let myString = " hello, world " + /// myString.trim(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 @_disfavoredOverload public mutating func trim( @@ -125,6 +173,22 @@ extension BidirectionalCollection where Self: RangeReplaceableCollection { replaceSubrange(startIndex.. Bool @@ -143,6 +223,22 @@ extension BidirectionalCollection where Self == Self.SubSequence { 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 From 8a67ef34db0a481a56dbd7463c8e5c6a85f18e5c Mon Sep 17 00:00:00 2001 From: Federico Ciardi Date: Fri, 21 May 2021 18:34:02 +0200 Subject: [PATCH 8/8] Update Guides --- Guides/Trim.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) 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