chs*_*242 63 javascript recursion
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)Run Code Online (Sandbox Code Playgroud)
上面的代码采用一个整数,并通过将它乘以它自己的数字将它减少到一个数字。
示例为 39。
3 x 9 = 27.
2 x 7 = 14.
1 x 4 = 4.
Run Code Online (Sandbox Code Playgroud)
控制台将记录:
27
14
4
Run Code Online (Sandbox Code Playgroud)
如何跟踪递归函数被调用了 3 次?
我曾尝试添加计数器,但无法更新。将不胜感激任何帮助
She*_*aff 76
您应该在函数定义中添加一个反参数:
function singleDigit(num, counter = 0) {
console.log(`called ${counter} times`)
//...
return singleDigit(number, counter+1)
}
singleDigit(39)
Run Code Online (Sandbox Code Playgroud)
sle*_*man 38
传统的解决方案是将计数作为参数传递给另一个答案所建议的函数。
但是,js 中还有另一种解决方案。其他一些答案建议简单地在递归函数之外声明计数:
let counter = 0
function singleDigit(num) {
counter++;
// ..
}
Run Code Online (Sandbox Code Playgroud)
这当然有效。然而,这使得函数不可重入(不能正确调用两次)。在某些情况下,您可以忽略此问题,只需确保您不调用singleDigit两次(javascript 是单线程的,因此不太难做到),但如果您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
}
Run Code Online (Sandbox Code Playgroud)
这类似于全局解决方案,但每次调用singleDigit(现在不是递归函数)时,它都会创建一个新的counter变量实例。
phi*_*ler 27
这几乎是纯粹的学术变体,但您可以为此目的使用修改后的定点组合器。
让我们稍微缩短和改进您的原始函数:
function singleDigit(n) {
let digitProduct = [...(n + '')].reduce((x, y) => x * y, 1);
return digitProduct <= 9 ? digitProduct : singleDigit(digitProduct);
}
// singleDigit(123234234) == 0
Run Code Online (Sandbox Code Playgroud)
从这个变体中,我们可以将递归调用分解出来并进行柯里化:
function singleDigitF(recur) {
return function (n) {
let digitProduct = [...(n + '')].reduce((x, y) => x * y, 1);
return digitProduct <= 9 ? digitProduct : recur()(digitProduct);
};
}
Run Code Online (Sandbox Code Playgroud)
此函数现在可以与定点组合器一起使用;具体来说,我实现了一个适用于(严格)JavaScript 的 Y 组合器,如下所示:
function Ynormal(f, ...args) {
let Y = (g) => g(() => Y(g));
return Y(f)(...args);
}
Run Code Online (Sandbox Code Playgroud)
我们在哪里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];
}
Run Code Online (Sandbox Code Playgroud)
快速检查节点 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 ]
Run Code Online (Sandbox Code Playgroud)
这个组合器现在将用于计算任何以singleDigitF.
(请注意,有两个来源将零作为非常常见的答案:数字溢出(123345456999999999成为123345457000000000等),以及当输入的大小增加时,您几乎肯定会在某处获得零作为中间值。)
cus*_*der 22
另一种方法,因为你产生所有的数字,是使用生成器。
最后一个元素是您的数字n减少到一位数,并计算您迭代了多少次,只需读取数组的长度。
const digits = [...to_single_digit(39)];
console.log(digits);
//=> [27, 14, 4]Run Code Online (Sandbox Code Playgroud)
<script>
function* to_single_digit(n) {
do {
n = [...String(n)].reduce((x, y) => x * y);
yield n;
} while (n > 9);
}
</script>Run Code Online (Sandbox Code Playgroud)
最后的想法
您可能需要考虑在您的函数中设置早返回条件。任何带有零的数字都将返回零。
singleDigit(1024); //=> 0
singleDigit(9876543210); //=> 0
// possible solution: String(n).includes('0')
Run Code Online (Sandbox Code Playgroud)
任何1仅由组成的数字也可以这样说。
singleDigit(11); //=> 1
singleDigit(111); //=> 1
singleDigit(11111); //=> 1
// possible solution: [...String(n)].every(n => n === '1')
Run Code Online (Sandbox Code Playgroud)
最后,您没有说明是否只接受正整数。如果您接受负整数,那么将它们转换为字符串可能会有风险:
[...String(39)].reduce((x, y) => x * y)
//=> 27
[...String(-39)].reduce((x, y) => x * y)
//=> NaN
Run Code Online (Sandbox Code Playgroud)
可能的解决方案:
const mult = n =>
[...String(Math.abs(n))].reduce((x, y) => x * y, n < 0 ? -1 : 1)
mult(39)
//=> 27
mult(-39)
//=> -27
Run Code Online (Sandbox Code Playgroud)
您可以为此使用闭包。
只需简单地存储counter到函数的闭包中即可。
这是示例:
function singleDigitDecorator() {
let counter = 0;
return function singleDigitWork(num, isCalledRecursively) {
// Reset if called with new params
if (!isCalledRecursively) {
counter = 0;
}
counter++; // *
console.log(`called ${counter} times`);
let number = [...(num + "")].map(Number).reduce((x, y) => {
return x * y;
});
if (number <= 9) {
console.log(number);
} else {
console.log(number);
return singleDigitWork(number, true);
}
};
}
const singleDigit = singleDigitDecorator();
singleDigit(39);
console.log('`===========`');
singleDigit(44);Run Code Online (Sandbox Code Playgroud)
如果你只是想指望它得到了多少次下降,并且不关心递归专...你可以只取出递归。下面的代码仍然忠实于原始帖子,因为它不算num <= 9作需要减少。因此,singleDigit(8)will havecount = 0和singleDigit(39)will have count = 3,就像 OP 和接受的答案所证明的那样:
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;
}
Run Code Online (Sandbox Code Playgroud)
没有必要处理 9 或更少的数字(即num <= 9)。不幸的是,num <= 9即使不计算OP 代码也会处理。上面的代码根本不会处理也不会计数num <= 9。它只是通过它。
我选择不使用,.reduce因为进行实际的数学运算执行起来要快得多。而且,对我来说,更容易理解。
我感觉好的代码也很快。如果您使用这种类型的归约(在命理学中经常使用),您可能需要在大量数据上使用它。在这种情况下,速度将成为最重要的。
使用.map(Number)和console.log(在每个缩减步骤中)都需要非常长的执行时间并且是不必要的。简单地.map(Number)从 OP 中删除,速度提高了大约 4.38 倍。删除console.log速度如此之快,以至于几乎不可能正确测试(我不想等待它)。
因此,类似于customcommander的答案,不使用.map(Number)norconsole.log并将结果推送到数组中,而使用.lengthforcount快得多。不幸的是,对于customcommander的回答,使用生成器函数真的很慢(该答案比没有.map(Number)和的 OP 慢约 2.68 倍console.log)
另外,.reduce我没有使用,而是使用了实际的数学。仅此一项更改就将我的函数版本加快了 3.59 倍。
最后,递归更慢,它占用堆栈空间,使用更多内存,并且对它可以“递归”的次数有限制。或者,在这种情况下,它可以使用多少步减少来完成完全减少。将您的递归推广到迭代循环可将其全部放在堆栈上的同一个位置,并且对于它可以使用多少减少步骤来完成没有理论上的限制。因此,这里的这些函数可以“减少”几乎任何大小的整数,仅受执行时间和数组长度的限制。
想到这一切...
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
Run Code Online (Sandbox Code Playgroud)
上述功能运行速度极快。这是约3.13x快于OP(不.map(Number)和console.log)和大约8.4倍的速度比customcommander的答案。请记住,console.log从 OP中删除会阻止它在每一步减少时产生一个数字。因此,这里需要将这些结果推送到数组中。
PT
这里有很多有趣的答案。我认为我的版本提供了一个额外的有趣选择。
您可以使用所需的功能做几件事。您递归地将其减少到一位数。您记录中间值,并且您想要进行递归调用的计数。处理所有这些的一种方法是编写一个纯函数,该函数将返回一个包含最终结果、所采取的步骤和调用计数的数据结构:
{
digit: 4,
steps: [39, 27, 14, 4],
calls: 3
}
Run Code Online (Sandbox Code Playgroud)
然后,您可以根据需要记录这些步骤,或存储它们以供进一步处理。
这是一个这样做的版本:
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))Run Code Online (Sandbox Code Playgroud)
请注意,我们跟踪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, [])
}
Run Code Online (Sandbox Code Playgroud)
在任何一种情况下,将数字乘法提取到辅助函数中可能会更简洁一些:
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])
Run Code Online (Sandbox Code Playgroud)
为什么不在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)
Run Code Online (Sandbox Code Playgroud)
我让它在 Chrome 79 和 Firefox 72 中工作