프로젝트 오일러 31
영국 화폐 액면가를 조합하는 방법의 수
재귀를 이용하면 쉽게 풀 수 있다. 동전의 종류를 숫자 1, 2, ...로 표현하면 문제를 풀 때 도움이 된다. 따라서 다음과 같이 종류 숫자를 인자로 주면 동전 값을 리턴하는 함수를 만들 수 있다.
(defn- value [n]
(case n
1 1
2 2
3 5
4 10
5 20
6 50
7 100
8 200))
Clojure에서는 벡터를 함수로 사용할 수도 있으므로 다음과 같이 벡터를 사용해 구현할 수도 있다.
(defn- value [n]
([1 2 5 10 20 50 100 200] (dec n)))
벡터의 인덱스는 0부터 시작하고 동전 종류 숫자는 1부터 시작하므로, dec 함수로 인자 값을 1 감소시켰다. 동전 종류 숫자를 0부터 시작하게 할 수도 있다. 그렇게 한다면 이 함수를 호출하는 쪽에서 그에 맞게 조건을 설정하면 된다.
편의상 동전을 금액의 오름차순으로 정렬해 놓았지만, 사실 순서는 상관없다. 두 구현 모두 충분히 간단하지만, 벡터를 이용한 구현은 dec 함수를 호출하는 부분이 있어서인지 case를 사용한 구현보다 조금 느리다.
금액을 amt라고 할 때, n가지 동전을 가지고 amt를 만드는 방법은 다음 두 가짓수의 합과 같다.
- 맨 처음 나오는 동전을 제외한 나머지 동전으로
amt를 만드는 가짓수 - 처음 나온 동전 금액을
d라 할 때amt - d금액을n가지 동전으로 만드는 가짓수
첫 번째 경우를 계산하려면 동전을 한 가지 제외하고 amt를 만드는 가짓수를 구해야 한다. 사용할 수 있는 동전은 한 가지 줄어들었지만 문제는 동일하다. 두 번째 경우는 사용할 수 있는 동전은 그대로지만 금액이 줄어든 상태에서 가짓수를 구해야 하는데, 금액만 줄었지 문제는 동일함을 알 수 있다. 즉, 첫 번째 경우나 두 번째 경우 모두 동전 가짓수나 금액만 줄어들 뿐 로직(또는 알고리즘)은 그대로 쓸 수 있다. 즉 재귀를 사용할 수 있다.
재귀를 사용할 때는 항상 규칙이 끝나는 경우를 정리해야 한다.
amt가 0이면 해당 금액을 만드는 방법은 1가지 밖에 없다.amt가 0보다 작은 금액을 만드는 방법은 없다. (0가지)n이 0이면 금액을 만드는 방법은 없다. (0가지)
이 규칙을 이용해 금액을 만드는 방법의 가짓수를 구하는 함수를 다음과 같이 만들 수 있다.
(defn- cc [amt n]
(cond
(= amt 0) 1
(< amt 0) 0
(= n 0) 0
:else (+ (cc amt (dec n))
(cc (- amt (value n)) n))))
문제를 다 푼 것이나 마찬가지다. (cc 200 8)을 실행하면 답을 구할 수 있다. 다른 풀이와 코드 형식을 맞추기 위해 다음과 같이 solve 함수를 만든다.
(defn solve []
(cc 200 8))
실행 결과는 다음과 같다.
p031=> (time (solve)) "Elapsed time: 171.989327 msecs" 736??
참고
- 프로젝트 오일러 31 풀이 소스 코드
- 컴퓨터 프로그램의 구조와 해석, p50 동전 종류와 개수만 다를뿐 동일한 문제와 풀이가 있다. 프로젝트 오일러 31 풀이는 책의 설명을 글쓴이의 취향에 맞게 재구성한 것이다.