Skip to content

Instantly share code, notes, and snippets.

@CTMacUser
Last active March 2, 2019 07:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CTMacUser/61a12737e8a22c04baa6881d408c7777 to your computer and use it in GitHub Desktop.
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.
// 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