How do I create a Matrix of Vectors?

Hello everyone!

I’m currently doing the Advent of Code challenges (I’m not asking for help regarding solving it) and for day 4 I’m trying to use an array of vectors.

For the creation I tried fill(Vector{Int}(), (1000, 1000)) however this copies the result of Vector{Int}()
So, whenever I do push!(arr[y, x], elem) all positions get elem added to them since they’re all the same one.

Is this desired behavior? I understand it works for literal values like 3.14 or "hello" however for my use case the only solution I see would be

for i in arr
    arr[i] = Vector{Int}()
end

Thank you in advance!

1 Like

Consider something like

function array_of_empty_vectors(T, dims...)
    array = Array{Vector{T}}(undef, dims...)
    for i in eachindex(array)
        array[i] = Vector{T}()
    end
    array
end

# this creates a 2x3 matrix with independent Vector{Int}()'s
array_of_empty_vectors(Int, 2, 3)

Also, regading

Yes, fill does not create copies, so each element will be the same vector.

A =  fill(Vector{Int}(), (1000, 1000));
A .= copy.((Vector{Int}(),));

Or

A = [copy(Vector{Int}()) for i in 1:1000, j in 1:1000];

No need for the copy here and I’d say that the comprehension is the standard solution. But this is also cute:

julia> 0 .|> fill(x->Vector{Int}(), 2, 3)
2×3 Array{Array{Int64,1},2}:
 []  []  []
 []  []  []

Does someone know if this can be done without a dummy input variable?

2 Likes

Slightly shorter solution:

[Int[] for i=1:1000, j=1:1000]

You can do (f->f()).(fill(()->Vector{Int}(),2,3)), but it’s not that readable. And unlike the comprehension, it needlessly allocates a temporary array of functions.

1 Like

That doesn’t necessarily make a blip in your profile though, unless your matrices are tiny.

julia> @btime fill(()->Int[], 1000, 1000);
  24.891 ns (1 allocation: 80 bytes)

I kinda like this construction, although there’s still a high amount of magic in it.

array_of_empty_vectors(T, dims...) = T .|> fill(T -> T[], dims...)

Can you break down the syntax here?
The whole beginning 0 .|> is weird for me

What does the .= do?
The list comprehension seems more elegant to me, any guidelines on better practices?

Please review the documentation about broadcasting.

I think you need some shape to broadcast on. Another version could be

(_ -> Vector{Int}()).(fill(nothing, 2, 3))

where, if you wrap it in a function, I think the compiler is smart enough to ignore the redundant parts. Another option is

(T -> Vector{T}()).(fill(Int, 2, 3))

which almost borders on being readable :wink:

In short, fill a matrix with copies of an anonymous function that for any input value produces a fresh empty vector. Then transform that matrix into a new matrix by, element for element, running that function with 0 as input. The result is a matrix of empty vectors.

Sorry about that, I had to trick the dot broadcast syntax into doing what I wanted. That’s why I regarded the comprehension as the standard solution.

1 Like