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.

+3


source to share


3 answers


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)
}

      

+2


source


What's wrong with your tuple proposal?



let arr = ["hey", "ho"]

let  mapped = arr.map {e in
  return (e + e, e.capitalizedString)
}

      

+2


source


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"]

      

0


source







All Articles