dev404
dev404

Reputation: 1098

"<<" inserts a pointer to a variable when I try to append the value of this variable, to another variable. How do I avoid this?

Sorry for the messy title.

If I use static values (rails console):

 2.1.2 :013 > h=[]
  => [] 
 2.1.2 :014 > h<<[1]
  => [[1]] 
 2.1.2 :015 > h<<[1,2]
  => [[1], [1, 2]] 
 2.1.2 :016 > h<<[1,2,3]
  => [[1], [1, 2], [1, 2, 3]] 

It works as expected. Same if I use a variable that uses static values, like this:

 2.1.2 :018 > h=[]
  => [] 
 2.1.2 :019 > a=1
  => 1 
 2.1.2 :020 > h<<a
  => [1] 
 2.1.2 :021 > a=[1,2]
  => [1, 2] 
 2.1.2 :022 > h<<a   
  => [1, [1, 2]] 
 2.1.2 :023 > a=[1,2,3]
  => [1, 2, 3] 
 2.1.2 :024 > h<<a
  => [1, [1, 2], [1, 2, 3]] 

But if I do the following, the values of the variable are replaced onto the entire array, like it was a pointer:

 2.1.2 :036 > h=[]
  => [] 
 2.1.2 :037 > a=[]
  => [] 
 2.1.2 :038 > a<<1
  => [1] 
 2.1.2 :039 > h<<a
  => [[1]] 
 2.1.2 :040 > a<<2
  => [1, 2] 
 2.1.2 :041 > h<<a
  => [[1, 2], [1, 2]] 
 2.1.2 :042 > a<<3
  => [1, 2, 3] 
 2.1.2 :043 > h<<a
  => [[1, 2, 3], [1, 2, 3], [1, 2, 3]] 

Notice how all the values of h get replaced by the new value of a.The same happens if I use "h.append(X)". If I use another variable, the values don't get replaced:

 2.1.2 :044 > b=[1,2,3]
  => [1, 2, 3] 
 2.1.2 :045 > b<<4
  => [1, 2, 3, 4] 
 2.1.2 :046 > h<<b
  => [[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3, 4]] 

I've temporary solved this by using something like "h << a.to_s", but that converts the numerical values to strings, which causes problems when I need to do math with them. Another alternative I ran into is by using "h << a.inspect", except the values get encased by quotes like this:

 ["[1]", "[1,2]", "[1,2,3]"]

which is ok'ish, but the quotes cause other problems as well. Ideally, I'd want them like this:

 [[1], [1,2], [1,2,3]]

Is there a way to prevent this from happening? Or perhaps, using a different way to insert the arrays into a hash/array/json variable?

Upvotes: 1

Views: 50

Answers (3)

Patrick Oscity
Patrick Oscity

Reputation: 54674

You need to understand that Ruby effectively passes parameters by reference¹ – only basic types like floats, ints, true, false and nil are immutable and thus will not expose the behaviour you observed. For all other objects, one possible workaround is to use dup:

h = []
a = []
a = a.dup << 1 #=> [1]
h << a         #=> [[1]]
a = a.dup << 2 #=> [1, 2]
h << a         #=> [[1], [1, 2]]
a = a.dup << 3 #=> [1, 2, 3]
h << a         #=> [[1], [1, 2], [1, 2, 3]]
h              #=> [[1], [1, 2], [1, 2, 3]]

Another way is to make use of +=, which implicitly returns a copy. However, the right hand argument then must be an array instead of a single value:

h = []
a = []
a += [1] #=> [1]
h << a   #=> [[1]]
a += [2] #=> [1, 2]
h << a   #=> [[1], [1, 2]]
a += [3] #=> [1, 2, 3]
h << a   #=> [[1], [1, 2], [1, 2, 3]]
h        #=> [[1], [1, 2], [1, 2, 3]]

¹Although internally, Ruby passes them by value, but these values are references

Upvotes: 2

pangpang
pangpang

Reputation: 8821

You can get object id for variable 'a' by Object#object_id, it can help you understand.

irb(main):060:0> a= 1
=> 1
irb(main):061:0> a.object_id
=> 3
irb(main):062:0> a= [1,2]
=> [1, 2]
irb(main):063:0> a.object_id
=> 20017440
irb(main):064:0> a = [1,2,3]
=> [1, 2, 3]
irb(main):065:0> a.object_id
=> 19627820

The object_id of variable a is different.

irb(main):066:0> a= []
=> []
irb(main):067:0> a << 1
=> [1]
irb(main):068:0> a.object_id
=> 18718920
irb(main):069:0> a << 2
=> [1, 2]
irb(main):070:0> a.object_id
=> 18718920
irb(main):071:0> a << 3
=> [1, 2, 3]
irb(main):072:0> a.object_id
=> 18718920

If you use "<<", the object_id of variable 'a' will not change, if you change the value of variable a, all elements of array 'h' will change.

So you can use 'a.dup' get a shallow copy of object a, then add it to array h.

Upvotes: 0

spickermann
spickermann

Reputation: 106802

You can duplicate the reference before inserting into the array:

> h = []
> a = []
> a << 1
> h << a.dup
> a << 2
> h << a.dup
> a << 3
> h << a.dup
> h
# => [[1], [1, 2], [1, 2, 3]]

Upvotes: 3

Related Questions