In Ruby, Why Does Array.New(Size, Object) Create an Array Consisting of Multiple References to the Same Object

Changing one array in an array of arrays changes them all; why?

Use this instead:

a = Array.new(3){ [] }

With your code the same object is used for the value of each entry; once you mutate one of the references you see all others change. With the above you instead invoke the block each time a new value is needed, which returns a new array each time.


This is similar in nature to the new user question about why the following does not work as expected:

str.gsub /(<([a-z]+)>/, "-->#{$1}<--"

In the above, string interpolation occurs before the gsub method is ever called, so it cannot use the then-current value of $1 in your string. Similarly, in your question you create an object and pass it to Array.new before Ruby starts creating array slots. Yes, the runtime could call dup on the item by default…but that would be potentially disastrous and slow. Hence you get the block form to determine on your own how to create the initial values.

Ruby: Nested Array behaving strangley as an Instance Variable

There is nothing wrong with the Board class nor with attr_accessor. The initialization of the grid however does not work. @grid = Array.new(3, Array.new(3, nil)) is the culprit. The fourth code sample in the docs
shows how it should be done, the text above it explains why OP's code behaves like it does. Every Ruby student gets trapped by this at least once.

Ruby array creation, Array.new vs []

Those two statements are functionally identical. Array.new however can take arguments and a block:

Array.new # => []
Array.new(2) # => [nil,nil]
Array.new(5,"A") # =>["A","A","A","A","A"]

a = Array.new(2,Hash.new)
a[0]['cat'] = 'feline'
a # => [{"cat"=>"feline"},{"cat"=>"feline"}]
a[1]['cat'] = 'Felix'
a # => [{"cat"=>"Felix"},{"cat"=>"Felix"}]

a = Array.new(2){Hash.new} # Multiple instances
a[0]['cat'] = 'feline'
a # =>[{"cat"=>"feline"},{}]
squares = Array.new(5){|i|i*i}
squares # => [0,1,4,9,16]

copy = Array.new(squares) # initialized by copying
squares[5] = 25
squares # => [0,1,4,9,16,25]
copy # => [0,1,4,9,16]

Note: the above examples taken from Programming Ruby 1.9

Why are there different results when assigning a value in a two-dimension array?

Because arr3 contains two identical objects, but arr4 contains two different objects.

>> arr3 = Array.new(2, Array.new(2, 0))
=> [[0, 0], [0, 0]]
>> arr3.map { |ary| ary.object_id }
=> [73703490, 73703490]
>> arr4 = [[0, 0], [0, 0]]
=> [[0, 0], [0, 0]]
>> arr4.map { |ary| ary.object_id }
=> [73670930, 73670920]
>>

Read new(size=0, default=nil)

...In the first form, if no arguments are sent, the new array will be empty. When a size and an optional default are sent, an array is created with size copies of default. Take notice that all elements will reference the same object default.

You created arr3 using the form above, while creating arr4 using the literal constructor [].

A new array can be created by using the literal constructor []. Arrays can contain different types of objects.

If you want Array::new to behave as literal construct, then go with new(size) {|index| block } form.

>> arr3 = Array.new(2){ Array.new(2, 0) }
=> [[0, 0], [0, 0]]
>> arr3.map { |ary| ary.object_id }
=> [73551460, 73551450]
>>

Why doesn't terse way of defining new hashes in Ruby work (they all refer to same object)

In answer to the original question, an easy way to initialize multiple copies would be to use the Array.new(size) {|index| block } variant of Array.new

a, b = Array.new(2) { Hash.new }
a, b, c = Array.new(3) { Hash.new }
# ... and so on

On a side note, in addition to the assignment mix-up, the other seeming issue with the original is that it appears you might be making the mistake that == is comparing object references of Hash and String. Just to be clear, it doesn't.

# Hashes are considered equivalent if they have the same keys/values (or none at all)
hash1, hash2 = {}, {}
hash1 == hash1 #=> true
hash1 == hash2 #=> true

# so of course
[Hash.new, Hash.new] == [Hash.new] * 2 #=> true

# however
different_hashes = [Hash.new, Hash.new]
same_hash_twice = [Hash.new] * 2
different_hashes == same_hash_twice #=> true
different_hashes.map(&:object_id) == same_hash_twice.map(&:object_id) #=> false

Why does map enumerable return value not return expected value

The result of map, in your case, is an array which consists of references to same array new_empty_array multiple times. You are not creating three different arrays, but modifying the same array in the map block.

To get the output you are expecting, you need to do:

new_names_array = first_names.map do |name|
(new_empty_array << name).dup
end

As a side note, you could use this code, which is more obvious than the code above, for the output you desire:

(1..first_names.size).map do |num|
first_names.take(num)
end
#=> [["Donald"], ["Donald", "Daisy"], ["Donald", "Daisy", "Daffy"]]


Related Topics



Leave a reply



Submit