arjun
arjun

Reputation: 1614

Ruby empty array changes values in iterations because of another array

An empty array is defined outside a block and within the block, it is assigned to a new variable. If the new array changes, the empty array also modifies its values. Why?

# THIS CODE CHECK WHICH LETTERS IN klas APPEAR IN docs
klas = ["a", "b", "c"]

docs = [[1, "a"], [2, "a"], [3, "b"], [4, "b"], [5, "c"], [6, "c"]]

output = []
empty_array = Array.new(klas.size) { 0 } # EMPTY ARRAY DEFINED HERE

docs.each do |doc|
    puts empty_array.inspect
    puts (output_row = empty_array).inspect # EMPTY ARRAY ASSIGNED TO ANOTHER

    # FIND INDEX OF THE LETTER IN klas FROM docs. ASSIGN 1.
    output_row[klas.index(doc[1])] = 1 # PROBLEM! THIS ALSO CHANGES THE EMPTY ARRAY VALUES
    output << output_row
end

CONSOLE OUTPUT to show empty array changing its value based on another array

###
empty_array is [0, 0, 0]
output_row is [0, 0, 0]
---
###
empty_array is [1, 0, 0]
output_row is [1, 0, 0]
---
###
empty_array is [1, 0, 0]
output_row is [1, 0, 0]
---
###
empty_array is [1, 1, 0]
output_row is [1, 1, 0]
---
###
empty_array is [1, 1, 0]
output_row is [1, 1, 0]
---
###
empty_array is [1, 1, 1]
output_row is [1, 1, 1]
---

# INCORRECT output IS
=> [[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]]
# SHOULD BE
=> [[1, 0, 0], [1, 0, 0], [0, 1, 0], [0, 1, 0], [0, 0, 1], [0, 0, 1]]

But if the empty array is defined within the block and assigned to a new variable it works as expected.

docs.each do |doc|
    empty_array = Array.new(klas.size) { 0 } # THIS MAKES SURE empty_array stays with zero values
    output_row = empty_array    
    output_row[klas.index(doc[1])] = 1
    output << output_row
end

CORRECT output IS
=> [[1, 0, 0], [1, 0, 0], [0, 1, 0], [0, 1, 0], [0, 0, 1], [0, 0, 1]]

Why is the empty array modifying its value when it is outside a block? Shouldn't it remain the same irrespective of another array changing its value?

Upvotes: 1

Views: 60

Answers (1)

Roland Studer
Roland Studer

Reputation: 4415

By using

output_row = empty_array

you are not copying the empty array. You are creating a reference to the same underlying array that is referenced in empty_array.

You can create a clone or a duplicate of the array. So use:

output_row = empty_array.dup

This will create a new Array, which is a duplicate of empty_array, see http://ruby-doc.org/core-2.4.1/Object.html#method-i-dup

A more complete explanation on what happens with an example:

a = [1] => [1] # is creating a new array 'x'
               # a is referencing this array, a is not the array itself!

b = a   => [1] # b now references the array 'x'

a = [2] => [2] # is creating a new array 'y', a is referencing this new array
               # the reference of b is not changing

b       => [1] # b still pointing to array 'x'

So with b = a you just tell them to reference the same array x, but b is not a reference to a.

Upvotes: 4

Related Questions