月初が日曜日の月を数えるメソッドをRubyでテスト駆動開発する

はじめに
---
昨日に引き続き、Project Eulerの問題を解いてみました。
問19を Ruby と Minitest を用いて解いてみたという内容になります。

Project Euler についてはこちらに書いていますので御覧ください。
[数字を英名に変換するメソッドをRubyでテスト駆動開発する](https://qiita.com/hawkingSao/items/c21a360ae8f0e95aaf5f)

開発環境
---
```
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 と記載しました。

```ruby:test_problem.rb
require 'minitest/autorun'
require './problem'

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

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

```ruby:test_problem.rb
assert_equal 1, count_sunday(1900, 1, 1900, 1)
assert_equal 335, count_sunday(1900, 1, 1900, 12)
```
Dateライブラリを読み込み、
開始年月から終了年月までの日数を`count`メソッドで
ただ数え上げてテストを通します。

```ruby:problem.rb
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
```


### 指定された範囲の月数を返すテスト
最終的な答えは**月初が日曜日になる月の数**なので月数を返すようにテストを書き換えます。

```ruby:test_problem.rb
assert_equal 1, count_sunday(1900, 1, 1900, 1)
assert_equal 12, count_sunday(1900, 1, 1900, 12)
```

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

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

```ruby:sample.rb
Date.today.mday
#=17（17日という情報が返る）
```

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

```ruby:problem.rb
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年に変更しています。

```ruby:test_problem.rb
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`という数字が返ります。

```ruby:sample.rb
Date.today.wday
#=6（土曜日という情報が返る）
```

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

```ruby:problem.rb
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`と出すことが出来ました。

```ruby:test_problem.rb
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
```

```ruby:problem.rb
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