Rubyの二次元配列

これは「フィヨルドブートキャンプ Part 1 Advent Calendar 2020」の7日目の記事です。

adventar.org

昨日は、Sakiさんの 「初めて輪読会をやってみたらいいことばかりだったので、良かったことをまとめました 」でした。
Part. 2 もあります。

adventar.org

私はWebエンジニアに転職したいと思い、現在フィヨルドブートキャンプで学んでいます。

bootcamp.fjord.jp

そのフィヨルドブートキャンプで、Advent Calendarの企画が立ち上がりました。
私はAdvent Calendarに参加するのは初めてなのですが、アウトプットを現在意識しているため書いてみることにしました。

とはいえ、Rubyのプラクティスに入ったばかりで何を書いたらいいのか迷いました。
先日、Unixのcalコマンド(カレンダー表示)をRubyで実装するプラクティスを行った際に、Rubyにおける二次元配列について少し調べたので、そのことを書くことにしました。
カレンダーつながりですね!☺️
Ruby初心者ですので、暖かい目で読んでいただけると助かります。

一次元配列

まず、一次元配列から考えます。
(参考:「スラスラ読める Rubyふりがなプログラミング」「ゼロからわかる Ruby 超入門」)

配列は要素として複数のオブジェクトをまとめて扱うクラスです。
オブジェクトとして、数値や文字列を扱います。
そして、配列自身もまた、オブジェクトです。
オブジェクトの種類のことをクラスと言います。

作成

配列を作成します。
wdays = ["月", "火", "水", "木", "金", "土", "日"]

要素を取得する

配列内の要素を取得する時は、インデックス(添え字)を使います。
puts wdays[2]
とすると
f:id:keikosmile:20201206073346p:plain
と表示されます。

配列全体を表示する時は、
puts wdays
とすると、
f:id:keikosmile:20201206074720p:plain
と各要素が縦に表示されます。

要素書き換え

配列の要素を書き換える時は、
wdays[1] = "雨"
puts wdays
とすると
f:id:keikosmile:20201206103907p:plain
と配列の要素が変わっています。

その他のメソッド

配列はArrayというクラスの一つで、元々色々便利なメソッドが備わっています。
(参考 Ruby 2.7.0 リファレンスマニュアル>ライブラリ一覧>組み込みライブラリ>Arrayクラス)

Arrayクラスの主なメソッド

メソッド 働き
配列.first 配列の最初の要素を返す
配列.last 配列の最後の要素を返す
配列.push(要素) 配列の末尾に要素を追加する
配列.unshift(要素 配列の先頭に要素を追加する
配列.delete(値) 配列から値と一致する要素を全て削除する
配列.shift 配列の先頭から要素を1つ削除し、削除した要素を返す
配列.pop 配列の末尾から要素を1つ削除し、削除した要素を返す
配列1 + 配列2 2つの配列の要素をつなげた配列を作る
配列1 - 配列2 配列1から配列2の要素を削除した新しい配列を作る
配列.size 配列の要素数を返す
配列.sum 配列の全要素の合計を返す
配列.sample 配列からランダムに要素を取得する
配列.shuffle 配列をランダムに並び換える
配列.sort 配列の要素を順に並び換える(数値:小さい順、文字列:abc順)
配列.sort.reverse 配列の要素の並びが逆順になる(数値:大きい順、文字列:cba順)
配列.join 配列中の文字列を連結する

初めから色々揃っていて便利ですね!

例1:deleteメソッド

例えば、deleteメソッドを使ってみます。
wdays.delete("雨")
puts wdays
f:id:keikosmile:20201206111331p:plain
"雨"が消えています!

例2:revereseメソッド

reverseメソッドがややこしかったので詳しく書きますと、
① 要素が数値の場合
p [4, 2, 8].sort

f:id:keikosmile:20201206165224p:plain
p [4, 2, 8].sort.reverse

f:id:keikosmile:20201206165435p:plain
p[4, 2, 8].reverse

f:id:keikosmile:20201206165558p:plain
reverseだけだと、先頭だけ逆順になり、全体はうまく並ばないので注意が必要です。
reverseだと、ただひっくり返った逆順になります。
(指摘していただき訂正しました!  2020.12.08)

② 要素が文字列の場合
p ["midori", "aka", "kiiro"].sort
f:id:keikosmile:20201206183858p:plain

p ["midori", "aka", "kiiro"].sort.reverse
f:id:keikosmile:20201206184010p:plain

p["midori", "aka", "kiiro"].reverse
f:id:keikosmile:20201206184126p:plain
reverseだけだと、同様に、先頭だけ逆順になり、全体はうまく並ばないので注意が必要です。
reverseだと、同様に、ただひっくり返った逆順になります。
(指摘していただき訂正しました! 2020.12.08)

例3:joinメソッド

joinメソッドは、配列の各要素をつなげるだけでなく、つなげるときに間へ入れる文字を指定できます。
puts ["空", "海", "太陽"].join("と")
f:id:keikosmile:20201206213444p:plain

二次元配列

配列は複数のオブジェクトを要素として扱うことができる。
配列自身もまたオブジェクトである。
それなら、配列の中で配列を要素として持てるはず…。

ということで、Rubyでは二次元配列も可能になっています。


しかし、どうやって作ればいいのだろう…。
calendar = [["1月1日", "1月2日", "1月3日"], ["2月1日", "2月2日", "2月3日"], ["3月1日", "3月2日", "3月3日"]]
とすれば初期化された二次元配列を作成することができ、

puts calendar[0][2]
puts calendar[2][1]
f:id:keikosmile:20201206220648p:plain
とすれば、各要素を表示することがわかりました。


でも、宣言はどうやってするのだろう...?
色々調べた結果、配列の宣言と初期化はnewメソッドを使えばできるようです。
calendar = Array.new(3, " 月 日")
calendar[2] = "2月1日"
puts calendar
f:id:keikosmile:20201206224734p:plain
できています!


それでは、二次元配列は、配列の中に要素として配列を持つのだから、こう書けばいいのかしら…
calendar = Array.new(3, Array.new(3, 0))
p calendar
calendar[0][1] = 2
puts calendar
f:id:keikosmile:20201206225139p:plain
…残念。初期化はうまくいっていますが、[0][1]要素に代入したことにより、[0][1]、[1][1]、[2][1]全ての要素が変化しています。
この書き方だと、[0]、[1]、[2]の要素が全て同じ配列オブジェクトを参照するようになっているようです。
(参照:Rubyで二次元配列の初期化


それでは、どう書けばいいかというと、結論は、
calendar = Array.new(3) { Array.new(3, 0) }
とし、Array.newにブロックを渡せば、0で初期化された3x3の二次元配列を作成することができるとわかりました。
p calendar
calendar[0][1] = 2
puts calendar
f:id:keikosmile:20201206230357p:plain
初期化がうまくいっていて、代入した後も、[0][1]要素しか変化していません!

 Rubyの標準ライブラリには多次元配列はなく、配列をネストすることで代用できます。特定の値で初期化するには、ブロック付きで「Array#new」を使用します。
 ただし、ネストした配列は代用品に過ぎず、範囲を超えたインデックスに代入したときに問題があります。本書では「擬似多次元配列」と呼ぶことにします。
 数値計算が目的ならばNArrayクラスを、行列計算が目的ならばMatrixクラスやVectorクラスを使用しましょう。
(参考:「改訂2版 Ruby逆引きハンドブック」)


ここまでたどりつくのが結構大変でしたので、ブログ記事に残すことができて良かったです!