Ruby力向上のための基礎トレーニング

Ruby力向上のための基礎トレーニング

hagiyat エンジニアリング

萩谷です。Railsへの移行開始から、はや半年が経とうとしています。
リリースまでは動かすことで手一杯で、メンテナブルなコードにしよう!みたいな観点を持つことは難しかったですが、最近はよりRubyらしく書いていこう!といった取り組みができる素地ができつつあります。
その取り組みの一環として、社内でRuby力向上トレーニングという企画をたてましたので、その紹介をしようと思います。

Railsの知識も大事だけど、まずはRuby力だ!

Ruby力とは何か、という観点ですが、

Rubyの魅力の8割はmapにある

という意見もあることですし、Array/Hash/Stringといったクラスで大活躍するメソッドチェインを使ったロジックを構築する力を養うというのが、Ruby力のひとつだろうと考えました。
それには小さな課題をひとつひとつ解いていき、経験値を積むという、ストイックな筋トレのような取り組みが必要ではないかと。

問題探し

要は計算ドリル的なものがあればいいのですが、がっつり英語で問題の内容を理解するのでヘトヘトとか、充分な数学の予備知識が必要、といったことになると、長く楽しく続けるというのが難しくなってしまうのではないかと思いました。
CodeIQなどの学習のための素晴らしいサービスもありますが、それきっかけでヘッドハントされました、といった事故があったとき、企画者の私の責任を問われかねません。

ですので、こちらを問題集として選ばせていただきました!
AOJ – オンライン プログラミング チャレンジ

問題も豊富だし、日本語での表示も可能です。素晴らしい!!

じゃあ解こう!とはならない

我々は長くPHPをやってきたのです。いきなりメソッドチェインで、というのは脳が追いつきません。
まずは考え方を整理しないといけません。

考え方を整理

こちらの課題を解いていく過程をトレースしてみます。
AOJ – オンライン プログラミング チャレンジ / Matrix-like Computation

以下の表のように、与えられた表の縦・横の値の合計を、それぞれの行・列で求め出力して終了するプログラムを作成してください。

- col1 col2 col3 col4
row1 9 85 92 20
row2 68 25 80 55
row3 43 96 71 73
row4 43 19 20 87
row5 95 66 73 62

こういうのを

- col1 col2 col3 col4 sum
row1 9 85 92 20 206
row2 68 25 80 55 228
row3 43 96 71 73 283
row4 43 19 20 87 169
row5 95 66 73 62 296
sum 258 291 336 297 1182

こうするのが目的になります!

まずはデータが必要

縦横10以下の引数をとって、99までのランダムな数が入った2次元配列を返す処理が必要ですね。
※ただし、10以下という制限は蛇足になるので、当記事では無視させていただきます。

def make_matrix(width = 4, height = 4)
  matrix = []
  (1..height).each do
    row = []
    (1..width).each do
      row << Random.rand(99)
    end
    matrix << row
  end
  return matrix
end

こちら一生懸命どん臭く書きました。でも実際最初はこんなものだったと記憶しています!
これではRubyのmapで云々は一切出てこず、進歩もないので、一旦この処理の目的を整理して、メソッドチェインに仕上げてみることにしましょう。

  • 配列がほしい
  • その配列の中にはwidth分の値の入った配列が、height分入ってる
  • 要素の中には99までのランダムな数が入っている

目的はこれだけですね。だったらそれをそのまま書いてしまいましょう。

def make_matrix(width: 4, height: 4)
  # 要素は全部でwidth * height分ある
  [*1..(width * height)]
    # 99までのランダムな数が入っている
    .fill { Random.rand(99) }
    # heightで分割すれば、width分の要素をもったheight分の配列になる
    .each_slice(height)
    # 配列がほしい/けど、後続処理を考えればここは蛇足
    .to_a
  # このブロックの中での最後の返り値がreturnされるので、明示的なreturnは要らない
end

・・・記事のために都合よく解釈した感はありますが、まあこんな感じの思考で出来上がる感じでしょうか。

計算する

手順はデータ作成のときと同じです。やりたいことを並べて、それをそのまま書く!

  • 行ごとの合計を出す
  • それを行の末尾に追加する
  • 列の合計を出す
  • それを新しい行として追加する
def calcurate(matrix)
  matrix
    .map { |a| a << a.sum } # 行ごとの合計を、末尾に追加
    .transpose # 列の計算のために、行と列を入れ替え
    .map { |a| a << a.sum } # 列ごとの合計を、行の末尾に追加
    .transpose # 計算が終わったので、行と列を元に戻す
end

ポイントは行と列を入れ替えて計算するってところですかね。処理の流れを考えれば、calcurate_columns(map)とcalcurate_rows(transpose/map/transpose)にしても良いかもしれませんねー。 手段を満たすメソッドを探すGoogle力というのが重要です! あとはもうひとつ重要なことがあります。 それは、メソッドの前後の入出力の形をイメージできていること、です!

# matrixが[[1,3,5], [2,4,6]]だとすると
def calcurate(matrix)
  matrix
    # -> [[1,3,5], [2,4,6]]
    .map { |a| a << a.sum }     # -> [[1,3,5,9], [2,4,6,12]]
    .transpose
    # -> [[1,2], [3,4], [5,6], [9,12]]
    .map { |a| a << a.sum }     # -> [[1,2,3], [3,4,7], [5,6,11], [9,12,21]]
    .transpose # 計算が終わったので、行と列を元に戻す
    # -> [[1,3,5,9], [2,4,6,12], [3,7,11,21]]
end

こんな感じです!

表示用に表を加工する

次は表示用の加工です。

  • 4桁でスペースパディングして、右寄せにする
  • そのためには文字列に変換する必要がある
  • 行の各要素は | で結合する
  • 1列ごとに改行
def stringify(matrix)
  matrix
    .reduce('') do |result, a| # 文字列に畳み込む
      result
        << a.map(&:to_s) # 要素を文字列化して
          .map { |v| v.rjust(4) } # スペースパディングして右寄せして
          .join('|') # | で連結した文字列を結果に詰める
        << "\n" # 改行して区切る
    end
end

reduceを使うと、いかにも結果を出している感が出るのでオススメです。Enumerable#each_with_objectの方が推奨されることもあるみたいですね。

表示してみる

# こういうどっちがどっちかわからない!ってなりがちな定義は、キーワード引数がとても便利ですね〜
matrix = make_matrix(width: 5, height: 4)
calclated = calcurate(matrix)
p stringify(calcurated)

完成!まずますのできになったのではないでしょうか!!

まとめ

いかがでしたか?Ruby初心者の方々の、Ruby力向上の一助になれば幸いです!
ポイントをもう一度おさらいすると、

  • 処理の目的を洗い出して、それをそのまま書いてつなげる
  • 手段を満たすメソッドをしっかり検索すること
  • メソッドチェイン間の入出力をしっかりイメージする

ですね。考え方が理解できれば、あとは継続的に問題を解いていけば、個人はもちろん、チームとしてもかなりのレベルアップが図れるんじゃないでしょうか?
では良いRubyライフを〜!