1

i'm attempting to understand why the following code behaves (to my knowledge) oddly.

[1] pry(main)> a = {}
=> {}
[2] pry(main)> a[1] = [[0,0]] * 7
=> [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
[3] pry(main)> a[2] = [[0,0]] * 7
=> [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
[4] pry(main)> a[1][2][0] = 3 # Should be one value changed, right? 
=> 3
[5] pry(main)> a
=> {1=>[[3, 0], [3, 0], [3, 0], [3, 0], [3, 0], [3, 0], [3, 0]],
 2=>[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]}

What i thought should happen is that one value of the array in the hash a at key 1 at index 2 should change to 3, but instead all the first values of the entire array change to 3. What's going on here, what am I missing? Here's my Ruby version.

$ ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-linux]

EDIT:

I also tried the following

[1] pry(main)> a = {}
=> {}
[2] pry(main)> a[1] = ([[0,0].dup].dup * 7).dup
=> [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
[3] pry(main)> a[2] = ([[0,0].dup].dup * 7).dup
=> [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
[4] pry(main)> a[1][2][0] = 3
=> 3
[5] pry(main)> a
=> {1=>[[3, 0], [3, 0], [3, 0], [3, 0], [3, 0], [3, 0], [3, 0]],
 2=>[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]}
[6] pry(main)> a = {}
=> {}
[7] pry(main)> a[1] = ([[0,0].clone].clone * 7).clone
=> [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
[8] pry(main)> a[2] = ([[0,0].clone].clone * 7).clone
=> [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
[9] pry(main)> a
=> {1=>[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]],
 2=>[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]}
[10] pry(main)> a[1][2][0] = 3
=> 3
[11] pry(main)> a
=> {1=>[[3, 0], [3, 0], [3, 0], [3, 0], [3, 0], [3, 0], [3, 0]],
 2=>[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]}

Surely the values should be copies?

2 Answers 2

3

All the elements in a[1] refer to the same array.

[0,0] isn't deep-copied when you do [[0,0]] * 7.

Solution: a[1] = Array.new(7) { [0,0] } (thanks @Stefan!)

Sign up to request clarification or add additional context in comments.

3 Comments

To solve that problem: a[1] = Array.new(7) { [0,0] }
Thanks a lot to both of you, now I understand.
The key here is you use an initializer block rather than an initializer element. To see the original problem: a.map(&:object_id) shows that every object in the array is an identical reference.
0

Here's how to diagnose this problem in the future:

object_id will tell you the "slot" where Ruby keeps an object. Objects should have unique IDs:

foo = []
bar = []
foo.object_id # => 70322472940660
bar.object_id # => 70322472940640

If they don't, though they might be different variable names, they're still pointing to the same object. That might be desirable in more advanced uses, but generally it's not something you want because changing one will change the other leading to confusion, gnashing teeth and possibly the return of "he who should not be named". For instance, we get fooled when we look at:

[[]] * 2 # => [[], []]

because it seems that we're getting back two arrays, which could be assigned using parallel assignment, like:

foo, bar = [[]] * 2

But, when we look at foo and bar to see where they're stored we can see the problem, they're both pointing to the same slot, and so changing one changes the other:

foo.object_id # => 70323794190700
bar.object_id # => 70323794190700
foo << 1
bar # => [1]

This is an ages old problem when writing code; You gotta learn about "pass by value" vs. "pass by reference" and when a language is doing one or the other otherwise this problem will occur. And then you have to learn how to tell the language to avoid the problem.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.