简体   繁体   English

Eratosthenes 算法在 JavaScript 中的筛法为大量运行无穷无尽

[英]Sieve of Eratosthenes algorithm in JavaScript running endless for large number

I have been trying to write Sieve of Eratosthenes algorithm in JavaScript. Basically I just literally followed the steps below:我一直在尝试在 JavaScript 中编写埃拉托色尼筛法算法。基本上我只是按照以下步骤操作:

  1. Create a list of consecutive integers from 2 to (n-1)创建一个从 2 到 (n-1) 的连续整数列表
  2. Let first prime number p equal 2让第一个质数 p 等于 2
  3. Starting from p, count up in increments of p and removes each of these numbers (p and multiples of p)从 p 开始,以 p 为增量向上计数并删除每个数字(p 和 p 的倍数)
  4. Go to the next number in the list and repeat 2,3,4 Go 到列表中的下一个数字并重复 2、3、4
  5. Add unintentionally deleted prime numbers back to the list将无意中删除的质数添加回列表

and this is what I have come up with:这就是我想出的:

function eratosthenes(n){
var array = [];
var tmpArray = []; // for containing unintentionally deleted elements like 2,3,5,7,...
var maxPrimeFactor = 0;
var upperLimit = Math.sqrt(n);
var output = [];

// Eratosthenes algorithm to find all primes under n

// Make an array from 2 to (n - 1)
//used as a base array to delete composite number from
for(var i = 2; i < n; i++){
    array.push(i);
}

// Remove multiples of primes starting from 2, 3, 5,...
for(var i = array[0]; i < upperLimit; i = array[0]){
    removeMultiples: 
    for(var j = i, k = i; j < n; j += i){
        var index = array.indexOf(j);
        if(index === -1)
            continue removeMultiples;
        else
            array.splice(index,1);
    }
    tmpArray.push(k);
}
array.unshift(tmpArray);
return array;
}

It works for small numbers but not for numbers larger than one million.它适用于小数字,但不适用于大于一百万的数字。 I used Node.js to test and the process just seems endless and no memory error shown up.我使用 Node.js 进行测试,这个过程似乎无穷无尽,没有显示 memory 错误。 I've read a solution here (also in javascript) but still cannot fully comprehend it.我在这里(也在 javascript 中)阅读了一个解决方案,但仍然无法完全理解它。

Question: How to make this work for sufficiently large numbers such as one million and above?问题:如何使这项工作适用于足够大的数字,例如 100 万及以上?

You are making the Sieve of Eratosthenes much slower by making use of array manipulation functions such as Array#indexOf and Array#splice which runs in linear time.通过使用以线性时间运行的数组操作函数(例如Array#indexOfArray#splice ,您正在使 Eratosthenes 的筛选速度变慢。 When you can have O(1) for both operations involved.当涉及的两个操作都可以使用 O(1) 时。

Below is the Sieve of Eratosthenes following conventional programming practices:以下是遵循传统编程实践的埃拉托色尼筛法:

var eratosthenes = function(n) {
    // Eratosthenes algorithm to find all primes under n
    var array = [], upperLimit = Math.sqrt(n), output = [];

    // Make an array from 2 to (n - 1)
    for (var i = 0; i < n; i++) {
        array.push(true);
    }

    // Remove multiples of primes starting from 2, 3, 5,...
    for (var i = 2; i <= upperLimit; i++) {
        if (array[i]) {
            for (var j = i * i; j < n; j += i) {
                array[j] = false;
            }
        }
    }

    // All array[i] set to true are primes
    for (var i = 2; i < n; i++) {
        if(array[i]) {
            output.push(i);
        }
    }

    return output;
};

You can see a live example for n = 1 000 000 here.您可以在此处查看n = 1 000 000的实时示例。

This question is a little stingy on the low side in the definition of what a "large number" is and accepts that it starts at only about a million, for which the current answer works;这个问题在定义什么是“大数”时有点吝啬,并接受它从大约一百万开始,目前的答案适用; however, it uses quite a lot of memory as in one 8-byte number (a double real of 64 bits) for each element to be sieved and another 8-byte number for every found prime.然而,它使用了相当多的内存,因为每个要筛选的元素使用一个 8 字节数(64 位的双实数),每个找到的素数使用另一个 8 字节数。 That answer wouldn't work for "large numbers" of say about 250 million and above as it would exceed the amount of memory available to the JavaScript execution machine.该答案不适用于大约 2.5 亿及以上的“大量”,因为它会超过 JavaScript 执行机器可用的内存量。

The following JavaScript code implementing the "infinite" (unbounded) Page Segmented Sieve of Eratosthenes overcomes that problem in that it only uses one bit-packed 16 Kilobyte page segmented sieving buffer (one bit represents one potential prime number) and only uses storage for the base primes up to the square root of the current highest number in the current page segment, with the actual found primes enumerated in order without requiring any storage;以下实现 Eratosthenes 的“无限”(无界)页面分段筛选的 JavaScript 代码克服了这个问题,因为它只使用一个位打包的 16 KB 页面分段筛选缓冲区(一位代表一个潜在的质数)并且只使用存储基数直到当前页段中当前最大数的平方根,实际找到的素数按顺序枚举,无需任何存储; also saving time by only sieving odd composites as the only even prime is 2:还可以通过仅筛选奇数复合物来节省时间,因为唯一的偶数素数是 2:

var SoEPgClass = (function () {
  function SoEPgClass() {
    this.bi = -1; // constructor resets the enumeration to start...
  }
  SoEPgClass.prototype.next = function () {
    if (this.bi < 1) {
      if (this.bi < 0) {
        this.bi++;
        this.lowi = 0; // other initialization done here...
        this.bpa = [];
        return 2;
      } else { // bi must be zero:
        var nxt = 3 + 2 * this.lowi + 262144; //just beyond the current page
        this.buf = [];
        for (var i = 0; i < 2048; i++) this.buf.push(0); // faster initialization 16 KByte's:
        if (this.lowi <= 0) { // special culling for first page as no base primes yet:
          for (var i = 0, p = 3, sqr = 9; sqr < nxt; i++, p += 2, sqr = p * p)
            if ((this.buf[i >> 5] & (1 << (i & 31))) === 0)
              for (var j = (sqr - 3) >> 1; j < 131072; j += p)
                this.buf[j >> 5] |= 1 << (j & 31);
        } else { // other than the first "zeroth" page:
          if (!this.bpa.length) { // if this is the first page after the zero one:
            this.bps = new SoEPgClass(); // initialize separate base primes stream:
            this.bps.next(); // advance past the only even prime of 2
            this.bpa.push(this.bps.next()); // keep the next prime (3 in this case)
          }
          // get enough base primes for the page range...
          for (var p = this.bpa[this.bpa.length - 1], sqr = p * p; sqr < nxt;
            p = this.bps.next(), this.bpa.push(p), sqr = p * p);
          for (var i = 0; i < this.bpa.length; i++) { //for each base prime in the array
            var p = this.bpa[i];
            var s = (p * p - 3) >> 1; //compute the start index of the prime squared
            if (s >= this.lowi) // adjust start index based on page lower limit...
              s -= this.lowi;
            else { //for the case where this isn't the first prime squared instance
              var r = (this.lowi - s) % p;
              s = (r != 0) ? p - r : 0;
            }
            //inner tight composite culling loop for given prime number across page
            for (var j = s; j < 131072; j += p) this.buf[j >> 5] |= 1 << (j & 31);
          }
        }
      }
    }
    //find next marker still with prime status
    while (this.bi < 131072 && this.buf[this.bi >> 5] & (1 << (this.bi & 31))) this.bi++;
    if (this.bi < 131072) // within buffer: output computed prime
      return 3 + ((this.lowi + this.bi++) * 2);
    else { // beyond buffer range: advance buffer
      this.bi = 0;
      this.lowi += 131072;
      return this.next(); // and recursively loop just once to make a new page buffer
    }
  };
  return SoEPgClass;
})();

The above code can be utilized to count the primes up to the given limit by the following JavaScript code:上面的代码可用于通过以下 JavaScript 代码计算达到给定限制的素数:

window.onload = function () {
  var elpsd = -new Date().getTime();
  var top_num = 1000000000;
  var cnt = 0;
  var gen = new SoEPgClass();
  while (gen.next() <= top_num) cnt++;
  elpsd += (new Date()).getTime();
  document.getElementById('content')
    .innerText = 'Found ' + cnt + ' primes up to ' + top_num + ' in ' + elpsd + ' milliseconds.';
};

If the above two pieces of JavaScript code are put into a file named app.js in the same folder as the following HTML code named whatever.html, you will be able to run the code in your browser by opening the HTML file in it:如果将上述两段 JavaScript 代码放入与以下名为whatever.html 的 HTML 代码相同文件夹中的名为 app.js 的文件中,您将能够通过打开其中的 HTML 文件在浏览器中运行该代码:

<!DOCTYPE html>

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Page Segmented Sieve of Eratosthenes in JavaScript</title>
    <script src="app.js"></script>
  </head>
  <body>
    <h1>Page Segmented Sieve of Eratosthenes in JavaScript.</h1>

    <div id="content"></div>
  </body>
</html>

This code can sieve to the one billion range is a few 10's of seconds when run on a JavaScript execution engine using Just-In-Time (JIT) compilation such as Google Chrome's V8 engine.当在使用即时 (JIT) 编译的 JavaScript 执行引擎(例如 Google Chrome 的 V8 引擎)上运行时,此代码可以筛选到 10 亿的范围,即几十秒。 Further gains can be achieved by using extreme wheel factorization and pre-culling of the page buffers of the lowest base primes in which case the amount of work performed can be cut by a further factor of four, meaning that the number of primes can be counted up to a billion in a few seconds (counting does not require enumeration as used here but rather can use bit count techniques on the page segment buffers directly), although at the cost of increased code complexity.通过使用极限轮分解和对最低基本素数的页面缓冲区的预剔除可以实现进一步的收益,在这种情况下,执行的工作量可以进一步减少四倍,这意味着可以计算素数的数量在几秒钟内高达 10 亿(计数不需要这里使用的枚举,而是可以直接在页段缓冲区上使用位计数技术),尽管以增加代码复杂性为代价。

EDIT_ADD:编辑_添加:

The execution speed can be sped up by a factor of three or more by using TypedArray and the asm.js optimizations from ECMAScript 2015 (now supported in all common browsers) with the code modifications as follows:通过使用 TypedArray 和 ECMAScript 2015 中的 asm.js 优化(现在所有常见浏览器都支持),执行速度可以提高三倍或更多,代码修改如下:

"use strict";
var SoEPgClass = (function () {
  function SoEPgClass() {
    this.bi = -1; // constructor resets the enumeration to start...
    this.buf = new Uint8Array(16384);
  }
  SoEPgClass.prototype.next = function () {
    if (this.bi < 1) {
      if (this.bi < 0) {
        this.bi++;
        this.lowi = 0; // other initialization done here...
        this.bpa = [];
        return 2;
      } else { // bi must be zero:
        var nxt = 3 + 2 * this.lowi + 262144; // just beyond the current page
        for (var i = 0; i < 16384; ++i) this.buf[i] = 0 >>> 0; // zero buffer
        if (this.lowi <= 0) { // special culling for first page as no base primes yet:
          for (var i = 0, p = 3, sqr = 9; sqr < nxt; ++i, p += 2, sqr = p * p)
            if ((this.buf[i >> 3] & (1 << (i & 7))) === 0)
              for (var j = (sqr - 3) >> 1; j < 131072; j += p)
                this.buf[j >> 3] |= 1 << (j & 7);
        } else { // other than the first "zeroth" page:
          if (!this.bpa.length) { // if this is the first page after the zero one:
            this.bps = new SoEPgClass(); // initialize separate base primes stream:
            this.bps.next(); // advance past the only even prime of 2
            this.bpa.push(this.bps.next()); // keep the next prime (3 in this case)
          }
          // get enough base primes for the page range...
          for (var p = this.bpa[this.bpa.length - 1], sqr = p * p; sqr < nxt;
            p = this.bps.next(), this.bpa.push(p), sqr = p * p);
          for (var i = 0; i < this.bpa.length; ++i) { // for each base prime in the array
            var p = this.bpa[i] >>> 0;
            var s = (p * p - 3) >>> 1; // compute the start index of the prime squared
            if (s >= this.lowi) // adjust start index based on page lower limit...
              s -= this.lowi;
            else { // for the case where this isn't the first prime squared instance
              var r = (this.lowi - s) % p;
              s = (r != 0) ? p - r : 0;
            }
            if (p <= 8192) {
              var slmt = Math.min(131072, s + (p << 3));
              for (; s < slmt; s += p) {
                var msk = (1 >>> 0) << (s & 7);
                for (var j = s >>> 3; j < 16384; j += p) this.buf[j] |= msk;
              }
            }
            else
              // inner tight composite culling loop for given prime number across page
              for (var j = s; j < 131072; j += p) this.buf[j >> 3] |= (1 >>> 0) << (j & 7);
          }
        }
      }
    }
    //find next marker still with prime status
    while (this.bi < 131072 && this.buf[this.bi >> 3] & ((1 >>> 0) << (this.bi & 7)))
      this.bi++;
    if (this.bi < 131072) // within buffer: output computed prime
      return 3 + ((this.lowi + this.bi++) << 1);
    else { // beyond buffer range: advance buffer
      this.bi = 0;
      this.lowi += 131072;
      return this.next(); // and recursively loop just once to make a new page buffer
    }
  };
  return SoEPgClass;
})();

The speed up works because it uses the pre-typed ECMAScript primitive arrays to avoid overheads by directly using integers in the arrays (also avoiding wasting space by using float representations) and also uses the type hints available using asm.js in causing bit manipulations to use unsigned integers/bytes.加速是有效的,因为它使用预先类型化的 ECMAScript 原始数组,通过直接在数组中使用整数来避免开销(也通过使用浮点表示避免浪费空间),并且还使用使用 asm.js 的可用类型提示导致位操作使用无符号整数/字节。 as well, to save the time for array allocations, it now allocations the sieving array once and just zeros it for each new page segment.同样,为了节省数组分配的时间,它现在分配筛分数组一次,并为每个新页面段将其归零。 It now sieves to a billion in about 16 seconds on a low end 1.92 Gigahertz CPU rather than about 50 seconds.现在,它在低端 1.92 Gigahertz CPU 上在大约 16 秒内筛选到 10 亿,而不是大约 50 秒。 As well, the algorithm is modified to simplify the inner composite number representation (in bit packed bits) for extra speed for smaller primes, which is the majority of the culling operations.同样,该算法经过修改以简化内部合数表示(以位打包位),以提高较小素数的速度,这是大部分剔除操作。

Note that now about 60% of the expended time is now spent in just enumerating the found primes.请注意,现在大约 60% 的时间花费在枚举找到的素数上。 This could be greatly reduced for the usual use of such a sieve to just count the found primes by just summing the number of zero bits in the array for each segment page.对于通常使用这种筛子来计算找到的素数的情况,这可以大大减少,只需对每个段页面的数组中的零位数求和即可。 If this was done, the time to sieve to a billion would be something close to 7 seconds on this low end CPU and there may be another few optimizations possible (all timing using the Google Chrome version 72 V8 JavaScript engine which is constantly being improved and later versions may run faster).如果这样做,在这个低端 CPU 上筛选到 10 亿的时间将接近 7 秒,并且可能还有其他一些优化可能(所有时间都使用 Google Chrome 版本 72 V8 JavaScript 引擎,该引擎不断改进和更高版本可能运行得更快)。

TBH, I personally dislike JavaScript with all its extensions and complexities that have been necessary to make it a "modern" language and in particular don't like dynamic typing, so embraced Microsoft's TypeScript when it appeared some years ago. TBH,我个人不喜欢 JavaScript 的所有扩展和复杂性,这些扩展和复杂性是使其成为“现代”语言所必需的,特别是不喜欢动态类型,因此在几年前出现时接受了 Microsoft 的 TypeScript。 The above code is actually a modification of the code as output from TypeScript along with its emphasis on statically typed Object Oriented Programming (OOP).上面的代码实际上是对作为 TypeScript 输出的代码的修改,同时强调了静态类型的面向对象编程 (OOP)。 It occurred to me that the calling of the "next" instance method through the standard way of adding methods to "prototype" may be much slower than just calling a function so I tested it and found that to be exactly the case, with this runnable link enumerating the found primes about two and a half times faster just by changing the enumeration to a simple output closure function.我突然想到,通过向“原型”添加方法的标准方式调用“下一个”实例方法可能比仅调用函数慢得多,因此我对其进行了测试并发现情况确实如此,使用 此可运行通过将枚举更改为简单的输出闭包函数, 链接枚举找到的素数大约快两倍半。

Now, we can eliminate the primes enumeration entirely by just counting the number of found primes as shown in this other runnable link with modified code , showing that even with the above improvement, enumeration of the found primes still costs almost as much time as actually doing the sieving with this algorithm with one able to determine the enumeration time as the difference between the run time for the above two links to runnable code.现在,我们可以通过仅计算找到的素数的数量来完全消除素数枚举,如 另一个带有修改代码的可运行链接所示,表明即使进行了上述改进,找到的素数的枚举仍然花费几乎与实际一样多的时间用这种算法筛选可以确定枚举时间作为上述两个链接到可运行代码的运行时间之间的差异。

Note that the run times for the links will be different (and likely shorter) than as I mention here as most current CPU's will be faster and more powerful than the tablet Windows CPU I am currently using (the Intel x5-Z3850 at 1.92 Gigahertz and the JavaScript gets run on the machine on which you are viewing the link.请注意,链接的运行时间将与我在此处提到的不同(并且可能更短),因为大多数当前的 CPU 将比我目前使用的平板电脑 Windows CPU(英特尔 x5-Z3850,1.92 GHz 和JavaScript 在您查看链接的机器上运行。

This then makes JavaScript only a little slower than the same algorithm implemented on the JVM or DotNet, which is of course still much slower than the highly optimized native code as compiled from languages such as C/C++, Rust, Nim, Haskell, Swift, FreePascal, Julia, etc. which can run this algorithm in about two seconds on this low end CPU.这使得 JavaScript 只比在 JVM 或 DotNet 上实现的相同算法慢一点,当然这仍然比从 C/C++、Rust、Nim、Haskell、Swift 等语言编译的高度优化的本机代码慢得多, FreePascal、Julia 等可以在这个低端 CPU 上运行该算法大约两秒钟。 WebAssembly can run this algorithm up to about two to three times faster than the JavaScript here, depending on the browser implementation;根据浏览器的实现,WebAssembly 可以比这里的 JavaScript 快两到三倍地运行这个算法; as well, when the WebAssembly specification is fully complete and implemented, we will have multi-threading support for further gains by the factor of the number of effective cores used.同样,当 WebAssembly 规范完全完成并实现时,我们将获得多线程支持,以通过使用的有效内核数量来进一步提高收益。

END_EDIT_ADD END_EDIT_ADD

EDIT_ADD_MORE_AND_LATER_EVEN_MORE: EDIT_ADD_MORE_AND_LATER_EVEN_MORE:

Once the above fairly small modifications are done to just count the found primes efficiently rather then enumerate them thus making the counting time a small overhead as compared to sieving them, it would be worth while to make the more extensive changes to use maximum wheel factorization (by not only 2 for "odds-only", but also 3, 5, and 7 for a wheel that covers a span of 210 potential primes) and also pre-culling on initialization of the small sieving arrays so that it is not necessary to cull by the following primes of 11, 13, 17, and 19. This reduces the number of composite number culling operations when using the page segmented sieve by a factor of about four to a range of a billion and can be written so that it runs about four times as fast due to the reduced operations with each culling operation about the same speed as for the above code.一旦完成上述相当小的修改以有效地计算找到的素数而不是枚举它们,从而使计数时间与筛选它们相比是一个很小的开销,则值得进行更广泛的更改以使用最大轮因式分解(不仅 2 表示“仅赔率”,而且 3、5 和 7 表示覆盖 210 个潜在素数的轮子),并且还预先剔除小筛选阵列的初始化,这样就没有必要通过以下 11、13、17 和 19 的素数进行剔除。这将使用分页筛选器时的合数剔除操作次数减少了大约 4 到 10 亿的范围,并且可以编写为运行由于每个剔除操作的速度与上述代码大致相同,因此速度大约是其四倍。

The way of doing the 210-span wheel factorization efficiently is to follow on this method of doing the "odds-only" sieving efficiently: the current algorithm above can be thought of as sieving one bit-packed plane out of two where the other plane can be eliminated as it contains only the even numbers above two;有效地进行 210 跨度轮分解的方法是遵循这种有效地进行“仅几率”筛分的方法:上面的当前算法可以被认为是从两个平面中筛出一个位填充平面,而另一个平面可以消除,因为它只包含两个以上的偶数; for the 210-span we can define 48 bit-packed arrays of this size representing possible primes of 11 and above where all the other 162 planes contain numbers which are factors of two, three, five, or seven and thus don't need to be considered.对于 210 跨度,我们可以定义 48 个此大小的位打包数组,表示 11 及以上的可能素数,其中所有其他 162 个平面包含的数字是 2、3、5 或 7 的因数,因此不需要被考虑。 In this way it is just as efficient to sieve with less memory requirements (by over a half as compared to "odds-only" and as much efficiency as here, where one 48-plane "page" represents 16 Kilobytes = 131072 bits per plane times 210 which is a range of 27,525,120 numbers per sieve page segment, thus only 40 page segments to sieve to a billion (instead of almost four thousand as above), and therefore less overhead in start address calculation per base prime per page segment for a further gain in efficiency.通过这种方式,以较少的内存要求进行筛选同样有效(与“仅几率”相比,效率高了一半以上,这里的效率与此处一样高,其中一个 48 平面“页面”代表 16 千字节 = 每平面 131072 位乘以 210,这是每个筛选页面段 27,525,120 个数字的范围,因此只有 40 个页面段可以筛选到 10 亿(而不是上面的近 4000 个),因此每个页面段的每个基本素数的起始地址计算开销更少进一步提高效率。

Although the extended code described above is a few hundred lines and long to post here, it can count the number of primes to a billion in under two seconds on my low end Intel 1.92 Gigahertz CPU using the Google V8 JavaScript engine, which is about four to five times slower than the same algorithm run in native code.虽然上面描述的扩展代码有几百行而且很长,在这里发布,但它可以在我使用 Google V8 JavaScript 引擎的低端 Intel 1.92 Gigahertz CPU 上在不到两秒的时间内计算出 10 亿的素数,大约为 4比在本机代码中运行的相同算法慢五倍。 This is about the limit of what we can do in JavaScript, with further advanced techniques of "loop-unrolling" and (of course) multi-processing unavailable.这是关于我们在 JavaScript 中可以做的事情的限制,“循环展开”和(当然)多处理等更先进的技术不可用。 However, it is enough to almost match the hand-optimized reference C implementation of the Sieve of Atkin on this low-end CPU, which runs at about 1.4 seconds.不过,在这台低端CPU上几乎可以匹配Sieve of Atkin的手工优化参考C实现,运行时间约为1.4秒。

ADDED: I have explained this in even better detail with a runnable snippet in another StackOverflow answer , and in cross-linked other answers to that thread.补充:我已经在另一个 StackOverflow 答案中用一个可运行的代码段更详细地解释了这一点,并在该线程的其他答案中交叉链接。

Although the above code is quite efficient up to a range of about 16 billion, other improvements can help maintain the efficiency to even larger ranges of several tens of thousands of billions such that one could count the number of primes up to about 1e14 in a few days using JavaScript on a faster CPU.尽管上面的代码在大约 160 亿的范围内非常有效,但其他改进可以帮助将效率保持在数万亿的更大范围内,这样人们就可以在几个小时内计算出大约 1e14 的素数。在更快的 CPU 上使用 JavaScript 的日子。 This is interesting as the number of primes to this range wasn't known until 1985 and then was determined by a numerical analysis technique as the computers of that day weren't powerful enough to run the Sieve of Eratosthenes fast enough for that range in a reasonable time.这很有趣,因为这个范围内的素数数量直到 1985 年才知道,然后由数值分析技术确定,因为当时的计算机不够强大,无法在一个范围内足够快地运行 Eratosthenes 筛网。合理的时间。

With my current "anti-JavaScript" and pro functional coding style bias, I would write this code using Fable, which is a implementation of F# (statically typed ML "functional" language that also supports OOP, if desired) that transpiles to JavaScript very efficiently such that the generated code is likely to be about as fast as if it was written directly in JavaScript.由于我目前的“反 JavaScript”和专业函数式编码风格偏见,我将使用 Fable 编写此代码,它是 F#(静态类型的 ML“函数”语言,如果需要,也支持 OOP)的实现,可以非常转换为 JavaScript有效地使得生成的代码可能与直接用 JavaScript 编写的代码一样快。

To show that the code can run almost as fast in Chrome V8 JavaScript engine using Fable (with the Elmish React interface) as writing pure JavaScript as in the last link above here is a link to a Fable online IDE containing the above algorithm .为了表明代码在使用 Fable(带有 Elmish React 界面)的 Chrome V8 JavaScript 引擎中的运行速度与编写纯 JavaScript 的速度几乎一样快,如上面最后一个链接所示, 这里是包含上述算法的 Fable 在线 IDE 的链接 It runs very slightly slower than pure JavaScript, and the JavaScript output "Code" view shows why: the code generated for Tail Call Optimizations (TCO) isn't quite a simple loop as for the JavaScript - it would be easy to hand tune the code just for the tight inner culling loops to get the same speed.它的运行速度比纯 JavaScript 稍微慢一些,并且 JavaScript 输出“代码”视图显示了原因:为尾调用优化 (TCO) 生成的代码对于 JavaScript 来说并不是一个简单的循环 - 可以轻松手动调整代码仅用于紧密的内部剔除循环以获得相同的速度。 The code is written in functional style except for the array content mutation and as necessary for the sequence generator functions, which are in the same form as the JavaScript for easy understanding;代码除了数组内容的变化和序列生成器函数的需要外,均采用函数式编写,形式与JavaScript相同,便于理解; it would work about as fast if this streaming generator part of the code were written to use F# sequences, with no visible mutation.如果代码的这个流生成器部分被编写为使用 F# 序列,并且没有可见的变化,它的工作速度也会一样快。

Since the above Fable code is pure F#, it could also run with the Fabulous library as a JavaScript generator from DotNet Core, or could run multi-platform and a little faster by directly running it under DotNet Core.由于上面的 Fable 代码是纯 F#,它也可以作为来自 DotNet Core 的 JavaScript 生成器与 Fabulous 库一起运行,或者直接在 DotNet Core 下运行它可以运行多平台并且速度更快。

END_EDIT_ADD_MORE_AND_EVEN_MORE END_EDIT_ADD_MORE_AND_EVEN_MORE

In summary, there are all sorts of algorithms that can find the primes to a few million in the order of a second, but it takes an efficient page segmented array based Sieve of Eratosthenes algorithm to determine the primes to billions in that order of execution time.总之,有各种各样的算法可以在一秒的数量级内找到几百万的素数,但是需要一个高效的基于页面分段数组的埃拉托色尼筛算法来确定按执行时间顺序排列的十亿的素数.

I would post this as a comment to Alexander, but I don't have the reputation to do that.我会将此作为对亚历山大的评论发布,但我没有这样做的声誉。 His answer is awesome, and this just tweaks it to make it faster.他的回答很棒,这只是对其进行了调整以使其更快。 I benchmarked by testing n = 100,000,000.我通过测试 n = 100,000,000 进行基准测试。

Instead of using true and false in 'array', I get a big speed boost by using 1 and 0. This reduced my time in Chrome from 5000 ms to 4250 ms.我没有在“数组”中使用 true 和 false,而是通过使用 1 和 0 获得了很大的速度提升。这将我在 Chrome 中的时间从 5000 毫秒减少到 4250 毫秒。 Firefox was unaffected (5600 ms either way). Firefox 不受影响(5600 毫秒)。

Then we can take into account that even numbers will never be prime.然后我们可以考虑到偶数永远不会是素数。 Put 2 into 'output' off the bat and you can do i=3;立即将 2 放入“输出”中,您可以执行 i=3; i += 2, and j += i*2 during the sieve (we can skip the even multiples since any number times an even number is even), as long as we also i += 2 while pushing to 'output' at the end. i += 2, and j += i*2 在筛选过程中(我们可以跳过偶数倍数,因为任何数乘以偶数都是偶数),只要我们在推到“输出”时也 i += 2结尾。 This reduced my time in Chrome from 4250 ms to 3350 ms.这将我在 Chrome 中的时间从 4250 毫秒减少到 3350 毫秒。 Firefox benefited a bit less, dropping from 5600 ms to 4800 ms. Firefox 受益较少,从 5600 毫秒下降到 4800 毫秒。

Anyway, the combination of those two tweaks gave me a 33% speed boost in Chrome, and a 14% boost in Firefox.无论如何,这两项调整的结合使我在 Chrome 中的速度提高了 33%,在 Firefox 中提高了 14%。 Here's the improved version of Alexander's code.这是亚历山大代码的改进版本。

var eratosthenes = function(n) {
    // Eratosthenes algorithm to find all primes under n
    var array = [], upperLimit = Math.sqrt(n), output = [2];

    // Make an array from 2 to (n - 1)
    for (var i = 0; i < n; i++)
        array.push(1);

    // Remove multiples of primes starting from 2, 3, 5,...
    for (var i = 3; i <= upperLimit; i += 2) {
        if (array[i]) {
            for (var j = i * i; j < n; j += i*2)
                array[j] = 0;
        }
    }

    // All array[i] set to 1 (true) are primes
    for (var i = 3; i < n; i += 2) {
        if(array[i]) {
            output.push(i);
        }
    }

    return output;
};

Just for the fun of it, I implemented the Erastoten sieve algorithm (run with Node) strictly following the rules of TDD.只是为了好玩,我严格按照 TDD 的规则实现了 Erastoten 筛算法(使用 Node 运行)。 This version should be enough for interviews, as a school exercise or just like I was - for messing around a bit.这个版本应该足够面试了,作为学校练习或者就像我一样 - 有点乱。

Let me state, that I definitely think the accepted answer should be the one provided by GordonBGood.让我声明,我绝对认为公认的答案应该是 GordonBGood 提供的答案。

module.exports.compute = function( size )
{
    if ( !utils.isPositiveInteger( size ) )
    {
        throw new TypeError( "Input must be a positive integer" );
    }

    console.time('optimal');
    console.log();
    console.log( "Starting for optimal computation where size = " + size );
    let sieve = utils.generateArraySeq( 2, size );

    let prime = 2;
    while ( prime )
    {
        // mark multiples
        for ( let i = 0; i < sieve.length; i += prime )
        {
            if ( sieve[i] !== prime )
            {
                sieve[i] = -1;
            }
        }

        let old_prime = prime;
        // find next prime number
        for ( let i = 0; i < sieve.length; i++ )
        {
            if ( ( sieve[i] !== -1 ) && ( sieve[i] > prime ) )
            {
                prime = sieve[i];
                break;
            }
        }

        if ( old_prime === prime )
        {
            break;
        }
    }
    console.timeEnd('optimal');
    // remove marked elements from the array
    return sieve.filter( 
        function( element )
        {
            return element !== -1;
        } );
} // compute

I will appreciate any senseful critique.我会感谢任何有意义的批评。

The whole repository can be found on my github account.整个存储库可以在我的 github 帐户中找到。

Since I am a bit late to the party.因为我参加聚会有点晚了。 I would like to add my simple and a bit hacky contribution that finds all the prime numbers up to 100:我想添加我的简单和有点hacky的贡献,找到所有100以内的质数:

<!DOCTYPE html>
<html>
<title>Primes</title>
<head>
<script>
function findPrimes() {
    var primes = []
    var search = []

    var maxNumber = 100
    for(var i=2; i<maxNumber; i++){
        if(search[i]==undefined){
            primes.push(i);
            for(var j=i+i; j<maxNumber; j+=i){
                search[j] = 0;
            }
        }
    }
   document.write(primes);
}
findPrimes();
</script>
</head>
<body>
</body>
</html>
function sieveOfEratosthenes(num, fromSt = null) {
let boolArr = Array(num + 1).fill(true); // Taking num+1 for simplicity
boolArr[0] = false;
boolArr[1] = false;

for (
    let divisor = 2;
    divisor * divisor <= num;
    divisor = boolArr.indexOf(true, divisor + 1)
)
    for (let j = 2 * divisor; j <= num; j += divisor) boolArr[j] = false;

let primeArr = [];
for (
    let idx = fromSt || boolArr.indexOf(true);
    idx !== -1;
    idx = boolArr.indexOf(true, idx + 1)
)
    primeArr.push(idx);

return primeArr;
}

This algorithm takes few millisec ( ok 2 - big test # time=498.815ms ) per 1 million:此算法每 100 万需要几毫秒( ok 2 - big test # time=498.815ms ):

module.exports.fast = function eratosthenes (max) {
  let sqrt = Math.sqrt(max)
  let sieve = new Array(max).fill(0)

  for (let primeCandidate = 2; primeCandidate < sqrt; primeCandidate++) {
    if (sieve[primeCandidate] === true) {
      continue // already processed
    }
    for (let multiple = primeCandidate * primeCandidate; multiple < max; multiple += primeCandidate) {
      if (sieve[multiple] === 0) {
        sieve[multiple] = true
      }
    }
  }

  return sieve
    .map((isPrime, i) => ({ i, isPrime })) // find the number associated with the index
    .filter(({ i, isPrime }) => isPrime === 0 && i >= 2) // remove not prime numbers
    .map(({ i }) => i) // output only the values
}

eratosthenes(1000000) returns an array with 78498 prime numbers. eratosthenes(1000000)返回一个包含78498素数的数组。

function get_primes_of(n)
{
    let primes = [];
    for (i = 0; i <= n; i++)
    {
        primes[i] = 1;
    }
    primes[0] = primes[1] = 0;
    for (i = 2; i <= n; i++)
    {
        if (primes[i] == 1)
        {
            for (j = 2; i * j <= n; j++)
            {
                primes[j] = 0;
            }
        }
    }
    return primes;
}

There is the code:有代码:

function eratoSthenes(n){
    let isPrime = new Array(n + 1);
    isPrime.fill(true);

    isPrime[0] = false;
    isPrime[1] = false;

    for(let i= 2;i * i <= n;i++){
        for(let j = 2 * i;j <=n; j += i){
            isPrime[j] = true
        }
    };

    return isPrime
};

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

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