Reputation: 10331
My code below finds all prime numbers below number
by creating a list of primes and checking to see if the next potential prime is evenly divisible by any primes in the list.
I'm trying to learn the ins and outs of yield return
. Right now I have a List<int> primes
that I use inside the function. But I'm returning the same data via yield return
. So my question is
Can I access the IEnumerable< int > from inside the function as I am creating it? So I can remove the List< int > primes altogether.
/// <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;
}
}
}
Upvotes: 6
Views: 469
Reputation: 56769
The overall enumerator isn't a container like List<int> primes
is. It is a "process" for finding primes. If you recursively use your own process to generate the list of primes to work against for finding the next prime, you will be recursively enumerating the same results over and over again which will be extremely inefficient. Consider what would happen (if you could actually do this) for finding the primes up to 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.
It's an exponentially increasing enumartion. It's akin to naively finding fibonacci numbers by calculating f(n-1) + f(n-2)
. You're doing a lot of the same work over and over again, and even more so as you reach higher numbers. The internal List of primes serves as a sort of cache to make your enumeration very efficient.
Upvotes: 2
Reputation: 726479
No, you cannot do that. The compiler builds a state machine to implement yield return
, and the calling code that enumerates through your enumerable is as much a part of its working as your code is. The compiler builds a hidden object that stores the current state of your code, including its call stack and locals, and it calls different pieces of your method as the caller invokes Current
and MoveNext
. Trying to enumerate your object from the beginning while another enumeration is in progress would mess up the ongoing enumeration, which would not be good.
In this particular case, you don't want it to happen either: the implementation of yield return
does not store the values that you produce, so if even if you could access your own IEnumerable
while enumerating, it would recursively call back itself multiple times to produce each new item, so it would take you ridiculously long time to produce even a moderate number of primes.
Upvotes: 4