Evaluación perezosa

Quizá a estas alturas te hayas dado cuenta de que usar este enfoque indiscriminadamente puede llevar a realizar una cantidad de cálculos desproporcionada y a usar una gran cantidad de memoria, aún cuando el problema no lo necesita. Por ejemplo, para calcular el segundo primo entre 10000 y 100000 podemos hacer:
ls = srange(10000,100000) primos = [t for t in ls if is_prime(t) ] print primos[1]

pero esto nos obliga a guardar en memoria todos los números naturales entre 10000 y 100000, y después a calcular si cada uno es primo, sólo para quedarnos con el segundo.
sage: %time sage: ls = srange(10000,100000) sage: primos = [t for t in ls if is_prime(t) ] sage: print primos[2] 10037 CPU time: 0.39 s, Wall time: 0.39 s

Sin embargo, es posible mantener esta sintaxis sin hacer cálculos innecesarios. Para ello, usamos generadores , objetos que generan los elementos de la lista uno por uno, según se vayan solicitando desde otras partes del programa. A esta técnica se le denomina evaluación perezosa , porque no se hacen cálculos hasta que no son necesarios. Por ejemplo, la función xsrange es la versión perezosa de srange (con la misma sintaxis). Mientras que srange(100) inmediatamente calcula los enteros menores que 100 y los guarda en una lista, xsrange(100) devuelve un generador, que no calcula los números en ese momento:
sage: srange(100) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99] sage: xsrange(100) <generator object generic_xsrange at 0x5dacf50>

podemos pedir los números uno por uno.01 s.next() print xlista..next() sage: print primos. Wall time: 0. usando un bucle for igual que si fuera una lista: sage: sage: sage: 0 1 sage: sage: . print c 0 1 4 9 16 25 36 49 64 81 Ahora podemos modificar el programa original para calcular el segundo primo entre 10000 y 100000. o recorrerlos todos.Una vez creado el generador. if is_prime(j): . if contador == 0: .next() 10009 CPU time: 0.. usando el método next() .01 s sage: %time sage: contador = 2 sage: for j in xsrange(10000.. usando una cantidad de memoria y cpu equivalente a un programa tradicional: sage: %time sage: ls = xrange(10000.100000) sage: primos = (t for t in ls if is_prime(t) ) sage: primos.100000): .. sage: 5050 xlista = xsrange(100) print xlista.next() acumulador = 0 for j in xsrange(101): acumulador += j print acumulador Podemos escribir nuestros propios generadores usando una notación similar a la de transformaciones de listas. contador -= 1 ..... sólo que poniendo paréntesis alrededor de nuestra expresión en vez de corchetes: generador = (expresion for x in secuencia if condicion) sage: genera_cuadrados = (x^2 for x in xsrange(10)) sage: for c in genera_cuadrados: ...

En la primera llamada a next .00 s. en cuanto encontremos un valor de k para el que 2*k+1 es primo podemos parar.next() pedimos otro número más a primos .100000) primos = (t for t in ls if is_prime(t) ) con estas dos líneas. break sage: print j 10009 CPU time: 0. Un enfoque tradicional se aprovecha de esta propiedad: hay_alguno = False for k in srange(1000. el primer primo mayor que 10000. Por supuesto.. definimos dos generadores. pero no se realiza ningún cálculo. El programa concluye. print primos. Cuando encuentra uno que pasa el filtro is_prime(t) . Consideremos por ejemplo la pregunta: ¿Hay algún número k entre 1000 y 10000 tal que 2*k +1 es primo? Para comprobar la pregunta. podemos comprobar si el número 2*k+1 es primo para cada valor de k. Recorre primero el número 10008 e inmediatemente después encuentra 10009. que debe rodar hasta devolver un número primo.10000): if is_prime(2*k+1): . es interesante combinar los generadores con el enfoque de flujos que tratamos hoy. Todo a la vez Por supuesto. Wall time: 0. y responder a la pregunta con un sí . Para ello.00 s Sigamos la ejecución del programa paso a paso: ls = xrange(10000. que es primo. ponemos a trabajar al generador primos . primos devuelve el número 10007.. que pide a su vez números al generador ls continuando donde lo dejó.next() al llamar al método next . prueba los números de la lista ls uno por uno. primos. lo devuelve..

no necesitamos poner dobles paréntesis. tendremos que comprobar si todos los números son primos: any([is_prime(2*k+1) for k in srange(1000...00 s. hay_alguno = True . como el generador ya estaba entre parentésis...hay_alguno = True break print hay_alguno mientras que si transformamos listas de forma naive. Wall time: 0. Wall time: 0.00 s sage: %time sage: any([is_prime(2*k+1) for k in srange(1000.10000)) Observa que.10000)) True CPU time: 0..00 s .05 s. sage: %time sage: hay_alguno = False sage: for k in srange(1000. break sage: print hay_alguno True CPU time: 0.05 s sage: %time sage: any(is_prime(2*k+1) for k in srange(1000.10000): .00 s sage: %time sage: any(is_prime(2*k+1) for k in xsrange(1000.01 s.01 s.10000)) True CPU time: 0.10000)]) Pero basta usar un generador para volver a conseguir un programa eficiente: any(is_prime(2*k+1) for k in xsrange(1000. Wall time: 0..10000)]) True CPU time: 0. Wall time: 0. if is_prime(2*k+1): .