Why does in-place array assignment behave differently here?

Hi - I’m new to Julia, been playing around a bit. I’ve been struggling to understand the below. I have some code where I need to switch some values in an array. I get (surprisingly) different results if I use

w[3], w[2] = w[2], w[3]

vs. 
w_copy = copy(w)
w_copy[3], w_copy[2] = w_copy[2], w_copy[3]  # or w_copy[3], w_copy[2] = w[2], w[3]

Note this is being run in a large for loop, where w3 is being inititalised along with other similar arrays.

This seems to give the correct results:

for m1 in all_prefs, m2 in all_prefs, m3 in all_prefs, w1 in all_prefs, w2 in all_prefs, w3 in all_prefs
    w1_p1 = f([m1,m2,m3], [w1,w2,w3])[1] 
    w2_copy = copy(w2)
    w2_copy[2], w2_copy[3] = w2_copy[3], w2_copy[2]
    w1_p2 = f([m1,m2,m3], [w1,w2_copy,w3])[1] 

But the below gives different results, which doesn’t seem correct:

for m1 in all_prefs, m2 in all_prefs, m3 in all_prefs, w1 in all_prefs, w2 in all_prefs, w3 in all_prefs
    w1_p1 = f([m1,m2,m3], [w1,w2,w3])[1] 
    w2[2], w2[3] = w2[3], w2[2]
    w1_p2 = f([m1,m2,m3], [w1,w2,w3])[1] 

Why should this be? Please help

Hey there and welcome!

These two points don’t seem to be connected. First of all:

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

julia> w[3], w[2] = w[2], w[3]
(2, 3)

julia> w
3-element Array{Int64,1}:
 1
 3
 2

julia> w_copy = copy(w)
3-element Array{Int64,1}:
 1
 3
 2

julia> w_copy[3], w_copy[2] = w_copy[2], w_copy[3]
(3, 2)

julia> w_copy
3-element Array{Int64,1}:
 1
 2
 3

Looks pretty much like one would expect, right?

So why do you still get different results?
The difference is that in the copy-version you always do the swap with a fresh, copied version. That means that only one change happened to the array. In the non-copy-version you swap around in the same array again and again.

Just as a test, if you swap things back after you used them (assuming that f doesn’t change w!):

for m1 in all_prefs, m2 in all_prefs, m3 in all_prefs, w1 in all_prefs, w2 in all_prefs, w3 in all_prefs
    w1_p1 = f([m1,m2,m3], [w1,w2,w3])[1] 
    w2[2], w2[3] = w2[3], w2[2]
    w1_p2 = f([m1,m2,m3], [w1,w2,w3])[1] 
    w2[2], w2[3] = w2[3], w2[2]

you should get the same results from both versions.

1 Like

Hi. Thanks for the reply. I see what you’re saying. Because the loop is around many variables, w2 isn’t initialised each time - and so I end up changing the same array repeatedly.

Surprisingly though - now I get a 3rd set of results upon switching it back. :frowning:

I can confirm the function f doesn’t change any of its arguments. I really need to think now what’s going on…

Hm, that’s interesting. I would suggest investigating what happens to that array, for example @showing it before and after swapping.
Note that if you change the contents of the array w2 in f the actual array changes as well, just like

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

julia> b = a
2-element Array{Int64,1}:
 1
 2

julia> b[1] = 3
3

julia> a
2-element Array{Int64,1}:
 3
 2

Maybe that’s what’s happening?

Besides all that, what is the behavior which you want? If it’s the copy-version, you can just put the swap outside the loop and you should have no problems.

1 Like

The problem is in all_prefs

for m1 in all_prefs, m2 in all_prefs, m3 in all_prefs, w1 in all_prefs, w2 in all_prefs, w3 in all_prefs

I bet all_prefs is an array of array

So when you change w2 (aka swap two of its elements) this starts to affect all_prefs

which in turn affects m1,m2,m3,w1 and w3. Note that w2 is already affect by your swap.

But when you use w2_copy, it does not affect all_prefs

2 Likes

That’s a good point, I hadn’t really thought about that. But swapping back (in the loop) should still make it alright. (Which means there has to be some other problem as well.)

Please calm down and make sure you respect the Julia Community Standards. I find your message both aggressive and condescending. Also, I understand your point perfectly well, if you read above I made the same point as well.

But if you swap two values, do something, swap them back again, and don’t get the same array then there has to be something else going on as well, probably in the “do something” part which is f in this case.

2 Likes
for m1 in all_prefs, m2 in all_prefs, m3 in all_prefs, w1 in all_prefs, w2 in all_prefs, w3 in all_prefs
    w1_p1 = f([m1,m2,m3], [w1,w2,w3])[1] 
    w2[2], w2[3] = w2[3], w2[2]
    # At this point m1 is corrupted and the next line will give the wrong result
    w1_p2 = f([m1,m2,m3], [w1,w2,w3])[1] 
    w2[2], w2[3] = w2[3], w2[2]

Ah, I see now what you were saying, thanks for the explanation.

No problems. I am very sorry that I had used a very aggressive and overbearing tone in my past responses.

2 Likes

Thanks to both of you. Yes - Steven is right. I verified it.

My prior experience is Clojure - which has immutable data structures by default. So I’ve been spoiled by that. Reasoning about state is hard.

Lesson learned for me that make copies when possible.

No problems. I am very sorry that I had used a very aggressive and overbearing tone in my past responses.

Don’t worry about it, but in the future please be mindful of how you engage with others here. Even if you are right in what you are saying and others are slow to understand your point (as I was), being friendly and patient usually work out better for everyone in the end. :slightly_smiling_face:

which has immutable data structures by default
Lesson learned for me that make copies when possible.

Plus points for mutation though: you don’t have to make copies (and additional allocations) so you can get juicy performance improvements :wink:

That’s definitely the wrong lesson. The right lesson to learn is to think about when you should and shouldn’t mutate an object.

2 Likes

Fair enough. I will try.

From that use case, if f() is a function written by yourself, looks like it’s better to just add an optional argument to it which indicates whether the second and third elements of w2 are taken in that or the reverse order.
Or, if all ws are short enough and their lengths are known in advance, use tuples instead of arrays and construct a tuple like w2_tmp = (w2[1], w2[3], w2[2]). Tuples are immultable, so that you won’t accidentally modify the original array.

Thanks for the suggestions. Event though in this case these were not practical (see below), I will keep these in mind.

  1. Sometimes there are c. 20+ elements in the arrays. The 3-case was just a representative problem. I could create another function and abstract away the switching though.
  2. The array vs tuple construction is not in my hands. These functions are downstream consumers of the data of a library function - where an array of arrays is produced. But yes - I will keep the tuple use case in mind going forward.