月初が日曜日の月を数えるメソッドをテスト駆動開発する(Project Euler 問19)

スポンサーリンク

今回の問題

Counting Sundays

You are given the following information, but you may prefer to do some research for yourself.

1 Jan 1900 was a Monday.
Thirty days has September,
April, June and November.
All the rest have thirty-one,
Saving February alone,
Which has twenty-eight, rain or shine.
And on leap years, twenty-nine.
A leap year occurs on any year evenly divisible by 4, but not on a century unless it is divisible by 400.
How many Sundays fell on the first of the month during the twentieth century (1 Jan 1901 to 31 Dec 2000)?

次の情報が与えられている.

1900年1月1日は月曜日である.
9月, 4月, 6月, 11月は30日まであり, 2月を除く他の月は31日まである.
2月は28日まであるが, うるう年のときは29日である.
うるう年は西暦が4で割り切れる年に起こる. しかし, 西暦が400で割り切れず100で割り切れる年はうるう年でない.
20世紀(1901年1月1日から2000年12月31日)中に月の初めが日曜日になるのは何回あるか?

方針

うるう年かどうかによって2月の日数を場合分けしないといけないですが、
Dateライブラリを用いれば、その点を考慮せずに済むのので利用する方針で考えました。

書いた内容

テストの作成と仮メソッド定義

Minitest を用いて、4引数をもつcount_sundayメソッドのテストを仮で記載しました。
まずはテストが通るようにメソッド自体も返り値を直接 0 と記載しました。

require 'minitest/autorun'
require './problem'

require 'minitest/reporters'
Minitest::Reporters.use!

class ProblemTest < Minitest::Test
  def test_count_sunday
    assert_equal 0, count_sunday(1900, 1, 1900, 1)
  end
end
def count_sunday(start_year, start_month, end_year, end_month)
  0
end

指定された範囲の日数を返すテスト

引数で指定された開始年月から終了年月までの日数が返ることを
期待するようにテスト内容を変更してテストを落とします。

assert_equal 1, count_sunday(1900, 1, 1900, 1)
assert_equal 335, count_sunday(1900, 1, 1900, 12)

Dateライブラリを読み込み、
開始年月から終了年月までの日数をcountメソッドで
ただ数え上げてテストを通します。

require 'date'

def count_sunday(start_year, start_month, end_year, end_month)
  Date.new(start_year, start_month).upto(Date.new(end_year, end_month)).count
end

指定された範囲の月数を返すテスト

最終的な答えは月初が日曜日になる月の数なので月数を返すようにテストを書き換えます。

assert_equal 1, count_sunday(1900, 1, 1900, 1)
assert_equal 12, count_sunday(1900, 1, 1900, 12)

メソッドもテストが通るようにするために、
レシーバの日付情報を返すmdayメソッドを利用しました。

例えば2018年11月17日に対して、
mdayメソッドを用いると17という数字が返ります。

Date.today.mday
#=> 17(17日という情報が返る)

これを利用してmdayが1と等しい、
つまり月初に該当するデータだけを
selectメソッドで配列化して数え上げてテストを通します。

def count_sunday(start_year, start_month, end_year, end_month)
  Date.new(start_year, start_month).upto(Date.new(end_year, end_month)).select { |day| day.mday == 1 }.count
end

月初が日曜日の月だけカウントするテスト

月初が日曜日の月だけカウントするようにテストを変更しましたが、
1900年の日曜日を調べるのが手間だったので一部を2018年に変更しています。

assert_equal 0, count_sunday(1900, 1, 1900, 1)
assert_equal 2, count_sunday(2018, 1, 2018, 12)

メソッドもテストが通るようにするために、
今度はレシーバの曜日情報を返すwdayメソッドを利用しました。
これは日曜日を0として土曜日6までの数字が返すメソッドです。

例えば2018年11月17日に対して、
wdayメソッドを用いると6という数字が返ります。

Date.today.wday
#=> 6(土曜日という情報が返る)

これを利用してwdayが0である、
つまり月初であり日曜日であるデータだけを
selectメソッドで配列化して数え上げてテストを通します。

def count_sunday(start_year, start_month, end_year, end_month)
  Date.new(start_year, start_month).upto(Date.new(end_year, end_month)).select { |day| day.mday == 1 && day.wday.zero? }.count
end

最終的なコード

つくったプログラムを利用して、最終的な答えは 171 と出すことが出来ました。

require 'minitest/autorun'
require './problem'

require 'minitest/reporters'
Minitest::Reporters.use!

class ProblemTest < Minitest::Test
  def test_count_sunday
    assert_equal 0, count_sunday(1900, 1, 1900, 1)
    assert_equal 2, count_sunday(2018, 1, 2018, 12)
  end
end
require 'date'

def count_sunday(start_year, start_month, end_year, end_month)
  Date.new(start_year, start_month).upto(Date.new(end_year, end_month)).select { |day| day.mday == 1 && day.wday.zero? }.count
end

puts count_sunday(1901, 1, 2000, 12)
#=> 171

※問19ブランチのプルリクエスト

          
    

スポンサーリンク

  
Scroll Up