プレミアムデー

私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「プレミアムデー」(CodeIQ)を参照してください。

問題の概要

問題を引用します。
プレミアムデー問題
その月の任意の曜日の最後の日付をプレミアムデーとします。
年月と曜日を表す数値を入力としてうけとり、プレミアムデーをYYYYMMDD形式で出力するプログラムを作成してください。

標準入力
  • 年, 月, 曜日を表す数値がカンマ区切りで入力されます
  • 年は4桁の数値です。入力される範囲は 2000-2100 です。
  • 月は1-2桁の数値です。入力される範囲は 1-12 です。
    例えば1月は1として入力されます。01として入力されることはありません。
  • 曜日を表す数値は以下の対応関係になっています。
    0: 日曜日
    1: 月曜日
    2: 火曜日
    3: 水曜日
    4: 木曜日
    5: 金曜日
    6: 土曜日
例(2017年3月の金曜日)
2017,3,5

標準出力
  • YYYYMMDD 形式で出力する
  • MM 月は必ず2桁で0詰して表示する
    1月: 01
    10月: 10

例(入力の例に対する出力の例)
20170331

その他の仕様
・標準入力の末尾には改行があります
・標準出力の末尾に改行をつけてください
・存在しない日付の入力はありません
・標準入力の仕様で説明した内容以外の入力は行われません(不正入力に対するチェックは不要)
・暦はグレゴリオ暦を扱うものとします

Samples
Sample1
標準入力
2013,12,5

標準出力
20131227

Sample2

標準入力
2012,6,0

標準出力
20120624

Sample3

標準入力
2017,2,2

標準出力
20170228

私のプログラム

Rubyで解答しています。

#!/usr/bin/ruby
require 'date'


def solve(y, m, wd)
	# 月ごとの日数
	#             1   2   3   4   5   6   7   8   9  10  11  12
	days = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
	d = days[m]

	# うるう年
	d += (1 / (y % 4 + 1)) * (1 - 1 / (y % 100 + 1)) + (1 / (y % 400 + 1)) if m == 2

	lwd =  Date.new(y, m, d).wday
	x = (lwd >= wd) ? wd - lwd : wd - lwd - 7

	return "%d%02d%02d"%[y,m,d+x]
end

# main
while line = gets
	line.strip!
	if line.empty? then next end

	input = line.split(",").map{|a| a.to_i}
	puts solve(input[0], input[1], input[2])
end

解説

この出題者の問題としては少し変わっていてやや難しい目です。
とはいえうるう年だけの問題で大したことはありません。

考え方

指定された年月の最後の日を求め、その曜日から指定された曜日を計算すれば良いだけです。

main

入力値を年,月,曜日に分けてsolve()に渡し、結果を印字します。

solve(y, m, wd)

引数yは年、mは月、wdは曜日です。

yとmからその年のその月の最後の日を求めます。うるう年以外の月ごとの日数を定数daysにしておき、2月の場合だけうるう年かどうかを判断してうるう年なら1足します(12行目)。12行目の式は「うるう年 ワンライナー」とかで検索すれば見つかる式です。

指定された年月の最後の日がわかったら、最後の日からその曜日を求めます。何も考えずDateクラスを使って曜日を求めています。
15行目は最後の日から何日さかのぼれば指定の曜日になるかを計算しています。指定の曜日が月の最後の曜日以上の場合、単純に指定の曜日から最後の曜日を引けばさかのぼるべき日数がわかります(例:最後の曜日が3(水曜日)で指定が0(日曜日)なら-3日)。指定の曜日が月の最後の曜日未満の場合、指定の曜日から最後の曜日を引き、さらに-7するとさかのぼるべき日数になります(例:最後の曜日が3(水曜日)で指定が5(金曜日)なら-5日)。

後は書式をフォーマットして文字列として返却します。

雑感

最初は頭悪く最後の日付からループでさかのぼりながら指定の曜日を探そうかと思いましたが、あまりに格好悪すぎるので計算にしました。