Aaron Godhard
Aaron Godhard

Reputation: 41

Getting messed with wrong array

I saved an array sequence to reset. I deleted each element from sequence, performing an operation, then reset sequence to its former state.

reset = sequence
(0..sequence.length).each do |i|
  puts "Sequence #{sequence}, index is #{i}.  Deleting #{sequence[i]}"
  sequence.delete_at(i)
  puts "New sequence is #{sequence.inspect}, sorted makes #{sequence.sort}, unique makes #{sequence.uniq}"
  return true if sequence.sort == sequence && sequence.uniq == sequence
  puts "This is reset before the sequence = reset: #{reset.inspect}"
  sequence = reset
  puts "Resetting sequence. Reset is #{reset.inspect}, sequence is #{sequence.inspect}"
  dummy = gets.chomp
end

Then, what happened to sequence also happened to reset. Here is some output:

Sequence [1, 1, 1, 2, 3], index is 0.  Deleting 1
New sequence is [1, 1, 2, 3], sorted makes [1, 1, 2, 3], unique makes [1, 2, 3]
This is reset before the sequence = reset: [1, 1, 2, 3]
Resetting sequence. Reset is [1, 1, 2, 3], sequence is [1, 1, 2, 3]

It's mucking about reset when I only commanded it to delete from sequence. It's not the case that reset = sequence is inside the loop. What gives? The loop was supposed to break at oldarray.size, inside which it was pushing elements into a new array. But inexplicably, it pushed the same numbers into the same array as well, thereby creating an infinite loop.

Upvotes: 0

Views: 94

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110665

Suppose we write

sequence = [1, 1, 1, 2, 3]
reset = sequence
  #=> [1, 1, 1, 2, 3]

The variables sequence and reset do not only hold the same value, they hold the same object:

sequence.object_id
  #=> 4044000
reset.object_id
  #=> 4044000

Note that variables do not have object id's; it is their values--Ruby objects-- that have unique ids (see Object#object_id). When we write sequence.object_id, sequence returns [1, 1, 1, 2, 3] and then [1, 1, 1, 2, 3].object_id #=> 4044000 is returned.

If the object [1, 1, 1, 2, 3] is changed the values of the two variables will both equal the changed object.

sequence.replace [1, 4]
  #=> [1, 4]
sequence.object_id
  #=> 4044000
reset
  #=> [1, 4]
reset.object_id
  #=> 4044000

On the other hand, if we set either of these variables to equal a difference object (which will have a different object id), the value of the other variable is unaffected.

sequence = [99, 31]
sequence.object_id
  #=> 5564720
reset
  #=> [1, 4]
reset.object_id
  #=> 4044000

Upvotes: 0

Michael Geary
Michael Geary

Reputation: 28850

reset and sequence are the same array. The = operator doesn't copy the contents of an array or other object; it creates a new reference to the same array.

For example, try this in irb:

irb(main):001:0> a = [ 10, 20 ]
=> [10, 20]
irb(main):002:0> b = a
=> [10, 20]
irb(main):003:0> a
=> [10, 20]
irb(main):004:0> b
=> [10, 20]
irb(main):005:0> a.push( 30 )
=> [10, 20, 30]
irb(main):006:0> b.push( 40 )
=> [10, 20, 30, 40]
irb(main):007:0> a
=> [10, 20, 30, 40]
irb(main):008:0> b
=> [10, 20, 30, 40]

The same is true when you pass an array or any object in a method call. The method doesn't get a separate copy of the object data; it receives a reference to the same object:

irb(main):001:0> a = [ 10, 20 ]
=> [10, 20]
irb(main):002:0> def foo( x );  x.push( 30 );  end
=> :foo
irb(main):003:0> foo( a )
=> [10, 20, 30]
irb(main):004:0> a
=> [10, 20, 30]

Note that foo didn't just modify its x parameter, it also modified the a that was passed into it, because x and a are both references to the same array.

If you want to make a copy of an array so that operations on it are independent of the original, you can use dup or clone:

irb(main):001:0> a = [ 10, 20 ]
=> [10, 20]
irb(main):002:0> b = a.clone
=> [10, 20]
irb(main):003:0> a
=> [10, 20]
irb(main):004:0> b
=> [10, 20]
irb(main):005:0> a.push( 30 )
=> [10, 20, 30]
irb(main):006:0> b.push( 40 )
=> [10, 20, 40]
irb(main):007:0> a
=> [10, 20, 30]
irb(main):008:0> b
=> [10, 20, 40]

Many languages work like this; the assignment operator or a function parameter doesn't copy any data but merely creates a new reference to the same data.

Here's the same example in Python:

>>> a = [ 10, 20 ]
>>> b = a
>>> a
[10, 20]
>>> b
[10, 20]
>>> a.append( 30 )
>>> b.append( 40 )
>>> a
[10, 20, 30, 40]
>>> b
[10, 20, 30, 40]

and in JavaScript:

a = [ 10, 20 ]
  (2) [10, 20]
b = a
  (2) [10, 20]
a.push( 30 )  // .push() returns the array length
  3
b.push( 40 )
  4
a
  (4) [10, 20, 30, 40]
b
  (4) [10, 20, 30, 40]

Upvotes: 3

Related Questions