Ruby手習い(Dateクラス)

アウトプットのネタに困ったらこれ!?Ruby初心者向けのプログラミング問題を集めてみた(全10問) - give IT a try

上記記事のカレンダー作成問題。

出力結果

p-064% ruby calendar.rb
      May 2019
Su Mo Tu We Th Fr Sa
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30

自分で書いたコード

require 'date'

def main
  puts calendar
end

def calendar
  today = Date.today
  title = today.strftime("%B %Y").center(20)
  head = "Su Mo Tu We Th Fr Sa"
  
  last_day = Date.new(today.year, today.month + 1, -1).day
  wday = Date.new(today.year, today.month, 1).wday

  body = "   " * wday
  d = 1

  (1..last_day).each do
    body << d.to_s.rjust(2)
    d += 1
    wday += 1
    body << (wday % 7 == 0 ? "\n" : " ")
  end

  [title, head, body].join("\n") 
end

if __FILE__ == $0
  main
end
  • 文字を表示するだけなので1つ1つのDateインスタンスつくらなくていいやと考えた。
  • あと細かくメソッドに分割するのはYAGNIでやらなくていいやと考えた。

出題者による回答例

require 'date'

class CalendarRenderer
  DAY_LENGTH = 3
  WEEK_LENGTH = DAY_LENGTH * 7

  def initialize(year, month)
    @first_date = Date.new(year, month, 1)
  end

  def to_s
    (header_rows + body_rows).join("\n")
  end

  private

  def body_rows
    split_pattern = /.{#{DAY_LENGTH},#{WEEK_LENGTH}}/
    body_text.scan(split_pattern)
  end

  def body_text
    first_week_offset + calendar_text
  end

  def first_week_offset
    ' ' * @first_date.wday * DAY_LENGTH
  end

  def calendar_text
    last_day = @first_date.next_month.prev_day.day
    rjust_all 1..last_day
  end

  def header_rows
    [month_year, sun_to_sat]
  end

  def month_year
    indent_length = 1
    @first_date.strftime("%B %Y").center(WEEK_LENGTH + indent_length).rstrip
  end

  def sun_to_sat
    rjust_all %w(Su Mo Tu We Th Fr Sa)
  end

  def rjust_all(enum)
    enum.to_a.map{|e| e.to_s.rjust(DAY_LENGTH) }.join
  end
end
  • (ちょっと仕様=インターフェースがちがう)
  • 1日当たりの幅を定数で指定できるようにしてる。
  • 1日から最終日までの文字列をつくってから、カレンダーの幅でsplit → \nでjoin なるほど、これが最大の違い。まず材料をつくって、それを整形するというこっちの処理の方が拡張性高そう。発想できたかったな。

学んだこと

  • ふだんRailsbeginning_of_monthとかつかってるけど、あれRubyの組み込みじゃないんだな。
  • String#centerをはじめて知った。