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])
+ }
}