Question

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;
        }
    }
}
Was it helpful?

Solution

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.

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top