Question regarding push! and array of arrays

Hello, I have a question about a very unexpected behavior I encountered, I’d like an ELI5.

Why does this:

vi = fill(Int[], 5)
vi[2] = [5,6]
push!(vi[3], 9)

result in

 [9]
 [5, 6]
 [9]
 [9]
 [9]

instead of in

 []
 [5, 6]
 [9]
 []
 []

?

While we’re at it, why was the [5, 6] part left unchanged, unlike all the other elements?

BTW, my intention was to use a dynamic data structure consisting of n (determined at run time) independent dynamically created arrays, what’s the right way to do this (in case I’m doing it wrong)?

(Julia 1.5.3)

julia> vi = [Int[] for _=1:5]
5-element Array{Array{Int64,1},1}:
 []
 []
 []
 []
 []

julia> vi[2] = [5,6]
2-element Array{Int64,1}:
 5
 6

julia> push!(vi[3], 9)
1-element Array{Int64,1}:
 9

julia> vi
5-element Array{Array{Int64,1},1}:
 []
 [5, 6]
 [9]
 []
 []

Long story short, fill with Int arrays will just add “references” to the same array, not separate copies of arrays.

4 Likes

The reason is order of operations. This is a common trip-up. In the expression:

fill(Int[], 5)

First the the array Int[] is created, then the “outer” (5-element) array is created, and each element of that array references the empty Int[] array. These are all references to the same array, so when you modify one of them (by pushing) you modify them all.

It breaks down like this:

x = Int[]
vi = [x for _ in 1:5]  # vi == [[], [], [], [], []]
push!(x, 1)            # vi == [[1], [1], [1], [1], [1]]
vi[2] = [5, 6]         # vi == [[1], [5, 6], [1], [1], [1]]
3 Likes

Thank you both! I learned something.

You might be looking for

julia> vi = [Int[] for _ ∈ 1:5];

julia> vi[2] = [5, 6]
2-element Vector{Int64}:
 5
 6

julia> push!(vi[3], 9)
1-element Vector{Int64}:
 9

julia> vi
5-element Vector{Vector{Int64}}:
 []
 [5, 6]
 [9]
 []
 []

Here the comprehension creates distinct [] objects.