Possible Bug with push! and overwriting array

question

#1

Hello everyone.
I have searched through the forums and haven’t found similar situation, so I am posting here to see if this is a bug or intended feature. Without further ado, I am posting a minimal working example (I am using JuliaPRO 0.6.0 from Julia Computing on a Windows 7 machine.):

struct Foo
    x::Array{Int64,1}
end

n_foo_pop = 5
n_arr_elems = 3

arr = Array{Int64,1}(n_arr_elems) # So I initiate an array of integers: [0, 0, 0]
foo_population = Array{Foo,1}() # This will be an array of Foo's.

for i=1:n_foo_pop
    for j=1:n_arr_elems
        arr[j] = rand(1:10)
    end
    println(arr)
    push!(foo_population, Foo(arr))
end

println(foo_population)

The previous code produces de following result:

[5, 7, 6]
[3, 2, 6]
[4, 8, 10]
[1, 2, 4]
[9, 2, 7]
Foo[Foo([9, 2, 7]), Foo([9, 2, 7]), Foo([9, 2, 7]), Foo([9, 2, 7]), Foo([9, 2, 7])]

So basically I have created (“pre-allocated”) an array “arr” with size 3 and for each step “i” I am generating random numbers from range 1:10. What I want to accomplish is to append to the array “foo_population” that said array. But it looks like it is only appending the last value ([9,2,7]), n_foo_pop times. It generates the samples the intended way and it is appending the right number of times. But instead of pushing “arr” to the “foo_population” array at the right moment (inside the step “i”), it seems to delay the push operation and perform it separately (thus only appending the last “arr” value). It seems to be performing this operation instead:

for i=1:n_foo_pop
    for j=1:n_arr_elems
        arr[j] = rand(1:10)
    end
end

for i=1:n_foo_pop
    push!(foo_population, Foo(arr))
end

If I multiplicate each element of array “arr” by zero (thus making all entries zero), or if I create a brand new array at each iteration step “i”, then it does what is intended to do:

struct Foo
    x::Array{Int64,1}
end

n_foo_pop = 5
n_arr_elems = 3

arr = Array{Int64,1}(n_arr_elems) # So I initiate an array of integers: [0, 0, 0]
foo_population = Array{Foo,1}() # This will be an array of Foo's.

for i=1:n_foo_pop
    arr *= 0
    for j=1:n_arr_elems
        arr[j] = rand(1:10)
    end
    println(arr)
    push!(foo_population, Foo(arr))
end

println(foo_population)

Producing the right output:

[5, 5, 3]
[1, 9, 10]
[2, 8, 5]
[3, 9, 2]
[7, 2, 8]
Foo[Foo([5, 5, 3]), Foo([1, 9, 10]), Foo([2, 8, 5]), Foo([3, 9, 2]), Foo([7, 2, 8])]

So my question is if it is inteded to be this way or if it is a bug. If it’s a bug, i would like to know if it has been resolved on later versions or not (sorry by bringing it up if it has already been fixed). If it’s not a bug, I would like to know why it’s behaving like this so that I can pay more attention. My concern is that Julia is implicitly executing another operation than the one written on code. It is not pushing at each iteration step as told so.

Thanks in advance.


#2

Thanks for the thorough report! As a quick moderation note, this is the expected behavior so I’m moving it to the #user category. I can give a thorough explanation later but I’ll wager someone will beat me to it.


#3

Yeah, this is expected and not a bug. As a rule, Julia does not copy things unless told to do so. In this case, you have a single array called arr, and you’re mutating that same array in each iteration of the loop. push!() does not copy arr. So foo_population consists of exactly the same array repeated 5 times. Mutating any one of those arrays mutates all of them because they are all the same.

A simpler example might be easier to see:

julia> x = [1, 2, 3]
3-element Array{Int64,1}:
 1
 2
 3

julia> y = [x, x, x]
3-element Array{Array{Int64,1},1}:
 [1, 2, 3]
 [1, 2, 3]
 [1, 2, 3]

julia> y[1][1] = 100
100

julia> y
3-element Array{Array{Int64,1},1}:
 [100, 2, 3]
 [100, 2, 3]
 [100, 2, 3]

You get the result you expect with arr *= 0 because that syntax means arr = arr * 0 which does create a brand-new copy of arr. We could similarly do:

julia> x = [1, 2, 3]
3-element Array{Int64,1}:
 1
 2
 3

julia> y = [copy(x), copy(x), copy(x)]
3-element Array{Array{Int64,1},1}:
 [1, 2, 3]
 [1, 2, 3]
 [1, 2, 3]

julia> y[1][1] = 100
100

julia> y
3-element Array{Array{Int64,1},1}:
 [100, 2, 3]
 [1, 2, 3]  
 [1, 2, 3]

#4

Ah ok!
Now I can understand what is going on.
I’m going to pay more attention next time.
Sorry for the inconvenience and thanks a lot for the explanations!


#5

Thanks @rdeits!


#6

You’re welcome! Have fun!