64 votes

Comment implémenter un générateur infini efficace de nombres premiers en Python?

Ce n'est pas des devoirs à faire, je suis juste curieux.

L'INFINI est le mot clé ici.

Je souhaite l'utiliser comme p dans les nombres premiers(). Je crois que c'est une fonction intégrée dans Haskell.

Donc, la réponse ne peut pas être aussi naïf que "Juste faire un Tamis".

Tout d'abord, vous ne savez pas combien de nombres premiers consécutifs seront consommés. Eh bien, supposons que vous pourriez vous concocter 100 d'entre eux à un moment. Voulez-vous utiliser le même Tamis approche ainsi que la fréquence de nombres premiers de la formule?

Je préfère le non-cumul approche.

Merci pour la lecture (et l'écriture ;) )!

78voto

tzot Points 32224

"Si j'ai vu plus loin..."

L' erat2 fonction à partir du livre de cuisine peut être encore accéléré (environ 20 à 25%):

erat2a

import itertools as it
def erat2a( ):
    D = {  }
    yield 2
    for q in it.islice(it.count(3), 0, None, 2):
        p = D.pop(q, None)
        if p is None:
            D[q*q] = q
            yield q
        else:
            # old code here:
            # x = p + q
            # while x in D or not (x&1):
            #     x += p
            # changed into:
            x = q + 2*p
            while x in D:
                x += 2*p
            D[x] = p

L' not (x&1) check vérifie que x est impair. Cependant, depuis les deux q et p sont impairs, par l'ajout d' 2*p de la moitié des mesures sont évités avec le test de curiosité.

erat3

Si on n'a pas l'esprit un peu plus fanciness, erat2 peut être accéléré par 35 à 40% avec les changements suivants (NB: les besoins de Python 2.7+ ou Python 3+ en raison de l' itertools.compress de la fonction):

import itertools as it
def erat3( ):
    D = { 9: 3, 25: 5 }
    yield 2
    yield 3
    yield 5
    MASK= 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0,
    MODULOS= frozenset( (1, 7, 11, 13, 17, 19, 23, 29) )

    for q in it.compress(
            it.islice(it.count(7), 0, None, 2),
            it.cycle(MASK)):
        p = D.pop(q, None)
        if p is None:
            D[q*q] = q
            yield q
        else:
            x = q + 2*p
            while x in D or (x%30) not in MODULOS:
                x += 2*p
            D[x] = p

L' erat3 fonction prend avantage du fait que tous les nombres premiers (à l'exception de 2, 3, 5) modulo 30 résultat, à seulement huit chiffres: ceux inclus dans l' MODULOS frozenset. Ainsi, après ce qui donne les trois premiers nombres premiers, nous commençons à partir de 7 et de travailler uniquement avec les candidats.
Le candidat de filtrage utilise l' itertools.compress de la fonction; la "magie" est dans l' MASK de la séquence; MASK a 15 éléments (il y a 15 nombres impairs dans tous les 30 numéros choisis par l' itertools.islice de la fonction) avec un 1 pour chaque candidat possible, à partir de 7. Le cycle se répète, comme spécifié par l' itertools.cycle fonction.
L'introduction du candidat du filtrage a besoin d'une autre modification: la or (x%30) not in MODULOS vérifier. L' erat2 algorithme traité tous les nombres impairs; maintenant que l' erat3 algorithme ne traite que r30 candidats, nous devons nous assurer que tous D.keys() ne peut être tel -faux candidats.

Repères

Résultats

Sur un Atom 330 Ubuntu 9.10 server, versions 2.6.4 et 3.1.1+:

$ testit
up to 8192
==== python2 erat2 ====
100 loops, best of 3: 18.6 msec per loop
==== python2 erat2a ====
100 loops, best of 3: 14.5 msec per loop
==== python2 erat3 ====
Traceback (most recent call last):
…
AttributeError: 'module' object has no attribute 'compress'
==== python3 erat2 ====
100 loops, best of 3: 19.2 msec per loop
==== python3 erat2a ====
100 loops, best of 3: 14.1 msec per loop
==== python3 erat3 ====
100 loops, best of 3: 11.7 msec per loop

Sur un AMD Geode LX Gentoo home server, Python 2.6.5 et 3.1.2:

$ testit
up to 8192
==== python2 erat2 ====
10 loops, best of 3: 104 msec per loop
==== python2 erat2a ====
10 loops, best of 3: 81 msec per loop
==== python2 erat3 ====
Traceback (most recent call last):
…
AttributeError: 'module' object has no attribute 'compress'
==== python3 erat2 ====
10 loops, best of 3: 116 msec per loop
==== python3 erat2a ====
10 loops, best of 3: 82 msec per loop
==== python3 erat3 ====
10 loops, best of 3: 66 msec per loop

Référence code

Un primegen.py module contient l' erat2, erat2a et erat3 fonctions. Voici le script de test:

#!/bin/sh
max_num=${1:-8192}
echo up to $max_num
for python_version in python2 python3
do
    for function in erat2 erat2a erat3
    do
        echo "==== $python_version $function ===="
        $python_version -O -m timeit -c \
        -s  "import itertools as it, functools as ft, operator as op, primegen; cmp= ft.partial(op.ge, $max_num)" \
            "next(it.dropwhile(cmp, primegen.$function()))"
    done
done

73voto

Will Ness Points 18581

Depuis l'OP demande pour une efficace mise en œuvre, ici, est une amélioration significative de l' état actif à 2002 code par David Eppstein/Alex Martelli (vu ici dans sa réponse): ne pas enregistrer un premier de l'info dans le dictionnaire jusqu'à ce que son carré est considéré parmi les candidats. Apporte de la complexité de l'espace vers le bas au-dessous de O(sqrt(n)) au lieu de O(n), pour n en produit de nombres premiers ( pi(sqrt(n log n)) ~ 2 sqrt(n log n) / log(n log n) ~ 2 sqrt(n / log n) ). Par conséquent, le temps de la complexité est également améliorée, c'est à dire qu'il s'exécute plus rapidement.

Crée un coulissantes "tamis" comme un dictionnaire de courant multiples de chaque base, le premier (c'est à dire en dessous de la racine carrée de la production actuelle point), ainsi que leur étape valeurs:

def postponed_sieve():                   # postponed sieve, by Will Ness      
    yield 2; yield 3; yield 5; yield 7;  # original code David Eppstein, 
    sieve = {}                           #            ActiveState Recipe 2002
    ps = (p for p in postponed_sieve())  # a separate Primes Supply:
    p = next(ps) and next(ps)            # (3) a Prime to add to dict
    q = p*p                              # (9) when its sQuare is 
    c = 9                                # the next Candidate
    while True:
        if c not in sieve:            # not a multiple of any prime seen so far:
            if c < q:                 #   a prime, 
                yield c ; c += 2 ;    #
                continue              #            or
            else:   # (c==q):         #   the next prime's square:
                s=2*p                 #     (9+6,6 : 15,21,27,33,...)
                p=next(ps)            #     (5)
                q=p*p                 #     (25)
        else:                         # 'c' is a composite:
            s = sieve.pop(c)          #   step of increment
        c2 = c+s                      #   next multiple, same step
        while c2 in sieve: c2 += s    #   no multiple keys in sieve (dict):
        sieve[c2] = s                 #         (increment by the given step)
        c += 2                        # next odd candidate

#                - base -             - postponed -
# 1500000                         13.28s-4.7   n^1.09 
# 1000000 10.83s-28.0  n^1.23      8.53s-4.7   n^1.08   original test entry:
#  800000  8.23s-28.0  n^1.13      6.70s-4.7   n^1.09    http://ideone.com/WFv4f
#  400000  3.76s-15.8  n^1.11      3.14s-4.7   n^1.07
#  200000  1.74s-4.9MB             1.50s-4.7MB

voir aussi http://stackoverflow.com/a/8871918/849891 pour une discussion.

45voto

Tim Peters Points 16225

Pour la postérité, voici une réécriture de la Volonté Ness est beau algorithme pour Python 3. Certains changements sont nécessaires (itérateurs n'ont plus d' .next() méthodes, mais il y a un nouveau next() builtin fonction). D'autres changements sont pour le fun (à l'aide de la nouvelle - yield from <iterable> remplace quatre yield des déclarations à l'original. Plus sont pour des raisons de lisibilité (je ne suis pas un fan de trop ;-) 1-lettre des noms de variables).

C'est nettement plus rapide que l'original, mais pas pour des raisons algorithmiques. L'accélération est principalement en raison de la suppression de l'original add() de la fonction, en faisant que inline à la place.

def psieve():
    import itertools
    yield from (2, 3, 5, 7)
    D = {}
    ps = psieve()
    next(ps)
    p = next(ps)
    assert p == 3
    psq = p*p
    for i in itertools.count(9, 2):
        if i in D:      # composite
            step = D.pop(i)
        elif i < psq:   # prime
            yield i
            continue
        else:           # composite, = p*p
            assert i == psq
            step = 2*p
            p = next(ps)
            psq = p*p
        i += step
        while i in D:
            i += step
        D[i] = step

19voto

Alex Martelli Points 330805

J'aime toujours ce que j'ai écrit ici (une recette de livre de recettes avec de nombreux autres auteurs) - cela montre à quel point un tamis d'Eratosthène n'a pas de limites intrinsèques, et les commentaires et la discussion, je crois, sont assez clairs. Cela a récemment été discuté sur Stack Overflow (recherche des noms des auteurs, je suppose) et quelqu'un a proposé une version beaucoup plus rapide (mais à mon humble avis moins claire) ;-).

8voto

Dominic Bou-Samra Points 4343

Ce n'est pas à l'origine mon code, cependant, ça vaut la peine de poster. L'original peut être trouvé ici: http://code.activestate.com/recipes/117119/

 def gen_primes():
  D = {}
  q = 2  # first integer to test for primality.

  while True:
    if q not in D:
      # not marked composite, must be prime  
      yield q 

      #first multiple of q not already marked
      D[q * q] = [q] 
    else:
      for p in D[q]:
        D.setdefault(p + q, []).append(p)
      # no longer need D[q], free memory
      del D[q]

    q += 1
 

C'est un générateur, alors utilisez-le comme un autre.

 primes = gen_primes()
for p in primes:
  print p
 

Il faut 1,62s pour générer et mettre dans un ensemble, 1 million de nombres premiers, sur mon bureau.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X