配列を生成するときに要素を別々のオブジェクトにする

Array.new(size, val) で配列を生成すると

a = Array.new(3, 'hoge')
a.each {|v| p v.object_id }
# >> 21625090
# >> 21625090
# >> 21625090

要素は全て同じオブジェクトになる。


これだと、どれかの要素を破壊的に変更したときに全ての要素に影響が出てしまう。

p a # => ["hoge", "hoge", "hoge"]
a[1].replace 'fuga'
p a # => ["fuga", "fuga", "fuga"]


別のオブジェクトにする一番簡単な方法はブロックで値を渡すこと。

a = Array.new(3) { 'hoge' }
a.each {|v| p v.object_id }
# >> 21625020
# >> 21625010
# >> 21625000


ちなみに、以下のスクリプトで配列の一部だけが自然に変わるのは
新しいオブジェクトを生成して「代入」でそれを要素に入れ直しているから。

a = Array.new(3, 'hoge')
a.each {|v| p v.object_id }
# >> 21625090
# >> 21625090
# >> 21625090

a[1] = 'fuga'
p a # => ["hoge", "fuga", "hoge"]
a.each {|v| p v.object_id }
# >> 21625090
# >> 21625060
# >> 21625090

要素の値を変更するときに代入を使っているときは
要素が同じオブジェクトか別のオブジェクトなのかを気にしなくてもいいのだけど、
両者の違いをちゃんと理解しておかないと
少し複雑な配列を扱おうとしたときにハマる(経験談)。


前にも似たようなこと書いた気がするけど
リファレンス読んでたら気になったのでもう一度書いておく。