콘웨이의
둠스데이 알고리즘
(Doomsday algorithm)
임백준님의 누워서 읽는 알고리즘을 읽다가, 날짜를 보고 요일을 알 수 있는 신기한 알고리즘을 접하게 되어 더 알아보게 되었다.
학교 전공 수업 중 콘웨이의 생명 게임에 대해서 배운 적 있는 데, 같은 분이 만든 알고리즘이라 해서 더 신기하게 느껴졌다.
둠스데이 알고리즘의 기본적인 아이디어는, 한 해의 둠스데이 (그 해 2월의 마지막 날)의 요일을 알면, 그 날과 요일이 무조건 같은 날들을 통해 특정한 날짜에 빠르게 접근함으로서 요일을 알아내는 것이다.
둠스데이와 요일이 같은 날들은 다음과 같다.
둠스데이
즉 둠스데이가 일요일이라면 6월 6일, 10월 10일은 일요일이 되는 것이다.
이 글을 작성하는 2021년의 둠스데이 (2월 28일) 는 일요일
이다.
둠스데이 알고리즘을 통해 크리스마스가 무슨 요일인지 알아보는 절차는 다음과 같다.
일요일
12일
mod
7 = 6이므로 일요일에서 6일 지난 토요일
이 정답이 된다.(A mod
B 의 값은 A를 B로 나눈 나머지이다.)
(매년 크리스마스는 항상 둠스데이 하루 전이다.)
같은 방법으로 10월 8일을 알아보면...
mod
7) = - 2 이므로 일요일이 되기 2일 전인 금요일
이 정답이 된다.2월을 제외한 짝수달은 월=일
인 날이 둠스데이와 요일이 같은 날이지만, 홀수 달에서는 성립하지 않는다.
그래서 사람마다 외우기 쉽게 다양한 바리에이션이 존재한다.
nine-to-five
를 연상할 수 있는 9월 5일과 그것을 뒤집은 5월 9일세븐일레븐
을 연상할 수 있는 7월 11일과 그것을 뒤집은 11월 7일그렇다면 둠스데이가 무슨 요일인지는 어떻게 알 수 있을까?
우선 2021년의 둠스데이는 일요일이다.
한 번 외워두면 2021년의 모든 날짜에 대해서 요일을 알아낼 수 있다.
둠스데이는 매년 한 요일씩 증가한다.
2021년의 둠스데이가 일요일
이었으므로, 2022년의 둠스데이는 월요일
이고, 2023년의 둠스데이는 화요일
이 된다.
예외적으로 윤년으로 넘어갈 때는 둠스데이가 2일 증가한다.
2024년은 윤년이므로 목요일
이 된다.
복잡하지만 항상 적용 가능한 방법이다.
[1]
세기별로 기준일
이 있다.
1800 ~ 1899: 금요일
1900 ~ 1999: 수요일
2000 ~ 2099: 화요일
2100 ~ 2199: 일요일
금, 수, 화, 일의 기준일은 계속해서 반복된다. (ex: 2200 ~ 2299: 금요일)
[2]
기준일
+ A + B + C 이 그 해의 둠스데이가 된다.2021년을 예시로 계산해보면
mod
12 = 9화요일 + 12 = 일요일
임을 확인 할 수 있다.위에서 나온 방법들을 모두 사용해 1888년의 11월 11일이 무슨 요일인지 계산해보면...
mod
12 = 4둠스데이 = 금요일 + 12 = 수요일
11월의 둠스데이와 요일이 같은 날은 11월 7일이다.
11 - 7 = 4이므로, 수요일 + 4 = 일요일
이다.
정답!
파이썬으로 구현해본 둠스데이 알고리즘
# 인자로 받은 연도의 doomsday를 반환하는 함수
def get_doomsday(year):
# 기준일 계산을 위해 연도를 앞 2자리, 뒷 2자리로 나눔
century, year_end = divmod(year, 100)
# century를 4로 나눈 나머지가 ...
# 0이면 화요일 (2), 1이면 일요일 (0) ...
base_dates = [2, 0, 5, 3]
base_date = base_dates[century % 4]
# 방법2로 doomsday 구하기
a, b = divmod(year_end, 12)
c = b // 4
doomsday = (base_date + a + b + c) % 7
return doomsday
# 인자로 받은 연도가 윤년인지 확인하는 함수
def is_leap_year(year):
if year % 400 == 0:
return True
if year % 100 == 0:
return False
if year % 4 == 0:
return True
return False
# 더해줘야 할 날짜가 며칠인지 구하는 함수
def get_plus_day(year, month, day):
# 0월은 없으므로 -1
# 이후 차례대로 1월 3일, 2월 28일, 3월 0일, 4월 4일 ...
same_days = [-1, 3, 28, 0, 4, 9, 6, 11, 8, 5, 10, 7, 12]
# 윤년일 경우 1월 4일, 2월 29일로 만들어줌
if is_leap_year(year):
same_days[1] += 1
same_days[2] += 1
plus_day = (day - same_days[month]) % 7
return plus_day
def doomsday():
year, month, day = map(
int, input("yyyy mm dd의 형태로 날짜를 입력해주세요. (띄어쓰기로 구분)\n").split()
)
doomsday = get_doomsday(year)
plus_day = get_plus_day(year, month, day)
dow_index = (doomsday + plus_day) % 7
day_of_the_week = ["일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일"]
return f"{year}년 {month}월 {day}일은 {day_of_the_week[dow_index]} 입니다."
print(doomsday())