Swift loop line once, write a lot
Consider the following silly, simple example:
let arr = ["hey", "ho"]
let doubled = arr.map {$0 + $0}
let capitalized = arr.map {$0.capitalizedString}
As you can see, I am processing the same initial array in multiple ways in order to get multiple processed arrays.
Now imagine that it is arr
very long and that I have many such processes generating many finite arrays. I don't like the above code because we are iterating over multiple times, times for each call map
. I would rather have the loop only once.
Now, obviously, we could handle this with brute force, that is, starting with a few mutable arrays and writing them across all iterations:
let arr = ["hey", "ho"]
var doubled = [String]()
var capitalized = [String]()
for s in arr {
doubled.append(s + s)
capitalized.append(s.capitalizedString)
}
Fine. But now we don't get the joy of using it map
. So my question is, is there a better, faster way? Vaguely I imagine that I am using map
or something map
to generate something like a tuple and magically split that tuple into all the resulting arrays as we iterate, as if I can say something like this (pseudocode, don't try it's at home):
let arr = ["hey", "ho"]
let (doubled, capitalized) = arr.map { /* ???? */ }
If I were developing my own language, I might even allow some sort of splatting by assigning to the lvalues ββpseudo-array:
let arr = ["hey", "ho"]
let [doubled, capitalized] = arr.map { /* ???? */ }
It is not difficult if it cannot be done, but it would be interesting if we could speak this way.
source to share
How about a function, multimap
that takes a collection of transforms and applies each one, returning them as an array of arrays:
// yay protocol extensions
extension SequenceType {
// looks like T->U works OK as a constraint
func multimap
<U, C: CollectionType
where C.Generator.Element == Generator.Element->U>
(transformations: C) -> [[U]] {
return transformations.map {
self.map($0)
}
}
}
Then use it like this:
let arr = ["hey", "ho"]
let double: String->String = { $0 + $0 }
let uppercase: String->String = { $0.uppercaseString }
arr.multimap([double, uppercase])
// returns [["heyhey", "hoho"], ["HEY", "HO"]]
Or it can be pretty nice in a variation form:
extension SequenceType {
func multimap<U>(transformations: (Generator.Element->U)...) -> [[U]] {
return self.multimap(transformations)
}
}
arr.multimap({ $0 + $0 }, { $0.uppercaseString })
Edit: if you want separate variables, I think the best you can do is a function destructure
(which you have to declare n times for each n-tuple, unfortunately):
// I don't think this can't be expressed as a protocol extension quite yet
func destructure<C: CollectionType>(source: C) -> (C.Generator.Element,C.Generator.Element) {
precondition(source.count == 2)
return (source[source.startIndex],source[source.startIndex.successor()])
}
// and, since it a function, let declare pipe forward
// to make it easier to call
infix operator |> { }
func |> <T,U>(lhs: T, rhs: T->U) -> U {
return rhs(lhs)
}
And then you can declare variables like this:
let (doubled,uppercased)
= arr.multimap({ $0 + $0 }, { $0.uppercaseString }) |> destructure
Yes, this is a win-win bit, because you have to collect the array and then split it into pieces - but that really won't be significant since arrays are copied to write and talk about a small number of them in the outer array.
edit: pretext for using the new operator guard
:
func destructure<C: Sliceable where C.SubSlice.Generator.Element == C.Generator.Element>(source: C) -> (C.Generator.Element,C.Generator.Element) {
guard let one = source.first else { fatalError("empty source") }
guard let two = dropFirst(source).first else { fatalError("insufficient elements") }
return (one,two)
}
source to share
How about this, we process the "capitalized" array while we map the doubled array:
let arr = ["hey", "ho"]
var capitalized = [String]()
let doubled = arr.map {(var myString) -> String in
capitalized.append(myString.capitalizedString)
return myString + myString
}
//doubled ["heyhey", "hoho"]
//capitalized: ["Hey", "Ho"]
source to share