はじめに
『先輩の誕生日はいつですか?』
『私?7月19日。平成4年。』
『日曜日です。』
言わずと知れたサマーウォーズの名シーン。
健二くんが夏希先輩の誕生日をYYYYMMDDから導く、健二くんのヤバさが如実に現れていますね。
しかも自然に好きな子の誕生日を聞き出す高等テク。脱帽です。
これをPythonで再現する、そして暗算でもできるようになることが目的です。
(思いつきで書いた記事ですが先駆者がいました。こちらも参考にさせていただきます。)
Datetime を使う方法
先駆者様の引用。datetimeライブラリを使用する方法です。
import sys
from datetime import *
from time import *
def main():
print('先輩の誕生日いつですか?')
year = input('年:')
month = input('月:')
day = input('日:')
week = ['月','火','水','木','金','土','日']
input_date = year + '/' + month + '/' + day
date = datetime.strptime(input_date,'%Y/%m/%d')
print(week[date.weekday()]+'曜日です。');
print(date.date().strftime("%Y年%m月%d日は") + week[date.weekday()]+"曜日です。");
if __name__ == '__main__':
main()
# python natsuki_sen.py
先輩の誕生日いつですか?
年:1992
月:7
日:19
日曜日です。
1992年7月19日は日曜日です。
1996年2月29日のような、うるう年もきちんと計算してくれました。
存在しない日付を入力するとエラーになるので気になる人はエラー処理を入れましょう。
ツェラーの公式
ここからが本題。
まずはy年m月d日から曜日hを求めるツェラーの公式を見てみよう。
\begin{align}
h &= \left( d + \left\lfloor \frac{26(m+1)}{10} \right\rfloor + y + \left\lfloor \frac{Y}{4} \right\rfloor+ \Gamma \right) \\
\\
Y &= y \bmod 100
\end{align}
ここで1月と2月はそれぞれ前年の13月14月として扱う。
例えば1994年1月であれば1993年13月とする。
Γはグレゴリオ暦かユリウス暦で変わる変数らしいが、友達の誕生日ならばグレゴリオ暦なので特に気にする必要はありません。その場合、
\Gamma =-2 \times \left\lfloor \frac{y}{100} \right\rfloor + \left\lfloor \frac{1}{4} \times \left\lfloor \frac{y}{100} \right\rfloor \right\rfloor
となります。hの値により、曜日を割り出します。
| h | 1 | 2 | 3 | 4 | 5 | 6 | 0 |
|---|---|---|---|---|---|---|---|
| 曜日 | 日 | 月 | 火 | 水 | 木 | 金 | 土 |
んで、プログラムは?
小難しい話はここまでにして、Pythonでプログラムを作ります。
先駆者様のプログラムを参考にしつつ、誤りを修正しました。
こちらが正しいです(多分)。
# coding:utf-8
from decimal import *
if __name__ == '__main__':
print('先輩の誕生日いつですか?')
year = int(input('年:'))
month = int(input('月:'))
day = int(input('日:'))
# 1, 2月の場合は前年の13月、14月として扱う。
if month <= 2:
month = month + 12
year = year - 1
year = str(year)
C = Decimal(int(year[0:2]))
Y = Decimal(int(year)%100)
F = day + (26*(month+1)/10) + int(Y) + int(Y/4) - 2*int(C) + int(C/4)
X = (F % 7)
week = ['土','日','月','火','水','木','金']
print(week[int(X)]+'曜日です')
# 13, 14月の場合はもとに戻す。
if month >= 13:
month = month - 12
year = str(int(year) + 1)
print(str(year) +'年'+str(month) + '月'+ str(day) +'日は'+ str(week[int(X)])+'曜日でした。')
グレゴリオ暦である限りこれで誕生日の曜日がわかります。
暗算でやりたい!
ここまで読むと健二くんヤベー!と思いますが実際はそうでもないという説を唱えます。
実際、慣れればそれなりに高速でできるようになると思いますのでその方法をお伝えします。
結論
友達の誕生日であれば1900年代が2000年代のどちらかだと思うので、めんどくさい計算を大幅にカットできます。
1.下の表を覚える
| 月 | 1月 | 2月 | 3月 | 4月 | 5月 | 6月 | 7月 | 8月 | 9月 | 10月 | 11月 | 12月 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 3 | 3 | 6 | 1 | 4 | 6 | 2 | 5 | 0 | 3 | 5 |
2. 西暦の下2桁を4で割った商を覚える
例: 1995年なら95÷4=23.75なので23を覚えておく。
3. 足し算して7で割る
step1. 次の計算をする。
西暦の下2桁 + 手順2の数 + 手順1の数 + 誕生日の日付
例えば1995年5月22日なら、
95 + 23 + 1 + 22 = 141となる。この数字(Aとする)を覚えておく。
step2. うるう年の1,2月ならAから1を引く
step3. 1900年代ならAに1を足す
step4. Aを7で割ったあまりを出す
例1:
1995年5月22日なら、
A = 95 + 23 + 1 + 22 = 141
1900年代なので+1して142。7で割るとあまりは2となり、答えは月曜日となります。
例2:
2000年2月1日なら、
A = 0 + 0 + 3 + 1 = 4
うるう年の2月なので-1して3。
7で割るとあまりは3となり答えは火曜日となります。
計算の工夫
2000年代の場合そんなに計算は難しくありません。
1900年代の場合数が大きくなる場合があるため、暗算が大変です。
そこでmodの性質を利用しましょう。
上述の1995年5月22日について考えてみます。
下2桁の95を4で割ると23あまり3。
95+23は...とここで足すのではなく、どうせ後で7で割ったあまりを出すのですから今あまりを出してしまいましょう。詳しくは高校数学を復習してみてくださいね。
95を7で割ったあまり = (95-70)を7で割ったあまりなので25割る7のあまりで4。
23を7で割ったあまり = 2。
4+2=6の6を覚えておきましょう。
あとはさっきの計算の, 95 + 23の部分を6に置き換えればいいです。
A = 6 + 1 + 22 = 29
1900年代なので+1して30。
30を7で割って、4あまり2となり、先程と同じ結果になります。
最後に7で割ってあまりを出すのだから、7の倍数の数はちょっとずつ引いていくというのが計算のコツです。
健二くんは天才なのか?
健二くんは夏希先輩の年齢は知っていたでしょうから、あらかじめ西暦の部分だけは計算していた可能性はありますね。
- 夏希先輩は1992年か1993年生まれ
- 1992年の場合に備えて (92 + 92/4)を7で割ったあまりである3を覚えておく
- 1993年の場合に備えて(93 + 93/4)を7で割ったあまりである4を覚えておく
- 『1992年7月19日』と聞いたので、7月に対応する6と、1900年代の場合の1を加えて3+6+19+1 = 29
- 29を7で割るとあまりは1なので日曜日とわかる
これでだいぶシンプルになりました。
相手が同級生だったり、年齢がわかっていれば前もって計算しておくことで一目置かれる存在になれるかもしれません。