Last active
March 2, 2019 07:07
-
-
Save CTMacUser/61a12737e8a22c04baa6881d408c7777 to your computer and use it in GitHub Desktop.
Extension to loop over each Collection Index without the retention issues indices has. Plus, example usage for MutableCollection, and an extra related method for RangeReplaceableCollection.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ForEachIndex.swift by Daryle Walker | |
extension Collection { | |
/** | |
Calls the given closure on each element index in the collection in the | |
same order as a `for`-`in` loop. | |
The two loops in the following example produce the same output: | |
let numberWords = ["one", "two", "three"] | |
numberWords.forEach { word in | |
print(word) | |
} | |
/* | |
Prints """ | |
one | |
two | |
three | |
""" | |
*/ | |
numberWords.forEachIndex { | |
print(numberWords($0)) | |
} | |
// Same as above | |
Using the `forEachIndex` method is distinct from a `for`-`in` loop upon | |
`indices` in two important ways: | |
1. You cannot use a `break` or `continue` statement to exit the current | |
call of the `body` closure or skip subsequent calls. | |
2. Using the `return` statement in the `body` closure will exit only from | |
the current call to `body`, not from any outer scope, and won't skip | |
subsequent calls. | |
Using the `forEachIndex` method differs from using `forEach` with | |
`indices` in that the former can be used with a closure that can mutate | |
elements of the collection. (The latter can't since it may retain a | |
reference to the collection, possibly instigating a copy-on-write cycle | |
upon mutation.) | |
- Parameter body: A closure that takes an index of the collection as a | |
parameter. | |
*/ | |
public func forEachIndex(_ body: (Index) throws -> Void) rethrows { | |
var i = startIndex | |
while i < endIndex { | |
try body(i) | |
formIndex(after: &i) | |
} | |
} | |
} | |
extension MutableCollection { | |
/** | |
Calls the given closure on each element in the collection to mutate said | |
elements. | |
In this example, `remap` is used to clear each string. | |
var cast = ["Vivien", "Marlon", "Kim", "Karl"] | |
//... | |
cast.remap { $0.removeAll(keepingCapacity: true) } | |
// cast == ["", "", "", ""] | |
- Parameter body: A self-mapping closure. It takes an element of this | |
collection as its mutable parameter. | |
*/ | |
public mutating func remap(_ body: (inout Element) throws -> Void) rethrows { | |
try forEachIndex { try body(&self[$0]) } | |
} | |
/** | |
Calls the given closure on each element in the collection to re-assign said | |
elements. | |
In this example, `remap` is used to lowercase each string. | |
let cast = ["Vivien", "Marlon", "Kim", "Karl"] | |
cast.remap { $0.lowercased() } | |
// cast == ["vivien", "marlon", "kim", "karl"] | |
- Parameter body: A mapping closure. It takes an element of this | |
collection as its parameter and returns a transformed value of the same | |
type. | |
*/ | |
public mutating func remap(_ body: (Element) throws -> Element) rethrows { | |
try remap { $0 = try body($0) } | |
} | |
} | |
extension RangeReplaceableCollection { | |
/** | |
Calls the given closure on each element in the collection for their | |
respective replacement, or removal whenever `nil` is returned. | |
In this example, `remapOrRemove` is used to lowercase strings with more | |
than three characters. | |
var cast = ["Vivien", "Marlon", "Kim", "Karl"] | |
cast.remapOrRemove { $0.count <= 3 ? nil : $0.lowercased() } | |
// cast == ["vivien", "marlon", "karl"] | |
- Parameter body: A mapping closure. It takes an element of this | |
collection as its parameter and returns a transformed value as an | |
`Optional` of the original type. | |
*/ | |
public mutating func remapOrRemove(_ body: (Element) throws -> Element?) rethrows { | |
self.replaceSubrange(startIndex..<endIndex, with: try compactMap(body)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment