⇒ 함수가 자기 자신을 호출할 때 사용하는 용어
// 루프
function countdown(number){
for(let i=number; i>=0; i--){
console.log(i);
}
}
countdown(10);
// 재귀
function countdown(number){
console.log(i);
if(number === 0) return;
countdown(number - 1);
}
countdown(10);
루프를 사용할 수 있는 경우엔, 거의 대부분 재귀도 쓸 수 있다.
if(number === 0) return;
→ 기저조건(함수가 반복되지 않는 경우 === 무한대로 출력되지 않게)
function factorial(number){
if(number === 1){
return 1;
}else{
return number * factorial(number - 1);
}
}
기저조건 → number가 1일때,
factorial(1) → 1
factorial(2) → 2
factorial(3) → 6
이런식으로 기저조건부터 분석을 시작해서 나아가는 것 === 재귀코드의 추론
→ 인간이 봤을 때는 어딘가에 기록(예를 들어 냅킨) 할 수 있어 이것들을 금방 추론할 수 있다. 하지만, 컴퓨터라면? → 어딘가에 기록을 할 수 있을까?
factorial(2) 실행을 시작할 때 아직 factorial(3) 실행은 끝나지 않는다.! 즉, 컴퓨터는 factorial(3)를 다 실행하지 않았는데, factorial(3) 를 실행하는 중에 factorial(2) 실행을 시작하는 것이다.
마찬가지로 factorial(1)도 마찬가지이다.
결국 factorial(1)은 factorial(2)와 factorial(3) 둘 다를 실행하는 중에 실행되는 것이다.
컴퓨터는 factorial(1)이 끝나면 다시 factorial(2)로 가서 마저 실행해야 한다는 사실을 어떻게든 기억해야한다.
?→ 어떻게 기록할까?
컴퓨터는 스택을 사용해 어떤 함수를 호출 중인지 기록한다.
위 그림은 컴퓨터가 factorial(3)을 실행중이라는 뜻이다. (실제로 컴퓨터는 실행하던 코드 줄과 변숫값 등도 저장해야 하지만 위 그림에서는 단순하게 묘사)
스택은 LIFO(last in first out) 이기 때문에 가장 최근에 호출된 함수가 먼저 POP 된다.
function blah(){
blah();
}
blah() 는 자기 자신을 무한정 호출한다.
이렇게 될 경우, 호출 스택은 어떻게 될까??
무한 재귀에서는 컴푸터가 반복해서 같은 함수를 호출 스택에 푸시한다. 단기 메모리에 더 이상 데이터를 저장할 공간이 없을 때까지 호출 스택은 점점 늘어난다. 결국 스택 오버플로 라는 오류가 발생!! → 컴퓨터는 재귀를 강제로 중단하고 “메모리가 다 찼으니 더 이상의 함수 호출을 거부한다” 라는 멘트를 날리는 것!
어떤 디렉터리 내에 있는 모든 파일에 대해 모든 하위 디렉터리명을 출력하는 등의 작업을 하는 스크립트
단 하나의 디렉터리 내에 있는 파일만 처리하는 스크립트가 아니라 그 디렉터리의 하위 디렉터리, 그리고 하위 디렉토리의 하위 디렉터리에 잇는 모든 파일에 대해 수행 되어야한다.
⇒ 이걸 반복문으로 한다면? 2단계만 해도 벌써, 반복문이 2번 써진다. 3이나 4, 5단계 밑까지 찾으려면 어떻게 할까? depth가 얼마나 깊은지 모르니, 지정할 수 없다..
이럴 땐 재귀를 사용한다. 재귀를 사용하면 원하는 만큼 아래로 가는 스크립트를 작성할 수 있다.
const fs = require('fs');
const path = require('path');
function findDirectories(directory) {
// 디렉토리 내의 모든 파일 및 디렉토리 가져오기
fs.readdirSync(directory).forEach((filename) => {
const fullPath = path.join(directory, filename);
// 해당 경로가 디렉토리인지 확인
if (fs.lstatSync(fullPath).isDirectory() && filename !== '.' && filename !== '..') {
console.log(fullPath); // 디렉토리 경로 출력
findDirectories(fullPath); // 재귀적으로 하위 디렉토리 탐색
}
});
}
// 테스트 실행
const startDirectory = './start'; // 탐색 시작 경로를 지정
findDirectories(startDirectory);
알고리즘이 임의의 단계 만큼 깊이 들어가야한다면 재귀가 좋은 방법일 수 있다. 하지만 막상 재귀 함수를 작성하려고 할 때 어려움을 격는다. 다음 포스팅에서는 재귀적으로 작성하는 법을 보다 쉽게 익히는 기법을 알아보겠다.!
여
const data = [1, [2, 3], [4, [5, 6]], 7];
function recursive(array){
array.forEach((item)=>{
if(Array.isArray(item)){
recursive(item);
} else{
console.log(item);
}
})
}
recursive(data); // 모든 숫자 출력!
바깥 배열의 각 항목을 순회한다. 값이 또 다른 배열이면 그 하위 배열에 함수를 재귀적으로 호출한다. 배열이 아니면 값을 그냥 화면에 출력하는 것이 기저 조건이다.