Javascript模式打破长时间运行的递归函数

时间:2015-06-01 18:48:44

标签: javascript asynchronous recursion

我有一个长期运行的函数,可以进行大量计算: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. 取消
  2. 进度报告
  3. 一旦我有异步模式,取消将很容易(我可以自己做),所以我真的只需要帮助进度报告,或者更确切地说,简单地将递归模式分解为基于{{{ 1}}变量。即

    permutationsCount

    我更愿意使用Javasciprt /* ... */ permutationsCount++; if (permutationsCount % chunkSize === 0) /* end this chunk and start a new one */ ,但我可以接受其他建议。

    想法?

3 个答案:

答案 0 :(得分:0)

这是我写的一个类似功能的函数。它是完全用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();
};

答案 1 :(得分:0)

对于报告状态,您可以将回调函数传递到recursuve函数中并随意执行(增加计数器,将状态推送到页面等)。

还要考虑将递归重写为迭代算法,因为它会有较少的内存开销,并且更容易放一些其他逻辑(比如你提到的取消)

答案 2 :(得分:0)

您可以使用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;
});