|
| 1 | +# Trim |
| 2 | + |
| 3 | +[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Trim.swift) | |
| 4 | + [Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/TrimTests.swift)] |
| 5 | + |
| 6 | +Returns a `SubSequence` formed by discarding all elements at the start and end of the collection |
| 7 | +which satisfy the given predicate. |
| 8 | + |
| 9 | +This example uses `trimming(where:)` to get a substring without the white space at the beginning and end of the string. |
| 10 | + |
| 11 | +```swift |
| 12 | +let myString = " hello, world " |
| 13 | +print(myString.trimming(where: \.isWhitespace)) // "hello, world" |
| 14 | + |
| 15 | +let results = [2, 10, 11, 15, 20, 21, 100].trimming(where: { $0.isMultiple(of: 2) }) |
| 16 | +print(results) // [11, 15, 20, 21] |
| 17 | +``` |
| 18 | + |
| 19 | +## Detailed Design |
| 20 | + |
| 21 | +A new method is added to `BidirectionalCollection`: |
| 22 | + |
| 23 | +```swift |
| 24 | +extension BidirectionalCollection { |
| 25 | + |
| 26 | + public func trimming(where predicate: (Element) throws -> Bool) rethrows -> SubSequence |
| 27 | +} |
| 28 | +``` |
| 29 | + |
| 30 | +This method requires `BidirectionalCollection` for an efficient implementation which visits as few elements as possible. |
| 31 | + |
| 32 | +A less-efficient implementation is _possible_ for any `Collection`, which would involve always traversing the |
| 33 | +entire collection. This implementation is not provided, as it would mean developers of generic algorithms who forget |
| 34 | +to add the `BidirectionalCollection` constraint will receive that inefficient implementation: |
| 35 | + |
| 36 | +```swift |
| 37 | +func myAlgorithm<Input>(input: Input) where Input: Collection { |
| 38 | + |
| 39 | + let trimmedInput = input.trimming(where: { ... }) // Uses least-efficient implementation. |
| 40 | +} |
| 41 | + |
| 42 | +func myAlgorithm2<Input>(input: Input) where Input: BidirectionalCollection { |
| 43 | + |
| 44 | + let trimmedInput = input.trimming(where: { ... }) // Uses most-efficient implementation. |
| 45 | +} |
| 46 | +``` |
| 47 | + |
| 48 | +Swift provides the `BidirectionalCollection` protocol for marking types which support reverse traversal, |
| 49 | +and generic types and algorithms which want to make use of that should add it to their constraints. |
| 50 | + |
| 51 | +### Complexity |
| 52 | + |
| 53 | +Calling this method is O(_n_). |
| 54 | + |
| 55 | +### Naming |
| 56 | + |
| 57 | +The name `trim` has precedent in other programming languages. Another popular alternative might be `strip`. |
| 58 | + |
| 59 | +| Example usage | Languages | |
| 60 | +|-|-| |
| 61 | +| ''String''.Trim([''chars'']) | C#, VB.NET, Windows PowerShell | |
| 62 | +| ''string''.strip(); | D | |
| 63 | +| (.trim ''string'') | Clojure | |
| 64 | +| ''sequence'' [ predicate? ] trim | Factor | |
| 65 | +| (string-trim '(#\Space #\Tab #\Newline) ''string'') | Common Lisp | |
| 66 | +| (string-trim ''string'') | Scheme | |
| 67 | +| ''string''.trim() | Java, JavaScript (1.8.1+), Rust | |
| 68 | +| Trim(''String'') | Pascal, QBasic, Visual Basic, Delphi | |
| 69 | +| ''string''.strip() | Python | |
| 70 | +| strings.Trim(''string'', ''chars'') | Go | |
| 71 | +| LTRIM(RTRIM(''String'')) | Oracle SQL, T-SQL | |
| 72 | +| string:strip(''string'' [,''option'', ''char'']) | Erlang | |
| 73 | +| ''string''.strip or ''string''.lstrip or ''string''.rstrip | Ruby | |
| 74 | +| trim(''string'') | PHP, Raku | |
| 75 | +| [''string'' stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] | Objective-C/Cocoa | |
| 76 | +| ''string'' withBlanksTrimmed ''string'' withoutSpaces ''string'' withoutSeparators | Smalltalk | |
| 77 | +| string trim ''$string'' | Tcl | |
| 78 | +| TRIM(''string'') or TRIM(ADJUSTL(''string'')) | Fortran | |
| 79 | +| TRIM(''string'') | SQL | |
| 80 | +| String.trim ''string'' | OCaml 4+ | |
| 81 | + |
| 82 | +Note: This is an abbreviated list from Uncyclopedia. [Full table](https://en.wikipedia.org/wiki/Comparison_of_programming_languages_(string_functions)#trim) |
| 83 | + |
| 84 | +The standard library includes a variety of methods which perform similar operations: |
| 85 | + |
| 86 | +- Firstly, there are `dropFirst(Int)` and `dropLast(Int)`. These return slices but do not support user-defined predicates. |
| 87 | + If the collection's `count` is less than the number of elements to drop, they return an empty slice. |
| 88 | +- Secondly, there is `drop(while:)`, which also returns a slice and is equivalent to a 'left-trim' (trimming from the head but not the tail). |
| 89 | + If the entire collection is dropped, this method returns an empty slice. |
| 90 | +- Thirdly, there are `removeFirst(Int)` and `removeLast(Int)` which do not return slices and actually mutate the collection. |
| 91 | + If the collection's `count` is less than the number of elements to remove, this method triggers a runtime error. |
| 92 | +- Lastly, there are the `popFirst()` and `popLast()` methods, which work like `removeFirst()` and `removeLast()`, |
| 93 | + except they do not trigger a runtime error for empty collections. |
| 94 | + |
| 95 | +The closest neighbours to this function would be the `drop` family of methods. Unfortunately, unlike `dropFirst(Int)`, |
| 96 | +the name `drop(while:)` does not specify which end(s) of the collection it operates on. Moreover, one could easily |
| 97 | +mistake code such as: |
| 98 | + |
| 99 | +```swift |
| 100 | +let result = myString.drop(while: \.isWhitespace) |
| 101 | +``` |
| 102 | + |
| 103 | +With a lazy filter that drops _all_ whitespace characters regardless of where they are in the string. |
| 104 | +Besides that, the root `trim` leads to clearer, more conscise code, which is more aligned with other programming |
| 105 | +languages: |
| 106 | + |
| 107 | +```swift |
| 108 | +// Does `result` contain the input, trimmed of certain elements? |
| 109 | +// Or does this code mutate `input` in-place and return the elements which were dropped? |
| 110 | +let result = input.dropFromBothEnds(where: { ... }) |
| 111 | + |
| 112 | +// No such ambiguity here. |
| 113 | +let result = input.trimming(where: { ... }) |
| 114 | +``` |
0 commit comments