はじめに
昨日に引き続き、Project Eulerの問題を解いてみました。
問19を Ruby と Minitest を用いて解いてみたという内容になります。
Project Euler についてはこちらに書いていますので御覧ください。
数字を英名に変換するメソッドをRubyでテスト駆動開発する
開発環境
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]
minitest (5.10.3)
問題文
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'
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
参考文献
[1] Project Euler:https://projecteuler.net/problem=19
[2] 日本語訳:http://odz.sakura.ne.jp/projecteuler/index.php?cmd=read&page=Problem%2019
[3] Dateライブラリ:https://docs.ruby-lang.org/ja/latest/class/Date.html
[4] mday:https://docs.ruby-lang.org/ja/latest/class/Date.html#I_DAY
[5] wday:https://docs.ruby-lang.org/ja/latest/class/Date.html#I_WDAY