85

Just to clarify, this is not a homework problem :)

I wanted to find primes for a math application I am building & came across Sieve of Eratosthenes approach.

I have written an implementation of it in Python. But it's terribly slow. For say, if I want to find all primes less than 2 million. It takes > 20 mins. (I stopped it at this point). How can I speed this up?

def primes_sieve(limit):
    limitn = limit+1
    primes = range(2, limitn)

    for i in primes:
        factors = range(i, limitn, i)
        for f in factors[1:]:
            if f in primes:
                primes.remove(f)
    return primes

print primes_sieve(2000)

UPDATE: I ended up doing profiling on this code & found that quite a lot of time was spent on removing an element from the list. Quite understandable considering it has to traverse the entire list (worst-case) to find the element & then remove it and then readjust the list (maybe some copy goes on?). Anyway, I chucked out list for dictionary. My new implementation -

def primes_sieve1(limit):
    limitn = limit+1
    primes = dict()
    for i in range(2, limitn): primes[i] = True

    for i in primes:
        factors = range(i,limitn, i)
        for f in factors[1:]:
            primes[f] = False
    return [i for i in primes if primes[i]==True]

print primes_sieve1(2000000)
starblue
  • 55,348
  • 14
  • 97
  • 151
Srikar Appalaraju
  • 71,928
  • 54
  • 216
  • 264
  • 1
    There's a similar question here http://stackoverflow.com/questions/2897297/ that you might find useful. – Scott Griffiths Oct 15 '10 at 08:18
  • Check [that](http://stackoverflow.com/questions/2211990/how-to-implement-an-efficient-infinite-generator-of-prime-numbers-in-python/3796442#3796442) answer. – tzot Oct 15 '10 at 19:57
  • http://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python – Robert William Hanks Oct 18 '10 at 22:57
  • 1
    @Srikar: Rather than iterating upto limit, you can just iterate upto the square root of limit, since any composite number in your dictionary will have one factor less than the square root of limit. – sayantankhan Aug 17 '13 at 01:36
  • 2
    Using the `step` parameter to `range` is brilliant. `factors` is a misnomer and should be `multiples`. – Tom Russell Feb 28 '18 at 23:50

24 Answers24

128

You're not quite implementing the correct algorithm:

In your first example, primes_sieve doesn't maintain a list of primality flags to strike/unset (as in the algorithm), but instead resizes a list of integers continuously, which is very expensive: removing an item from a list requires shifting all subsequent items down by one.

In the second example, primes_sieve1 maintains a dictionary of primality flags, which is a step in the right direction, but it iterates over the dictionary in undefined order, and redundantly strikes out factors of factors (instead of only factors of primes, as in the algorithm). You could fix this by sorting the keys, and skipping non-primes (which already makes it an order of magnitude faster), but it's still much more efficient to just use a list directly.

The correct algorithm (with a list instead of a dictionary) looks something like:

def primes_sieve2(limit):
    a = [True] * limit                          # Initialize the primality list
    a[0] = a[1] = False

    for (i, isprime) in enumerate(a):
        if isprime:
            yield i
            for n in range(i*i, limit, i):     # Mark factors non-prime
                a[n] = False

(Note that this also includes the algorithmic optimization of starting the non-prime marking at the prime's square (i*i) instead of its double.)

nnyby
  • 4,748
  • 10
  • 49
  • 105
Pi Delport
  • 10,356
  • 3
  • 36
  • 50
  • 9
    another optimization, the step size of your `xrange(i*i,limit,i)` can be made `2*i` – st0le Oct 21 '10 at 12:20
  • Well spotted! If i'm not mistaken, that is a step in the direction of Euler's version of the sieve, right? – Pi Delport Oct 21 '10 at 19:14
  • 3
    I like your succinct implementation of the Sieve of Eratosthenes. : ) However, I'm having a OverflowError: Python int too large to convert to C long. I changed xrange(i*i, limit, i) to xrange(i, limit, i). Thanks for sharing this code snippet! – Annie Lagang Apr 02 '12 at 13:26
  • 12
    @st0le: No, the step-size cannot be made `2*i`. Just tried it. It yields 14 as a prime. – mpen Jul 13 '12 at 01:33
  • 2
    @Mark, I'm sorry I didn't really explain it in full. Eliminate all even numbers by doing an iteration with `i=2` with steps of `i` but for the rest you can use `2*i`. In fact, in my implementation I use half the booleans since I don't store even numbers and instead use a simple `mod 2`. You can find my Java implementation here which uses even less (1/8th) the memory. [HERE](http://stackoverflow.com/a/3099112/216517) – st0le Jul 13 '12 at 03:44
  • @AnneLagang You lose a lot of the optimisation if you do that, since you are redundantly checking a lot of numbers. For an alternative to xrange, see my answer. – Asad Saeeduddin Apr 04 '13 at 00:03
  • 4
    +1, just a small detail, if you use `[False] * 2 + [True] * (limit-2)` in the initialisation, you can avoid IndexError on passing number < 2 as an argument – Jan Vorcak Nov 10 '13 at 17:57
  • @st0le Can your optimization be extended to whatever the previous prime is? I.e. store a value x which is the previously discovered prime, and then right after the yield define an increment as i*x and increment by that instead of i? this seems like it would work since all multiples of previously occurring numbers have already been checked – Vyas Nov 27 '13 at 20:48
  • @Vyas,Seems plausible but I'll need to verify it. – st0le Nov 27 '13 at 20:52
  • @st0le It looks like it works up to 10 million, I can't test further than that without the run substantially impacting my comp's performance. I also did a quick timing test, and the results were interesting. Looks like just creating the generator by assigning x = prime_sieve(n) is faster, but if I do x = list(prime_sieve(n)) to actually run through the entire generation then the original version is faster. Any idea why? – Vyas Nov 27 '13 at 21:15
  • @Vyas, That's coz when you do a list() it allocates memory based on the generator run. The list() is kinda like vector in C++, its capacity increases gradually, ergo it needs constant reallocations. This will definitely impact the time. Therefore, a vanilla run of the generator will be faster. – st0le Nov 27 '13 at 21:18
  • @st0le I understand why converting to a list is slow, but since the two generators create equal sized lists I'm confused as to why one would be considerably slower than the other. Does list() try to guess a size based on the content of the generator function? – Vyas Nov 27 '13 at 21:21
  • @Vyas, No it doesn't (and it can't). It starts off with the default capacity. Are you creating a list when running through the generator? It should in theory take the same time, or longer infact. – st0le Nov 27 '13 at 21:25
  • @st0le Is there somewhere that I can post my code to show you? – Vyas Nov 27 '13 at 21:26
  • @st0le Sorry for the delay, had to catch a flight and realized what was wrong with my idea but couldn't post. This method fails to mark products of prime numbers as prime; it only works for multiples of i up to i, which are already excluded by starting checks at i*i. In my code the indentation for the incrementing of x was off, so it was actually just doing i*1. For creating the generator it must have been faster for one iteration because it lacks an if clause, but past a certain point the i*2 optimization made the other version faster I'm guessing – Vyas Nov 28 '13 at 13:09
  • FYI, a much more memory efficient approach would be to replace the `list` of `bool` with a `bytearray` (dropping the cost per sieve entry from 4-8 bytes down to 1 byte; anything better would require bit manipulation that would lower performance or need a third party C extension to avoid the hit). The code would be identical, except you'd change `a = [True] * limit` to `a = bytearray(b'\x01') * limit`. – ShadowRanger Jan 22 '16 at 01:02
  • In addition, using a `bytearray` can dramatically reduce sieving costs; instead of a loop setting `False`, you can do slice assignment with an appropriately multiplied `b'\0'`; a loop is still occurring under the hood, but it's a C level loop that runs a few orders of magnitude faster. – ShadowRanger Jan 22 '16 at 01:04
15
def eratosthenes(n):
    multiples = []
    for i in range(2, n+1):
        if i not in multiples:
            print (i)
            for j in range(i*i, n+1, i):
                multiples.append(j)

eratosthenes(100)
Saurabh Rana
  • 3,350
  • 2
  • 19
  • 22
8

Removing from the beginning of an array (list) requires moving all of the items after it down. That means that removing every element from a list in this way starting from the front is an O(n^2) operation.

You can do this much more efficiently with sets:

def primes_sieve(limit):
    limitn = limit+1
    not_prime = set()
    primes = []

    for i in range(2, limitn):
        if i in not_prime:
            continue

        for f in range(i*2, limitn, i):
            not_prime.add(f)

        primes.append(i)

    return primes

print primes_sieve(1000000)

... or alternatively, avoid having to rearrange the list:

def primes_sieve(limit):
    limitn = limit+1
    not_prime = [False] * limitn
    primes = []

    for i in range(2, limitn):
        if not_prime[i]:
            continue
        for f in xrange(i*2, limitn, i):
            not_prime[f] = True

        primes.append(i)

    return primes
Glenn Maynard
  • 55,829
  • 10
  • 121
  • 131
5

Much faster:

import time
def get_primes(n):
  m = n+1
  #numbers = [True for i in range(m)]
  numbers = [True] * m #EDIT: faster
  for i in range(2, int(n**0.5 + 1)):
    if numbers[i]:
      for j in range(i*i, m, i):
        numbers[j] = False
  primes = []
  for i in range(2, m):
    if numbers[i]:
      primes.append(i)
  return primes

start = time.time()
primes = get_primes(10000)
print(time.time() - start)
print(get_primes(100))
MrHIDEn
  • 1,723
  • 1
  • 25
  • 23
2

I realise this isn't really answering the question of how to generate primes quickly, but perhaps some will find this alternative interesting: because python provides lazy evaluation via generators, eratosthenes' sieve can be implemented exactly as stated:

def intsfrom(n):
    while True:
        yield n
        n += 1

def sieve(ilist):
    p = next(ilist)
    yield p
    for q in sieve(n for n in ilist if n%p != 0):
        yield q


try:
    for p in sieve(intsfrom(2)):
        print p,

    print ''
except RuntimeError as e:
    print e

The try block is there because the algorithm runs until it blows the stack and without the try block the backtrace is displayed pushing the actual output you want to see off screen.

Paul Gardiner
  • 468
  • 2
  • 7
  • 4
    no, it's not [the sieve of Eratosthenes](http://stackoverflow.com/a/10733621/849891), but rather a sieve of trial division. Even that is very suboptimal, because it's not [*postponed*](http://stackoverflow.com/a/8871918/849891): any candidate number need only be tested by primes not above its square root. Implementing this along the lines of the pseudocode at the bottom of the linked above answer (the latter one) will give your code immense speedup (even before you switch to the proper sieve) and/because it'll greatly minimize the stack usage - so you mightn't need your `try` block after all. – Will Ness Jul 21 '14 at 14:11
  • ... see also: [more discussion about the "sqrt" issue and its effects](http://stackoverflow.com/a/14821313/849891), an [actual Python code for a postponed trial division](http://stackoverflow.com/a/14648519/849891), and [some related Scala](http://stackoverflow.com/a/20991776/849891). --- And kudos to you, if you came up with that code on your own! :) – Will Ness Jul 21 '14 at 14:15
  • Interesting, although I'm not yet understanding why what I put is different from the sieve of Eratosthenes. I thought it was described as placing all the intergers from 2 in a line, then repeadly take the first in the line as a prime and strike out all multiples. the "n for n in ilist if n%p != 0" bit was supposed to represent striking out the multiples. Admittedly highly suboptimal though, definitely – Paul Gardiner Feb 18 '15 at 14:05
  • 1
    `n for n in ilist if n%p != 0` tests each number `n` in a range for divisibility by `p`; but `range(p*p, N, p)` generates the multiples directly, all by itself, without testing all these numbers. – Will Ness Feb 25 '15 at 04:06
2

By combining contributions from many enthusiasts (including Glenn Maynard and MrHIDEn from above comments), I came up with following piece of code in python 2:

def simpleSieve(sieveSize):
    #creating Sieve.
    sieve = [True] * (sieveSize+1)
    # 0 and 1 are not considered prime.
    sieve[0] = False
    sieve[1] = False
    for i in xrange(2,int(math.sqrt(sieveSize))+1):
        if sieve[i] == False:
            continue
        for pointer in xrange(i**2, sieveSize+1, i):
            sieve[pointer] = False
    # Sieve is left with prime numbers == True
    primes = []
    for i in xrange(sieveSize+1):
        if sieve[i] == True:
            primes.append(i)
    return primes

sieveSize = input()
primes = simpleSieve(sieveSize)

Time taken for computation on my machine for different inputs in power of 10 is:

  • 3 : 0.3 ms
  • 4 : 2.4 ms
  • 5 : 23 ms
  • 6 : 0.26 s
  • 7 : 3.1 s
  • 8 : 33 s
Ajay
  • 130
  • 6
  • the comparison with True or False are unneeded more so as they are already Boolean, – Copperfield Sep 04 '16 at 13:32
  • @Copperfield Thanks! It helped in increasing speed by 10-20%. – Ajay Sep 04 '16 at 20:04
  • This `sieve = [True] * (sieveSize+1)` is faster than my solution, but `sieve[0]/[1]` and `xrange(sieveSize+1)` at primes[] does not improve anything. `xrange(2, sieveSize+1)` is good enouth. :). Also instead of `for i in xrange(2,int(math.sqrt(sieveSize))+1):` we can just use `for i in xrange(2, int((sieveSize+1)**0.5):` Good code. :) – MrHIDEn Nov 28 '16 at 08:49
2

Using a bit of numpy, I could find all primes below 100 million in a little over 2 seconds.

There are two key features one should note

  • Cut out multiples of i only for i up to root of n
  • Setting multiples of i to False using x[2*i::i] = False is much faster than an explicit python for loop.

These two significantly speed up your code. For limits below one million, there is no perceptible running time.

import numpy as np

def primes(n):
    x = np.ones((n+1,), dtype=np.bool)
    x[0] = False
    x[1] = False
    for i in range(2, int(n**0.5)+1):
        if x[i]:
            x[2*i::i] = False

    primes = np.where(x == True)[0]
    return primes

print(len(primes(100_000_000)))
storm125
  • 510
  • 4
  • 9
1

A simple speed hack: when you define the variable "primes," set the step to 2 to skip all even numbers automatically, and set the starting point to 1.

Then you can further optimize by instead of for i in primes, use for i in primes[:round(len(primes) ** 0.5)]. That will dramatically increase performance. In addition, you can eliminate numbers ending with 5 to further increase speed.

1

My implementation:

import math
n = 100
marked = {}
for i in range(2, int(math.sqrt(n))):
    if not marked.get(i):
        for x in range(i * i, n, i):
            marked[x] = True

for i in range(2, n):
    if not marked.get(i):
        print i
SilentDirge
  • 827
  • 9
  • 17
1

Here's a version that's a bit more memory-efficient (and: a proper sieve, not trial divisions). Basically, instead of keeping an array of all the numbers, and crossing out those that aren't prime, this keeps an array of counters - one for each prime it's discovered - and leap-frogging them ahead of the putative prime. That way, it uses storage proportional to the number of primes, not up to to the highest prime.

import itertools

def primes():

    class counter:
        def __init__ (this,  n): this.n, this.current,  this.isVirgin = n, n*n,  True
            # isVirgin means it's never been incremented
        def advancePast (this,  n): # return true if the counter advanced
            if this.current > n:
                if this.isVirgin: raise StopIteration # if this is virgin, then so will be all the subsequent counters.  Don't need to iterate further.
                return False
            this.current += this.n # pre: this.current == n; post: this.current > n.
            this.isVirgin = False # when it's gone, it's gone
            return True

    yield 1
    multiples = []
    for n in itertools.count(2):
        isPrime = True
        for p in (m.advancePast(n) for m in multiples):
            if p: isPrime = False
        if isPrime:
            yield n
            multiples.append (counter (n))

You'll note that primes() is a generator, so you can keep the results in a list or you can use them directly. Here's the first n primes:

import itertools

for k in itertools.islice (primes(),  n):
    print (k)

And, for completeness, here's a timer to measure the performance:

import time

def timer ():
    t,  k = time.process_time(),  10
    for p in primes():
        if p>k:
            print (time.process_time()-t,  " to ",  p,  "\n")
            k *= 10
            if k>100000: return

Just in case you're wondering, I also wrote primes() as a simple iterator (using __iter__ and __next__), and it ran at almost the same speed. Surprised me too!

Jules May
  • 753
  • 2
  • 10
  • 18
  • interesting idea - it would improve performance if you store the prime counters in a min-heap though (inner loop would be O(log num_primes) instead of O(num_primes)) – anthonybell Jul 25 '17 at 17:34
  • Why? Even if they were in a heap, we still have to account for every one. – Jules May Jul 26 '17 at 10:26
  • If you store each prime in the heap keyed by it's next value you would only have to look at primes whose next value is the current value n. the largest primes will sink to the bottom of the heap and would need to be evaluated much more rarely than the smaller primes. – anthonybell Jul 26 '17 at 19:30
1

I prefer NumPy because of speed.

import numpy as np

# Find all prime numbers using Sieve of Eratosthenes
def get_primes1(n):
    m = int(np.sqrt(n))
    is_prime = np.ones(n, dtype=bool)
    is_prime[:2] = False  # 0 and 1 are not primes

    for i in range(2, m):
        if is_prime[i] == False:
            continue
        is_prime[i*i::i] = False

    return np.nonzero(is_prime)[0]

# Find all prime numbers using brute-force.
def isprime(n):
    ''' Check if integer n is a prime '''
    n = abs(int(n))  # n is a positive integer
    if n < 2:  # 0 and 1 are not primes
        return False
    if n == 2:  # 2 is the only even prime number
        return True
    if not n & 1:  # all other even numbers are not primes
        return False
    # Range starts with 3 and only needs to go up the square root
    # of n for all odd numbers
    for x in range(3, int(n**0.5)+1, 2):
        if n % x == 0:
            return False
    return True

# To apply a function to a numpy array, one have to vectorize the function
def get_primes2(n):
    vectorized_isprime = np.vectorize(isprime)
    a = np.arange(n)
    return a[vectorized_isprime(a)]

Check the output:

n = 100
print(get_primes1(n))
print(get_primes2(n))    
    [ 2  3  5  7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97]
    [ 2  3  5  7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97]

Compare the speed of Sieve of Eratosthenes and brute-force on Jupyter Notebook. Sieve of Eratosthenes in 539 times faster than brute-force for million elements.

%timeit get_primes1(1000000)
%timeit get_primes2(1000000)
4.79 ms ± 90.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.58 s ± 31.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
FooBar167
  • 2,721
  • 1
  • 26
  • 37
  • Should your inner loop content not better be (taking previous answers and comments into account) the one line `if is_prime[i]: is_prime[i*i::2*i]=False`? – Lutz Lehmann May 06 '19 at 08:02
1

I figured it must be possible to simply use the empty list as the terminating condition for the loop and came up with this:

limit = 100
ints = list(range(2, limit))   # Will end up empty

while len(ints) > 0:
    prime = ints[0]
    print prime
    ints.remove(prime)
    i = 2
    multiple = prime * i
    while multiple <= limit:
        if multiple in ints:
            ints.remove(multiple)
        i += 1
        multiple = prime * i
Tom Russell
  • 1,015
  • 2
  • 10
  • 29
1
import math
def sieve(n):
    primes = [True]*n
    primes[0] = False
    primes[1] = False
    for i in range(2,int(math.sqrt(n))+1):
            j = i*i
            while j < n:
                    primes[j] = False
                    j = j+i
    return [x for x in range(n) if primes[x] == True]
nhern121
  • 3,831
  • 6
  • 27
  • 40
1

The fastest implementation I could come up with:

isprime = [True]*N
isprime[0] = isprime[1] = False
for i in range(4, N, 2):
    isprime[i] = False
for i in range(3, N, 2):
    if isprime[i]:
        for j in range(i*i, N, 2*i):
            isprime[j] = False
Madiyar
  • 779
  • 11
  • 7
1

i think this is shortest code for finding primes with eratosthenes method

def prime(r):
    n = range(2,r)
    while len(n)>0:
        yield n[0]
        n = [x for x in n if x not in range(n[0],r,n[0])]


print(list(prime(r)))
Pythoscorpion
  • 166
  • 1
  • 10
1

I just came up with this. It may not be the fastest, but I'm not using anything other than straight additions and comparisons. Of course, what stops you here is the recursion limit.

def nondivsby2():
    j = 1
    while True:
        j += 2
        yield j

def nondivsbyk(k, nondivs):
    j = 0
    for i in nondivs:
        while j < i:
            j += k
        if j > i:
            yield i

def primes():
    nd = nondivsby2()
    while True:
        p = next(nd)
        nd = nondivsbyk(p, nd)
        yield p

def main():
    for p in primes():
        print(p)
tamir
  • 81
  • 2
  • 9
  • very nice formulation, clean and clear and succinct! I'll bookmark it. of course, to produce the 100th prime with it, the `nd` chain will be 99 levels deep. but only 10 are truly needed. and it becomes worse and worse the farther we go along the primes list. can you find a way to deal with this problem? :) – Will Ness Oct 06 '20 at 13:30
  • also, I don't see any recursion here really, so there shouldn't be any limit to it here either. (of course I don't know Python almost at all) – Will Ness Oct 06 '20 at 13:33
  • I was surprised at first, when I got the `RecursionError: maximum recursion depth exceeded` exception. But then I thought it makes some sense. Because we can think of the generators as object with a `__next__` function. So each `nondivsbyk` generator is an object of the same class (only different initialization). SUppose we call that `class_nondivsbyk`, so when one calls the other its actually `class_nondivsbyk.__next__` calling another `class_nondivsbyk.__next__` on another object. – tamir Oct 08 '20 at 06:30
  • About the 100th prime requiring only the first 10 primes, so first I can say that (as long as I don't want to give an upper limit) we need to 'collect' the primes on the way, so creating these generators seems necessary. I really don't know at the moment if I could 'skip' those irrelevant ones for the computation. Because now, I let each check if it's a divider, but if I put them aside, I would need something else to 'trigger' them when the numbers increase and I don't know how to integrate that to the recursion. I also made a "flat" version, I can take a look at that there. Thanks @WillNess – tamir Oct 08 '20 at 06:38
  • the two `nd`s after the assignment `nd = nondivsbyk(p, nd)` are supposed to be two different objects. i.e., `nd` is a variable referring to a object, first; then new generator object is constructed by the function call, and assigned to the same variable (which forgets its old value). but inside, the new generator object refers to the older - different - object. but as I said, I don't know Python. about the 10 primes vs the 100 -- here's a hint: hopefully each call to `primes()` creates a separate, new, generator object. (or what's the proper terminology?) – Will Ness Oct 08 '20 at 18:07
  • here's some more hints: :) in [python](https://stackoverflow.com/questions/2211990/how-to-implement-an-efficient-infinite-generator-of-prime-numbers-in-python/10733621#10733621), [haskell](https://stackoverflow.com/questions/1764163/explain-this-chunk-of-haskell-code-that-outputs-a-stream-of-primes/8871918#8871918), [go](https://rosettacode.org/wiki/Sieve_of_Eratosthenes#Concurrent_Daisy-chain_sieve). the last one directly corresponds to what you wrote here. (!) the next entry there is what I had in mind. :) cheers. – Will Ness Feb 11 '21 at 17:00
1

I made a one liner version of the Sieve of Eratosthenes

sieve = lambda j: [print(x) for x in filter(lambda n: 0 not in map(lambda i: n % i, range(2, n)) and (n!=1)&(n!=0), range(j + 1))]

In terms of performance, I am pretty sure this isn't the fastest thing by any means, and in terms of readability / following PEP8, this is pretty terrible, but it's more the novelty of the length than anything.

EDIT: Note that this simply prints the sieve & does not return (if you attempt to print it you will get a list of Nones, if you want to return, change the print(x) in the list comprehension to just "x".

pythoteric
  • 11
  • 1
0

not sure if my code is efficeient, anyone care to comment?

from math import isqrt

def isPrime(n):
    if n >= 2: # cheating the 2, is 2 even prime?
        for i in range(3, int(n / 2 + 1),2): # dont waste time with even numbers
            if n % i == 0:
                return False
    return True

def primesTo(n): 
    x = [2] if n >= 2 else [] # cheat the only even prime
    if n >= 2:
        for i in range(3, n + 1,2): # dont waste time with even numbers
            if isPrime(i):
                x.append(i)  
    return x

def primes2(n): # trying to do this using set methods and the "Sieve of Eratosthenes"
    base = {2} # again cheating the 2
    base.update(set(range(3, n + 1, 2))) # build the base of odd numbers
    for i in range(3, isqrt(n) + 1, 2): # apply the sieve
        base.difference_update(set(range(2 * i, n + 1 , i)))
    return list(base)

print(primesTo(10000)) # 2 different methods for comparison
print(primes2(10000))
lonny
  • 135
  • 1
  • 4
0

Probably the quickest way to have primary numbers is the following:

import sympy
list(sympy.primerange(lower, upper+1))

In case you don't need to store them, just use the code above without conversion to the list. sympy.primerange is a generator, so it does not consume memory.

Prokhozhii
  • 622
  • 1
  • 8
  • 12
  • Please explain in the body of your answer why this is necessary and what improvement it brings to make it look a meaningful answer. – dmitryro Nov 28 '20 at 14:52
0

Using recursion and walrus operator:

def prime_factors(n):
    for i in range(2, int(n ** 0.5) + 1):
        if (q_r := divmod(n, i))[1] == 0:
            return [i] + factor_list(q_r[0])
    return [n]
Vlad Bezden
  • 83,883
  • 25
  • 248
  • 179
0

Basic sieve

with numpy is amazing fast. May be the fastest implementation

# record: sieve 1_000_000_000 in 6.9s (core i7 - 2.6Ghz)
def sieve_22max_naive(bound):
    sieve = np.ones(bound, dtype=bool)  # default all prime
    sieve[:2] = False  # 0, 1 is not prime

    sqrt_bound = math.ceil(math.sqrt(bound))

    for i in range(2, sqrt_bound):
        if sieve[i]:
            inc = i if i == 2 else 2 * i
            sieve[i * i:bound:inc] = False

    return np.arange(bound)[sieve]


if __name__ == '__main__':
    start = time.time()
    prime_list = sieve_22max_naive(1_000_000_000)
    print(f'Count: {len(prime_list):,}\n'
          f'Greatest: {prime_list[-1]:,}\n'
          f'Elapsed: %.3f' % (time.time() - start))

Segment sieve (use less memory)

# find prime in range [from..N), base on primes in range [2..from)
def sieve_era_part(primes, nfrom, n):
    sieve_part = np.ones(n - nfrom, dtype=bool)  # default all prime

    limit = math.ceil(math.sqrt(n))
    # [2,3,5,7,11...p] can find primes < (p+2)^2
    if primes[-1] < limit - 2:
        print(f'Not enough base primes to find up to {n:,}')
        return

    for p in primes:
        if p >= limit: break

        mul = p * p
        inc = p * (2 if p > 2 else 1)
        if mul < nfrom:
            mul = math.ceil(nfrom / p) * p
            (mul := mul + p) if p > 2 and (mul & 1) == 0 else ...  # odd, not even

        sieve_part[mul - nfrom::inc] = False

    return np.arange(nfrom, n)[sieve_part]
    # return np.where(sieve_part)[0] + nfrom
    # return [i + nfrom for i, is_p in enumerate(sieve_part) if is_p]
    # return [i for i in range(max(nfrom, 2), n) if sieve_part[i - nfrom]]


# find nth prime number, use less memory,
# extend bound to SEG_SIZE each loop
# record: 50_847_534 nth prime in 6.78s, core i7 - 9850H 2.6GHhz
def nth_prime(n):
    # find prime up to bound
    bound = 500_000
    primes = sieve_22max_naive(bound)

    SEG_SIZE = int(50e6)

    while len(primes) < n:
        # sieve for next segment
        new_primes = sieve_era_part(primes, bound, bound + SEG_SIZE)
        # extend primes
        bound += SEG_SIZE
        primes = np.append(primes, new_primes)

    return primes[n - 1]


if __name__ == '__main__':
    start = time.time()
    prime = nth_prime(50_847_534)
    print(f'{prime:,} Time %.6f' % (time.time() - start))
KevinBui
  • 880
  • 15
  • 27
0

here is my solution, the same as Wikipedia

import math


def sieve_of_eratosthenes(n):
    a = [i for i in range(2, n+1)]
    clone_a = a[:]
    b = [i for i in range(2, int(math.sqrt(n))+1)]
    for i in b:
        if i in a:
            c = [pow(i, 2)+(j*i) for j in range(0, n+1)]
            for j in c:
                if j in clone_a:
                    clone_a.remove(j)
    return clone_a


if __name__ == '__main__':
    print(sieve_of_eratosthenes(23))
HoangYell
  • 4,100
  • 37
  • 31
0

Thanks for interesting question!

Right now I wrote from scratch two versions of classical Sieve of Eratosthenes.

One is single-core (CPU core), another one is multi-core (using all CPU cores).

Main speedup of both (single and multi core) versions was due to using Numpy, official Python package. Install in once through command python -m pip install numpy. Another great speedup of multi-core version was due to usage of all CPU cores, which gives almost speedup N times for N cores, compared to single core version.

Right now I have only two-core laptop. My program produced following timings:

Computing primes less than 8M
Number of CPU cores 2
Original time 72.57 sec
Simple time 5.694 sec
Simple boost (compared to original) 12.745x
Multi core time 2.642 sec
Multi core boost (compared to simple) 2.155x

Original above means your code from your question's body, the second one that you optimized already. Simple is my single core version. Multi is my multi core version.

As you see above computing primes less than 8 Million took 72 seconds on your original code, my single core version took 5.7 seconds, which is 12.7x times faster than your code, and my 2-core version took 2.6 seconds, which is 2.1x times faster than my single core and 27x times faster than your original code.

In other words I got 27x times speedup in my multi-core code compared to your code, really a lot! And this is only on 2-core laptop. If you have 8 cores or more then you'll get much bigger speedup. But remember that real speedup on multi core machine will be only for quite big prime number limit, try 64 Million limit or bigger, for this modify line primes_limit_M = 8 in my code to set amount of Millions.

Just to dwell into details. My single core version is almost like your code, but uses Numpy which makes any array operations very fast, instead of using pure pythonic loops with lists.

Multi core version also uses Numpy, but splits array with sieved range into as many pieces as there are CPU cores on your machine, each piece of array having equal size. Then every CPU core sets boolean flags in its own part of array. This technique gives speedup only till you hit speed limit of your memory (RAM), so after some point with growth of amount of CPU cores you don't get extra speedup.

By default I use all CPU cores in multi core version, but you may experiment by setting less cores than you have on your machine, this may give even better speedup, because it is not guaranteed that most of cores will give exactly fastest result. Tweak amount of cores through changing line cpu_cores = mp.cpu_count() to something like cpu_cores = 3.

Try it online!

def SieveOfEratosthenes_Original(end):
    import numpy as np
    limit = end - 1
    limitn = limit+1
    primes = dict()
    for i in range(2, limitn): primes[i] = True

    for i in primes:
        factors = range(i,limitn, i)
        for f in factors[1:]:
            primes[f] = False
    return np.array([i for i in primes if primes[i]==True], dtype = np.uint32)

def SieveOfEratosthenes_Simple(end):
    # https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
    import numpy as np
    composites = np.zeros((end,), dtype = np.uint8)
    composites[:2] = 1
    for p, c in enumerate(composites):
        if c:
            continue
        composites[p * p :: p] = 1
    return np.array([p for p, c in enumerate(composites) if not c],
        dtype = np.uint32)

def SieveOfEratosthenes_Task(small_primes, begin, end):
    import numpy as np
    composites = np.zeros((end - begin,), dtype = np.uint8)
    offsets = np.full((len(small_primes),), begin, dtype = np.uint32)
    offsets = small_primes - offsets % small_primes
    offsets[offsets == small_primes] = 0
    for off, p in zip(offsets, small_primes):
        composites[off :: p] = 1
    return np.array([begin + i for i, c in enumerate(composites) if not c],
        dtype = np.uint32)

def SieveOfEratosthenes_MultiCore(end, *, nthreads = None):
    import math, multiprocessing as mp, numpy as np
    end_small = math.ceil(math.sqrt(end)) + 1
    small_primes = SieveOfEratosthenes_Simple(end_small)
    if nthreads is None:
        nthreads = mp.cpu_count()
    block = (end - end_small + nthreads - 1) // nthreads
    with mp.Pool(nthreads) as pool:
        return np.concatenate([small_primes] + pool.starmap(SieveOfEratosthenes_Task, [
            (small_primes, min(end_small + ithr * block, end), min(end_small + (ithr + 1) * block, end))
            for ithr in range(nthreads)]))

def Test():
    import time, numpy as np, multiprocessing as mp
    primes_limit_M = 8
    cpu_cores = mp.cpu_count()
    end = primes_limit_M * 2 ** 20
    print(f'Computing primes less than {primes_limit_M}M')
    print('Number of CPU cores', cpu_cores, flush = True)
    
    tim_orig = time.time()
    res_orig = SieveOfEratosthenes_Original(end)
    tim_orig = time.time() - tim_orig
    print('Original time', round(tim_orig, 3), 'sec', flush = True)
    
    tim_simple = time.time()
    res_simple = SieveOfEratosthenes_Simple(end)
    tim_simple = time.time() - tim_simple
    print('Simple time', round(tim_simple, 3), 'sec', flush = True)
    
    assert np.all(res_orig == res_simple)
    print(f'Simple boost (compared to original) {tim_orig / tim_simple:.3f}x')
    
    tim_multi = time.time()
    res_multi = SieveOfEratosthenes_MultiCore(end, nthreads = cpu_cores)
    tim_multi = time.time() - tim_multi
    print('Multi core time', round(tim_multi, 3), 'sec', flush = True)
    
    assert np.all(res_simple == res_multi)
    print(f'Multi core boost (compared to simple) {tim_simple / tim_multi:.3f}x')
    
if __name__ == '__main__':
    Test()
Arty
  • 14,883
  • 6
  • 36
  • 69
0
import math

def atkin_sieve(limit):
   primes = [False] * (limit + 1)
   square_limit = int(math.sqrt(limit))

# Отметить все числа, делящиеся нацело на 2, 3 или 5
for i in range(1, square_limit + 1):
    for j in range(1, square_limit + 1):
        num = 4 * i**2 + j**2
        if num <= limit and (num % 12 == 1 or num % 12 == 5):
            primes[num] = not primes[num]

        num = 3 * i**2 + j**2
        if num <= limit and num % 12 == 7:
            primes[num] = not primes[num]

        num = 3 * i**2 - j**2
        if i > j and num <= limit and num % 12 == 11:
            primes[num] = not primes[num]

# Удалить кратные квадратам простых чисел
for i in range(5, square_limit):
    if primes[i]:
        for j in range(i**2, limit + 1, i**2):
            primes[j] = False

# Вернуть список простых чисел
return [2, 3] + [i for i in range(5, limit) if primes[i]]

# Пример использования
print(atkin_sieve(100))