2. When the app executes, another compiler (known as the just-in-time compiler
5.9 Formulating Algorithms: Sentinel-Controlled
Let us generalize Section 5.8’s class-average problem. Consider the following problem:
Develop a class-averaging app that processes grades for an arbitrary number of students each time it’s run.
In the previous class-average example, the problem statement specified the number of stu- dents, so the number of grades (10) was known in advance. In this example, no indication is given of how many grades the user will enter during the app’s execution. The app must 13
14 } // end Main
15 } // end class GradeBookTest
Welcome to the grade book for
CS101 Introduction to C# Programming!
Enter grade: 88 Enter grade: 79 Enter grade: 95 Enter grade: 100 Enter grade: 48 Enter grade: 88 Enter grade: 92 Enter grade: 83 Enter grade: 90 Enter grade: 85
Total of all 10 grades is 848 Class average is 84
Common Programming Error 5.4
Assuming that integer division rounds (rather than truncates) can lead to incorrect results.
For example, 7 ÷ 4, which yields 1.75 in conventional arithmetic, truncates to 1 in inte- ger arithmetic, rather than rounding to 2.
Fig. 5.7 | CreateGradeBookobject and invoke itsDetermineClassAveragemethod. (Part 2 of 2.)
myGradeBook.DetermineClassAverage(); // find average of 10 grades
5.9 Formulating Algorithms: Sentinel-Controlled Repetition 159
process anarbitrarynumber of grades. How can it determine when tostopthe input of grades? How will it knowwhento calculate and display the class average?
One way to solve this problem is to use a special value called asentinel value(also called asignal value, adummy valueor aflag value) to indicate “end of data entry.” This is called sentinel-controlled repetition.The user enters grades until alllegitimategrades have been entered. The user then types the sentinel value to indicate that no more grades will be entered. Sentinel-controlled repetition is often called indefinite repetition because the number of repetitions isnotknown by the app before the loop begins executing.
Clearly, a sentinel value must be chosen thatcannotbe confused with an acceptable input value. Grades on a quiz are nonnegative integers, so –1 is an acceptable sentinel value for this problem. Thus, a run of the class-average app might process a stream of inputs such as 95, 96, 75, 74, 89 and –1. The app would then compute and display the class average for the grades 95, 96, 75, 74 and 89. Since –1 is the sentinel value, it should not enter into the averaging calculation.
Developing the Pseudocode Algorithm with Top-Down, Stepwise Refinement:
The Top and First Refinement
We approach the class-average app with a technique calledtop-down, stepwise refine- ment, which is essential to the development of well-structured apps. We begin with a pseudocode representation of thetop—a single statement that conveys the overall func- tion of the app:
The top is, in effect, acompleterepresentation of an app. Unfortunately, the top rarely con- veys sufficient detail from which to write a C# app. So we now begin the refinement pro- cess. We divide the top into a series of smaller tasks and list these in the order in which they’ll be performed. This results in the followingfirst refinement:
This refinement uses only the sequence structure—the steps listed should execute in order, one after the other.
Common Programming Error 5.5
Choosing a sentinel value that’s also a legitimate data value is a logic error.
determine the class average for the quiz
initialize variables
input, sum and count the quiz grades calculate and display the class average
Software Engineering Observation 5.2
Each refinement, as well as the top itself, is acompletespecification of the algorithm—
only the level of detail varies.
Software Engineering Observation 5.3
Many apps can be divided logically into three phases: an initialization phase that initializes the variables; a processing phase that inputs data values and adjusts variables (e.g., counters and totals) accordingly; and a termination phase that calculates and outputs the final results.
Proceeding to the Second Refinement
The precedingSoftware Engineering Observationis often all you need for the first refine- ment in the top-down process. To proceed to the next level, thesecond refinement, we specify individual variables. In this example, we need a running total of the numbers, a count of how many numbers have been processed, a variable to receive the value of each grade as it’s input by the user and a variable to hold the calculated average. The pseudo- code statement
can be refined as follows:
Only the variablestotalandcounterneed to be initialized before they’re used. The variables averageandgrade(for the calculated average and the user input, respectively) neednotbe initialized, because their values will be replaced as they’re calculated or input.
The pseudocode statement
requires a repetition statement that successively inputs each grade. We do not know in ad- vance how many grades are to be processed, so we’ll use sentinel-controlled repetition. The user enters grades one at a time.Afterentering the last grade, the user enters the sentinel value. The app tests for the sentinel value after each grade is input and terminates the loop when the user enters the sentinel value. The second refinement of the preceding pseudo- code statement is then
In pseudocode, we do not use braces around the statements that form the body of thewhile structure. We simply indent the statements under thewhileto show that they belong to thewhile. Again, pseudocode is only an informal app-development aid.
The pseudocode statement
can be refined as follows:
initialize variables
initialize total to zero initialize counter to zero
input, sum and count the quiz grades
prompt the user to enter the first grade input the first grade (possibly the sentinel) while the user has not yet entered the sentinel
add this grade into the running total add one to the grade counter
prompt the user to enter the next grade input the next grade (possibly the sentinel)
calculate and display the class average
if the counter is not equal to zero
set the average to the total divided by the counter display the average
else
display “No grades were entered”
5.9 Formulating Algorithms: Sentinel-Controlled Repetition 161
We’re careful here to test for the possibility ofdivision by zero—a logic error that, if unde- tected, would cause the app to fail. The complete second refinement of the pseudocode for the class-average problem is shown in Fig. 5.8.
In Fig. 5.5 and Fig. 5.8, we included some completely blank lines and indentation in the pseudocode to make it more readable. The blank lines separate the pseudocode algo- rithms into their various phases and set off control statements, and the indentation empha- sizes the bodies of the control statements.
The pseudocode algorithm in Fig. 5.8 solves the more general class-averaging problem. This algorithm was developed after only two refinements. Sometimes more refinements are necessary.
Error-Prevention Tip 5.2
When performing division by an expression whose value could be zero, explicitly test for this possibility and handle it appropriately in your app (e.g., by displaying an error mes- sage) rather than allowing the error to occur.
1 initialize total to zero 2 initialize counter to zero 3
4 prompt the user to enter the first grade 5 input the first grade (possibly the sentinel) 6
7 while the user has not yet entered the sentinel 8 add this grade into the running total 9 add one to the grade counter
10 prompt the user to enter the next grade 11 input the next grade (possibly the sentinel) 12
13 if the counter is not equal to zero
14 set the average to the total divided by the counter 15 display the average
16 else
17 display “No grades were entered”
Fig. 5.8 | Class-average problem pseudocode algorithm with sentinel-controlled repetition.
Software Engineering Observation 5.4
Terminate the top-down, stepwise refinement process when you’ve specified the pseudocode algorithm in sufficient detail for you to convert the pseudocode to C#. Normally, implementing the C# app is then straightforward.
Software Engineering Observation 5.5
Some experienced programmers write apps without ever using app-development tools like pseudocode. They feel that their ultimate goal is to solve the problem on a computer and that writing pseudocode merely delays the production of final outputs. Although this method may work for simple and familiar problems, it can lead to serious errors and delays in large, complex projects.
Implementing Sentinel-Controlled Repetition in ClassGradeBook
Figure 5.9 shows the C# classGradeBook containing method DetermineClassAverage
that implements the pseudocode algorithm of Fig. 5.8. Although each grade is an integer, the averaging calculation is likely to produce a number with a decimal point—in other words, a real number or floating-point number. The typeintcannot represent such a number, so this class uses typedoubleto do so.
1 // Fig. 5.9: GradeBook.cs
2 // GradeBook class that solves the class-average problem using 3 // sentinel-controlled repetition.
4 using System;
5
6 public class GradeBook 7 {
8 // auto-implemented property CourseName 9 public string CourseName { get; set; } 10
11 // constructor initializes the CourseName property 12 public GradeBook( string name )
13 {
14 CourseName = name; // set CourseName to name 15 } // end constructor
16
17 // display a welcome message to the GradeBook user 18 public void DisplayMessage()
19 {
20 Console.WriteLine( "Welcome to the grade book for\n{0}!\n",
21 CourseName );
22 } // end method DisplayMessage 23
24 // determine the average of an arbitrary number of grades 25
26 {
27 int total; // sum of grades
28 int gradeCounter; // number of grades entered 29 int grade; // grade value
30 31
32 // initialization phase
33 total = 0; // initialize total 34
35
36 // processing phase 37
38 39 40 41
42 while ( grade != -1 )
43 {
Fig. 5.9 | GradeBookclass that solves the class-average problem using sentinel-controlled repetition. (Part 1 of 2.)
public void DetermineClassAverage()
double average; // number with decimal point for average
gradeCounter = 0; // initialize loop counter
// prompt for and read a grade from the user Console.Write( "Enter grade or -1 to quit: " );
grade = Convert.ToInt32( Console.ReadLine() );
// loop until sentinel value is read from the user
5.9 Formulating Algorithms: Sentinel-Controlled Repetition 163
In this example, we see that control statements may bestackedon top of one another (in sequence) just as a child stacks building blocks. Thewhilestatement (lines 42–50) is fol- lowed in sequence by anif…elsestatement (lines 54–65). Much of the code in this app is identical to the code in Fig. 5.6, so we concentrate on the new features and issues.
Line 30 declaresdoublevariableaverage. This variable allows us to store the calcu- lated class average as a floating-point number. Line 34 initializes gradeCounterto 0, because no grades have been entered yet. Remember that this app uses sentinel-controlled repetition to input the grades from the user. To keep an accurate record of the number of grades entered, the app incrementsgradeCounteronly when the user inputs a valid grade value.
Program Logic for Sentinel-Controlled Repetition vs. Counter-Controlled Repetition Compare the program logic for sentinel-controlled repetition in this app with that for counter-controlled repetition in Fig. 5.6. In counter-controlled repetition, each repetition of thewhilestatement (e.g., lines 38–44 of Fig. 5.6) reads a value from the user, for the specified number of repetitions. In sentinel-controlled repetition, the app reads the first value (lines 38–39 of Fig. 5.9) before reaching thewhile. This value determines whether the app’s flow of control should enter the body of thewhile. If the condition of thewhile isfalse, the user entered the sentinel value, so the body of thewhiledoesnotexecute (be- cause no grades were entered). If, on the other hand, the condition istrue, the body begins execution, and the loop adds thegradevalue to thetotal(line 44) and adds1tograde-
Counter(line 45). Then lines 48–49 in the loop’s body input the next value from the user.
44 total = total + grade; // add grade to total
45 gradeCounter = gradeCounter + 1; // increment counter 46
47 48 49
50 } // end while 51
52 // termination phase
53 // if the user entered at least one grade...
54 if ( )
55 {
56 57 58
59 // display the total and average (with two digits of precision) 60 Console.WriteLine( "\nTotal of the {0} grades entered is {1}",
61 gradeCounter, total );
62 Console.WriteLine( "Class average is ", average );
63 } // end if
64 else // no grades were entered, so output error message 65 Console.WriteLine( "No grades were entered" );
66 } // end method DetermineClassAverage 67 } // end class GradeBook
Fig. 5.9 | GradeBookclass that solves the class-average problem using sentinel-controlled repetition. (Part 2 of 2.)
// prompt for and read the next grade from the user Console.Write( "Enter grade or -1 to quit: " );
grade = Convert.ToInt32( Console.ReadLine() );
gradeCounter != 0
// calculate the average of all the grades entered average = ( double ) total / gradeCounter;
{0:F}
Next, program control reaches the closing right brace of the body at line 50, so execution continues with the test of thewhile’s condition (line 42).
The condition uses the most recentgradeinput by the user to determine whether the loop’s body should execute again. The value of variablegradeis always input from the user immediately before the app tests thewhilecondition. This allows the app to determine whether the value just input is the sentinel valuebeforethe app processes that value (i.e., adds it to thetotal). If the sentinel value is input, the loop terminates; the app doesnot add–1to thetotal.
Notice thewhilestatement’s block in Fig. 5.9 (lines 43–50). Without the braces, the loop would consider its body to beonlythe first statement, which adds thegradeto the
total. The last three statements in the block would fall outside the loop’s body, causing the computer to interpret the code incorrectly as follows:
The preceding code would cause aninfinite loopif the user did not enter the sentinel-1at line 39 (before thewhilestatement).
After the loop terminates, theif…elsestatement at lines 54–65 executes. The con- dition at line 54 determines whether any grades were input. If none were input, theelse part (lines 64–65) of the if…else statement executes and displays the message "No
grades were entered", and the method returns control to the calling method.
Explicitly and Implicitly Converting Between Simple Types
If at least one grade was entered, line 57 calculates the average of the grades. Recall from Fig. 5.6 that integer division yields an integer result. Even though variableaverageis de- clared as adouble(line 30), the calculation
loses the division’s fractional part before the result is assigned toaverage. This occurs be- causetotalandgradeCounterarebothintegers, and integer division yields an integer re- sult. To perform a floating-point calculation with integer values, we musttemporarilytreat these values as floating-point numbers for use in the calculation. C# provides theunary cast operatorto accomplish this task. Line 57 uses the(double)cast operator—which has high- er precedence than the arithmetic operators—to create atemporaryfloating-point copy of
Good Programming Practice 5.6
In a sentinel-controlled loop, the prompts requesting data entry should explicitly remind the user of the sentinel value.
while ( grade != -1 )
total = total + grade; // add grade to total gradeCounter = gradeCounter + 1; // increment counter // prompt for input and read next grade from user Console.Write( "Enter grade or -1 to quit: " );
grade = Convert.ToInt32( Console.ReadLine() );
Error-Prevention Tip 5.3
Omitting the braces that delimit a block can lead to logic errors, such as infinite loops. To prevent this problem, some programmers enclose the body of every control statement in braces even if the body contains only a single statement.
average = total / gradeCounter;
5.9 Formulating Algorithms: Sentinel-Controlled Repetition 165
its operandtotal(which appears to the right of the operator). Using a cast operator in this manner is calledexplicit conversion. The value stored intotalis still an integer.
The calculation now consists of a floating-point value (the temporarydoubleversion oftotal) divided by the integergradeCounter. C# knows how to evaluate only arithmetic expressions in which the operands’ types areidentical. To ensure that the operands are of the same type, C# performs an operation calledpromotion(orimplicit conversion) on selected operands. For example, in an expression containing values of the typesintand
double, theintvalues are promoted todoublevalues for use in the expression. In this example, the value ofgradeCounteris promoted to typedouble, then floating-point divi- sion is performed and the result of the calculation is assigned toaverage. As long as the
(double)cast operator is applied toanyvariable in the calculation, the calculation will yield adoubleresult.
Cast operators are available for all simple types. We’ll discuss cast operators for refer- ence types in Chapter 12. The cast operator is formed by placing parentheses around the name of a type. This operator is aunary operator(i.e., an operator that takes only one operand). In Chapter 3, we studied the binary arithmetic operators. C# also supports unary versions of the plus (+) and minus (–) operators, so you can write expressions like+5 or-7. Cast operators associate from right to left and have the same precedence as other unary operators, such as unary+and unary-. This precedence is one level higher than that of the multiplicative operators *, / and %. (See the operator precedence chart in Appendix A.) We indicate the cast operator with the notation(type)in our precedence charts, to indicate that any type name can be used to form a cast operator.
Line 62 outputs the class average. In this example, we decided that we’d like to display the class averagerounded to the nearest hundredthand output the average with exactly two digits to the right of the decimal point. The format specifierFinWriteLine’s format item (line 62) indicates that variableaverage’s value should be displayed as a real number. By default, numbers output withF have two digits to the right of the decimal point. The number of decimal places to the right of the decimal point is also known as the number’s precision. Any floating-point value output withFwill be rounded to the hundredths posi- tion—for example, 123.457 will be rounded to 123.46, and 27.333 will be rounded to 27.33. In this app, the three grades entered during the sample execution of classGrade-
BookTest(Fig. 5.10) total 263, which yields the average 87.66666…. The format item rounds the average to the hundredths position, and the average is displayed as87.67.
Common Programming Error 5.6
A cast operator can be used to convert between simple numeric types, such asintanddou- ble, and between related reference types (as we discuss in Chapter 12, OOP: Polymor- phism, Interfaces and Operator Overloading). Casting to the wrong type may cause compilation or runtime errors.
1 // Fig. 5.10: GradeBookTest.cs
2 // Create GradeBook object and invoke its DetermineClassAverage method.
3 public class GradeBookTest 4 {
Fig. 5.10 | CreateGradeBookobject and invokeDetermineClassAveragemethod. (Part 1 of 2.)