2012-04-21 15 views
6

Mi código a continuación encuentra todos los números primos por debajo de number creando una lista de números primos y verificando si el primer potencial potencial es divisible uniformemente por cualquier número primo en la lista.¿Se puede acceder a IEnumerable a medida que se devuelve?

Estoy tratando de aprender los pormenores de yield return. En este momento tengo un List<int> primes que uso dentro de la función. Pero devuelvo los mismos datos a través del yield return. Entonces mi pregunta es

¿Puedo acceder al IEnumerable < int> desde el interior de la función tal como la estoy creando? Así que puedo eliminar la lista < int> primes por completo.

/// <summary> 
/// Finds all primes below <paramref name="number"/> 
/// </summary> 
/// <param name="number">The number to stop at</param> 
/// <returns>All primes below <paramref name="number"/></returns> 
private static IEnumerable<long> PrimeNumbers(long number) 
{ 
    yield return 2; 
    List<long> primes = new List<long>(2);   

    for(long num = 3; num < number; num += 2) 
    { 

     //if any prime lower then num divides evenly into num, it isn't a prime 
     //what I'm doing now 
     if(!primes.TakeWhile(x => x < num).Any(x => num % x == 0)) 
     { 
      primes.Add(num); 
      yield return num; 
     } 

     //made-up syntax for what I'd like to do 
     if(!this.IEnumerable<long> 
       .TakeWhile(x => x < num).Any(x => num % x == 0)) 
     { 
      yield return num; 
     } 
    } 
} 
+0

Respuesta corta: No. Respuesta larga: hay mejores formas de obtener números primos hasta cierto número. Echa un vistazo a Sieve of Sundaram: http://en.wikipedia.org/wiki/Sieve_of_Sundaram – SimpleVar

+0

También puedes mejorar yendo por Sieve of Eratosthenes: http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes y aún más yendo por Sieve of Atkin: http://en.wikipedia.org/wiki/Sieve_of_Atkin Creo que es una de las formas más rápidas conocidas hoy en día para generar números primos (si no el más rápido) y también hay un pseudo código en wiki para ayudarte a obtener empezado. – SimpleVar

+0

@YoryeNathan Vi esos tamices en la wikipedia y si fuera a mantener este código en algún lugar importante, probablemente los usaría. Este código fue más para ayudar a entrenarme a mí mismo sobre cómo pensar en la mentalidad de "retorno de rendimiento". –

Respuesta

4

No, usted no puede hacer eso. El compilador crea una máquina de estado para implementar yield return, y el código de llamada que enumera a través de su enumerable forma parte de su funcionamiento tanto como su código. El compilador crea un objeto oculto que almacena el estado actual de su código, incluida su pila de llamadas y locales, y llama a diferentes partes de su método cuando la persona que llama invoca Current y MoveNext. Tratar de enumerar su objeto desde el principio mientras otra enumeración está en progreso arruinaría la enumeración en curso, lo cual no sería bueno.

En este caso particular, tampoco desea que suceda: la implementación de yield return no almacena los valores que usted produce, por lo que incluso si pudiera acceder a su propio IEnumerable al enumerarlo, volvería a llamar de manera recursiva varias veces para producir cada nuevo artículo, por lo que te tomaría un tiempo ridículamente largo producir incluso un número moderado de primos.

+0

Por supuesto que puedes. Se supone que las enumeraciones recursivas o anidadas se manejan automáticamente, eso es parte de su belleza. Esto lo haría: 'if (! PrimeNumbers (num) .Any (x => num% x == 0)) yield return num'. Acabo de probar en LINQPad. Sin embargo, su segundo punto es que es terriblemente ineficiente. Puede decir esto simplemente haciendo una 'Console.WriteLine (..)' dentro de la función para ver cuántas veces enumera el mismo resultado. – mellamokb

+1

@mellamokb Eso no está accediendo * a sí mismo * desde la función que crea la secuencia. Está accediendo * a una copia idéntica * de sí mismo, con su propio estado, máquina de estados y todo lo demás. Es por eso que no se rompe, y es por eso que es muy lento :) – dasblinkenlight

+0

¡Ah! Estoy en la misma página ahora :) Sí, no puede acceder a las entradas anteriores de la misma instancia porque el estado anterior ya no existe. – mellamokb

2

El enumerador general no es un contenedor como List<int> primes es. Es un "proceso" para encontrar primos. Si usas recursivamente tu propio proceso para generar la lista de primos a los que vas a oponer para encontrar el próximo primo, enumerarás recursivamente los mismos resultados una y otra vez, lo que será extremadamente ineficiente. Considere lo que sucedería (si en realidad se podría hacer esto) para la búsqueda de los números primos hasta 10.

yield return 2 
num = 3 
IEnumerable<long>.TakeWhile(x => x < 3).Any(x => num % x == 0) 
    new enumerator 
    yield return 2 
yield return 3 
num = 4 
IEnumerable<long>.TakeWhile(x => x < 4).Any(x => num % x == 0) 
    new enumerator 
    yield return 2 
    num = 3 
    IEnumerable<long>.TakeWhile(x => x < 3).Any(x => num % x == 0) 
     new enumerator 
     yield return 2 
    yield return 3 
num = 5 
IEnumerable<long>.TakeWhile(x => x < 5).Any(x => num % x == 0) 
    new enumerator 
    yield return 2 
    num = 3 
    IEnumerable<long>.TakeWhile(x => x < 4).Any(x => num % x == 0) 
     new enumerator 
     yield return 2 
     num = 3 
     IEnumerable<long>.TakeWhile(x => x < 3).Any(x => num % x == 0) 
      new enumerator 
      yield return 2 
     yield return 3 
     num = 4 
etc. 

Es una enumartion aumentando exponencialmente. Es similar a encontrar ingenuamente los números de Fibonacci calculando f(n-1) + f(n-2). Estás haciendo el mismo trabajo una y otra vez, y aún más cuando alcanzas números más altos. La Lista interna de primos sirve como un tipo de caché para hacer su enumeración muy eficiente.

Cuestiones relacionadas