3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-11-17

はじめに

昨日に引き続き、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 と記載しました。

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
problem.rb
def count_sunday(start_year, start_month, end_year, end_month)
  0
end

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

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

test_problem.rb
assert_equal 1, count_sunday(1900, 1, 1900, 1)
assert_equal 335, count_sunday(1900, 1, 1900, 12)

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

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

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

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

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という数字が返ります。

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

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

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年に変更しています。

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という数字が返ります。

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

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

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と出すことが出来ました。

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
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

3
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?