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!