Edited at

「シェリルの誕生日」の問題作成

More than 1 year has passed since last update.

年始に作成した『シェリルの誕生日』の処理。

https://qiita.com/hnkyi/items/dbd937a3e11ef7682804

ふと「逆も行けるんじゃね?」と思って、入力された誕生日から問題となる候補日を作成してみました。


create_question_in_sheryl.py

"""

『シェリルの誕生日』の問題作り

- 「m/d形式」で誕生日を受け取り、問題を作成
- 誕生日を基点に「前後4ヶ月、左右6日」を取得
- 「ユニークな日を持つ月」を選定
- ユニークな日が誕生日の場合、バーナードが初見で正解できてしまう
- 「重複する日を持つ月」を選定
- 重複する日が誕生日の場合、アルバートの言葉を聞いてもバーナードは特定できない

"""

import sys
import random
import re
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta

# 引数チェック
if len(sys.argv) < 2 or not re.search(r"^[0-9]{1,2}/[0-9]{1,2}$", sys.argv[1]):
print("usage: python create_question_in_sheryl.py mm/dd")
exit()

birthday = datetime.strptime(sys.argv[1], "%m/%d")
birth_month = birthday.strftime("%m")
birth_day = birthday.strftime("%d")

# 前後4ヶ月、左右6日のテーブル作成
months = []
start_month = random.choice(list(range(-3, 1)))
for add_val in range(start_month, start_month + 4):
month = birthday + relativedelta(months=add_val)
months.append(month.strftime("%m"))
days = []
start_day = random.choice(list(range(-5, 1)))
for add_val in range(start_day, start_day + 6):
day = birthday + timedelta(days=add_val)
days.append(day.strftime("%d"))

# ユニークな日を選定
unique_days = []
while len(unique_days) < 2:
choice = random.choice(days)
if choice != birth_day and choice not in unique_days:
unique_days.append(choice)

# ユニークな日を持つ月を選定
unique_months = []
while len(unique_months) < 2:
choice = random.choice(months)
if choice != birth_month and choice not in unique_months:
unique_months.append(choice)

# 重複する日を持つ月を選定
duplicate_month = [month for month in months if month != birth_month and month not in unique_months][0]

# 重複させる日を選定
duplicate_day = ""
while True:
choice = random.choice(days)
if choice != birth_day and choice not in unique_days:
duplicate_day = choice
break

# XXX 20180627 modified ダミー日を使うよう修正
# # 重複させない日を選定
# unduplicate_days = []
# while len(unduplicate_days) < 2:
# choice = random.choice(days)
# if choice != duplicate_day and choice not in unique_days and choice not in unduplicate_days:
# unduplicate_days.append(choice)
#
# # 重複する日を持つ月を選定
# duplicate_month = [month for month in months if month != birth_month and month not in unique_months][0]
#
# # ユニークでない日を選定
# ununique_days = [day for day in days if day not in unique_days and (day == duplicate_day or day in unduplicate_days)]
dummy_days = {birth_day: 1}
for dummy_day in [day for day in days if day not in unique_days and day != birth_day and day != duplicate_day]:
dummy_days[dummy_day] = 2

# 候補日を作成
candidates = []
candidates.append("{}/{}".format(birth_month, birth_day))
candidates.append("{}/{}".format(birth_month, duplicate_day))

candidates.append("{}/{}".format(duplicate_month, duplicate_day))
# XXX 20180627 modified ダミー日を使うよう修正
# for d in unduplicate_days:
# candidates.append("{}/{}".format(duplicate_month, d))
for dummy_day in random.sample([day for day in dummy_days.keys() if dummy_days[day] != 0 and day != birth_day], k=2):
candidates.append("{}/{}".format(duplicate_month, dummy_day))
dummy_days[dummy_day] -= 1

for m, d in zip(unique_months, unique_days):
candidates.append("{}/{}".format(m, d))
for idx in range(len(unique_months)):
unique_month = unique_months[idx]
# XXX 20180627 modified ダミー日を使うよう修正
# if 0 == idx:
# candidates.append("{}/{}".format(unique_month, random.choice(ununique_days)))
# else:
# choices = []
# while True:
# choices = random.choices(ununique_days, k=2)
# if choices[0] != choices[1]:
# break
# candidates.append("{}/{}".format(unique_month, choices[0]))
# candidates.append("{}/{}".format(unique_month, choices[1]))
if 0 == idx:
dummy_day = random.choice([day for day in dummy_days.keys() if dummy_days[day] != 0])
candidates.append("{}/{}".format(unique_month, dummy_day))
dummy_days[dummy_day] -= 1
else:
for dummy_day in random.sample([day for day in dummy_days.keys() if dummy_days[day] != 0], k=2):
candidates.append("{}/{}".format(unique_month, dummy_day))
dummy_days[dummy_day] -= 1

# 結果出力
print(sorted(candidates))


3回ほど実行してみたけど、毎度違う値が生成されてるのが分かる。

それぞれ解いてみて正解まで行き着くから、とりあえず試験もOK。

$ python create_question_in_sheryl.py 9/17

['08/17', '08/21', '09/17', '09/18', '10/17', '10/18', '10/19', '11/17', '11/19', '11/20']

$ python create_question_in_sheryl.py 9/17
['07/13', '07/14', '07/17', '08/14', '08/18', '09/14', '09/17', '10/14', '10/15', '10/17']

$ python create_question_in_sheryl.py 9/17
['08/18', '08/19', '08/22', '09/17', '09/22', '10/18', '10/20', '10/22', '11/20', '11/21']

試験も兼ねて、別の誕生日でも試してみた。

とりあえず最近世話になってるとある4人の誕生日でも試してみた。

それぞれ解いて正解まで辿り着く事は確認した。

$ python create_question_in_sheryl.py 10/30

['08/01', '08/30', '08/31', '09/02', '09/30', '10/29', '10/30', '11/01', '11/29', '11/30']

$ python create_question_in_sheryl.py 4/14
['03/11', '03/13', '03/14', '04/13', '04/14', '05/11', '05/12', '06/10', '06/11', '06/14']

$ python create_question_in_sheryl.py 1/12
['01/11', '01/12', '02/10', '02/12', '02/15', '11/12', '11/14', '12/10', '12/11', '12/12']

$ python create_question_in_sheryl.py 10/5
['08/05', '08/07', '08/09', '09/04', '09/06', '10/05', '10/09', '11/04', '11/05', '11/09']

これ使って候補日を割り出して、酒の席とかで使ったら盛り上がるね🍺🍺

以上。


追記:20190627

@shiracamus さんにご指摘いただきまして、ソースコードを修正しました。

コメントアウトして新しいソースコードを書き足す、古臭い修正方法してますが、ご確認下さい、、、

なお、「内包表記」と「ジェネレーター」も採用すると、全取っ替えになるので、問題箇所のみの修正としております。


  • 指摘内容


    • 元のソースコードで出力されたリストを、シェリルの誕生日の方に食わせると、正解しない



  • 原因



  • 改修内容


    • ダミー日としてユニークな日重複する日以外」を設定

    • 誕生日(birth_day)となる日は「重複する日」には用いない



  • 試験結果


    • 私の誕生日である「9/17」で3回試して、3回とも正解

    • すでに上げてる「4人の誕生日」でそれぞれ1回ずつ試して、それぞれ正解



以上です。