你会如何限制递归函数?

时间:2017-10-04 03:35:53

标签: javascript asynchronous recursion

假设我想运行一个需要数周,数月甚至数年才能完成的递归函数。它根据指定的参数返回字符串的所有可能排列。在它运行的同时,我希望能够看到它的进展有多远 - 例如到目前为止它产生了多少排列。简而言之,我想要一个非常长时间运行的递归函数来执行而不会锁定我的UI。

另外,我想在严格模式下使用vanilla ES5,,并且没有WebWorkers。它应该能够在IE9中运行。

我的工作正常,但是当我将numspaces提升到10时,浏览器会锁定。所以我假设我只是在努力工作浏览器,并且"限制"它必须做的工作量将有助于解决这个问题。我确实尝试将setTimeout延迟从1增加到250甚至1000,但浏览器仍然被锁定。

我对此感兴趣只是因为我试图这样做,而且无法做到。此外,我知道这个代码非常低效,并且有很多更好的方法来实现我想要实现的目标。所以推荐他们!



var inputString = "abcdefghijklmnopqrstuvwxyz";

function allPossibleCombinations(input, length, curstr, callback) {
  if (curstr.length === length) return callback(curstr);
  
  (function(n) {
    setTimeout(allPossibleCombinations.bind(n, input, length, curstr + input[n], callback), 1);

    n++;
    
    if (n < input.length) setTimeout(arguments.callee.bind(n,n), 1);
  })(0);
}

var totalResults = 0,
    numDigits = inputString.length,
    numSpaces = 2,
    maxResults = Math.pow(numDigits, numSpaces),
    consoleElement = document.getElementById('console'),
    startTime = +new Date();

console.log("Starting.. expecting", maxResults, "total results...");

allPossibleCombinations(inputString.split(""), numSpaces, "", function(result) {
  totalResults++;

  if (totalResults === maxResults) {
    var elapsed = +new Date() - startTime;
    
    consoleElement.innerText = "Done.";
    console.log("Completed in", elapsed, "ms!");
  } else {
    // Do something with this permutation...
    //...
    
    // Show progress...
    var progress = ((totalResults / maxResults) * 100).toFixed(2) * 1;
    consoleElement.innerText = progress + "%";
  }
});
&#13;
<div id="console"></div>
&#13;
&#13;
&#13;

1 个答案:

答案 0 :(得分:0)

您正在接近setTimeout,但当前实现会立即为给定前缀的所有计时器排队,从而导致指数数量的计时器和快速内存耗尽。一个小的改变是创建另一个回调来指示完成并使用它来等待递归调用,从不一次持有多个定时器:

&#13;
&#13;
var inputString = "abcdefghijklmnopqrstuvwxyz";

function allPossibleCombinations(input, length, curstr, resultCallback, doneCallback) {
  if (curstr.length === length) {
    resultCallback(curstr);
    doneCallback();
    return;
  }
  
  var n = 0;
  
  (function next() {
    if (n === input.length) {
      doneCallback();
      return;
    }
  
    allPossibleCombinations(
      input, length, curstr + input[n],
      resultCallback,
      function () {
        n++;
        setTimeout(next, 0);
      });
  })();
}

var totalResults = 0,
    numDigits = inputString.length,
    numSpaces = 4,
    maxResults = Math.pow(numDigits, numSpaces),
    consoleElement = document.getElementById('console'),
    startTime = +new Date();

console.log("Starting.. expecting", maxResults, "total results...");

allPossibleCombinations(
  inputString.split(""), numSpaces, "",
  function (result) {
    totalResults++;

    // Do something with this permutation...
    //...

    // Show progress...
    var progress = ((totalResults / maxResults) * 100).toFixed(2) * 1;
    consoleElement.innerText = progress + "%";
  },
  function () {
    var elapsed = +new Date() - startTime;

    consoleElement.innerText = "Done.";
    console.log("Completed in", elapsed, "ms!");
  });
&#13;
<div id="console"></div>
&#13;
&#13;
&#13;

但是,这真的很慢。考虑如何将其写为生成器:

function* strings(input, length, current) {
    if (current.length === length) {
        yield current;
        return;
    }

    for (let i = 0; i < input.length; i++) {
        yield* strings(input, length, current + input[i]);
    }
}

并将其转换为回调负责恢复生成的系统:

function strings(input, length, current, yield_, continue_) {
    if (current.length === length) {
        yield_(current, continue_);
        return;
    }

    var i = 0;

    (function next() {
        if (i === input.length) {
            continue_();
            return;
        }

        strings(input, length, current + input[i++], yield_, next);
    })();
}

您可以灵活地将计时器设置为您不希望的性能。

&#13;
&#13;
"use strict";

function countSequences(n, k) {
    var result = 1;

    for (var i = 0; i < k; i++) {
        result *= n--;
    }

    return result;
}

function strings(input, length, current, yield_, continue_) {
    if (current.length === length) {
        yield_(current, continue_);
        return;
    }

    var i = 0;

    (function next() {
        if (i === input.length) {
            continue_();
            return;
        }

        var c = input[i++];
        strings(input.replace(c, ''), length, current + c, yield_, next);
    })();
}

var inputString = "abcdefghijklmnopqrstuvwxyz";
var totalResults = 0;
var numDigits = inputString.length;
var numSpaces = 5;
var maxResults = countSequences(numDigits, numSpaces);
var consoleElement = document.getElementById('console');
var startTime = +new Date();

console.log("Starting… expecting", maxResults, "total results.");

strings(
    inputString, numSpaces, "",
    function (result, continue_) {
        if (totalResults++ % 1000 === 0) {
            var progress = (totalResults / maxResults * 100).toFixed(2);
            consoleElement.innerText = progress + "% (" + result + ")";
            setTimeout(continue_, 0);
        } else {
            continue_();
        }
    },
    function () {
        var elapsed = +new Date() - startTime;

        consoleElement.innerText = "Done.";
        console.log("Completed in", elapsed, "ms!");
    });
&#13;
<div id="console"></div>
&#13;
&#13;
&#13;

(这种风格仍然不是最优的,但无论个别操作有多快,它都永远不会完成26 10