はじめに
プログラミング問題で遊んでいた時のことです。
2次元配列を生成して、条件を満たしている要素を書き換えていこうとしたところ、
「ん?思ったのと違う要素まで書き換えられてるやん。あ〜、他の行と同じ配列を参照してるからか。」
と気がつくことはできたものの、いざ、それぞれの行で独立した配列を生成しようとすると手が止まる…
その時は力技で凌ぎましたが、もう少し綺麗に書きたいなと思い、調べた時のことをまとめておきます。
結論
縦2 × 横3の配列であれば、次のように生成します。
array = Array.new(2){ Array.new(3) }
各行が独立していない配列の生成
はじめに試して、うまくいかなかった配列の生成方法です。
new
メソッドの第1引数で1次元目(縦)の要素数を、第2引数で2次元目(横)に入る配列(=生成した配列)を指定しています。
array1 = Array.new(2, Array.new(3))
p array1 # => [[nil, nil, nil], [nil, nil, nil]]
array1[0][1] = 1
p array1 # => [[nil, 1, nil], [nil, 1, nil]]
上記のとおり、array1[0][1]
を書き換えたいだけなのに、array1[1][1]
が一緒に書き換えられてしまっています。
1行目と2行目が同じ配列を参照しているということをobject_id
で確認してみます。
p array1[0].object_id # => 60
p array1[1].object_id # => 60
Array.new(3)
は1度だけ実行されて、その評価結果(=同じオブジェクト)が各行に入るので、このような結果となります。
各行が独立している配列の生成方法(力技)
実際に問題を解いている時は時間的なこともあり、いい方法をすぐに見つけられなかったので、すぐに思いついた次の方法で配列を生成しました。
array2 = []
2.times{ array2 << []}
array2.each do |val|
3.times{ val << nil }
end
空の配列を作成→さらにその中に空の配列を作成→要素を1つずつ入れていくという流れです。
結果は意図どおりになっていますが、今あらためて見てもイケてない…
というわけで、もう少し綺麗に書きたいという今回の記事に繋がりました。
p array2 # => [[nil, nil, nil], [nil, nil, nil]]
array2[0][1] = 1
p array2 # => [[nil, 1, nil], [nil, nil, nil]]
p array2[0].object_id # => 60
p array2[1].object_id # => 80
各行が独立している配列の生成方法
冒頭の結論に書いているとおり、こちらの方法で生成します。
array3 = Array.new(2){ Array.new(3) }
最初の方法との違いは、2次元目の配列を第2引数ではなく、ブロックで渡していることです。
ブロックは要素ごとに実行され、その評価結果が各行に入る、つまり各行には違うオブジェクトが入るということになります。
(公式リファレンスのここに書かれているので、あわせてご参照ください。)
確認すると、次のとおり意図した結果が出力されます。
p array3 # => [[nil, nil, nil], [nil, nil, nil]]
array3[0][1] = 1
p array3 # => [[nil, 1, nil], [nil, nil, nil]]
p array3[0].object_id # => 60
p array3[1].object_id # => 80
さいごに
私の結論としては「2次元目の配列をブロックで渡す」ということになりました。
「もっといい方法知ってるよ!」「こんな方法もあるよ!」という方がいらっしゃいましたら、ぜひ教えてください!