Loop fusion and multiple return values


Assuming a function takes 2 arguments and returns 2 values, for example


now x and y are large vectors of size N which cannot be splatted.
The ouptut of


is an array of tuples but I would like to have a tuple of size 2 (the number of return values) of 1-d arrays of size N

I found some tips using zip and splatting, but it turned out to be really slow and impractical especially for larger arrays.

For now I preallocated the output arrays and then did a manual for loop to fill in the values using

for i=1:N 
    out_x[i], out_y[i] = f(x[i],y[i])

However, I thought there must be a solution somehow using loop fusion or at least something more simple here.
I’m on Julia 0.6.3. Any advice?

edit: fixed wording and missing i-index in input parameters




There’s nothing wrong with the code you posted above (although I agree it’s a bit verbose). Your implementation is efficient and the code is easy to understand, so you might want to consider just writing a function to encapsulate that behavior and call it a day (after all, the best thing about Julia is that your hand-written function will be just as fast as any built-in solution).

But if you want a different approach, you could consider:

  1. Store the result tuples and then just grab the relevant element from each:
out = [f(x[i], y[i]) for i in 1:N]  # or f.(x, y)
out_x = first.(out)  # or getindex.(out, 1)
out_y = last.(out) # or getindex.(out, 2)
  1. Create a struct to hold your two results and use something like https://github.com/simonster/StructsOfArrays.jl to store them
  2. Consider whether it makes more sense to treat the pair (x, y) as a scalar in your downstream code. If you’re computing those results together, perhaps they should actually stay together? For example, rather than storing x coordinates and y coordinates, consider storing a vector of xy points. If you do that, then your original problem just goes away. This is not applicable in all cases, but it’s something I’ve found myself doing more as I’ve gotten more comfortable with Julia.


@rdeits was faster than me, but since I already wrote up the StructsOfArray solution, here is how to do it:

N = 100
xin, yin = rand(N), rand(N)
out = StructOfArrays(Tuple{Float64, Float64}, N)
out .= f.(xin, yin)
julia> out.arrays[1]
100-element Array{Float64,1}:

I think StructsOfArrays could use a usability update and offer something like:

soa(x::Vector, y::Vector)::StructOfArrays{Tuple{Float64, Float64}}


ZippedArrays has the unzip function with should be useful and works with arbitrary iterators.


Thanks! That is very interesting, the proposed unzip for Base the linked issue https://github.com/JuliaLang/julia/issues/13942 would be exactly whats needed here to keep the solution in one line like

xout, yout = unzip(f.(x,y))

unfortunately it does not seem to come in the 1.0 release

However, in the issue above there was https://github.com/spalato/Destruct.jl mentioned which does exactly the same as the yet missing unzip.