오일러 프로젝트 19번 문제는 20세기에서 매월 1일이 일요일인 경우를 세는 문제이다. 복잡해 보이지만 사실 쉬운 문제이다.
다음은 달력에 관한 몇 가지 일반적인 정보입니다 (필요한 경우 좀 더 연구를 해 보셔도 좋습니다).
- 1900년 1월 1일은 월요일이다.
- 4월, 6월, 9월, 11월은 30일까지 있고, 1월, 3월, 5월, 7월, 8월, 10월, 12월은 * 31일까지 있다.
- 2월은 28일이지만, 윤년에는 29일까지 있다.
- 윤년은 연도를 4로 나누어 떨어지는 해를 말한다. 하지만 400으로 나누어 떨어지지 않는 매 100년째는 윤년이 아니며, 400으로 나누어 떨어지면 윤년이다
20세기 (1901년 1월 1일 ~ 2000년 12월 31일) 에서, 매월 1일이 일요일인 경우는 총 몇 번입니까?
접근
기준 날짜를 정해놓고 날짜를 하루씩 올려가면서 1일이면서 월요일인 경우를 세는 코드를 작성하면 된다. 이 때 2월은 윤년일 때 29일이 있다. 윤년 여부를 결정하는 것은 위에 설명되어 있는데, 나이브하게 코딩하면
if year % 4 == 0 {
if year % 100 == 0 {
if year % 400 == 0 { return true }
return false
}
return true
}
이렇게 중첩되는데, 넓은 범위에서부터 비교하면
- 400으로 나눠지면 윤년이다.
- 그 와중에 100으로 나눠지면 윤년이 아니다.
- 400으로 나눠지지 않는 경우에는 4로 나눠지면 윤년이다.
따라서 다음의 한 줄로 찾을 수 있다.
return (year % 400 == 0) || (year % 100 != 0) && (year % 4 == 0)
그외에 요일은 임의로 정하면 된다. 편의상 0을 일요일, 6을 토요일로 두겠다.
// 1900년 1월 1일은 일요일이다. (문제)
var (year, month, day, weekday) = (1900, 1, 1, 0)
var result = 0
while year < 2001 {
let days: [Int] = {
if year % 400 == 0 || year % 100 != 0 && year % 4 == 0 {
return [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
}
return [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
}()
/// 조건에 맞으면 카운트를 1올린다.
/// 20세기는 1901년부터 2000년이라는 점을 잊지말자
if (1901...2000) ~= year, day == 1, weekday == 0 { result += 1 }
/// 다음날로 이동한다.
(day, weekday) = (day + 1, (weekday + 1) % 7)
if day > days[month-1] {
(day, month) = (1, month + 1)
if month > 12 {
(month, year) = (1, year + 1)
}
}
}
print(result)
파이썬 풀이
풀이 패턴 자체가 매우 단순하기 때문에 파이썬으로 같은 코드를 쓰는 것보다는 다른 풀이를 쓰고자 한다. datetime
모듈의 datetime
은 두 날짜간의 거리를 계산할 수 있는데, 이 때 쓰이는 값이 timedelta
객체이다. 반대로 datetime
에 timedelta
객체를 더하면 그 날이 지난 날짜를 얻을 수 있다.1 이를 이용해서 계산해보자. 참고로 weekday
는 다른 날짜 속성과는 달리 메소드이므로 호출해서 값을 얻어야 한다.
import datetime
def solve():
## 1900-01-01을 참조할 필요없이 20세기 첫날부터 그냥 시작한다.
t = datetime.datetime(1901, 1, 1)
d = datetime.timedelat(days=1)
result = 0
while t.year < 2001:
if t.day == 0 and t.weekday() == 0:
result += 1
t += d
print(result)
-
이 때 사용되는 방식은 그냥 덧셈(
+
)이다. ↩