二次元配列への代入がうまくいかない
3✕3の二次元配列をつくります。
a = Array.new(3, Array.new(3, 0))
p a # => [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
見た目上は問題ありませんね。二次元配列ができています。
ですが、要素を代入してみると、
a[0][0] = 1
p a # => [[1, 0, 0],[1, 0, 0],[1, 0, 0]]
あれ...??1つ目の配列の1番目に要素を代入したはずですが、全ての配列の1番目が変わってしまっています。
オブジェクトIDを確認してみましょう。
p a.map(&:object_id)
# => [60, 60, 60]`
3つの配列とも同じオブジェクトになっちゃってます。これが原因っぽい。
対処法
mapメソッドを使うことで、上記の問題を回避できます。
a = Array.new(3).map { Array.new(3,0) }
p a # => [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
要素を追加してみます。
a[0][0] = 5
p a # => [[5, 0, 0], [0, 0, 0], [0, 0, 0]]
はい! ちゃんと、第一要素にだけ代入することができました!
念の為オブジェクトIDも確認しておきましょう。
p a.map(&:object_id)
# => [60, 80, 100]
さっきと違って、それぞれの配列を別のオブジェクトとして定義できています。
ちなみに、ブロックで渡すだけでもうまくいきます。↓
a = Array.new(3) { Array.new(3,0) }
原因
Rubyリファレンスマニュアル から引用
new(size = 0, val = nil) -> Array
長さ size の配列を生成し、各要素を val で初期化して返します。要素毎に val が複製されるわけではないことに注意してください。全要素が同じオブジェクト val を参照します。後述の例では、配列の各要素は全て同一の文字列を指します。
間違った方法↓
a = Array.new(3, Array.new(3, 0))
p a # => [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
これは、newメソッドで長さ3の配列をつくって、中の要素にArray.new(3.0)
を代入(初期化)しています。
しかし、Array.new(3.0)
が3つ複製されているわけではなく、1つのオブジェクトを3回参照しているだけですので今回のような問題が起こってしまいました。
値は同じでも、同じオブジェクトなのか違うオブジェクトなのかを意識することが大切ですね。