我有这个代码,它使用递归计算数组元素和,但是当数组太大时,它会抛出最大调用堆栈大小错误。
var a = new Array(10 ** 7);
a.fill(0);
function sum(arr, i = 0) {
if(i === arr.length) {
return 0;
}
return arr[i] + sum(arr, i + 1);
}
sum(a);
因此我需要以某种方式更改它以便在所有情况下正常工作,我认为我可以使它与Promises异步工作,但它总是返回承诺未决。
var a = new Array(10 ** 7);
a.fill(0);
var sum = (arr, i = 0) => new Promise((resolve) => {
setTimeout(() => {
if(i === arr.length) {
return resolve(0);
}
return sum(arr, i + 1).then(el => el + arr[i]);
}, 0);
});
sum(a);
我该如何解决?
任何帮助将不胜感激!
答案 0 :(得分:2)
你只是在解决我是arr.length的情况,所以所有链接的承诺永远都待决。退货不会自动为我们解决,所以需要明确:
var a = new Array(10);
a.fill(0);
a[0] = 3;
var sum = (arr, i = 0) => new Promise((resolve) => {
setTimeout(() => {
if(i === arr.length) {
resolve(0);
} else {
resolve(sum(arr, i + 1).then(el => el + arr[i]));
}
}, 0);
});
sum(a).then(console.log)
答案 1 :(得分:1)
我不知道你为什么要使用promises并使函数异步。但如果你这样做,你需要解决这两种情况:
const sum = (arr, i = 0) => new Promise((resolve) => {
setTimeout(() => {
if(i === arr.length) {
return resolve(0);
}
return sum(arr, i + 1).then(el => resolve(el + arr[i]));
}, 0);
});
现在这也会返回一个承诺。一旦你异步,你就永远不会回去。您需要使用返回的promise来获取返回值:
sum([1, 2, 3, 4]).then(return => console.log(return));
最好不要让它异步。 ES6支持尾递归,所以你可以这样做:
function sum(arr) {
function helper(acc, i) {
if (i<0) {
return acc;
}
return helper(arr[i]+acc, i-1);
}
return helper(0, arr.length - 1);
}
由于订单无关紧要,我冒昧地将值相加。现在信不信由你,但是helper
已经在语言中作为抽象存在,它为每个元素提供了价值,并且每次迭代都提供acc
。因此,您可以这样做:
function sum(arr) {
return arr.reduce((acc, val) => acc + val, 0)
}
答案 2 :(得分:1)
setTimeout
不是堆栈溢出问题的解决方案。管理堆栈是对函数调用进行排序的问题。您可以通过多种方式执行此操作,最直观的是loop
/ recur
蹦床。
const loop = f =>
{ let acc = f ()
while (acc && acc.tag === recur)
acc = f (...acc.values)
return acc
}
const recur = (...values) =>
({ tag: recur, values })
const sum = (xs = []) =>
loop ((result = 0, i = 0) => // initial state
i >= xs.length // exit condition
? result // return value
: recur (result + xs[i], i + 1)) // next state
const tenMillionNumbers =
Array.from (Array (1e7), (_,n) => n)
console.time ('recursion is not slow')
console.log (sum (tenMillionNumbers))
console.timeEnd ('recursion is not slow')
// => 49999995000000
// recursion is not slow: 2171ms
我为此问题涵盖了许多其他技巧here。
堆栈安全递归是我写的很多内容,主题为almost 30 answers
答案 3 :(得分:1)
对于使用本机任务的问题,有一些解决方案。这是使用Promise安排微任务的时间:
(function() {
function sum(arr, i = 0) {
if(arr.length === i) {
return Promise.resolve(0);
}
return Promise.resolve(null)
.then(() => sum(arr, i + 1))
.then((x) => x + arr[i]);
}
sum(a).then(s => console.log(s));
}());
,但这将迫使引擎等待执行完成。因此,对于大型数组,我不建议您在主线程上执行此操作。
您还可以执行以下操作:
(function() {
function sum(arr, i = 0) {
if(arr.length === i) {
return Promise.resolve(0);
}
return new Promise(resolve => {
setTimeout(() => {
sum(arr, i + 1).then(x => resolve(x + arr[i]));
});
});
}
sum(a).then(s => console.log(s));
}());
然后对此代码进行一些更改并使它更优雅,我们可以执行以下操作:
(function() {
const defer = () => new Promise((resolve) => setTimeout(resolve));
async function sum(arr, i = 0) {
if(arr.length === i) {
return 0
}
await defer();
return await sum(arr, i + 1) + arr[i];
}
sum(a).then(s => console.log(s));
}());
,如果您的环境支持尾递归,则可以将其更改为使用它:http://2ality.com/2015/06/tail-call-optimization.html
实际上,还有另一种方法可以做到这一点。 rxjs库提供了一个队列调度程序,可以帮助您使递归逻辑变得迭代,而无需在代码中进行很多更改。我已经创建了您的sum
方法here的示例。
答案 4 :(得分:0)
var a = new Array(1000);
a.fill(1);
function sum(arr, i = 0, res = 0, resolve) {
if(!i) return new Promise((resolve) => sum(arr, i + 1, res + arr[i], resolve), 0);
if(i === arr.length) return resolve(res);
setTimeout(function(){
sum(arr, i + 1, res + arr[i], resolve);
}, 0);
}
sum(a).then(console.log);
答案 5 :(得分:-1)
您需要使用迭代过程而不是递归过程。因此,不是累积调用,而是计算每次迭代调用的总和。这可以通过使用将获得(sum, array, currentValue)
答案 6 :(得分:-1)
如果由于某种原因你需要使用递归并且不介意这需要长,你可以使用超时;但是我绝对不会 推荐使用它。我主要是发布它,因为它是可能的。
var arr = new Array(10 ** 7);
arr.fill(0);
var len = arr.length,
idx = 0,
sum = 0,
sumFn = () => {
setTimeout(() => {
sum += arr[idx];
idx++;
if (idx < len) {
sumFn();
} else {
//complete
}
}, 1);
}
sumFn();