简体   繁体   English

您将如何限制递归函数?

[英]How would you throttle a recursive function?

Let's suppose I want to run a recursive function that will take weeks, months or even years to complete. 假设我要运行一个递归函数,它将需要数周,数月甚至数年的时间才能完成。 It returns all possible permutations of a string based on the specified parameters. 它根据指定的参数返回字符串的所有可能排列。 While it's running, I want to be able to see how far along it is progressing - eg how many permutations it has generated so far. 当它运行时,我希望能够看到它进展的程度-例如,到目前为止已经产生了多少排列。 In a nutshell, I want a very long-running recursive function to execute without locking up my UI. 简而言之,我希望执行一个运行时间很长的递归函数而不锁定我的UI。

Also, I would like to do this with vanilla ES5, not in strict mode, and without WebWorkers. 另外,我想使用香草ES5( 不是严格模式)并且没有WebWorkers来执行此操作。 It should be able to run in IE9. 它应该能够在IE9中运行。

What I have works fine as-is, but when I raise numspaces to 10, for example, the browser locks up. 我所拥有的numspaces都可以正常使用,但是例如,当我将numspaces提高到10时,浏览器就会锁定。 So I am assuming that I am just working the browser too hard, and "throttling" the amount of work it has to do would help solve this problem. 因此,我假设我只是在过于努力地使用浏览器,而“限制”它必须完成的工作量将有助于解决此问题。 I did try increasing the setTimeout delays from 1 to 250 and even 1000, but the browser still locked up. 我确实尝试将setTimeout延迟从1增加到250,甚至1000,但是浏览器仍然锁定。

I am interested in this simply because I tried to do it, and couldn't. 我对此感兴趣只是因为我尝试这样做,但是没有。 Also, I know for a fact that this code is terribly inefficient and there are much, much better ways to do what I am looking to achieve. 另外,我知道一个事实,该代码效率极低,并且有很多更好的方法可以完成我想要实现的目标。 So recommend them! 所以推荐他们!

 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 + "%"; } }); 
 <div id="console"></div> 

You're getting close with the setTimeout , but the current implementation queues up all the timers for a given prefix at once, resulting in an exponential number of timers and quick memory exhaustion. 您将很快接近setTimeout ,但是当前实现一次将所有计时器排入给定前缀的所有计时器中,从而导致计时器数量成倍增加,并且内存快速耗尽。 One small change would be to create another callback to indicate completion and use it to wait on recursive calls, never holding more than one timer at once: 一个小的更改是创建另一个回调以指示完成,并使用它来等待递归调用,而永远不要一次持有多个计时器:

 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!"); }); 
 <div id="console"></div> 

That's really slow, though. 不过,那确实很慢。 Thinking of how you could write this as a generator: 考虑如何将其编写为生成器:

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

and translating that to a system where the callback is responsible for resuming generation: 并将其转换为由回调负责恢复生成的系统:

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

you can have the flexibility of setting a timer as infrequently as you'd like for performance. 您可以随意设置计时器,以提高性能。

 "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!"); }); 
 <div id="console"></div> 

(This style is still non-optimal, but it'll never finish for 26 10 no matter how quick individual operations are.) (这种风格仍然不是最佳,但它永远不会结束的26 10,无论个人操作有多快,是。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM