【问题标题】:Javascript pattern to break up long-running recursive functionJavascript模式打破长期运行的递归函数
【发布时间】:2015-06-01 18:48:43
【问题描述】:

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

这是我的代码的 sn-p:

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. 进度报告

一旦我有了异步模式,取消将很容易(我可以自己做),所以我真的只需要进度报告方面的帮助,或者更准确地说,只需根据 @987654323 将递归模式分解成块@ 多变的。即

/* ... */
permutationsCount++;
if (permutationsCount % chunkSize === 0) 
    /* end this chunk and start a new one */

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

想法?

【问题讨论】:

  • JavaScript 是单线程的,您要么必须使用 WebWorker (developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/…),要么只需对服务器执行工作并使用 ajax 轮询它的进度/完成情况。
  • 我知道 JavaScript 是单线程的,但是使用 Promises,你可以做一些计算,并在当前运行的 Promise 中添加一个.then(),以便在块完成后继续计算。不过,在这两者之间,浏览器可以做其他事情(事件、更新 UI 等),模仿异步行为。
  • 对不起,我假设你是在客户端用JS计算,你是在服务器端计算吗?
  • 不,我在客户端上做。无论如何,服务器都在 node.js 上运行,所以这无济于事。 :)
  • 使用网络工作者,你的例子正是为网络工作者创建的:)

标签: javascript asynchronous recursion


【解决方案1】:

这是我写的一个函数来做类似的事情。这是一个完全在 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();
};

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-11-16
        • 1970-01-01
        相关资源
        最近更新 更多