Broadcast over a multiple output function and receive a Tuple of Arrays

Is there a convenient way to broadcast over a function that produces multiple outputs and receive a tuple of arrays instead of an array of tuples?

For example, with the sincos function, I get the following output upon broadcast:

julia> sincos.(2:4)
3-element Vector{Tuple{Float64, Float64}}:
 (0.9092974268256817, -0.4161468365471424)
 (0.1411200080598672, -0.9899924966004454)
 (-0.7568024953079282, -0.6536436208636119)

but what I want is:

([0.9092974268256817, 0.1411200080598672, -0.7568024953079282], [-0.4161468365471424, -0.9899924966004454, -0.6536436208636119])

The ugly solution I though of would be to do

let a = sincos.(2:4)
    Tuple([[b[i] for b in a] for i in eachindex(a[1])])
end

but this has way too much allocation overhead.

(I know for this particular example I could do (sin.(2:4), cos.(2:4)), but I am asking for functions in general, assuming there are no separate sin and cos methods. Ultimately, I would like to do something along the lines of s, c = sincos.(2:4) where s == sin.(2:4) and c == cos.(2:4))

Here’s some code I use for re-packing data:

function repack(x::AbstractArray{T}) where T<:Tuple
	# turn Array{Tuple{T1,T2...}} to Tuple{Array{T1},Array{T2},...}
	fT = ntuple(i->fieldtype(T,i),fieldcount(T)) # fieldtypes(T) would be the obvious choice but was type-unstable when I tried
	arrs = similar.(Ref(x),fT)
	for i in eachindex(x,arrs...)
		@inbounds setindex!.(arrs,x[i],Ref(i))
	end
	return arrs
end
function repack(x::AbstractArray{NamedTuple{N,T}}) where {N,T<:Tuple}
	# turn Array{NamedTuple{N,Tuple{T1,T2...}}} to NamedTuple{N,Tuple{Array{T1},Array{T2},...}}
	fT = ntuple(i->fieldtype(T,i),fieldcount(T)) # fieldtypes(T) would be the obvious choice but was type-unstable when I tried
	arrs = similar.(Ref(x),fT)
	for i in eachindex(x,arrs...)
		@inbounds setindex!.(arrs,Tuple(x[i]),Ref(i))
	end
	return NamedTuple{N}(arrs)
end

But this ultimately still requires extra allocations over what could be a variant of map that returns tuples-of-arrays rather than arrays-of-tuples. You can certainly achieve this with preallocation and a loop, but that’s only nice if you know the number and types of outputs of your function (which can be annoying to determine programmatically – map and broadcast are so nice because they don’t require you to do this manually). Maybe someone will eventually implement this map variant, but it doesn’t exist yet.

You can also look into StructArrays.jl, which may have some utilities for this sort of situation.

1 Like

One more option:

using SplitApplyCombine
invert(sincos.(2:4)) 
2 Likes

Wow, this package is exactly what I was looking for. Thanks!