Javascript模式分解长时间运行的递归函数

Javascript pattern to break up long-running recursive function

本文关键字:递归函数 运行 长时间 模式 分解 Javascript      更新时间:2023-09-26

我有一个长时间运行的函数,它需要进行大量的计算:x个n面骰子的所有可能排列以及这些结果的概率。对于较小的x和n,计算很快。对于较大的值(n = 100, x> 3),计算需要数十秒甚至更长时间;同时,浏览器会停止运行。

下面是我的代码片段:

let dist = [];
// min and max are the minimum and maximum possible values 
// (if dice are all 1's or all their maximum values)
for (let i = min; i <= max; i++) {
    // initialize possible values of our distribution to 0
    dist.push([ i, 0 ]);
}
// total is the total outcome value so far
// dIndex is the index into the dice-array (diceList) for the die we're
//   currently applying to our total--the die we're "rolling"
function applyDie(total, dIndex) {
    if (dIndex === diceList.length) {
        dist[total - min][1]++;
        permutationsCount++;
        return;
    }
    // diceList is an array of integers representing the number of sides
    // for each die (one array element = one die of element-value sides)
    for (let i = 1; i <= diceList[dIndex]; i++) {
        applyDie(total + i, dIndex + 1);
    }
}
// kick off recursive call
applyDie(0, 0);

我想添加两个功能:

    取消
  1. 进展报告
一旦我有了异步模式,取消将很容易(我可以自己做),所以我真的只需要进度报告的帮助,或者,更准确地说,简单地将递归模式分解为基于permutationsCount变量的块。例如
/* ... */
permutationsCount++;
if (permutationsCount % chunkSize === 0) 
    /* end this chunk and start a new one */

我更喜欢使用Javasciprt Promises,但我愿意接受其他建议。

想法?

我写了一个函数来做类似的事情。这是一个完全在javascript中完成的计算…从你的问题中我看不出你是完全在客户端工作还是什么。

// Break the list up into equal-sized chunks, applying f() to each item of
// the list, writing a %-complete value to the progress span after each
// chunk. Then execute a callback with the resulting data.
var chunkedLoop = function (list, f, progressSpan, callback) {
    var numChunks = 10,
        chunkSize = Math.round(list.length / numChunks),
        chunkedList = [],  // will be a list of lists
        // Concatenated results of all chunks, passed to the callback.
        resultList = [],
        x,  // just a loop variable
        chunkIndex = 0;  // tracks the current chunk across timeouts
    progressSpan.html(0);
    // Splice of chunks of equal size, but allow the last one to be of an
    // arbitrary size, in case numChunks doesn't divide the length of the
    // list evenly.
    for (x = 0; x < numChunks - 1; x += 1) {
        chunkedList.push(list.splice(0, chunkSize));
    }
    chunkedList.push(list);
    // Schedule a series of timeouts, one for each chunk. The browser
    // stops blocking for a moment between each chunk, allowing the screen
    // to update. This is the only way to have progress values reported to
    // the view mid-loop. If it was done in a single loop, execution would
    // block all the way to the end, and the screen would only update once
    // at 100%.
    var chunkFunction = function () {
        setTimeout(function () {
            // Run f on the chunk.
            var chunk = chunkedList[chunkIndex];
            var r = forEach(chunk, f);
            resultList = resultList.concat(r);
            chunkIndex += 1;
            // Update progress on the screen.
            progressSpan.html(Math.round(chunkIndex / numChunks * 100));
            // Schedule the next run, if this isn't the last chunk. If it
            // is the last chunk, execute the callback with the results.
            if (chunkIndex < chunkedList.length) {
                chunkFunction();
            } else if (callback instanceof Function) {
                callback.call(undefined, resultList);
            }
        // There's no reason to delay more than the minimum one
        // millisecond, since the point is just to break up javascript's
        // single-threaded blocking.
        }, 1);
    };
    chunkFunction();
};

对于报告状态,您可以将回调函数传递到递归函数中,并在那里做任何您喜欢的事情(增加计数器,将状态推入页面等)。

还可以考虑将递归算法重写为迭代算法,因为这样会减少内存开销,并且更容易放入其他逻辑(如您提到的取消)

您可以使用setTimeout让JavaScript做其他事情并解除事件循环。这样,即使无限循环也不会阻塞。下面是一个简单的示例:

http://jsfiddle.net/xx5adut6/

function isPrime(n) {
    // If n is less than 2 or not an integer then by definition cannot be prime.
    if (n < 2) {
        return false
    }
    if (n != Math.round(n)) {
        return false
    }
    var isPrime = true;
    for (var i = 2; i <= Math.sqrt(n); i++) {
        if (n % i == 0) {
            isPrime = false
        }
    }
    // Finally return whether n is prime or not.
    return isPrime;
}
var cancel = false;
var i = 0;
var primesFound = 0;
var status = $('.status');
var timeout;
function loop4Primes() {
    if (cancel) {
        clearTimeout(timeout);
        return;
    }
    if (isPrime(i++)) primesFound++;
    timeout = setTimeout(loop4Primes, 1);
}
function updateText() {
    status.text(primesFound);
}
var interval = setInterval(updateText, 1000);
$('#start').click(function () {
    loop4Primes();
    $(this).off('click');
});
$('#stop').click(function () {
    clearInterval(interval);
    updateText();
    cancel = true;
});