LoginSignup
2
4

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-06-26

年始に作成した『シェリルの誕生日』の処理。
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回ずつ試して、それぞれ正解

以上です。

2
4
4

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