はじめに
Rubyおよびアルゴリズムの学習の一環として競技プログラミングに参加しています。
ここでは、学習の中で学んだことをアウトプットしています。
今回は二次元配列の作り方について。
第三回アルゴリズム検定第二問ダイナミック・スコアリングにおいて、
二次元配列を使いたい場面が出てきました。
ここで少し手こずったので、復習の為にまとめておきます。
やりたいこと
以下のようなことがやりたくて、二次元配列を作ろうと思いました。
例えば、
ある試験に3名の参加者(1,2,3)が参加する。
その試験では、3問の問題が出題される。
参加者ごとにそれぞれの問題に解答出来たかどうかを管理する。
イメージは下記のような感じです。
# ①こんな感じの二次元配列を作っておく
[[0, 0, 0],[0, 0, 0],[0, 0, 0]]
# ②参加者1が1問目に正解したら、該当の要素に1をいれる
[[1, 0, 0],[0, 0, 0],[0, 0, 0]] #第一要素の配列の最初の要素に 1
# ③次に参加者3が2問目に正解したら、該当の要素に1をいれる
[[1, 0, 0],[0, 0, 0],[0, 1, 0]] #第三要素の配列の2番目の要素に 1
①最初にやったこと(間違い)
ary = Array.new(3, Array.new(3, 0))
print ary
# => [[0, 0, 0],[0, 0, 0],[0, 0, 0]]
見た目としては作りたいものが出来ているのですが、
解答の記録を入れようとすると
ary[0][0] = 1
print ary
# => [[1, 0, 0],[1, 0, 0],[1, 0, 0]]
第一要素の配列のみに変更を加えているつもりが、全ての配列に影響を与えてしまいました。
②mapメソッドを使って(正解!)
ary = Array.new(3).map{Array.new(3,0)}
print ary
# => [[0, 0, 0],[0, 0, 0],[0, 0, 0]]
ary[0][0] = 1
print ary
# => [[1, 0, 0],[0, 0, 0],[0, 0, 0]]
結果としてはmapメソッドを絡めることで、無事イメージ通りの配列を作ることが出来ました。
何が違うのか
上記の①、②の二次元配列は、見た目は完全に同じですが、どうやら全く違う性質を持つようです。
何が違うのかを考えていきます。
Arrayクラスのnewメソッド ①について
リファレンスには下記のように書かれています。
new(size = 0, val = nil) -> Array
長さ size の配列を生成し、各要素を val で初期化して返します。
要素毎に val が複製されるわけではないことに注意してください。
全要素が同じオブジェクト val を参照します。
ary = Array.new(3, "foo")
p ary #=> ["foo", "foo", "foo"]
ary[0].capitalize!
p ary #=> ["Foo", "Foo", "Foo"]
この例では、各要素の"foo"は全て同じオブジェクトであることが示されています。
説明文通り全ての要素が同じオブジェクト val になるということです。
となると、①で作った二次元配列は以下のようなイメージでしょうか。
配列の中では要素が3つに分かれているように見えても、結局は同じひとつのオブジェクトを参照しているだけ。
どの要素に変更を加えようが、変更されるのは同じオブジェクト、ということのようです。
こんなイメージでしょうか。
mapメソッド ②について
リファレンスには下記のように書かれています。
map -> Enumerator
map {|item| ... } -> [object]各要素に対してブロックを評価した結果を全て含む配列を返します。
ブロックを省略した場合、上で説明した繰り返しを実行し、
その結果として得られる配列を返すような Enumerator オブジェクトを返します。
# すべて 3 倍にする
p [1, 2, 3].map {|n| n * 3 } # => [3, 6, 9]
mapメソッドは、eachメソッドのように配列オブジェクトから要素をひとつずつ取り出し、
ブロック{}内の処理を行った結果を配列として返します。
ということは、②で作った二次元配列は以下のようなイメージになるでしょうか。
各要素に対して別個に配列の生成を行うため、それぞれ独立したオブジェクトとして存在している。
だから、別個に変更を加えることが出来る、ということのようです。
最後に
以上、二次元配列の作り方でつまずいたポイントについて、自分なりにまとめてみました。
リファレンスなどを読みながらまとめたつもりですが、
もし間違いなどございましたら、ご指摘いただけると嬉しいです。