非常大的递归javascript

时间:2018-06-18 19:22:25

标签: javascript recursion promise

我有这个代码,它使用递归计算数组元素和,但是当数组太大时,它会抛出最大调用堆栈大小错误。

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);

我该如何解决?

任何帮助将不胜感激!

7 个答案:

答案 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();