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
Convert To/From Datetime and Time in Ruby
How to Switch to Ruby 1.9.3 Installed Using Homebrew
How to "Activate" a Different Version of a Particular Gem
Why Can't I Install Rails on Lion Using Rvm
Using Send_File to Download a File from Amazon S3
How to Create a Class Instance from a String Name in Ruby
How to 'Bundle Install' When Your Gemfile Requires an Older Version of Bundler
Gem Install Permission Problem
How to Use Active Support Core Extensions
How to Wrap Link_To Around Some HTML Ruby Code
How to Use Bundler Behind a Proxy
Overriding a Module Method from a Gem in Rails