[javascript] 재귀 함수가 몇 번이나 호출되었는지 추적

 function singleDigit(num) {
      let counter = 0
      let number = [...num + ''].map(Number).reduce((x, y) => {return x * y})

      if(number <= 9){
          console.log(number)
      }else{
          console.log(number)
          return singleDigit(number), counter += 1
      }
   }
singleDigit(39)

위의 코드는 정수를 사용하여 자체 숫자로 곱하여 한 자리수로 줄입니다.

예는 39입니다.

3 x 9 = 27.
2 x 7 = 14.
1 x 4 = 4.

콘솔은 다음을 기록합니다 :

27
14
4

재귀 함수가 3 번 호출되었음을 어떻게 추적합니까?

카운터 추가를 시도했지만 업데이트하지 못했습니다. 어떤 도움을 주셔서 감사합니다



답변

이것은 거의 순수하게 학술적인 변형이지만 이 목적 으로 수정 된 고정 소수점 조합기 를 사용할 수 있습니다 .

원래 기능을 약간 단축하고 향상시킬 수 있습니다.

function singleDigit(n) {
    let digitProduct = [...(n + '')].reduce((x, y) => x * y, 1);
    return digitProduct <= 9 ? digitProduct : singleDigit(digitProduct);
}

// singleDigit(123234234) == 0

이 변형에서 우리는 재귀 호출을 제외하고 카레 할 수 있습니다.

function singleDigitF(recur) {
    return function (n) {
        let digitProduct = [...(n + '')].reduce((x, y) => x * y, 1);
        return digitProduct <= 9 ? digitProduct : recur()(digitProduct);
    };
}

이 기능은 이제 고정 소수점 조합기와 함께 사용할 수 있습니다. 특히 다음과 같이 (엄격한) JavaScript에 적합한 Y 결합기를 구현했습니다.

function Ynormal(f, ...args) {
    let Y = (g) => g(() => Y(g));
    return Y(f)(...args);
}

우리가있는 곳 Ynormal(singleDigitF, 123234234) == 0.

이제 트릭이 온다. Y 결합기에 대한 재귀를 계산 했으므로 그 내부의 재귀 수를 계산할 수 있습니다.

function Ycount(f, ...args) {
    let count = 1;
    let Y = (g) => g(() => {count += 1; return Y(g);});
    return [Y(f)(...args), count];
}

Node REPL에서 빠른 검사는 다음을 제공합니다.

> Ycount(singleDigitF, 123234234)
[ 0, 3 ]
> let digitProduct = (n) => [...(n + '')].reduce((x, y) => x * y, 1)
undefined
> digitProduct(123234234)
3456
> digitProduct(3456)
360
> digitProduct(360)
0
> Ycount(singleDigitF, 39)
[ 4, 3 ]

이 콤비는 지금에 호출 수를 계산을 위해 작동 할 어떤 스타일로 작성 재귀 함수 singleDigitF.

(주이 매우 빈번한 답변으로 제로를 가져 오는 소스가 있음 : 숫자 오버 플로우 ( 123345456999999999지고 123345457000000000. 등), 입력의 크기가 증가 할 때 당신은 거의 확실하게, 중간 값 곳으로 제로를 얻을 것이라는 사실을)


답변

함수 정의에 카운터 인수를 추가해야합니다.

function singleDigit(num, counter = 0) {
    console.log(`called ${counter} times`)
    //...
    return singleDigit(number, counter+1)
}
singleDigit(39)


답변

기존 솔루션은 다른 답변에서 제안한대로 카운트를 매개 변수로 함수에 전달하는 것입니다.

그러나 js에는 또 다른 해결책이 있습니다. 다른 답변은 재귀 함수 외부에서 카운트를 선언하는 것만 제안했습니다.

let counter = 0
function singleDigit(num) {
  counter++;
  // ..
}

이것은 물론 작동합니다. 그러나 이로 인해 함수가 재진입되지 않습니다 (두 번 올바르게 호출 할 수 없음). 어떤 경우에는이 문제를 무시하고 단순히 singleDigit두 번 호출하지 않아야합니다 (자바 스크립트는 단일 스레드이므로 너무 어렵지 않습니다). singleDigit나중에 비동기식으로 업데이트 하고 느낀 다면 버그가 발생합니다. 추한.

해결책은 counter변수를 외부 에 선언 하지만 전역 에 선언 하지 않는 것입니다. javascript에 클로저가 있기 때문에 가능합니다.

function singleDigit(num) {
  let counter = 0; // outside but in a closure

  // use an inner function as the real recursive function:
  function recursion (num) {
    counter ++
    let number = [...num + ''].map(Number).reduce((x, y) => {return x * y})

    if(number <= 9){
      return counter            // return final count (terminate)
    }else{
      return recursion(number)  // recurse!
    }
  }

  return recursion(num); // start recursion
}

이것은 전역 솔루션과 비슷하지만 singleDigit( 재귀 함수가 아닌) 호출 할 때마다 counter변수 의 새 인스턴스가 생성됩니다 .


답변

다른 방법은 모든 숫자를 생성하므로 생성기를 사용하는 것입니다.

마지막 요소는 당신의 숫자입니다 n 를 한 자리 줄이고 반복 횟수를 세려면 배열의 길이를 읽으십시오.

const digits = [...to_single_digit(39)];
console.log(digits);
//=> [27, 14, 4]
<script>
function* to_single_digit(n) {
  do {
    n = [...String(n)].reduce((x, y) => x * y);
    yield n;
  } while (n > 9);
}
</script>


마지막 생각들

당신은 고려하는 것이 좋습니다 함수에서 초기 상태를 . 거기에 제로로 모든 숫자는 것이다 0을 반환.

singleDigit(1024);       //=> 0
singleDigit(9876543210); //=> 0

// possible solution: String(n).includes('0')

숫자 1만으로도 같은 말을 할 수 있습니다 .

singleDigit(11);    //=> 1
singleDigit(111);   //=> 1
singleDigit(11111); //=> 1

// possible solution: [...String(n)].every(n => n === '1')

마지막으로 양의 정수만 허용하는지 여부는 명확하지 않았습니다. 음의 정수를 허용하면 문자열로 캐스트 것이 위험 할 수 있습니다.

[...String(39)].reduce((x, y) => x * y)
//=> 27

[...String(-39)].reduce((x, y) => x * y)
//=> NaN

가능한 해결책:

const mult = n =>
  [...String(Math.abs(n))].reduce((x, y) => x * y, n < 0 ? -1 : 1)

mult(39)
//=> 27

mult(-39)
//=> -27


답변

여기에는 많은 흥미로운 답변이 있습니다. 내 버전은 다른 흥미로운 대안을 제공한다고 생각합니다.

필요한 기능으로 몇 가지 작업을 수행합니다. 한 자릿수로 재귀 적으로 줄입니다. 중간 값을 기록하고 재귀 호출 횟수를 원합니다. 이 모든 것을 처리하는 한 가지 방법은 최종 결과, 취한 단계 및 호출 횟수를 모두 포함하는 데이터 구조를 반환하는 순수한 함수를 작성하는 것입니다.

  {
    digit: 4,
    steps: [39, 27, 14, 4],
    calls: 3
  }

원하는 경우 단계를 로그하거나 추가 처리를 위해 단계를 저장할 수 있습니다.

이를 수행하는 버전은 다음과 같습니다.

const singleDigit = (n, steps = []) =>
  n <= 9
    ? {digit: n, steps: [... steps, n], calls: steps .length}
    : singleDigit ([... (n + '')] .reduce ((a, b) => a * b), [... steps, n])

console .log (singleDigit (39))

우리는 추적 steps하지만를 유도합니다 calls. 추가 매개 변수로 통화 수를 추적 할 수는 있지만 아무것도 얻지 못하는 것 같습니다. 우리는 또한 생략map(Number) 단계를 . 어쨌든 곱셈에 의해 숫자로 강제됩니다.

기본 steps매개 변수가 API의 일부로 노출되는 것에 대해 우려가 있다면 다음 과 같은 내부 함수를 사용하여 숨길 수 있습니다.

const singleDigit = (n) => {
  const recur = (n, steps) =>
    n <= 9
      ? {digit: n, steps: [... steps, n], calls: steps .length}
      : recur ([... (n + '')] .reduce ((a, b) => a * b), [... steps, n])
  return recur (n, [])
}

그리고 두 경우 모두 숫자 곱셈을 도우미 함수로 추출하는 것이 약간 더 깨끗할 수 있습니다.

const digitProduct = (n) => [... (n + '')] .reduce ((a, b) => a * b)

const singleDigit = (n, steps = []) =>
  n <= 9
    ? {digit: n, steps: [... steps, n], calls: steps .length}
    : singleDigit (digitProduct(n), [... steps, n])


답변

당신은 그냥 사라지게과 재귀에 대해 걱정하지 않습니다 얼마나 많은 시간을 계산하려는 경우 특별히 … 당신은 단지 재귀를 제거 할 수 있습니다. 아래 코드는 num <= 9축소가 필요 하지 않은 것으로 원래 게시물에 충실합니다 . 따라서 singleDigit(8)count = 0, 그리고 singleDigit(39)count = 3다만 영업 이익과 허용 대답 보여주는 것 같은 :

const singleDigit = (num) => {
    let count = 0, ret, x;
    while (num > 9) {
        ret = 1;
        while (num > 9) {
            x = num % 10;
            num = (num - x) / 10;
            ret *= x;
        }
        num *= ret;
        count++;
        console.log(num);
    }
    console.log("Answer = " + num + ", count = " + count);
    return num;
}

숫자 9 이하 (즉, num <= 9) 를 처리 할 필요는 없습니다 . 불행히도 OP 코드는 num <= 9계산하지 않는 처리 합니다. 위의 코드는 처리하거나 계산하지 않습니다num <= 9 . 그냥 통과합니다.

.reduce실제 수학을 수행하는 것이 훨씬 빠르기 때문에 사용하지 않기로 선택했습니다 . 그리고 이해하기 쉬워졌습니다.


속도에 대한 추가 생각

좋은 코드도 빠릅니다. 이 유형의 축소 (수비학에서 많이 사용됨)를 사용하는 경우 대량의 데이터에서 사용해야 할 수도 있습니다. 이 경우 속도가 가장 중요합니다.

모두 사용 .map(Number)하고 console.log(각 환원 스텝) 모두 아주 긴 실행 불필요한한다. .map(Number)OP에서 삭제 하는 것만 으로도 약 4.38 배 증가했습니다. 삭제가 console.log너무 빨라 제대로 테스트하기가 거의 불가능했습니다 (기다리고 싶지 않았습니다).

따라서 customcommander 의 대답 과 유사하게 결과를 배열로 사용 .map(Number)하지 않고 console.log배열로 푸시하고 .lengthfor를 사용 하는 count것이 훨씬 빠릅니다. 불행히도에 대한 customcommander 의 대답은 사용하는 발전기 기능은 정말 정말 느린 (대답은하지 않고 영업 이익에 비해 2.68x에 대한 느린 것입니다 .map(Number)console.log)

또한 .reduce사용하는 대신 실제 수학을 사용했습니다. 이 단일 변경만으로도 내 함수 버전을 3.59x까지 올렸습니다.

마지막으로 재귀 속도가 느려지고 스택 공간을 차지하고 더 많은 메모리를 사용하며 “재귀”할 수있는 횟수에 제한이 있습니다. 또는이 경우 전체 축소를 완료하는 데 사용할 수있는 축소 단계 수입니다. 반복 루프로 재귀를 롤아웃하면 스택의 모든 동일한 위치에 유지되며 완료에 사용할 수있는 축소 단계 수에 대한 이론적 인 제한이 없습니다. 따라서이 함수들은 실행 시간과 배열의 길이에 의해서만 제한되는 거의 모든 크기의 정수를 “감소”할 수 있습니다.

이 모든 것을 염두에두고 …

const singleDigit2 = (num) => {
    let red, x, arr = [];
    do {
        red = 1;
        while (num > 9) {
            x = num % 10;
            num = (num - x) / 10;
            red *= x;
        }
        num *= red;
        arr.push(num);
    } while (num > 9);
    return arr;
}

let ans = singleDigit2(39);
console.log("singleDigit2(39) = [" + ans + "],  count = " + ans.length );
 // Output: singleDigit2(39) = [27,14,4],  count = 3

위의 기능은 매우 빠르게 실행됩니다. 이는 영업 이익에 비해 (없이 빠른 3.13x에 관한 것입니다 .map(Number)console.log보다 빠른)와 8.4 배에 대해 customcommander 의 대답. console.logOP에서 삭제 하면 각 축소 단계에서 숫자가 생성되지 않습니다. 따라서이 결과를 배열로 푸시해야합니다.

PT


답변

console.count함수를 호출하지 않습니까?

편집 : 브라우저에서 시도하는 스 니펫 :

function singleDigit(num) {
    console.count("singleDigit");

    let counter = 0
    let number = [...num + ''].map(Number).reduce((x, y) => {return x * y})

    if(number <= 9){
        console.log(number)
    }else{
        console.log(number)
        return singleDigit(number), counter += 1
    }
}
singleDigit(39)

Chrome 79 및 Firefox 72에서 작동합니다.