とある問題で「AさんとBさんの予想日、どっちが実際の開花日に近い?」を判定する場面がありました。
私が詰まった点は
“月日 (M/D) のままでは日数差が計算できない” ということでした。
この記事では、月日 → 通し日数(1年の何日目) に変換する最小ロジックと、差分比較の注意点をまとめます。
背景:なぜ「月日」のまま比較できないのか?
例えば、
- 実際:4/1
- 予想:3/29
この差は 3日 です。
でも、もし「401 - 329」みたいに 月日を数値として雑に引き算すると、
- 401 - 329 = 72
となり、当然ズレます。
月が変わると「日付の連続性」が壊れるため、日数差になりません。
解決策:月日を「通し日数」に変換する
月日を 1年の何日目か に変換します。
例:
- 3/29 → 1月31日 + 2月28日 + 29日 = 88日目
- 4/1 → 1月31日 + 2月28日 + 3月31日 + 1日 = 91日目
こうして 整数 にできれば、あとは引き算で差が取れます。
実装:serial(通し日数)関数
# [月,日] -> 通し日数(うるう年は無視)
def serial(m, d)
mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
mdays[1...m].sum + d
end
ポイント
-
mdaysは各月の日数の表-
mdays[1] = 31(1月) -
mdays[2] = 28(2月)…
-
-
mdays[1...m]は「1月〜(m-1)月」を取り出す-
...は 終端を含まない(m月は含めない)
-
-
.sumで前月までの日数合計を出し、最後に+ dで当月の日を足す
Rubyは メソッドの最後の式が戻り値になるので、return を書かなくても動きます(書いてもOK)。
差の計算は「引いてから abs」が正解
実際の開花日 / A予想 / B予想を通し日数にして比較します。
actual = serial(month, date)
a_day = serial(a_m, a_d)
b_day = serial(b_m, b_d)
diff_a = (actual - a_day).abs
diff_b = (actual - b_day).abs
ミス:abs の位置が違う
❌ 間違い
diff_a = actual - a_day.abs
これは「a_day の絶対値」を取っているだけで、
差の絶対値ではありません。
入力値によって期待通りの答えが出ないことで気づけました。
結果、actual < a_day のケースで diff_a が負になり、比較が壊れます。
✅ 正しいのはこれ
diff_a = (actual - a_day).abs
判定(A/B/DRAW)
result =
if diff_a < diff_b
"A"
elsif diff_b < diff_a
"B"
else
"DRAW"
end
最後に、問題の出力形式どおりに出します。
puts "#{month} #{date}"
puts result
例:DRAW になるケースを確認
- 実際の開花日:4/1
- A予想:4/4(差3日)
- B予想:3/29(差3日)
このときは、
diff_a = 3diff_b = 3
なので DRAW になります。
Conclusion
- 月日をそのまま比較すると、月をまたぐときに日数差が壊れる
- 解決は「月日 → 通し日数(1年の何日目)」へ変換すること
- 差分は (actual - predicted).abs の形で、「引いてから abs」が鉄則
- diff が同じなら
DRAW
「日付の近さ」を比べる問題では、この “通し日数化” が最適解でした💯🌸