Having trouble pushing to an array of arrays

I am trying to fill in an array of arrays using push. If I manually initialize the array of arrays pushing works, but it doesn’t if I try to initialize using fill. For example manually initializing

test = [Int[], Int[]]
push!(test[1], 1)

gives the following final resulting value for test (as I would expect)

2-element Array{Array{Int64,1},1}:
 [1]
 []

However, initializing using fill

test = fill(Int[], 2)
push!(test[1], 1)

gives the following final resulting value for test

2-element Array{Array{Int64,1},1}:
 [1]
 [1]

So both arrays in test seem to be pointing to the same array and if I modify one of them I modify all of them. Is there a way to make fill work in the same way as the manual approach?

julia> test=[ Int[] for x in 1:2 ]
2-element Array{Array{Int64,1},1}:
 []
 []

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

julia> test
2-element Array{Array{Int64,1},1}:
 [1]
 []

I don’t know how to achieve this with fill, but I love comprehensions

2 Likes

Maybe the comprehension is the way to go, as I think it’s not possible with fill:

help?> fill
search: fill fill! finally findall filter filter! filesize filemode isfile @__FILE__ fieldtype fieldname fieldtypes fieldnames fieldcount fieldoffset evalfile nfields getfield hasfield setfield! findlast finalize finalizer PROGRAM_FILE

  fill(x, dims)

  Create an array filled with the value x. For example, fill(1.0, (5,5)) returns a 5×5 array of floats, with each element initialized to 1.0.

  Examples
  ≡≡≡≡≡≡≡≡≡≡

  julia> fill(1.0, (5,5))
  5×5 Array{Float64,2}:
   1.0  1.0  1.0  1.0  1.0
   1.0  1.0  1.0  1.0  1.0
   1.0  1.0  1.0  1.0  1.0
   1.0  1.0  1.0  1.0  1.0
   1.0  1.0  1.0  1.0  1.0

  If x is an object reference, all elements will refer to the same object. fill(Foo(), dims) will return an array filled with the result of evaluating Foo() once.

So if you do something like fill(x->Int[], 2) to generate new empty arrays, the anonymus function will be evaluated only once, resulting that every element points to the same object.

@oheil This does indeed also give an automated of initializing test. So I will use that instead. But I am still curious about why fill behaves the way it does.

That’s what fill is supposed to do, fill an array with the same value. If the value is a container, that same container will appear everywhere.

If you absolutely want to use fill there are contortions like

map(x -> x(), fill(() -> Int[], 2))

or

0 .|> fill(_ -> Int[], 2)

possible, but you are most likely better off with the comprehension.

Actually, the most attractive solution that involves fill is probably

copy.(fill(Int[], 2))
julia> @benchmark copy.(fill(Int[], 2))
BenchmarkTools.Trial:
  memory estimate:  432 bytes
  allocs estimate:  5
  --------------
  minimum time:     100.106 ns (0.00% GC)
  median time:      106.006 ns (0.00% GC)
  mean time:        130.441 ns (16.75% GC)
  maximum time:     30.718 μs (99.35% GC)
  --------------
  samples:          10000
  evals/sample:     949

julia> @benchmark [ Int[] for x in 1:2 ]
BenchmarkTools.Trial:
  memory estimate:  256 bytes
  allocs estimate:  3
  --------------
  minimum time:     61.905 ns (0.00% GC)
  median time:      64.437 ns (0.00% GC)
  mean time:        79.246 ns (17.33% GC)
  maximum time:     29.433 μs (99.70% GC)
  --------------
  samples:          10000
  evals/sample:     987

Did I already say, that I love comprehensions? :wink:

And we can even force fill and comprehensions:

julia> test=[ fill(1,1) for x in 1:2 ]
2-element Array{Array{Int64,1},1}:
 [1]
 [1]

julia> push!(test[1], 2)
2-element Array{Int64,1}:
 1
 2

julia> test
2-element Array{Array{Int64,1},1}:
 [1, 2]
 [1]

but than its prefilled. But using empty solves that:

julia> test= [ empty(fill(1,1)) for x in 1:2 ]
2-element Array{Array{Int64,1},1}:
 []
 []

julia> push!(test[1], 2)
1-element Array{Int64,1}:
 2

julia> test
2-element Array{Array{Int64,1},1}:
 [2]
 []

But this is slower than copy.:

julia> @benchmark [ empty(fill(1,1)) for x in 1:2 ]
BenchmarkTools.Trial:
  memory estimate:  448 bytes
  allocs estimate:  5
  --------------
  minimum time:     106.249 ns (0.00% GC)
  median time:      111.229 ns (0.00% GC)
  mean time:        139.833 ns (19.57% GC)
  maximum time:     31.456 μs (99.47% GC)
  --------------
  samples:          10000
  evals/sample:     944

Isn’t Julia great again?
But sometimes I think, there are too much possible ways for simple things, but this is another discussion.

1 Like

This is getting off topic but empty(fill(1, 1)) would not be my first choice of alternative spelling of Int[]. Both empty!(fill(1, 1) and fill(1, 0) come ahead.

2 Likes