Skip to content

Add trimmingSuffix, trimmingPrefix and mutating variants #104

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 8 commits into from
May 25, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion Guides/Trim.md
Original file line number Diff line number Diff line change
@@ -48,6 +48,25 @@ func myAlgorithm2<Input>(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
Copy link
Contributor Author

@fedeci fedeci May 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added 0.3.0 supposing that this will be released in a next minor version, but I am not sure.


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: { ... })
```
```
218 changes: 215 additions & 3 deletions Sources/Algorithms/Trim.swift
Original file line number Diff line number Diff line change
@@ -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..<endIndex]
}
}

//===----------------------------------------------------------------------===//
// trimPrefix(while:)
//===----------------------------------------------------------------------===//

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(
while predicate: (Element) throws -> Bool
) rethrows {
let end = try endOfPrefix(while: predicate)
removeSubrange(startIndex..<end)
}
}

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
) 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..<end]
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.
///
/// This example uses `trimmingSuffix(while:)` to get a substring without the white
/// space at the end of the string:
///
/// let myString = " hello, world "
/// print(myString.trimmingSuffix(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 trimmingSuffix(
while predicate: (Element) throws -> Bool
) rethrows -> SubSequence {
let end = try startOfSuffix(while: predicate)
return self[startIndex..<end]
}
}

//===----------------------------------------------------------------------===//
// trim(while:) / trimSuffix(while:)
//===----------------------------------------------------------------------===//

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(
while predicate: (Element) throws -> Bool
) rethrows {
replaceSubrange(startIndex..<endIndex, with: 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
@_disfavoredOverload
public mutating func trimSuffix(
while predicate: (Element) throws -> Bool
) rethrows {
let start = try startOfSuffix(while: predicate)
removeSubrange(start..<endIndex)
}
}

extension BidirectionalCollection where Self == Self.SubSequence {
/// 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
public mutating func trim(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For what I understood from the issue we should also add an unconstrained version of trim, right @timvermeulen ?

while predicate: (Element) throws -> 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)
}
}
63 changes: 56 additions & 7 deletions Tests/SwiftAlgorithmsTests/TrimTests.swift
Original file line number Diff line number Diff line change
@@ -13,47 +13,96 @@ 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 {
$0.isMultiple(of: 2)
}
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])
}
}