Breezecom
Breezecom

Reputation: 47

Ruby appending to arrays

In the following lines of code, the outcome I was expecting is different from the actual result. Can someone help me understand why?

array1 = [["a", "b", "c"], ["a", "b", "c"]]
temp1 = ["x", "y", "z"]
array1 << temp1

2.times do
  temp1[0] = gets.chomp      #first loop enter 1 and then 4
  temp1[1] = gets.chomp      #first loop enter 2 and then 5
  temp1[2] = gets.chomp      #first loop enter 3 and then 6
  puts temp1.inspect
  array1 << temp1
  puts array1.inspect        
  # Actual result: [["a", "b", "c"], ["a", "b", "c"], ["4", "5", "6"], ["4", "5", "6"], ["4", "5", "6"]]                          
  # Expected Result: [["a", "b", "c"], ["a", "b", "c"], ["x", "y", "z"], ["1", "2", "3"], ["4", "5", "6"]]
end

Upvotes: 1

Views: 219

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110665

Whenever you have problems like this one, it's instructive to add some code to print out the object_id of each object of interest at each of several steps in the calculation. Each Ruby object has a unique object_id. The id can be retrieved with the method Object.object_id:

{ "a"=>1 }.object_id
  #=> 70225550848400

Let's try it. (I've shortened object_id's to their last three digits to make it easier to see when they change.)

array1 = [["a", "b", "c"], ["a", "b", "c"]]
puts "1a array1.object_id=#{array1.object_id % 1000}"
puts "1b array1.map(&:object_id)=#{array1.map { |e| e.object_id % 1000 } }"
puts
temp1 = ["x", "y", "z"]
puts "2a temp1.object_id=#{temp1.object_id % 1000}"
array1 << temp1
puts "2b array1=#{array1.inspect}"
puts "2c array1.object_id=#{array1.object_id % 1000}"
puts "2d array1.map(&:object_id)=#{array1.map { |e| e.object_id % 1000 } }"
puts
2.times do
  temp1[0] = gets.chomp
  temp1[1] = gets.chomp
  temp1[2] = gets.chomp
  puts "3a temp1=#{temp1.inspect}"
  puts "3b temp1.object_id=#{temp1.object_id % 1000}"
  array1 << temp1
  puts "3c array1=#{array1.inspect}"
  puts "3d array1.object_id=#{array1.object_id % 1000}"
  puts "3e array1.map(&:object_id)=#{array1.map { |e| e.object_id % 1000 } }"
  puts 
end

prints

1a array1.object_id=900
1b array1.map(&:object_id)=[0, 920]

2a temp1.object_id=480
2b array1=[["a", "b", "c"], ["a", "b", "c"], ["x", "y", "z"]]
2c array1.object_id=900
2d array1.map(&:object_id)=[0, 920, 480]

1
2
3
3a temp1=["1", "2", "3"]
3b temp1.object_id=480
3c array1=[["a", "b", "c"], ["a", "b", "c"], ["1", "2", "3"], ["1", "2", "3"]]
3d array1.object_id=900
3e array1.map(&:object_id)=[0, 920, 480, 480]

4
5
6
3a temp1=["4", "5", "6"]
3b temp1.object_id=480
3c array1=[["a", "b", "c"], ["a", "b", "c"], ["4", "5", "6"], ["4", "5", "6"],
           ["4", "5", "6"]]
3d array1.object_id=900
3e array1.map(&:object_id)=[0, 920, 480, 480, 480]

You need to study this carefully.

To understand what's going on it may be helpful to think of an array as a container. What you have done is change the contents of a container, but not the container itself, yet array1 is a list of containers.

To make your code work you merely have to change the container as well as the contents. One simple way is to replace:

temp1[0] = gets.chomp
temp1[1] = gets.chomp
temp1[2] = gets.chomp

with

temp1 = [gets.chomp, gets.chomp, gets.chomp]

array1 = [["a", "b", "c"], ["a", "b", "c"]]
puts "1a array1.object_id=#{array1.object_id % 1000}"
puts "1b array1.map(&:object_id)=#{array1.map { |e| e.object_id % 1000 } }"
puts
temp1 = ["x", "y", "z"]
puts "2a temp1.object_id=#{temp1.object_id % 1000}"
array1 << temp1
puts "2b array1=#{array1.inspect}"
puts "2c array1.object_id=#{array1.object_id % 1000}"
puts "2d array1.map(&:object_id)=#{array1.map { |e| e.object_id % 1000 } }"
puts
2.times do
  temp1 = [gets.chomp, gets.chomp, gets.chomp]
  puts "3a temp1=#{temp1.inspect}"
  puts "3b temp1.object_id=#{temp1.object_id % 1000}"
  array1 << temp1
  puts "3c array1=#{array1.inspect}"
  puts "3d array1.object_id=#{array1.object_id % 1000}"
  puts "3e array1.map(&:object_id)=#{array1.map { |e| e.object_id % 1000 } }"
  puts 
end

prints

1a array1.object_id=100
1b array1.map(&:object_id)=[220, 120]

2a temp1.object_id=660
2b array1=[["a", "b", "c"], ["a", "b", "c"], ["x", "y", "z"]]
2c array1.object_id=100
2d array1.map(&:object_id)=[220, 120, 660]

1
2
3
3a temp1=["1", "2", "3"]
3b temp1.object_id=800
3c array1=[["a", "b", "c"], ["a", "b", "c"], ["x", "y", "z"], ["1", "2", "3"]]
3d array1.object_id=100
3e array1.map(&:object_id)=[220, 120, 660, 800]

4
5
6
3a temp1=["4", "5", "6"]
3b temp1.object_id=580
3c array1=[["a", "b", "c"], ["a", "b", "c"], ["x", "y", "z"], ["1", "2", "3"],
           ["4", "5", "6"]]
3d array1.object_id=100
3e array1.map(&:object_id)=[220, 120, 660, 800, 580]

Upvotes: 2

Harsh Trivedi
Harsh Trivedi

Reputation: 1624

Do this and it will work (add .clone to all references to temp1):

 array1 = [["a", "b", "c"], ["a", "b", "c"]]
 temp1 = ["x", "y", "z"]
 array1 << temp1.clone
 2.times do
      temp1[0] = gets.chomp      #first loop enter 1 and then 4
      temp1[1] = gets.chomp      #first loop enter 2 and then 5
      temp1[2] = gets.chomp      #first loop enter 3 and then 6

      puts temp1.inspect
      array1 << temp1.clone
      puts array1.inspect 

 end

 # Actual result: [["a", "b", "c"], ["a", "b", "c"], ["x", "y", "z"], ["1", "2", "3"], ["4", "5", "6"]]

 # Expected Result: [["a", "b", "c"], ["a", "b", "c"], ["x", "y", "z"], ["1", "2", "3"], ["4", "5", "6"]]

Basically, when you append temp1 in array1 it occurs by reference rather than by value. So whenever the temp1 will be updated, the corresponding appended entry in array1 will also be automatically updated. To prevent this behaviour you need to clone / dup the object before appending it to the array. clone / dup duplicate the object (so it doesn't have same reference / object_id) and then assigns it.

For a detailed explanation check this post.

Upvotes: 4

Related Questions