2012-02-15 10 views
5

È il seguente codice per la generazione di numeri primi pythonic?è questo generatore di primoni pythonic

def get_primes(n): 
    primes=[False,False]+[True]*(n-1) 
    next_p=(i for i,j in enumerate(primes) if j) 
    while True: 
     p=next(next_p) 
     yield p 
     primes[p*p::p]=[False]*((n-p*p)//p+1) 

nota che il prossimo (next_p) finirà per lanciare un errore StopIteration che finisce in qualche modo i get_primes funzione. È così male?

Si noti inoltre che next_p è un generatore che esegue l'iterazione sui numeri primi, tuttavia i primi vengono modificati durante l'iterazione. È quello stile cattivo?

aggiungendo la seguente istruzione if ottiene sotto 0,25 secondi per il primo milione di numeri primi:

if p*p<=n: 
    primes[p*p::p]=[False]*((n-p*p)//p+1) 
+1

è possibile salvare una linea se si vuole utilizzare 'numeri primi = [False, False] + [vero] * (n-1)', aggiungendo anche la complessità è possibile ottimizzare di utilizzare la metà un setaccio, saltare i numeri pari . vedi http://stackoverflow.com/a/3035188/464543 – ChessMaster

+0

grazie a @ChessMaster –

+1

prova il tuo codice per 0,1,2,3 senza la riga 'se p * p <= n:' ... nella mia macchina che linea non è necessaria – ChessMaster

risposta

3

Non è male che next(next_p) genera un errore StopIteration - che è quello che un generatore di sempre fa quando si esaurisce di articoli !

Cambiare la lunghezza di un elenco mentre si scorre su di esso è una cattiva idea. Ma non c'è niente di sbagliato semplicemente cambiando il contenuto. Nel complesso, penso che questo sia un po 'elegante, se di base, seive.

Una piccola osservazione: quando "estrai" i multipli dei numeri primi, troverai, se ci penserai un po ', che non devi iniziare con p * 2. Puoi saltare a p ** 2.

+0

Ah, naturalmente, salta a p ** 2 poiché tutti i numeri primi più piccoli sono già stati trovati. Lo cambierò grazie –

2

Non c'è niente di sbagliato con lo StopIteration, che è il comportamento previsto per i generatori.

direi che questa implementazione è più divinatorio (non necessariamente più efficiente):

def get_primes(n): 
    """Generates prime numbers < n""" 
    return (x for x in xrange(2,n) if all(x % i for i in xrange(2,x))) 

Pythonic per me significa chiaro, conciso, leggibile, e utilizzando i punti di forza del linguaggio. Mentre posso vedere che la tua implementazione è una sorta di setaccio, so solo che da un'esperienza precedente con quel tipo di algoritmi. L'implementazione di cui sopra posso leggere direttamente come un test diretto di divisibilità.


Nota: c'è una differenza minore nell'interfaccia, l'implementazione sarebbe resa primi < = n mentre la mia realizzazione produrrebbe numeri primi < n. Ovviamente questo può essere cambiato facilmente e banalmente (basta cambiare n a n + 1 nel corpo della funzione), ma sento che è più pitonico generare numeri primi, ma non includere n per essere più coerenti con il modo, per dire , range() opere integrate.


EDIT: JUST FOR FUN

Ecco un almeno pythonic implementazione, e probabilmente abbastanza inefficiente troppo :)

def get_primes(n): 
    import re 
    return (x for x in xrange(2,n) if re.match(r'^1?$|^(11+?)\1+$', '1' * x) is None) 

ho questa chiamata il meno divinatorio perché ti staresti grattando la testa per qualche giorno per capire come funziona se prima non avevi visto questo trucco !!

+1

Grazie amico. Sì, questo diventerà molto lento una volta che n diventerà molto più grande di 1000, ma è facile da leggere. –

+1

Nota: sto solo cercando di rispondere a _pythonic_, se sei interessato al modo _fastest_ in python, questa domanda è stata ben trattata [qui] (http://stackoverflow.com/questions/2068372/fastest-way-to -list-all-primi-sotto-n-in-python). – wim

+0

sì, vorrei scrivere qualcosa del genere se avessi bisogno di alcuni piccoli numeri primi, o se avessi memoria 0. Ho usato la tua idea per presentare un'altra risposta che non è altrettanto pitone ma leggermente più efficiente. –

0

Ecco un'altra soluzione un po 'pitonica motivata da @wim, tuttavia è possibile vederlo leggermente più lento del primo metodo.

def get_primes2(n): 
    primes=[] 
    for i in range(2,n+1): 
     small_primes=(p for p in primes if p*p<=i) 
     if all(i%p for p in small_primes): 
      yield i 
      primes.append(i) 

import timeit 
print(timeit.timeit("list(get_primes(10**5))",number=5,setup="from __main__ import get_primes")/5.0) 
"0.0350940692182945" 
print(timeit.timeit("list(get_primes2(10**5))",number=5,setup="from __main__ import get_primes2")/5.0) 
"8.226938898658908" 
+0

quale imp è 'get_primes' riferendosi a qui? – wim

+0

quello nella mia domanda. Se aggiungi p * p <= n, ottieni 0.025. wim, non ho provato il tuo, ho pensato che sarebbe molto lento :) –

+0

sì, penso che probabilmente sarà circa 500 volte più lento, lol – wim

Problemi correlati