1.7.4 Changing Programs

Consider a new problem: count and print the number of primes that are no greater than n. Instead of immediately attempting a solution, can you think of a program that already solves a related problem? You've already seen one: primes. Perhaps it can be changed to help with the solution. Looking at the refinement of primes (Figure 1.3), you can see that replacing function print by a count function that prints the proper count does the trick. The solution countprimes, using the changed primes and count, is as follows.

countprimes (n)

/* This function counts the prime

numbers between 2 and n

and prints the count.

*/

int n;

{

collection candidates;

create (n, candidates);

remove(n,candidates);

count(n,candidates);

}

count(n,c)

/* Prints the number of integers

in c between 2 and n.

*/

int n;

collection c;

{

int i,counter;

counter = 0;

for (i = 2; i<=n;i++)

if (belongs(i,c))

counter++;

printf ("\n The number of primes between 2 and %d is %d\n",

n, counter);

}

Solutions are rarely obtained so easily. Yet this example teaches us something. Programs that have been designed wisely, with an eye to generality, may often be used as tools in the solution of related problems. When beginning the development of a solution to a problem, see first if a program for a related problem is already known, and if so whether it can be adapted. This can save significant time, effort, and money compared to writing a new program or function.

It was easy to see what to change and how to change it in our example, because primes is functionally modular, with narrowly defined tasks and independent functions. Changes are rarely as straightforward as this; for contrast, consider changing the algorithm anotherprimes to a program for countprimes. In general, though, isolating functions that carry out a single narrowly defined task tends to generate lower-level functions that are clear, simple, and of moderate size. At higher levels, such single-task functions need not be simple. For example, a function to "Select the best next chess move" is such a function, but it is not simple. The lower the level of a function that needs changing, the less the effort required. However, the higher the level of the task for which one can adapt a known program, the greater the savings, since much refinement is avoided. In an ideal situation, changes require little effort and benefits are great.

Often a programmer's assignment or goal is to increase the efficiency of a program, and this will involve changing the way data are stored. If the program has been written using data abstractions, it is much easier to see exactly which functions need changing. For example, in primes, to change the implementation of candidates involves changing only its declaration and set, insert, omit, and belongs.