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
배열로 푸시하고 .length
for를 사용 하는 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.log
OP에서 삭제 하면 각 축소 단계에서 숫자가 생성되지 않습니다. 따라서이 결과를 배열로 푸시해야합니다.
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에서 작동합니다.