多次元配列確保でハマり!
どこかのサイトでみて、「お、かっこいい!」とか思った多次元配列の初期化をやっていて、ハマった・・。
そのコードはこちら↓
# 多次元配列確保したい. height = 24 width = 32 array = Array.new( height, Array.new( width, 0 ) )
C言語とかでいうところの、 int array[height][width]; みたいな配列を確保して、中身はすべて0で初期化しようとしたのです。見た瞬間、「さすがRuby!1行できれいに初期化コードが書ける!」と真似してみたのです。
でもこのコードでは、今回意図している動作はしないのだ!
このコードを使うと、例えば、次のようなことになってしまうのです。
# 意図した挙動とは..と確認するコード. height = 24 width = 32 array = Array.new( height, Array.new( width, 0 ) ) # test. p array[0][0] #=> 0 p array[10][0] #=> 0 array[0][0] = 99 p array[0][0] #=> 99 p array[10][0] #=> 99 .. ここが意図どおりで無い.
まぁ、普通に参照とかに慣れている人ならすぐに気づくのかもしれない。しかし、ヘッポコなオレは1時間以上もはまってしまった。まぁ、最初にめぼしをつけてたところが間違っていたし、上記サンプルコードみたいな短いコードじゃないからだけど。。
そんな言い訳はいらないですか。ソーデスカ。
で、なぜダメなのか、それは、2次元目の配列要素がすべて同じ一つの配列を参照してるからなのです。(=各1次元目配列要素ごとにnewしてくれているわけではない)ガックシ・・orz
で、具体的にどうやればいいのか?は以下の通り。ちょっと画像をイメージして変数名を変えてみました。
# timesメソッドで多次元配列確保. height = 24 width = 32 image = Array.new( height, nil ) # 各要素は、とりあえずnilで初期化. image.length.times{ |y| # 要素の数だけ.. image[y] = Array.new( width, 0 ) # 各要素について、new. }
image.length.times がかっこ悪いという人には、以下の方法もある。
# each_indexメソッドで多次元配列確保. height = 24 width = 32 image.each_index{ |y| # Array.each_index メソッドを使ってみてもOK. image[y] = Array.new( width, 0 ) }
もうちょっとかっこよく、という人にはmap!メソッドがいいかもしれない。
# map!メソッドで多次元配列確保. height = 24 width = 32 image = Array.new( height ).map!{ Array.new( width, 0 ) }
ちなみに、似ているようだけど、以下のコードはNGです。
# きっとあなたが意図しない挙動をするコード. height = 24 width = 32 image = Array.new( height ).each{ |row| # eachでは? row=Array.new( width, 0 ) }
row は各要素のコピーであって、参照ではないのです。(テストしたらそうなったので、おそらくそうであろうという話ですが。