§ 1 · 자기 호출루프 대신 재귀
재귀(recursion)는 함수가 자기 자신을 호출하는 것의 공식 이름이다. 깜짝 퀴즈 — 아래 blah()를 호출하면?
function blah() {
blah();
}
blah()가 자신을 호출하고, 그것이 다시 자신을 호출하며 무한히 반복한다. 무한 호출은 쓸모없고 위험하지만, 길들인 재귀는 강력한 도구다.
NASA 카운트다운 함수를 떠올려 보자. 10부터 0까지 출력하는 함수. 대부분 for 루프로 짤 것이다. 그러나 재귀로도 짤 수 있다.
function countdown(number) { console.log(number); countdown(number - 1); } countdown(10);
§ 2 · 멈출 곳기저 사례
위 코드는 완벽하지 않다. 0을 지나 -1, -2... 무한히 음수를 출력한다. number가 0이면 멈추도록 조건문을 추가해야 한다.
function countdown(number) { console.log(number); if(number === 0) { return; // 기저 사례 — 재귀 중단 } else { countdown(number - 1); } }
§ 3 · 냅킨 기법재귀 코드 읽기
팩토리얼을 보자. 3! = 3×2×1 = 6. 재귀 구현은 이렇다.
def factorial(number) if number == 1 return 1 # 기저 사례 else return number * factorial(number - 1) end end
재귀 코드를 읽는 법 — ① 기저 사례를 파악한다. ② 기저 사례를 처리한다고 가정하고 함수를 실행한다. ③ 한 단계씩 위로 올라가며 분석한다. 마치 사실을 냅킨에 적어두고 다음 케이스에서 참고하듯이.
§ 4 · 쌓였다 풀린다호출 스택
컴퓨터도 비슷한 방식을 쓴다. 컴퓨터는 호출 스택(call stack)으로 호출 중인 함수를 추적한다. factorial(3)을 호출하면 — 완료되기 전에 factorial(2)를, 그것이 완료되기 전에 factorial(1)을 호출한다.
§ 5 · 임의의 깊이재귀의 활용
카운트다운과 팩토리얼은 재귀로도 루프로도 풀 수 있다 — 재귀가 특별한 이점을 주지 않는다. 그러나 재귀는 같은 알고리즘을 알고리즘 안에서 반복해야 하는 상황에 자연스럽게 맞는다.
파일 시스템 순회를 보자. 디렉터리 안에는 하위 디렉터리가, 그 안에 또 하위 디렉터리가 있다. 두 단계, 세 단계... 얼마나 깊을지 모른다. 재귀는 임의로 깊이 들어가는 스크립트를 간단하게 짜게 해준다.
def find_directories(directory) Dir.foreach(directory) do |filename| if File.directory?("#{directory}/#{filename}") && filename != "." && filename != ".." puts "#{directory}/#{filename}" find_directories("#{directory}/#{filename}") # 재귀! end end end
§ 6 · 정리이 장이 남긴 것
재귀는 알고리즘이 무언가를 얼마나 깊이 파고들어야 하는지 처음부터 모를 때 훌륭한 선택이다. 이제 재귀를 이해했으니 초능력을 잠금 해제했다 — 다음 장에서는 재귀가 코드를 훨씬 빠르게 만드는 알고리즘의 핵심임을 본다.