This package exports a function lazystack for turning a list of arrays
into one AbstractArray. Given several arrays with the same eltype,
or an array of such arrays, it returns a lazy Stacked{T,N} view of these:
julia> lazystack([1:2, 3:4, 5:6])
2×3 lazystack(::Vector{UnitRange{Int64}}) with eltype Int64:
1 3 5
2 4 6
julia> lazystack([pi^ℯ], [ℯ^pi])
1×2 lazystack(::Tuple{Vector{Float64}, Vector{Float64}}) with eltype Float64:
22.4592 23.1407Before v0.1 this function used to be called stack, but that name is now exported by Base (from Julia 1.9).
Like this package, Base.stack makes an array with size(result) = (size(inner)..., size(outer)...).
However, it always returns a new dense array, not a lazy container.
And instead of two vectors (in the above example) it would want a tuple stack(([pi^ℯ], [ℯ^pi])).
Generators such as lazystack([i,2i] for i in 1:5) and arrays of mixed eltype like lazystack([1,2], [3.0, 4.0], [5im, 6im]) used to be be handled here, making a dense array, but are now simply passed through to Base.stack.
When the individual slices aren't backed by an Array, as for instance with CuArrays on a GPU, then again Base.stack is called.
This should make one big CuArray, since scalar indexing of individual slices won't work well.
There is also a version which does not demand that slices have equal size (or equal ndims).
For now this is not lazy:
julia> raggedstack([10:10+n for n in 1:3])
4×3 Matrix{Int64}:
10 10 10
11 11 11
0 12 12
0 0 13
julia> using OffsetArrays
julia> raggedstack(OffsetArray(fill(1.0n, 3), rand(-1:1)) for n in 1:10; fill=NaN)
5×10 OffsetArray(::Matrix{Float64}, 0:4, 1:10) with eltype Float64 with indices 0:4×1:10:
NaN 2.0 NaN 4.0 NaN 6.0 7.0 NaN 9.0 NaN
1.0 2.0 3.0 4.0 5.0 6.0 7.0 NaN 9.0 10.0
1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0
1.0 NaN 3.0 NaN 5.0 NaN NaN 8.0 NaN 10.0
NaN NaN NaN NaN NaN NaN NaN 8.0 NaN NaNThis one plays well with OffsetArrays.jl, and ChainRules.jl-compatible AD such as Zygote.jl. It's also used internally by TensorCast.jl.
Besides which, there are several other ways to achieve similar things:
- For an array of arrays, you can also use
JuliennedArrays.Align. This requires (or enables) you to specify which dimensions of the output belong to the sub-arrays, instead of writingPermutedDimsArray(stack(...), ...). - There is also
RecursiveArrayTools.VectorOfArraywhich as its name hints only allows a one-dimensional container. (And unlike the package name, nothing is recursive.) Linear indexing retreives a slice, not an element, which is sometimes surprising. - For a tuple of arrays,
LazyArrays.Hcatis at present faster to index thanlazystack, but doesn't allow arbitrary dimensions.
And a few more:
- When writing this I missed
SplitApplyCombine.combinedimsview, which is very similar tostack, but doesn't handle tuples. - Newer than this package is StackViews.jl handles both, with
StackView(A,B,dims=4) == StackView([A,B],4)creating a 4th dimension; the container is always one-dimensional. Flux.stacksimilarly takes a dimension, but eagerly creates anArray.
The lazy inverse:
-
The package ArraysOfArrays.jl solves the opposite problem, of accessing one large array as if it were many slices.
-
As does
JuliennedArrays.Slices. -
As does
PackedVectorsOfVectors, although only 1+1 dimensions. Also has an eagerpackmethod which turns a vector of vectors into view of a single larger matrix. -
Base.eachslicealso views one large array as many slices. This was a generator, but JuliaLang#32310 upgrades it to a multi-dimensional indexable container, in Julia 1.9. -
AwkwardArray.jl fits in here somewhere.
Eager:
-
After writing this I learned of JuliaLang#31644 which extends
reduce(hcat,...)to work on generators. (Not merged yet.) -
Later, JuliaLang#43334 has added a better version of this package's
stack_itermethod to Base. (Available in Julia 1.9, or in Compat.jl.)
- FlexiMaps.flatten is another eager implementation.
- CatViews.jl offers a lazy
vcat. But the package is old and I think not so fast.