プログラミング漫遊記

思ったことや、勉強したことをつらつらと。

【Ruby】ボウリングプログラムで気づいたeach_with_objectの便利さ!

フィヨルドブートキャンプではプラクティスとしてボウリングのスコア計算プログラムを作成します。提出物に合格をもらった後、他の受講生のコードを読んでいて初めて知ったメソッドにeach_with_objectというメソッドがあります。これはめちゃくちゃ便利じゃね?(自分が知らなかっただけ)と思った次第ですので、忘備録として、また学習ノートとしてeach_with_objectの使い方を記しておきたいと思います。フィヨルド生には若干のネタバレにはなるかもしれませんが、あんまり影響しないよう断片的なコードにしています。

ボウリングのスコア計算プログラムとは

実行例は以下の通りターミナルでコマンドラインの引数としてカンマ区切りのスコアを渡すと得点が出力されます。

$ ./bowling.rb '6,3,9,0,0,3,8,2,7,3,X,9,1,8,0,X,6,4,5'
139

上記でストライクは'X'として表されています。またスペアは3,7などの数字で合計10になるときです。 また、ボウリングはスコアの計算方法が改正されていますが、今回は現行のカレントフレームスコア方式ではなく、旧のスコア計算方法で実装しています。

詳しくはコチラ:ボウリングの新スコア計算方法!カレントフレームスコアシステムで遊んでみたよ

自分が書いたコード

def convert_to_value(score)
  shots = []
  score.split(',').each do |shot|
    if shot == 'X' && shots.size < 18
      shots << 10 << 0
    elsif shot == 'X'
      shots << 10
    else
      shots << shot.to_i
    end
  end
  shots
end

上のコードで記述したメソッドは引数で'6,3,9,0,0,3,8,2,7,3,X,9,1,8,0,X,6,4,5'といった文字列のスコアを受け取る想定をしています。 カンマで区切られている数字やマークを数値に変換し、一つの配列として出力しています。そのとき最終フレーム以外のストライクのマークX10,0として処理し、最終フレームのストライクのマーク'X'は単に10としています。

実際に引数を渡してみると以下のようになります。

convert_to_value('6,3,9,0,0,3,8,2,7,3,X,9,1,8,0,X,6,4,5')
=>  [6, 3, 9, 0, 0, 3, 8, 2, 7, 3, 10, 0, 9, 1, 8, 0, 10, 0, 6, 4, 5]

each_with_indexを使って書くと

def convert_to_value(score)
  score.split(',').each_with_object([]) do |shot, shots|
    if shot == 'X' && shot.size < 18
      shots << 10 << 0
    elsif shot == 'X'
      shots << 10
    else
      shots << shot.to_i
    end
  end
end

さて、何が変わっているでしょうか

- shots = []
- score.split(',').each do |shot|

+ score.split(',').each_with_object([]) do |shot, shots|
  • 【変更前】空の配列を用意し、条件に合うものをその配列に突っ込んでいました。

  • 【変更後】each_with_objectを使ったことにより、引数に空の配列を渡すことで、ブロック変数の第二引数であるshotsにセットされます。

- shots
end

また【変更前】は最後にshotsを記述することで配列shotsを戻り値としていたのですが、【変更後】はeach_with_objectの戻り値がshotsなためコードがスッキリしました。

まとめ

空の配列(ハッシュ)を用意して、何らかの条件によって配列に入れていくような操作はeach_with_objectが使える。またメソッドの戻り値は引数のオブジェクトになる。

つまり、配列の用意と戻り値で2行ほどお得!(ちょっと俗っぽい言い方)

参考:https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/each_with_object.html