Converting a matrix into an array of arrays

Hi all

I have seen many examples of converting an array of arrays into a matrix, but I would like to go the other way. What I have cobbled together works, but may not be the most elegant or fast solution. I would appreciate any comments and builds.

function sliceMatrix(A)
    m, n = size(A)
    B = Array{Array{eltype(A), 1}, 1}(undef, m)

    for i = 1:m
        B[i] = A[i, :]
    end
    return B
end

Is there a way to pre-allocate the individual arrays inside B?

Thanks!

1 Like

I don’t think you gain much from preallocating in this situation, but here is one way where I only modified your functions a little bit:

julia> function slicematrix(A::AbstractMatrix{T}) where T
           m, n = size(A)
           B = Vector{T}[Vector{T}(undef, n) for _ in 1:m]
           for i in 1:m
               B[i] .= A[i, :]
           end
           return B
       end
slicematrix (generic function with 1 method)

julia> slicematrix(rand(2,3))
2-element Array{Array{Float64,1},1}:
 [0.686489, 0.016934, 0.638272]
 [0.317802, 0.543907, 0.208549]

but it is probably better and simpler to just define it as:

function slicematrix(A::AbstractMatrix)
    return [A[i, :] for i in 1:size(A,1)]
end

Note also that Julia’s Array is column major, so extracting columns (A[:, i]) is much faster than extracting rows (A[i, :]).

3 Likes

A one liner, likely not as efficient as the suggestion above, is
mapslices(x->[x], randn(5,5), dims=2)[:]

3 Likes

Thank you all! The suggestions are much appreciated.

While trying this myself, I found a faster solution than the simple suggestion above.

Iterate over the columns with eachcol or eachrow:

A = rand(5,10000)
@btime [c[:] for c in eachcol(A)]
@btime [A[:,c] for c in 1:size(A,2)]

julia>
382.300 μs (20005 allocations: 1.75 MiB)
  2.788 ms (38985 allocations: 1.74 MiB)

Note that your benchmarking produced false inferences due to accessing global variables. If you interpolate the variables into the expressions (using $). The results are much more similar.
I also include an even faster version, if you can work with static vectors:

using StaticArrays
A = rand(5,10000)
@btime [c[:] for c in eachcol($A)]
@btime [$A[:,c] for c in 1:size($A,2)]
@btime vec(reinterpret(SVector{size($A,1),eltype($A)},$A))

  506.052 μs (20005 allocations: 1.75 MiB)
  440.997 μs (10004 allocations: 1.30 MiB)
  1.542 μs (11 allocations: 704 bytes)

5 Likes

I think @MatthijsCox approx with eachcol is in fact faster (but still slower than StaticArrays), the problem is how columns are collected.

A = rand(5,10000);
@btime collect(eachcol($A));
@btime [c[:] for c in eachcol($A)];
@btime [$A[:,c] for c in 1:size($A,2)];
[A[:,c] for c in 1:size(A,2)] == collect(eachcol(A))

> julia
  96.849 μs (10004 allocations: 547.00 KiB)
  481.677 μs (20005 allocations: 1.75 MiB)
  413.083 μs (10004 allocations: 1.30 MiB)
  true

Note that the latter two create copies, while the first one returns views:

julia> @btime [c[:] for c in eachcol($A)];
  535.007 μs (20005 allocations: 1.75 MiB)

julia> @btime [c for c in eachcol($A)];
  80.548 μs (10005 allocations: 547.02 KiB)
4 Likes