(optional) The Sieve of Eratosthenes

Chapter: (optional) The Sieve of Eratosthenes

The ancient Greeks knew a thing or two about primes.

Wikipedia has a nice animation showing the process here.

Start with the numbers from 2 upwards:

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ...

Keep the first number but remove (or "sieve" out) all its multiples from the remaining stream:

2 3 X 5 X 7 X 9 XX 11 XX 13 XX 15 XX 17 XX 19 ...

This results in:

2 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 ...}

We know we will keep the first member(2) but now we'll turn our attention to the next number 3. We will keep it and sift out all the multiples:

2 3 5 7 X 11 13 XX 17 19 XX 23 25 XX 29 31 XX 35 37 XX 41 43 XX 47 49 ...}

Now we're left with:

2 3 5 7 11 13 17 19 23 25 29 31 35 37 41 43 47 49 ...}

2 and 3 are being kept, but we turn our attention to 5, and filter out all its multiples:

2 3 5 7 11 13 17 19 23 XX 29 31 XX 37 41 43 47 49 ...}

and so on. You can see this is a recursive process, and the code is much shorter than the explanation:

/* Code copyright 2007 Rhys Price Jones.  Fair use under GPL ok */
    public static Stream sieve(Stream s) {
        BigInteger number1 = s.next();
        return new ExtendedStream(filterMults(s, number1), number1) {
                public BigInteger next() {
                    if (first) {
                        first = false;
                        return data;
                    }
                    else {
                        return (sieve(oldStream)).next();
                    }
                }
            };
    }

Notice the recursion: We make a new class whose next() method will make a recursive call to sieve. But the operative and important word in the previous sentence is will. It won't happen until you invoke the next() method of the current stream. The recursive call is, by this cunning mechanism, delayed until its value is needed. We've accomplished lazy evaluation in Java!

Assuming you have a correct solution to the last exercise, you can now pull everything together for the prime number generating program.

Pull all your stream manipulation methods into a class, say StreamOps:

/* Code copyright 2007 Rhys Price Jones.  Fair use under GPL ok */
import java.math.*;
public class StreamOps {
    public static void removeFirst(Stream theStream) {
        theStream.next();
    }
    public static Stream insertFirst(Stream theStream, BigInteger newFirst) {
        return new ExtendedStream (theStream, newFirst) {
                public BigInteger next() {
                    if (first) {
                        first = false;
                        return data;
                    }
                    else return oldStream.next();
                }
            };
    }
    public static Stream filterMults(Stream theStream, BigInteger n) {
       // Your code goes here
    }
    public static Stream sieve(Stream s) {
        BigInteger number1 = s.next();
        return new ExtendedStream(filterMults(s, number1), number1) {
                public BigInteger next() {
                    if (first) {
                        first = false;
                        return data;
                    }
                    else {
                        return (sieve(oldStream)).next();
                    }
                }
            };
    }
}

And now here is one of the shortest prime number generating programs you can probably imagine. And it's unlimited: In principle it should never stop.

/* Code copyright 2007 Rhys Price Jones.  Fair use under GPL ok */
import java.math.*;
public class Primes {
    public static void main(String[] args) {
        BigInteger two = BigInteger.ONE.add(BigInteger.ONE);
        Stream myStream = new TheInts();
        myStream = StreamOps.filterMults(myStream, two);  // remove evens
        StreamOps.removeFirst(myStream);                  // remove 1
        myStream = StreamOps.sieve(myStream);             // apply Sieve of Eratosthenes
        myStream = StreamOps.insertFirst(myStream, two);  // reinstate 2
        while (myStream.hasNext())
            System.out.println(myStream.next());
    }
}
It has a small optimization not discussed above. I'm sure you'll notice it if you study my code.


Exercise 16

(optional -- extra credit) Test your code for the last exercise by compiling all classes and running java Primes. To show you're correct, be sure that you have the same value that I do for the 4306th prime and the 18800th prime.



rhyspj@gwu.edu