Mapping with Flattening - Like a Kronecker Product

I want to do something like

A = map(x -> [x,2*x], transpose([1,2,3]))

which yields:

1×3 transpose(::Vector{LinearAlgebra.Transpose{Int64, Vector{Int64}}}) with eltype Vector{Int64}:
 [1, 2]  [2, 4]  [3, 6]

But I wanted

6-element Vector{Int64}:
 1
 2
 2
 4
 3
 6

I suspect there is some one-liner (or at least very simple) way of doing this. I understand how to write a function, perhaps called KronLikeMap , that does this with 2 loops. But I can’t find anything useful in the manual.

The problem may be that I don’t know what searches to try.

1 Like
julia> A = reduce(vcat, map(x -> [x, 2*x], transpose([1, 2, 3])))
6-element Vector{Int64}:
 1
 2
 2
 4
 3
 6
2 Likes

Reduce-vcat and reduce-hcat solve a bunch of my other issues.

Thanks

This is about four times faster on my machine, probably because it does less allocation. Maybe not as pretty.

myfill(a) = (v = Int[]; foreach(x -> push!(v, x, 2*x), a); v)
4 Likes

Raf’s response is awesome. If you wanted to be even slicker you could use:

mapreduce(x->[x,2x], vcat, [1,2,3])

Also you might not even need the transpose.

3 Likes

I’m sure there is a scientific explanation for the following, but it is mysterious to me:

julia> const b = 1:1000;

julia> @btime mapreduce(x->(x,2x), vcat, b);
  764.033 μs (999 allocations: 7.78 MiB)

julia> @btime mapreduce(x->[x,2x], vcat, b);
  784.130 μs (1999 allocations: 7.87 MiB)

julia> @btime reduce(vcat, map(x -> (x, 2*x), b));
  792.793 μs (1000 allocations: 7.79 MiB)

julia> @btime reduce(vcat, map(x -> [x, 2*x], b));
  23.978 μs (1002 allocations: 117.44 KiB)

julia> @btime myfill(b);
  8.126 μs (11 allocations: 32.45 KiB)
1 Like

reduce(vcat, vector_of_arrays) is fast because it has the manual performance overload:

Since it is easy to know the output element type in this case, I’d use mapfoldl + append!. It’s faster and more robust (i.e., works even if the input is empty):

julia> @btime reduce(vcat, map(x -> [x, 2*x], 1:1000));
  30.709 μs (1002 allocations: 117.44 KiB)

julia> @btime mapfoldl(x -> (x, 2x), append!, 1:1000; init = Int[]);
  9.830 μs (11 allocations: 32.45 KiB)

There’s probably no need to micro-optimize this. But, it is helpful to use the information of the output vector size. BangBang.collector is a composable tool for encoding this.:

julia> using BangBang

julia> @btime finish!(append!!(collector(Vector{Int}(undef, 2000), Val(true)), (y for x in 1:1000 for y in (x, 2x))));
  839.000 ns (1 allocation: 15.75 KiB)
2 Likes

This variant looks simple too:
vcat([[x,2x] for x in [1,2,3]]...)

1 Like