Array flattening function

I’m looking for the function that takes a (m,n) matrix of (k,l) matrices and makes a (m*k, n*l) matrix? And likewise for other dimensions. This works for 2d but it’s a little verbose. Is there a simpler and more general way to write this?

    b = Matrix{Int}.(permutedims(reshape(Base.vect(
        zeros(3,4),  ones(3,2), 2ones(3,5),
        3ones(1, 4), 4ones(1, 2), 5ones(1, 5),
    ), 3, 2), (2,1)))
    @test reduce(hcat, map(splat(vcat), eachslice(b; dims=2))) ==
        [
            0  0  0  0  1  1  2  2  2  2  2
            0  0  0  0  1  1  2  2  2  2  2
            0  0  0  0  1  1  2  2  2  2  2
            3  3  3  3  4  4  5  5  5  5  5
        ]

Perhaps you’re looking for hvcat? It takes arguments in row-major order, like matrix syntax ([a b; c d]), so to flatten an existing block matrix you need to apply permutedims before splatting:

julia> hvcat(3, permutedims(b)...)
4×11 Matrix{Int64}:
 0  0  0  0  1  1  2  2  2  2  2
 0  0  0  0  1  1  2  2  2  2  2
 0  0  0  0  1  1  2  2  2  2  2
 3  3  3  3  4  4  5  5  5  5  5
3 Likes

That works on Matrix but seems wrong on Vector. The function I’m looking for has f(Matrix{Matrix{T}})::Matrix{T} and f(Vector{Vector{T}})::Vector{T}.

f([[1,2], [3,4]]) == [1,2,3,4].

is this close to what you want?

f(b::Matrix{Matrix{T}}) where T = hvcat(3, permutedims(b)...)
f(b::Vector{Vector{T}}) where T = vcat(b...)

using BlockArrays

Bb=mortar(b)

Matrix(Bb)


c=[[1,2], [3,4]]
Bc=mortar(c)
Vector(Bc)
1 Like

This works for both matrix b and vector c:

reduce(hcat, reduce(vcat, u) for u in eachcol(b)) 
2 Likes

I think the general case can be written with hvncat.

It’s possible that Base ought to have such a function… it could replace & generalise reduce(vcat, A), similar to how stack replaces/generalises reduce(hcat, A).

I wrote a version of this function here. It obeys these rules but should be faster than hvcat & hvncat:

concatenate(A::AbstractVector{<:AbstractVecOrMat}) = reduce(vcat, A)
concatenate(A::AbstractMatrix{<:AbstractVecOrMat}) = hvcat(size(A,2), permutedims(A)...)
concatenate(A::AbstractArray{<:AbstractArray}) = hvncat(size(A), false, A...)

concatenate([[1,2], [3,4,5], [6]]) == 1:6  # vector of vectors -> 6-element Vector

mats = [fill(10i+j, i, j) for i in 1:2, j in 3:5];
concatenate(mats)  # 3×12 block matrix

concatenate([rand(2,2,2,2) for _ in 1:3, _ in 1:4, _ in 1:5]) |> size  # 6×8×10×2 Array
4 Likes

@mcabbott, what am I doing wrong below, as I observe these performance figures for OP’s matrix b:

@btime concatenate($b)      # 801 ns (12 allocs: 880 bytes)

@btime reduce(hcat, reduce(vcat, u) for u in eachcol($b))  # 177 ns (10 allocs: 1.28 KiB)

EDIT:
Error was that code above was not using LazyStack.concatenate()

Usually hvcat / hvncat are fairly slow, they exist only to parse literal [a;;;b;;;c] input.

Note BTW that reduce(hcat, reduce(vcat, u) for u in eachcol(b)) misses the fast method reduce(hcat, xs), so it applies hcat pairwise. The fact that this is such an easy mistake to make is why we should not rely on these magic fast paths, and have explicit functions.

Timing almost all of them, on a slightly bigger example:

julia> conc_jar(b) = reduce(hcat, map(splat(vcat), eachslice(b; dims=2)));  # question

julia> conc_RG(b) = reduce(hcat, reduce(vcat, u) for u in eachcol(b));  # rafael.guerra

julia> conc_RG2(b) = reduce(hcat, [reduce(vcat, u) for u in eachcol(b)]);  # avoiding pairwise hcat

julia> conc_hvcat(A::AbstractMatrix{<:AbstractVecOrMat}) = hvcat(size(A,2), permutedims(A)...);

julia> import LazyStack, BlockArrays

julia> let A = [rand(10, 10) for _ in 1:10, _ in 1:10]
         @btime conc_jar($A)
         @btime conc_hvcat($A)
         @btime Matrix(mortar($A))
         println("RG:")
         @btime conc_RG($A)
         @btime conc_RG2($A)
         println("MA:")
         @btime LazyStack.concatenate($A)
       end;
  23.708 μs (283 allocations: 166.91 KiB)
  15.250 μs (13 allocations: 81.22 KiB)
  11.958 μs (13 allocations: 80.41 KiB)
RG:
  28.500 μs (57 allocations: 502.36 KiB)
  9.583 μs (35 allocations: 157.88 KiB)
MA:
  7.500 μs (3 allocations: 78.20 KiB)
4 Likes

Fortunately there are stars in the Julia sky to light the candle for us.