2012-02-15 12 views
5

¿El siguiente código para generar primos es pythonic?es este generador de primos 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) 

Tenga en cuenta que el próximo (next_p) finalmente lanzar un error StopIteration que de alguna manera termina la función get_primes. ¿Es tan malo?

También tenga en cuenta que next_p es un generador que itera sobre primos, sin embargo los primos cambian durante la iteración. ¿Es ese mal estilo?

agregando la siguiente instrucción if pone debajo de 0,25 segundos para el primer millón de números primos:

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

puede guardar una línea si desea usar 'primos = [Falso, falso] + [Real] * (n-1)', también añadir complejidad puede optimizar el uso medio de un tamiz, omita los números pares . vea http://stackoverflow.com/a/3035188/464543 – ChessMaster

+0

gracias @ChessMaster –

+1

pruebe su código para 0,1,2,3 sin la línea 'si p * p <= n:' ... en mi máquina que línea no es necesaria – ChessMaster

Respuesta

3

No es malo que next(next_p) genera un error StopIteration - eso es lo que un generador siempre cuando se queda sin elementos de !

Cambiar la longitud de una lista mientras se itera sobre ella es una mala idea. Pero no hay nada malo con simplemente cambiar los contenidos. En general, creo que esto es bastante elegante, si es básico, seive.

Una pequeña observación: cuando "tacha" los múltiplos de números primos, encontrará, si lo piensa por un momento, que no tiene que empezar por p * 2. Puede saltear adelante al p ** 2.

+0

Ah, por supuesto salte a p ** 2 ya que ya se han encontrado todos los primos más pequeños. Lo cambiaré gracias –

2

No hay nada de malo en el StopIteration, de hecho ese es el comportamiento esperado de los generadores.

Yo diría que esta implementación es más Pythonic (no necesariamente más eficiente):

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 para mí significa clara, concisa y fácil de leer, y el uso de los puntos fuertes de la lengua. Si bien puedo ver que su implementación es una especie de tamiz, solo lo sé por experiencia previa con ese tipo de algoritmos. La implementación anterior puedo leer directamente como una prueba directa de divisibilidad.


Nota: hay una diferencia menor en la interfaz, su aplicación sería dió primos < = n mientras que mi aplicación produciría primos < n. Obviamente, esto se puede cambiar de manera fácil y trivial (simplemente cambie n a n + 1 en el cuerpo de la función), pero creo que es más pitónico generar primos hasta-pero-no incluyendo n para ser más consistente con el modo, digamos , range() obras incorporadas.


EDITAR: JUST FOR FUN

Aquí es una aplicación Pythonic menos, y probablemente bastante ineficiente también :)

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) 

Yo llamo a esto la menor porque Pythonic ¡te estarías rascando la cabeza por algunos días para descubrir cómo funciona si no has visto este truco antes!

+1

Gracias amigo. Sí, esto se pondrá realmente lento una vez que n sea mucho mayor que 1000, pero es fácil de leer. –

+1

Nota: solo estoy tratando de responder sobre _pythonic_, si está interesado en la manera _fastest_ en python, esta pregunta ha sido bien cubierta [aquí] (http://stackoverflow.com/questions/2068372/fastest-way-to -list-all-primes-below-n-in-python). – wim

+0

yup, escribiría algo como esto si rápidamente necesitara algunos números primos pequeños, o si tuviera 0 memoria. He utilizado tu idea para enviar otra respuesta que no es tan pitonica, pero un poco más eficiente. –

0

Aquí hay otra solución algo pitonica motivada por @wim, sin embargo puedes ver que es un poco más lenta que el primer método.

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

que imp es 'get_primes' que se refiere a aqui? – wim

+0

el de mi pregunta. Si agrega si p * p <= n, obtiene 0.025. wim, no probé el tuyo, supuse que sería muy lento :) –

+0

sí, creo que probablemente sea unas 500 veces más lento, lol – wim

Cuestiones relacionadas