Skip to content

Commit 0e2941e

Browse files
authoredMay 25, 2021
Add trimmingSuffix, trimmingPrefix and mutating variants (#104)
1 parent e25cf27 commit 0e2941e

File tree

3 files changed

+291
-11
lines changed

3 files changed

+291
-11
lines changed
 

‎Guides/Trim.md

+20-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,25 @@ func myAlgorithm2<Input>(input: Input) where Input: BidirectionalCollection {
4848
Swift provides the `BidirectionalCollection` protocol for marking types which support reverse traversal,
4949
and generic types and algorithms which want to make use of that should add it to their constraints.
5050

51+
### >= 0.3.0
52+
53+
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.
54+
- `trimmingSuffix(while:)` can only be run on collections conforming to the `BidirectionalCollection` protocol.
55+
- `trimmingPrefix(while:)` can be run also on collections conforming to the `Collection` protocol.
56+
57+
```swift
58+
let myString = " hello, world "
59+
print(myString.trimmingPrefix(while: \.isWhitespace)) // "hello, world "
60+
61+
print(myString.trimmingSuffix(while: \.isWhitespace)) // " hello, world"
62+
```
63+
Also mutating variants for all the methods already existing and the new ones are added.
64+
```swift
65+
var myString = " hello, world "
66+
myString.trim(while: \.isWhitespace)
67+
print(myString) // "hello, world"
68+
```
69+
5170
### Complexity
5271

5372
Calling this method is O(_n_).
@@ -111,4 +130,4 @@ let result = input.dropFromBothEnds(while: { ... })
111130

112131
// No such ambiguity here.
113132
let result = input.trimming(while: { ... })
114-
```
133+
```

‎Sources/Algorithms/Trim.swift

+215-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,94 @@
77
//
88
// See https://swift.org/LICENSE.txt for license information
99
//
10+
11+
//===----------------------------------------------------------------------===//
12+
// trimmingPrefix(while:)
13+
//===----------------------------------------------------------------------===//
14+
15+
extension Collection {
16+
/// Returns a `SubSequence` formed by discarding all elements at the start
17+
/// of the collection which satisfy the given predicate.
18+
///
19+
/// This example uses `trimmingPrefix(while:)` to get a substring without the white
20+
/// space at the beginning of the string:
21+
///
22+
/// let myString = " hello, world "
23+
/// print(myString.trimmingPrefix(while: \.isWhitespace)) // "hello, world "
24+
///
25+
/// - Parameters:
26+
/// - predicate: A closure which determines if the element should be
27+
/// omitted from the resulting slice.
28+
///
29+
/// - Complexity: O(*n*), where *n* is the length of this collection.
30+
///
31+
@inlinable
32+
public func trimmingPrefix(
33+
while predicate: (Element) throws -> Bool
34+
) rethrows -> SubSequence {
35+
let start = try endOfPrefix(while: predicate)
36+
return self[start..<endIndex]
37+
}
38+
}
39+
40+
//===----------------------------------------------------------------------===//
41+
// trimPrefix(while:)
42+
//===----------------------------------------------------------------------===//
43+
44+
extension Collection where Self: RangeReplaceableCollection {
45+
/// Mutates a `Collection` by discarding all elements at the start
46+
/// of it which satisfy the given predicate.
47+
///
48+
/// This example uses `trimPrefix(while:)` to remove the white
49+
/// space at the beginning of the string:
50+
///
51+
/// let myString = " hello, world "
52+
/// myString.trimPrefix(while: \.isWhitespace)
53+
/// print(myString) // "hello, world "
54+
///
55+
/// - Parameters:
56+
/// - predicate: A closure which determines if the element should be
57+
/// removed from the string.
58+
///
59+
/// - Complexity: O(*n*), where *n* is the length of this collection.
60+
///
61+
@inlinable
62+
@_disfavoredOverload
63+
public mutating func trimPrefix(
64+
while predicate: (Element) throws -> Bool
65+
) rethrows {
66+
let end = try endOfPrefix(while: predicate)
67+
removeSubrange(startIndex..<end)
68+
}
69+
}
70+
71+
extension Collection where Self == Self.SubSequence {
72+
/// Mutates a `Collection` by discarding all elements at the start
73+
/// of it which satisfy the given predicate.
74+
///
75+
/// This example uses `trimPrefix(while:)` to remove the white
76+
/// space at the beginning of the string:
77+
///
78+
/// let myString = " hello, world "
79+
/// myString.trimPrefix(while: \.isWhitespace)
80+
/// print(myString) // "hello, world "
81+
///
82+
/// - Parameters:
83+
/// - predicate: A closure which determines if the element should be
84+
/// removed from the string.
85+
///
86+
/// - Complexity: O(*n*), where *n* is the length of this collection.
87+
///
88+
@inlinable
89+
public mutating func trimPrefix(
90+
while predicate: (Element) throws -> Bool
91+
) rethrows {
92+
self = try trimmingPrefix(while: predicate)
93+
}
94+
}
95+
96+
//===----------------------------------------------------------------------===//
97+
// trimming(while:) / trimmingSuffix(while:)
1098
//===----------------------------------------------------------------------===//
1199

12100
extension BidirectionalCollection {
@@ -29,8 +117,132 @@ extension BidirectionalCollection {
29117
public func trimming(
30118
while predicate: (Element) throws -> Bool
31119
) rethrows -> SubSequence {
32-
let start = try endOfPrefix(while: predicate)
33-
let end = try self[start...].startOfSuffix(while: predicate)
34-
return self[start..<end]
120+
return try trimmingPrefix(while: predicate).trimmingSuffix(while: predicate)
121+
}
122+
123+
/// Returns a `SubSequence` formed by discarding all elements at the end
124+
/// of the collection which satisfy the given predicate.
125+
///
126+
/// This example uses `trimmingSuffix(while:)` to get a substring without the white
127+
/// space at the end of the string:
128+
///
129+
/// let myString = " hello, world "
130+
/// print(myString.trimmingSuffix(while: \.isWhitespace)) // " hello, world"
131+
///
132+
/// - Parameters:
133+
/// - predicate: A closure which determines if the element should be
134+
/// omitted from the resulting slice.
135+
///
136+
/// - Complexity: O(*n*), where *n* is the length of this collection.
137+
///
138+
@inlinable
139+
public func trimmingSuffix(
140+
while predicate: (Element) throws -> Bool
141+
) rethrows -> SubSequence {
142+
let end = try startOfSuffix(while: predicate)
143+
return self[startIndex..<end]
144+
}
145+
}
146+
147+
//===----------------------------------------------------------------------===//
148+
// trim(while:) / trimSuffix(while:)
149+
//===----------------------------------------------------------------------===//
150+
151+
extension BidirectionalCollection where Self: RangeReplaceableCollection {
152+
/// Mutates a `BidirectionalCollection` by discarding all elements at the start
153+
/// and at the end of it which satisfy the given predicate.
154+
///
155+
/// This example uses `trim(while:)` to remove the white
156+
/// space at the beginning of the string:
157+
///
158+
/// let myString = " hello, world "
159+
/// myString.trim(while: \.isWhitespace)
160+
/// print(myString) // "hello, world"
161+
///
162+
/// - Parameters:
163+
/// - predicate: A closure which determines if the element should be
164+
/// removed from the string.
165+
///
166+
/// - Complexity: O(*n*), where *n* is the length of this collection.
167+
///
168+
@inlinable
169+
@_disfavoredOverload
170+
public mutating func trim(
171+
while predicate: (Element) throws -> Bool
172+
) rethrows {
173+
replaceSubrange(startIndex..<endIndex, with: try trimming(while: predicate))
174+
}
175+
176+
/// Mutates a `BidirectionalCollection` by discarding all elements at the end
177+
/// of it which satisfy the given predicate.
178+
///
179+
/// This example uses `trimSuffix(while:)` to remove the white
180+
/// space at the beginning of the string:
181+
///
182+
/// let myString = " hello, world "
183+
/// myString.trimSuffix(while: \.isWhitespace)
184+
/// print(myString) // " hello, world"
185+
///
186+
/// - Parameters:
187+
/// - predicate: A closure which determines if the element should be
188+
/// removed from the string.
189+
///
190+
/// - Complexity: O(*n*), where *n* is the length of this collection.
191+
///
192+
@inlinable
193+
@_disfavoredOverload
194+
public mutating func trimSuffix(
195+
while predicate: (Element) throws -> Bool
196+
) rethrows {
197+
let start = try startOfSuffix(while: predicate)
198+
removeSubrange(start..<endIndex)
199+
}
200+
}
201+
202+
extension BidirectionalCollection where Self == Self.SubSequence {
203+
/// Mutates a `BidirectionalCollection` by discarding all elements at the start
204+
/// and at the end of it which satisfy the given predicate.
205+
///
206+
/// This example uses `trim(while:)` to remove the white
207+
/// space at the beginning of the string:
208+
///
209+
/// let myString = " hello, world "
210+
/// myString.trim(while: \.isWhitespace)
211+
/// print(myString) // "hello, world"
212+
///
213+
/// - Parameters:
214+
/// - predicate: A closure which determines if the element should be
215+
/// removed from the string.
216+
///
217+
/// - Complexity: O(*n*), where *n* is the length of this collection.
218+
///
219+
@inlinable
220+
public mutating func trim(
221+
while predicate: (Element) throws -> Bool
222+
) rethrows {
223+
self = try trimming(while: predicate)
224+
}
225+
226+
/// Mutates a `BidirectionalCollection` by discarding all elements at the end
227+
/// of it which satisfy the given predicate.
228+
///
229+
/// This example uses `trimSuffix(while:)` to remove the white
230+
/// space at the beginning of the string:
231+
///
232+
/// let myString = " hello, world "
233+
/// myString.trimSuffix(while: \.isWhitespace)
234+
/// print(myString) // " hello, world"
235+
///
236+
/// - Parameters:
237+
/// - predicate: A closure which determines if the element should be
238+
/// removed from the string.
239+
///
240+
/// - Complexity: O(*n*), where *n* is the length of this collection.
241+
///
242+
@inlinable
243+
public mutating func trimSuffix(
244+
while predicate: (Element) throws -> Bool
245+
) rethrows {
246+
self = try trimmingSuffix(while: predicate)
35247
}
36248
}

‎Tests/SwiftAlgorithmsTests/TrimTests.swift

+56-7
Original file line numberDiff line numberDiff line change
@@ -13,47 +13,96 @@ import Algorithms
1313
import XCTest
1414

1515
final class TrimTests: XCTestCase {
16-
16+
1717
func testEmpty() {
1818
let results_empty = ([] as [Int]).trimming { $0.isMultiple(of: 2) }
1919
XCTAssertEqual(results_empty, [])
2020
}
21-
21+
2222
func testNoMatch() {
2323
// No match (nothing trimmed).
2424
let results_nomatch = [1, 3, 5, 7, 9, 11, 13, 15].trimming {
2525
$0.isMultiple(of: 2)
2626
}
2727
XCTAssertEqual(results_nomatch, [1, 3, 5, 7, 9, 11, 13, 15])
2828
}
29-
29+
3030
func testNoTailMatch() {
3131
// No tail match (only trim head).
3232
let results_notailmatch = [1, 3, 5, 7, 9, 11, 13, 15].trimming { $0 < 10 }
3333
XCTAssertEqual(results_notailmatch, [11, 13, 15])
3434
}
35-
35+
3636
func testNoHeadMatch() {
3737
// No head match (only trim tail).
3838
let results_noheadmatch = [1, 3, 5, 7, 9, 11, 13, 15].trimming { $0 > 10 }
3939
XCTAssertEqual(results_noheadmatch, [1, 3, 5, 7, 9])
4040
}
41-
41+
4242
func testBothEndsMatch() {
4343
// Both ends match, some string of >1 elements do not (return that string).
4444
let results = [2, 10, 11, 15, 20, 21, 100].trimming { $0.isMultiple(of: 2) }
4545
XCTAssertEqual(results, [11, 15, 20, 21])
4646
}
47-
47+
4848
func testEverythingMatches() {
4949
// Everything matches (trim everything).
5050
let results_allmatch = [1, 3, 5, 7, 9, 11, 13, 15].trimming { _ in true }
5151
XCTAssertEqual(results_allmatch, [])
5252
}
53-
53+
5454
func testEverythingButOneMatches() {
5555
// Both ends match, one element does not (trim all except that element).
5656
let results_one = [2, 10, 12, 15, 20, 100].trimming { $0.isMultiple(of: 2) }
5757
XCTAssertEqual(results_one, [15])
5858
}
59+
60+
func testTrimmingPrefix() {
61+
let results = [2, 10, 12, 15, 20, 100].trimmingPrefix { $0.isMultiple(of: 2) }
62+
XCTAssertEqual(results, [15, 20, 100])
63+
}
64+
65+
func testTrimmingSuffix() {
66+
let results = [2, 10, 12, 15, 20, 100].trimmingSuffix { $0.isMultiple(of: 2) }
67+
XCTAssertEqual(results, [2, 10, 12, 15])
68+
}
69+
70+
// Self == Self.Subsequence
71+
func testTrimNoAmbiguity() {
72+
var values = [2, 10, 12, 15, 20, 100] as ArraySlice
73+
values.trim { $0.isMultiple(of: 2) }
74+
XCTAssertEqual(values, [15])
75+
}
76+
77+
// Self == Self.Subsequence
78+
func testTrimPrefixNoAmbiguity() {
79+
var values = [2, 10, 12, 15, 20, 100] as ArraySlice
80+
values.trimPrefix { $0.isMultiple(of: 2) }
81+
XCTAssertEqual(values, [15, 20, 100])
82+
}
83+
84+
// Self == Self.Subsequence
85+
func testTrimSuffixNoAmbiguity() {
86+
var values = [2, 10, 12, 15, 20, 100] as ArraySlice
87+
values.trimSuffix { $0.isMultiple(of: 2) }
88+
XCTAssertEqual(values, [2, 10, 12, 15])
89+
}
90+
91+
func testTrimRangeReplaceable() {
92+
var values = [2, 10, 12, 15, 20, 100]
93+
values.trim { $0.isMultiple(of: 2) }
94+
XCTAssertEqual(values, [15])
95+
}
96+
97+
func testTrimPrefixRangeReplaceable() {
98+
var values = [2, 10, 12, 15, 20, 100]
99+
values.trimPrefix { $0.isMultiple(of: 2) }
100+
XCTAssertEqual(values, [15, 20, 100])
101+
}
102+
103+
func testTrimSuffixRangeReplaceable() {
104+
var values = [2, 10, 12, 15, 20, 100]
105+
values.trimSuffix { $0.isMultiple(of: 2) }
106+
XCTAssertEqual(values, [2, 10, 12, 15])
107+
}
59108
}

0 commit comments

Comments
 (0)