This algorithm is particularly suitable to be the basis for a nice example of Object Oriented Programming. We will see how to program it in C++. For more details, see also the book of Sethi, Section 7.5.
The general idea is to create dynamically new objects, one for each prime number which is generated, and to let each of these object be responsible for eliminating (filtering away) numbers which are not prime. The simplest organization is to have all these objects connected in a chain, and new numbers (generated by an object at the beginning of the chain) flowing through the chain. The picture below illustrates the organization of the objects and the flow of data:
------------- | Counter | generates natural numbers ------------- || | flow of data \/ V ------------- | Filter 2 | eliminates numbers that are multiples of 2 ------------- || \/ ------------- | Filter 3 | eliminates numbers that are multiples of 3 ------------- || \/ ------------- | Filter 5 | eliminates numbers that are multiples of 5 ------------- || \/ ------------- | Filter 7 | eliminates numbers that are multiples of 7 ------------- ...The design of the actual program will depend also on how the control is organized, namely how the various objects get activated. In general we can distinguish two main possibilities:
In this kind of architecture, each object must know the identity of the previous object in the chain (called source by Sethi). Thus we have a structure like the following (after the production of 5). The arrows indicate the pointers between objects.
------------- | 6 | Counter. Will generate 6 next ------------- ^ | ------+------ | | | | | ------------- | 2 | Filter with factor 2 ------------- ^ | ------+------ | | | | | ------------- | 3 | Filter with factor 3 ------------- ^ | ------+------ | | | | | ------------- | 5 | Filter with factor 5 ------------- ^ ^ | | flow of control ------+------ | | | Sieve | | -------------Note that, at the beginning, there are only two objects: Sieve and Counter. This means that the source of Sieve is originally a Counter, and later it becomes a Filter. One advantage of using Object-Oriented technology is that we can treat the two situations uniformly. This can be done by using a basic class Item (following Sethi's terminology), of which all the other classes (Counter, Filter, and Sieve) will be subclasses. The main purpose of Item is to contain a declaration of a metod out(), (following Sethi's terminology) which is the method invoked by the next object in the chain when making a request for a new item. Thus each object in the chain can be seen as an Item by the next object; namely its particular subclass need not be known by the next object. Of course, the method out() will have to be overriden in all the subclasses.
Another advantage of OO is that we can declare a single destructor method in Item and have it effective, by inheritance, in all the subclasses.
We are now ready to give the code. This is essentially the same code defined in Sethi, with a few additions to illustrate its possible use.
/* Eratosthenes' Sieve */ /* Demand driven model */ #include <iostream.h> class Item { protected: Item *source; public: virtual int out() {} // to be overriden in the subclasses ~Item() { delete source; } }; class Counter: public Item { int value; public: int out() { return value++; } Counter() { source = 0; value = 2; } }; class Filter: public Item { int factor; public: int out() { int n; n = source->out(); while(!(n%factor)) { n = source->out(); } return n; } Filter(Item *src, int f){ source = src; factor = f; } }; class Sieve :public Item{ public: int out() { int n = source->out(); source = new Filter(source, n); return n; } Sieve(Item *src){source =src;} }; main() { Counter *g = new Counter(); Sieve *s = new Sieve(g); char a; cout << "Do you want the first prime number? (y/n) "; cin >> a; while (a=='y'){ cout << s->out() << '\n'; cout << "Do you want the next prime number? (y/n) "; cin >> a; }; delete s; }
In this kind of architecture, each object must know the identity of the next object in the chain, which we will call destination by Sethi). Thus we have a structure like the following (after the production of 5). The arrows indicate the pointers between objects.
------------- | 6 | Counter. Will generate 6 next ------------- | | | | | ------+------ | | flow of control V V ------------- | 2 | Filter with factor 2 ------------- | | | | | ------+------ | V ------------- | 3 | Filter with factor 3 ------------- | | | | | ------+------ | V ------------- | 5 | Filter with factor 5 ------------- | / | | / | -------------
We are now ready to give the code.
/* Eratosthenes' Sieve */ /* Data driven model */ #include <iostream.h> class Item { protected: Item* destination; public: ~Item(){ delete destination; } virtual void in(int n){} // to be overriden in the subclasses }; class Filter: public Item { int factor; public: Filter(int f){ factor = f; destination = NULL; } void in(int n) { if (n%factor == 0) return; if (destination != NULL) destination->in(n); else { destination = new Filter(n); cout << "The next prime number is " << n << '\n'; } } }; class Counter: public Item { int value; int max; public: Counter(int m){ max = m; } void start(){ if (max < 2) return; destination = new Filter(2); cout << "The first prime number is " << 2 << '\n'; value = 3; while (value <= max) destination->in(value++); } }; class EratostenesSieve{ int max; Counter* gen; public: EratostenesSieve(int m){ max = m; } void start(){ gen = new Counter(max); gen->start(); } ~EratostenesSieve(){ delete gen; } }; void main() { int max; cout << "Please input the max number to be generated" << '\n'; cin >> max; EratostenesSieve es(max); es.start(); cout << "End\n"; }