web-dev-qa-db-fra.com

Tamis d'Eratosthenes - Trouver des Python

Juste pour clarifier, ce n'est pas un problème de devoirs :)

Je voulais trouver des nombres premiers pour une application mathématique que je construisais et suis tombé sur Sieve of Eratosthenes approche. 

J'en ai écrit une implémentation en Python. Mais c'est terriblement lent. Par exemple, si je veux trouver tous les nombres premiers inférieurs à 2 millions. Cela prend> 20 minutes. (Je l'ai arrêté à ce stade). Comment puis-je accélérer cela?

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: J'ai fini par faire du profilage sur ce code et j'ai constaté que beaucoup de temps était consacré à la suppression d'un élément de la liste. C'est tout à fait compréhensible de considérer qu'il faut parcourir toute la liste (dans le cas le plus défavorable) pour trouver l'élément, puis l'enlever et ensuite réajuster la liste (peut-être une copie continue?). Quoi qu'il en soit, j'ai chucked out list for dictionary. Ma nouvelle implémentation - 

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)
60
Srikar Appalaraju

Vous n'implémentez pas le bon algorithme:

Dans votre premier exemple, primes_sieve ne conserve pas la liste des drapeaux de primalité à supprimer/à effacer (comme dans l’algorithme), mais redimensionne en permanence une liste d’entiers, ce qui est très coûteux: la suppression d’un élément de la liste nécessite de décaler tous les éléments suivants. articles par un.

Dans le deuxième exemple, primes_sieve1 conserve un dictionary d'indicateurs de primalité, ce qui constitue un pas dans la bonne direction, mais il parcourt le dictionnaire dans un ordre indéfini et supprime de manière redondante les facteurs , comme dans l'algorithme). Vous pouvez résoudre ce problème en triant les clés et en sautant les non-premiers (ce qui le rend déjà plus rapide d'un ordre de grandeur), mais il est toujours beaucoup plus efficace d'utiliser simplement une liste.

L'algorithme correct (avec une liste au lieu d'un dictionnaire) ressemble à ceci:

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 xrange(i*i, limit, i):     # Mark factors non-prime
                a[n] = False

(Notez que cela inclut également l'optimisation algorithmique consistant à démarrer le marquage non premier sur le carré des nombres premiers (i*i) au lieu de son double.)

97
Pi Delport
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)
11
Saurabh Rana

Supprimer du début d'un tableau (liste) nécessite de déplacer tous les éléments après celui-ci. Cela signifie que supprimer tous les éléments d'une liste de cette manière en partant de l'avant est une opération O (n ^ 2).

Vous pouvez le faire beaucoup plus efficacement avec les 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)

... ou bien évitez de devoir réorganiser la liste:

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
6
Glenn Maynard

En combinant les contributions de nombreux enthousiastes (dont Glenn Maynard et MrHIDEn parmi les commentaires ci-dessus), j'ai créé le code suivant dans 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)

Le temps pris pour le calcul sur ma machine pour différentes entrées avec une puissance de 10 est:

  • 3: 0,3 ms
  • 4: 2,4 ms
  • 5: 23 ms
  • 6: 0,26 s
  • 7: 3,1 s
  • 8: 33 s
1
Ajay

Plus vite:

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))
1
MrHIDEn

Je réalise que cela ne répond pas vraiment à la question de savoir comment générer rapidement des nombres premiers, mais certains trouveront peut-être cette alternative intéressante: comme python fournit une évaluation paresseuse via des générateurs, le tamis d'eratosthenes peut être implémenté exactement comme indiqué:

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

Le bloc try existe, car l’algorithme est exécuté jusqu’à épuisement de la pile. Sans le bloc Try, la trace est affichée en poussant sur la sortie que vous voulez voir à l’écran.

1
Paul Gardiner

J'ai pensé qu'il devait être possible d'utiliser simplement la liste vide comme condition finale de la boucle et voici ce qui suit:

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
0
Tom Russell

Je préfère NumPy pour sa rapidité.

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)]

Vérifiez la sortie:

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]

Comparez la vitesse du tamis d'Eratosthène et la force brute sur le cahier Jupyter. Tamis d'Eratosthenes dans 539 fois plus rapide que la force brute pour million d'éléments. 

%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)
0
foo bar

je pense que c'est le code le plus court pour trouver des nombres premiers avec la méthode eratosthenes

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)))
0
Pythoscorpion

Ma mise en œuvre la plus rapide:

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
0
Madiyar

Voici une version un peu plus efficace en mémoire (et: un tamis approprié, pas des divisions d'essai). Fondamentalement, au lieu de conserver un tableau de tous les nombres et de rayer ceux qui ne sont pas premiers, cela conserve un tableau de compteurs - un pour chaque nombre de nombres premiers qu'il découvre - et de les faire passer avant le nombre premier présumé. De cette façon, il utilise un stockage proportionnel au nombre de nombres premiers, pas jusqu’au nombre premier.

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))

Vous noterez que primes() est un générateur, vous pouvez donc conserver les résultats dans une liste ou les utiliser directement. Voici les premiers n nombres premiers:

import itertools

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

Et, pour être complet, voici une minuterie pour mesurer la 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

Juste au cas où vous vous le demanderiez, j’ai aussi écrit primes() comme un simple itérateur (en utilisant __iter__ et __next__), et la vitesse était presque identique. Moi aussi surpris!

0
Jules May

Ma mise en oeuvre:

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
0
SilentDirge

Un simple piratage de vitesse: lorsque vous définissez la variable "nombres premiers", définissez le pas sur 2 pour ignorer automatiquement tous les nombres pairs et définissez le point de départ sur 1.

Ensuite, vous pouvez optimiser davantage au lieu de pour i dans les nombres premiers, utilisez pour i dans les nombres premiers [: round (len (nombres premiers) ** 0,5)]. Cela augmentera considérablement les performances. En outre, vous pouvez éliminer les nombres se terminant par 5 pour augmenter encore la vitesse.

0
user3917838
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]
0
Nestorghh