티스토리 뷰
오늘은 지식in 같은 곳에서 가장 쉽게 접하는 질문 중 하나인 윤년을 계산하는 방법에 대해서 이야기해보자. 사실 코드만 써 놓으면 너무 간단하니까 윤년에 대해 몇 가지 이야기를 좀 해보도록 하겠다.
윤년이란 무엇인가?
1년은 지구가 태양을 한 바퀴도는 것을 일수로 표현한 것으로 우리는 통상 1년 = 365일이라고 알고 있다. 아주 엉뚱한 소리는 아닌데, 실제로 우리가 일상에서 쓰는 1년은 '회귀년'으로 춘분점이 동일한 위치로 돌아오는 데까지 걸리는 시간을 의미한다. 이는 엄밀하게는 365.2422일로 365일 하고도 약 5시간가량이 된다. 이 실제 1년이 365일보다 조금 더 긴 이 차이가 누적이 되어 몇 백 년이 지나면 8월이 한 겨울이 되는 등 실제 계절과 달력이 차이나는 문제가 생긴다. 그래서 지금 우리가 많이 쓰는 그레고리력에서는 몇 가지 예외적인 규칙을 두어 윤년이라는 것을 정했다. 윤년이 되면 28일까지 있는 2월이 29일까지로 하루 늘어난다. 이 윤년은 4년에 한 번으로 정해서 매년 쌓이는 5시간 49분 정도의 차이를 모아 하루를 더하는 것이다.
- 기본적으로 4년에 한 번 윤년으로 4의 배수가 되는 해는 윤년이다. 이는 매년 쌓이는 시간이 약 6시간쯤 되니 이를 4번 모으면 하루가 되기 때문이다.
- 그런데 이렇게 되면 또 4년마다 3시간 정도가 모자란다. 그래서 매 100년은 윤년이 아니다.
- 이마저도 누적되면 또 차이가 나기 때문에 다시 400년마다 윤년으로 정한다.
그레고리력이 정하는 윤년의 규칙은 여기까지 이다. 물론 400년 주기에서 보정하는 부분 역시 오차가 있어서 2000년마다 한 번씩은 다시 윤년이 아닌 것으로 정해야 하지만, "그건 그때 가서 알아서 하겠지"라고 딱히 정의하지 않았다.
자 그러면 위의 규칙을 다시 정리하면 다음과 같다.
- 연도가 4로 나눠지는 해는 윤년이다.
- 그러나 연도가 100으로 나눠지는 해는 윤년이 아니다.
- 그런데 연도가 400으로 나눠지는 해는 윤년이다.
이렇게 해서 세 번의 분기를 판단해야 한다. 그래서 이 규칙을 곧이곧대로 코드로 옮기면 의외로 지저분하다.
def isLeapYear(y):
# 윤년은 영어로 leap year이다.
# leaf year 가 아니다.
if y % 4 == 0:
if y % 100 == 0:
if y % 400 == 0:
return True # 매 400년
return False # 400년은 아닌 100년
return True # 100년은 아닌 4년
return False # 4년이 아님
그런데 여기 등장하는 숫자들을 보자, 400, 100, 4이다. 400은 당연히 100의 배수이다. 그리고 100은 4의 배수이다. A가 B의 배수일 때 또 다른 Y에 대해서 Y가 B의 배수라면 Y는 A의 배수이다라는 명제가 참임을 알 수 있다. 이의 대우는 "Y가 A의 배수가 아니라면 Y는 B의 배수도 아니다."가 된다. 예를 들어 Y가 400의 배수가 아니라면 4의 배수가 아니라는 점은 자명하다.
이 점이 시사하는 바는 큰 숫자로 먼저 나눠보면 더 빠른 결론에 도달할 수 있다는 것이다.
def isLeapYear(y):
if y % 400 == 0:
return True
if y % 100 == 0:
return False
if y % 4 == 0:
return True
return False
코드 수는 똑같지만, 조금 더 알아보기 쉬워졌다. 자 그럼 위 로직을 말로 설명해보자면 "400의 배수이거나, 100의 배수가 아니면서 4의 배수이면 윤년이다"가 된다. 이건 파이썬의 and/or 연산의 short circuit 성질을 이용해서 한 줄로 표시할 수 있다.
shor circuit 이란 A, B의 평가식을 and 나 or 로 연결했을 때, 나머지 하나를 굳이 평가할 필요가 없는 상황에서는 후자를 평가하지 않는다는 것이다. 예를 들어 A and B는 A, B가 모두 참이어야 참이다. 만약 A가 거짓이라면 B를 볼 필요 없이 전체가 거짓임을 알 수 있다. 반대로 A or B는 둘 중 하나만 참이면 참이다. 따라서 A가 참이면 B를 볼 필요가 없다.
이 성질을 사용해서 이러한 중첩된 if 문을 간단히 하나의 조건을 결합할 수 있는 상황이 가끔 있는데, 공교롭게도 이 윤년 계산의 케이스가 딱 여기에 해당한다.
def isLeapYear(y):
return y % 400 == 0 or y % 100 > 0 and y % 4 == 0
# ~~~~~~~~~~~A ~~~~~~~~~~B ~~~~~~~~~C
이 코드는 이렇게 동작한다.
- y가 400의 배수라면 A가 참이고 B, C를 평가하지 않는다.
- y가 400의 배수가 아니라면, B를 평가한다. 만약 B가 참이라면 (y가 100의 배수가 아니라면) C(4의 배수인지)까지 평가할 것이다. 그러나 B가 거짓이라면 C를 평가할 필요가 없으니 거짓으로 결론날 것이다.
그러니까 and/or 연산의 short circuit 만 알고 있다면 윤년 체크 함수는 단 한줄에 작성할 수 있다는 것이다.
----
참고로 가독성을 좀 포기한다면 다음과 같이 쓸 수도 있는데, 해석은 각자가...
leaf = lamdba y: 400 % ((4, 400)[y%100==0]) == 0
'숙제는 지식즐' 카테고리의 다른 글
주어진 집합에서 랜덤하게 원소를 고르기 (0) | 2019.07.13 |
---|---|
[파이썬] 숫자를 한글로 읽는 함수 (0) | 2017.04.12 |
- Total
- Today
- Yesterday
- 튜플언패킹
- 리스트
- python list
- Python
- 사전
- 정렬키
- Lambda
- jupyter-notebook
- 복수기준정렬
- 단어 빈도수
- 함수형
- sorted
- 우분투
- 반복자
- 파이썬
- 변경가능
- 정렬기준
- leapYear
- 이중리스트를 사용하지 않기
- mutability
- 표준입력
- short_circuit
- 리눅스
- iterable
- ipython-notebook
- dict
- locals()
- 파일입출력
- 파이썬노트북
- globals()
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |