![]() |
II. Algorithmic Patterns |
Algorithmic Pattern Index |
|
This document consists of a number of templates or programming patterns
representing general solutions to common algorithm problems. Smart
programmers always break a programming task into some number of subtasks
or modules. The patterns presented here should be used in the coding of
those modules. Once you have determined the purpose of a module, you should go through
the patterns or the pattern index comparing the purpose statement of the
patterns with the purpose statement of the module. You should then write
the module using the appropriate pattern or patterns as a guide. Be aware
that although the C++ language is used here to demonstrate these patterns,
the patterns themselves are general to any procedural language. These
patterns are just as applicable to Basic, Pascal or Cobol. In certain
cases, they are even applicable to the higher level programming tools
often used to avoid the details of traditional programming. Be sure to check that all the elements of the appropriate
patterns appear in your module - one of the advantages in using the
patterns is to make sure that you include all the necessary code to
accomplish a given purpose. And, be sure to trace/test your module. The
use of patterns and the other tricks of the programming trade help avoid
errors but nothing takes the place of thorough testing. Hints on reading these patterns:
A.
Basic Patterns Pattern A1: The Basic Input
Pattern |
Purpose: To ask a user to enter a value and store that value in a
variable
The Pattern: Display 1 or more lines as a prompt to the user
cin >> some_variable name
Example: int anAge;
cout << "Please enter your age\n";
cin >> anAge;
Discussion: Please remember to ALWAYS include a message (called
a prompt), describing what is to be input, with every
request for data from a user. This message can take many
forms including a simply request as in "Please enter
your age" or it can be a menu - whatever works best. If
the above code did not include the "cout" line,
the system would stop to wait for the user's data to be
entered (That is part of what "cin" does) but the
user might not know why the system had stopped and
might believe that something was wrong with the computer.
This has actually happened!
Note 1: This pattern works for 1 variable. If you want to input
values for a number of variables, it is best to reuse this
whole pattern for each additional input.
Note 2: Although the >> operator is the simplest way to
get input, other input instructions such as 'get' or
'getline' could have been used in this example. In all cases
the same basic rule concerning the need for prompts applies
as long as the input comes from a user.
Discussion of prompts and input in "The Story of C++"
Pattern A2: Handling Options
Purpose: To easily branch to the code corresponding to a choice
entered by a user
Pattern: Get Users Choice
Use a switch statement to branch to the code for the
user's choice
Example 1: char choice;
choice = GetChoice();
switch (choice)
{
case 'A':
case 'a':
ProcessChoiceA();
break;
case 'B':
case 'b':
ProcessChoiceB();
break;
// other cases for other choice possibilities
default:
cout << "Invalid choice \n";
}
Discussion: One can also use an 'if' statement to handle user
choices. In most cases, however, the 'switch' statement produces
clearer code and is easier to write.
Note 1: Usually code like this is embedded in one of the looping
patterns below. See Pattern 'E2' below.
Note 2: Any appropriate message can be use in the code for the
'default' option.
| See the discussion of the 'switch' statement in "The Story of C++" B. While Loops To decide when to stop, C++ loops perform some test or set of tests each time through the loop. These tests are collectively referred to as the test condition. |
Purpose: To loop through a set of instructions until some ending or stopping condition is met.
Pattern: Assign a value to all variables involved in the test condition while the test condition is true { Do some set of instructions Re-assign a value to at least one of the variables in the test condition }
Example 1: int counter = 10; while (counter > 0) { /* some set of instructions */ counter--; }
Example 2: int counter = 0; while (counter < 10) { /* some set of instructions */ counter++; }
Example 3: bool found = false; int counter = 10; while ((counter > 0) && !found) { /* some set of instructions that include either decrementing the counter or setting 'found' to true */ }
Discussion: The middle example is probably the simplest. In it, the variable, 'counter' is set to 0 and each time through the loop it is incremented by 1. When it has the value 9, the loop has executed 10 times. (If you are uncomfortable with this statement, count on your fingers starting with the value 0 and not the value 1.) When the counter is incremented again to the value 10, the loop stops because the test condition is now false.
The third example follows the same pattern except that now there are two tests. The variables used in both tests are initialized before the loop. Inside the loop either of these variables can be changed and sooner or later the changes will cause one or both of the tests to go false and the loop will terminate.
It is very important that all the variables involved in the test condition be initialized before the loop begins execution. If the reason for this is not clear, be sure you review the section on loops and initialization in Chapter 3, Section IV of "The Story of C++".
Note 1: If at least one of the variables involved in the test condition is not re-assigned a value inside the WHILE loop, the loop will never stop. This is called an infinite loop.
Note 2: The test condition variables can be assigned and re-assigned using any of a number of valid C++ statements. One common value is to have the user enter a value using pattern A1 (See the next pattern for an example of this.) Another common way (as done in the examples above) is to continuously add or subtract a value to or from a variable.
Note 3: Be very careful in combining tests using the logical operators. You may want to read the material on '&&' and '||' in Chapter 3, Section V of "The Story of C++".
Note 4: There is a 'for' loop pattern, Pattern 'C2', that also meets this same purpose.
| Discussion in "The Story of C++" |
| Pattern B3: Loop Until Valid Data Entered |
Example 1: bool done; char choice; done = false; while (!done) { choice = GetChoice(); switch (choice) { // process the various choices except the one // for quitting case 'Q': case 'q': cout << "Do you really want to quit (Y) or (N)\n"; char answ; cin >> answ; if ((answ == 'Y') || (answ == 'y')) { done = true; }
// the default choice
}
Discussion: Although the pattern includes the word 'if', it is more common for this pattern to be implemented with a switch statement. Example The code for the 'quit' case could include pattern B2. As written here, if the user enters anything but 'Y' or 'y', the loop continues.
| See the discussion in "The Story of C++" C. The For Loop The for loop in other programming languages is often primarily used when one knows beforehand how many times the loop will be executed. While this instruction has been extended in power in C++, this is a good starting point. When you know the number of times a loop will execute, either because it is fixed in the program or because the number of times the loop will execute is calculated or provided by input BEFORE the loop starts, use the 'for' statement. |
Pattern: for (variable1 = 0; variable1 < the number of times to loop; variable1++) { // Some set of instructions }
Example 1: for (int i= 0; i < MAX_TIMES; i++) { // some set of instructions } Example 2: int numTimes; cout << "Enter the number of times to loop\n"; cin >> numTimes;
for (int count = 0; count < numTimes; count++) { // some set of instructions }
Discussion: The first example uses a constant to determine the number of times the loop executes. The second example has a user enter the number of times. In either case, the number of times to loop is known before the loop executes. Compare this pattern with patternB1. Example 1 here is essentially the same as example 2 from pattern B1 except for the constant.
Example 3 (below) shows a for loop similar to that of example 1 of pattern B1.
Example 3: for (int i= MAX_TIMES; i > 0; i--) { // some set of instructions }
Purpose: To loop through a set of instructions until the user enters some sentinel value.
Pattern: Get value(s) from the user for the variable(s) in the test condition for (; a set of tests ; ) { Do some set of instructions Get value(s) from the user for the variable(s) in the test condition }
Example 1: cout << "PLEASE ENTER COST (0 TO STOP)"; cin >> cost;
for (; cost > 0; ) /* cost is the initialized variable */ { // some set of instructions to do something with the cost cout << "PLEASE ENTER COST (0 TO STOP)"; cin >> cost; }
Discussion: Since the initialization is done before the loop and the variable is changed inside the loop, only the test condition part is coded inside the for loop parentheses. You could include the priming read and other inputs inside the code but it would not be as readable. (See example 2 below.)
Example 2: int cost; for (cout << "PLEASE ENTER COST (0 TO STOP)", cin >> cost;
cost > 0;
cout << "PLEASE ENTER COST (0 TO STOP)", cin >> cost ) { // some set of instructions
}
Discussion in "The Story of C++" on the 'for' statement Introduction Multiple Stopping Conditions and With Arrays Advanced
D. Counters, Running Totals and Averages
There are a number of patterns that include some looping pattern and add some further capability. We will take a look at some of them here. Note that the patterns themselves do not stipulate a specific looping pattern. Which one you use depends on the context in which the pattern is used and on personal preferences. The key point is that we are using basic patterns to build more complex patterns. As with programming in general, the trick is to combine patterns so as to correctly use all the elements of the individual patterns while creating an integrated whole. Be sure you study carefully how the basic patterns described above are combined in the patterns and examples below.
Purpose: To count the number of times a loop repeats; to implement a counter.
Pattern: Initialize the Counter Variable to 0 Use some loop pattern - see Parts 'B' and 'C' above Inside the loop pattern add the following code: Counter Variable++
Example 1 int loopCount = 0; cout << "PLEASE ENTER COST (0 TO STOP)"; cin >> cost; // a loop to count the number of times a real cost is entered while (cost != 0) /* cost is the initialized variable */ { // some set of instructions to do something with the cost loopCount++; cout << "PLEASE ENTER COST (0 TO STOP)"; cin >> cost; }
Discussion: The key to this pattern is contained in the two lines: int loopCount = 0; and loopCount++;
The rest of the code is purely to make the counter make some sense. The first of these lines ensures that the code actually starts counting from 0, while the second line does the actual counting.
Observe how this pattern sits half inside and half outside the loop pattern. We do not want to repeatedly initialize the counter to 0 so that part sits outside the loop. However, the line that increments the counter needs to be inside the loop so that it is repeatedly executed. Be careful to put the various elements where they belong.
The variable 'loopCount' in the example starts at 0 and every time through the loop it is incremented by one. When the loop terminates, 'loopCount' will hold the number of times the loop was executed.
In this example, nothing is done with the value in 'loopCount'. In actual programs this value could be output or used as part of some calculation as in the average pattern, D3, below.
Note 1: If you had some reason to begin your counter at some value other than zero, that would be possible simply by initializing the counter to the other value. Usually counter variables are initialized to 0 because one wants to start counting from 0. However, if, for example, you already knew that 556 of something existed and now wanted to count the rest, you might begin the above code fragment by initializing the counter to 556. If this was true for example 1, the initialization line would then be:
int loopCount = 556;
Note 2: If you wanted to count by some value other than one, for example, if you wanted to count by 5, you would change the line: count++ ; (or whatever counter variable you were using) to: count += 5; (or whatever value you wanted to count by). Note 3: It is possible to implement counters where there are no explicit loops although this is not as common and it has some unique challenges. No code will be provided here but an example of such a counter would be code that counted the number of times a specific function was called. Outside the function a counter would be initialized to 0 (or whatever value was appropriate), while, inside, the function itself would be the increment instruction. Note the similarity between this and the pattern under discussion. In both cases, the initialization takes place outside the code that repeats while the incrementing instruction must be inside the code that repeats.
| The Counter Pattern in "The Story of C++" |
Purpose: To calculate the sum of some set of numbers.
Pattern: Initialize the Sum variable to 0 Use some loop pattern - see Sections 'B' and 'C' above Inside the loop pattern add the following code:: Sum variable = Sum Variable + Amount To Add
Example 1 int sum = 0; cout << "Please enter the first grade"; cin >> grade; // a loop to add all the grades entered while (grade != -1) { sum += grade; cout << "Please enter another grade"; cin >> grade; }
Discussion: The key to this pattern is contained in the two lines: int sum = 0; and sum += grade;
The rest of the code is purely to make the summation make some sense. The first of these lines ensures that the code actually starts adding (summing) from 0 while the second line does the actual adding. The pattern gives the long hand way of adding a number to a summation (or running total) variable while the code example demonstrates C++'s shorthand way of doing the same thing. If this code is not clear, see the discussion of this in Chapter 3, Section VI of "The Story of C++".
Observe how this pattern sits half inside and half outside the loop pattern. We do not want to repeatedly initialize the summation variable to 0, so that part sits outside the loop. However, the line that does the adding needs to be inside the loop so that it is repeatedly executed. Be careful to put the various elements where they belong.
Note 1: If you had some reason to begin your running total at some value other than zero, that would be possible simply by initializing the running total variable to the other value. Usually running total variables are initialized to 0. However, if, for example, you had already found that the sum of a set of grades was 556, you might begin the above code fragment by initializing sum to 556 as is:.
int sum = 556;
Note 2: The sentinel value here was chosen under the assumption that a student would never receive a grade of -1. Other sentinel values are possible.
| D3: The Average Pattern |
Purpose: To calculate the average of some set of numbers.
Pattern: Use the counter and sum patterns with some loop pattern After the loop pattern: if Counter Variable != 0 then { average = Sum Variable / Counter Variable Display average } else { Display some message indicating there was nothing to take the average of. }
Example 1: int sumGrades = 0; int numGrades = 0; int grade; int averageGrade;
cout << "Please enter the first grade"; cin >> grade; while (grade != -1) { sumGrades += grade; numGrades++; cout << "Please enter another grade"; cin >> grade; } if (numGrades != 0) { averageGrade = sumGrades / numGrades; cout << "The average grade is: " << averageGrade << endl; } else { cout << "There were no grades to output\n"; }Discussion: This patterns combines three patterns to make a fourth. Notice that although both the counter and sum patterns call for a loop, we only use one loop in this pattern. Both the sum and counter patterns use the same loop. For more information see the discussion of this pattern in Chapter 3, Section 7 of "The Story of C++".
Note 1: Although in this pattern any average that is calculated is also output, this is not the only way to handle this. One could instead return the average if this code was part of some function or one could use the average in some further calculation.
Note 2: There are also a number of ways of handling the situation where no grades are input. In many cases, one does not want to display an error message. If this were a function that returned the average, one might, for example, wish to also return a code indicating whether the average was successfully calculated.
| E. Miscellaneous Patterns |
Purpose: To find the largest or smallest number in a list entered by a user.
Pattern For Finding the Largest: Get the first value from the user and put it into variable1. aValueEntered = true if variable1 != sentinel value { largest value so far = variable1; Get another value from the user and put into variable1 } else { aValueEntered = false } while (variable1 != sentinel value) { if (variable1 > largest value so far) { largest value so far = variable1; } Get the next value from the user and put into variable1 } if (aValueEntered) { Display largest value so far } else { Display "No values were entered".
} Example 1: int number; int largestValueSoFar; bool aValueEntered = true;
cout << "Enter a number or -1 to stop\n"; cin >> number;
if (number != -1) { largestValueSoFar = number; cout << "Enter a number or -1 to stop\n"; cin >> number; } else { aValueEntered = false; }
while (number != -1) { if (number > largestValueSoFar) { largestValueSoFar = number; } cout << "Enter a number or -1 to stop\n"; cin >> number; } if (aValueEntered) { cout << "The largest value entered was:" << largestValueSoFar << endl; } else { cout << "No values were entered\n"; }
Discussion: This algorithm works by always keeping track of the largest value encountered so far. The first value entered is the largest value encountered until a larger value is entered. Thus, it is immediately stored in the variable largest value so far. As each additional value is entered, it is compared with the value in this variable, and it replaces the value in the variable if it is truly larger.
Since the sentinel value (-1 in this example) should not be considered one of the values entered, special care must be taken with it. The Boolean variable 'aValueEntered' is used here to make sure that at least one value other than -1 was entered.
The pattern for finding the smallest value entered is the same except that the comparison symbol '>' is changed to '<'. (One might also wish to change the name of the variable largest value so far to smallest so far to make the code readable but technically, this is not necessary.
Note 1: This pattern simply outputs the value found. One could do many other things with the largest or smallest value, depending on the requirements of one's program.
Note 2: It is common for problems to say something like "Give me the name of the person with the smallest or largest ....". Example 2 (below) adds this feature. (Observe that this code takes advantage of the string type found in more recent versions of C++.)
Example 2: int grade; int largestGradeSoFar string name; string nameWithLargestGrade;
cout << "Please enter the name of a person"; cout << "Enter '*' to signify no more names."; cin >> name; if (name != "*") { cout << "Please enter the final grade for this person"; cin >> grade; largestGradeSoFar = grade; // This grade is the largest seen so far so...
nameWithLargestGrade = name; // Store the name of the person with largest grade seen so far
cout << "Please enter the name of the next person"; cout << "Enter '*' to signify no more names."; cin >> name; } else { nameWithLargestGrade = " "; // There were no valid names entered so store a blank name }
while (name != "*") { cout << "Please enter the final grade for the next person"; cin >> grade;; if (grade > largestGradeSoFar) { largestGradeSoFar = grade; nameWithLargestGrade = name; } cout << "Please enter the name of the next person"; cout << "Enter '*' to signify no more names."; cin >> name; } cout << "The Person with the largest grade is: " << nameWithLargestGrade << endl;
Note 3: If no valid name is entered, the variable 'name' holds a blank string and that is what will be output.
| Pattern E2. Handling Options |
Purpose: To continuously cause a program to branch to the appropriate code for each option in a set of user entered options.
Pattern: Set done to false while not done { Get user choice switch (user choice) { A series of case statements - at least one for each option In one case, change the value of done to true } }
Example 1: bool done = false; char choice; while (!done) { choice = GetUsersChoice(); switch(choice) { case 'A': case 'a': // code for choice 'A' or to call a function to // handle choice 'A' break; case 'B': case 'b': // code for choice 'B' or to call a function to // handle choice 'B' break; case 'C': case 'c: // code for choice 'C' or to call a function to // handle choice 'C' break;
// more case statements if needed
case 'Q': case 'q': cout << "Do you really want to quit (Y) or (N)\n"; char answ; cin >> answ; if ((answ =='Y') || (answ == 'y')) { done = true; } break;
// default case statement if needed } // end switch
} // end while Discussion: This pattern is actually an extension of Pattern A2. The use of the switch statement for handling choices is discussed in great detail in Chapter 6, Section III, Part 'B' of "The Story of C++"
| Pattern E3. String Assignment |
Purpose: To assign the contents of one pointer-based string variable to another
Pattern: Get the length of the string (call it s1) to be copied Allocate enough free memory to hold this string and assign its address to a second variable - call it s2 Use 'strcpy' to copy s1 to s2
Example 1: char* s1; s1 = new char [10]; strcpy(s1, "hello"); // these first three lines were to initialize 's1' to the string // "hello" // What follows is the pattern in use
int len = strlen(s1); s2 = new char[len+1]; strcpy(s2, s1);
Discussion: One cannot simply assign one pointer-based string variable to another because what gets assigned is the address. In other words, the end result is that both string variables use the same memory and thus a change to one produces a change in the other.
Note how the 'new' operator uses 'len + 1'. This is because the 'strlen' function returns the size of the string not including the null character.
| Pattern E4. A General Prompt Producer |
Purpose: To work with or perform some action on all the elements of an array
Pattern: Declare or somehow have access to some kind of an array. Here, we assume that the variable An Array represents an array of some type. for (int index = 0; index < MAX_ELEMENTS;index++) { Perform some operation on An Array[ index ] }
Example 1: // An array called grades has been declared and filled with grades // Assume that each grade is an integer representing the score on // some test
for (int testNum = 0; testNum < MAX_GRADES; testNum++) { cout << "The grade for test " << testNum + 1 << " is: " << grades[testNum]; }
Discussion: Any action can be performed inside the loop. One can input values into the elements of an array, sum up the elements, do some comparison, modify the elements, use each element in a calculation etc. Note 1: The output has the code "testNum + 1" because while the array counts from 0, we count from 1. In other words, the zero-th element of the array holds the score for test 1. Note 2: MAX_GRADES in the example here probably represents the number of elements in the array. If you wished to use only part of the array, you could change the starting point by initializing the index variable ('testNum' in the example) to some positive value other than 0 and/or compare it with some value other than the value representing the number of elements in the array. In the first example below, we just want to output the first 10 elements of the array. In the second example, we wish to output the scores for tests 10 through 20.
Example 2: for (int testNum = 0; testNum < 10; testNum++) { cout << "The grade for test " << testNum + 1 << " is: " << grades[testNum]; }
Example 3: for (int testNum = 9; testNum < 20; testNum++) { cout << "The grade for test " << testNum + 1 << " is: " << grades[testNum]; }
Note 3: 'testNum' starts at 9 because the array counts from 0.
| Discussion of 'for' loops with
arrays in "The Story of C++" |
| G. Complex Input and Output and File Handling |
Pattern G1: Clean Handling of UnAcceptable Data
Purpose: To allow a program to handle user entry of the wrong type of data in a loop and avoid infinite loops based on the failure of an input operation.
Pattern: Declare a variable of some type to store input - call
it user input
Get a value for user input
Use some loop to process the value entered and get the next value
In that loop:
if (cin.fail())
{
cin.clear();
// Clear stream of invalid data
cout << "Some error message"
}
else
{
Process the valid data
}
Example 1: int x;
cout << "Enter a number\n";
cin >> x;
while (x != 0)
{
if (cin.fail()) // a character or other unacceptable data
// was entered
{
cin.clear();
cin >> ch1; // to clear out the unacceptable data
cout << "Invalid value entered\n";
}
else
{
cout << " A number was read\n";
}
cout << "Enter a number\n";
cin >> x;
}
Discussion: The example found here is discussed in
Chapter 11, Section II, Part 'E'
of the of "The Story of C++". In the example, the stream
is cleared by simply reading the invalid data as a char. One could
also use the
ignore function.
| Pattern G2: Opening Files for Input |
Purpose: To open a file for input
Pattern: Get the name of the File (or have it hard coded) (Here we will say it is stored in the variable file name.) ifstream stream name ( file name); if (!stream name) { cout << "Error Message such as Cannot Open File" exit(1); }
Example 1: ifstream inputStream ("info.dat"); // Use hard coded name if (!inputStream) { cout << "Cannot open input file\n"; exit (1); } // if the file was successfully opened, begin reading data // from the file Example 2: String80 fileName; GetFileName("Please enter the file containing the contract database.", fileName);
ifstream inputData (fileName); if (!inputData) { { cout << "Cannot open input file\n"; exit(1); } // if the file was successfully opened, begin reading data // from the file Discussion: This code is discussed in more detail in Chapter 11, Section IV, Part 'B' of "The Story of C++". (The same section also discusses how to read data from a file.) The first example above assumes that we always want to open a file called "info.dat' The second example gets the file name from the user using a function based on 'E4'.
The pattern for opening a file for output is very similar. See Chapter 11, Section IV for details.
| Main Menu | Function Design Patterns |