简体   繁体   中英

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.

Also, I would like to do this with vanilla ES5, not in strict mode, and without WebWorkers. It should be able to run in IE9.

What I have works fine as-is, but when I raise numspaces to 10, for example, the browser locks up. 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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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