Skip to content

Make trimming helper methods public #211

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
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
79 changes: 50 additions & 29 deletions Guides/Trim.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Trim.swift) |
[Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/TrimTests.swift)]

Returns a `SubSequence` formed by discarding all elements at the start and end
of the collection which satisfy the given predicate.
A group of methods that return the `SubSequence` formed by discarding elements
at the start and/or end of the collection which satisfy the given predicate.

This example uses `trimming(while:)` to get a substring without the white space
at the beginning and end of the string.
Expand All @@ -17,24 +17,44 @@ let results = [2, 10, 11, 15, 20, 21, 100].trimming(while: { $0.isMultiple(of: 2
print(results) // [11, 15, 20, 21]
```

The `Algorithms` library also includes methods that trim from each end,
as well as mutating versions of all three methods.

## Detailed Design

A new method is added to `BidirectionalCollection`:
New methods are added to `Collection` and `BidirectionalCollection`:

```swift
extension Collection {
func trimmingPrefix(while predicate: (Element) throws -> Bool) rethrows -> SubSequence
}

extension BidirectionalCollection {
public func trimming(while predicate: (Element) throws -> Bool) rethrows -> SubSequence
func trimmingSuffix(while predicate: (Element) throws -> Bool) rethrows -> SubSequence

func trimming(while predicate: (Element) throws -> Bool) rethrows -> SubSequence
}

extension Collection where Self: RangeReplaceableCollection {
mutating func trimPrefix(while predicate: (Element) throws -> Bool) rethrows
}

extension BidirectionalCollection where Self: RangeReplaceableCollection {
mutating func trimSuffix(while predicate: (Element) throws -> Bool) rethrows

mutating func trim(while predicate: (Element) throws -> Bool) rethrows
}
```

This method requires `BidirectionalCollection` for an efficient implementation
which visits as few elements as possible.
There are also overloads of the mutating methods when `Self == Self.SubSequence`,
for non-range-replaceable self-slicing types.

A less-efficient implementation is _possible_ for any `Collection`, which would
involve always traversing the entire collection. This implementation is not
provided, as it would mean developers of generic algorithms who forget to add
the `BidirectionalCollection` constraint will receive that inefficient
implementation:
Though the `trimming` and `trimmingSuffix` methods are declared on
`BidirectionalCollection`, a less-efficient implementation is _possible_ for
any `Collection`, which would involve always traversing the entire collection.
This implementation is not provided, as it would mean developers of generic
algorithms who forget to add the `BidirectionalCollection` constraint will
receive that inefficient implementation:

```swift
func myAlgorithm<Input>(input: Input) where Input: Collection {
Expand All @@ -50,28 +70,29 @@ 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
#### Supporting Methods

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.
The `endOfPrefix(while:)` and `startOfSuffix(while:)` methods are used
in the implementation of the trimming methods described above. As these
supporting methods are independently useful, they are included in the library
as well.

```swift
let myString = " hello, world "
print(myString.trimmingPrefix(while: \.isWhitespace)) // "hello, world "
extension Collection {
/// Returns the exclusive upper bound of the prefix of elements that satisfy
/// the predicate.
func endOfPrefix(
while predicate: (Element) throws -> Bool
) rethrows -> Index
}

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"
extension BidirectionalCollection {
/// Returns the inclusive lower bound of the suffix of elements that satisfy
/// the predicate.
func startOfSuffix(
while predicate: (Element) throws -> Bool
) rethrows -> Index
}
```

### Complexity
Expand Down
5 changes: 5 additions & 0 deletions Sources/Algorithms/Documentation.docc/Trimming.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,8 @@ Remove unwanted elements from the start, the end, or both ends of a collection.
### Finding the Suffix of a Collection

- ``Swift/BidirectionalCollection/suffix(while:)``

### Finding Boundaries within a Collection

- ``Swift/Collection/endOfPrefix(while:)``
- ``Swift/BidirectionalCollection/startOfSuffix(while:)``
4 changes: 2 additions & 2 deletions Sources/Algorithms/Suffix.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ extension Collection {
///
/// - Complexity: O(*n*), where *n* is the length of the collection.
@inlinable
internal func endOfPrefix(
public func endOfPrefix(
while predicate: (Element) throws -> Bool
) rethrows -> Index {
var index = startIndex
Expand All @@ -72,7 +72,7 @@ extension BidirectionalCollection {
///
/// - Complexity: O(*n*), where *n* is the length of the collection.
@inlinable
internal func startOfSuffix(
public func startOfSuffix(
while predicate: (Element) throws -> Bool
) rethrows -> Index {
var index = endIndex
Expand Down
12 changes: 6 additions & 6 deletions Sources/Algorithms/Trim.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ extension Collection {
while predicate: (Element) throws -> Bool
) rethrows -> SubSequence {
let start = try endOfPrefix(while: predicate)
return self[start...]
return self[start..<endIndex]
}
}

Expand Down Expand Up @@ -61,8 +61,8 @@ extension Collection where Self: RangeReplaceableCollection {
public mutating func trimPrefix(
while predicate: (Element) throws -> Bool
) rethrows {
let end = try endOfPrefix(while: predicate)
removeSubrange(startIndex..<end)
let startOfResult = try endOfPrefix(while: predicate)
removeSubrange(startIndex..<startOfResult)
}
}

Expand Down Expand Up @@ -135,7 +135,7 @@ extension BidirectionalCollection {
while predicate: (Element) throws -> Bool
) rethrows -> SubSequence {
let end = try startOfSuffix(while: predicate)
return self[..<end]
return self[startIndex..<end]
}
}

Expand Down Expand Up @@ -188,8 +188,8 @@ extension BidirectionalCollection where Self: RangeReplaceableCollection {
public mutating func trimSuffix(
while predicate: (Element) throws -> Bool
) rethrows {
let start = try startOfSuffix(while: predicate)
removeSubrange(start..<endIndex)
let endOfResult = try startOfSuffix(while: predicate)
removeSubrange(endOfResult..<endIndex)
}
}

Expand Down
2 changes: 1 addition & 1 deletion Tests/SwiftAlgorithmsTests/SuffixTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//===----------------------------------------------------------------------===//

import XCTest
@testable import Algorithms
import Algorithms

final class SuffixTests: XCTestCase {
func testSuffix() {
Expand Down