Grade Calculator 2 Solution: Problem decomposition

Author: Jordan Kidney ( kidney@cpsc.ucalgary.ca )   Date: Feb 24, 2006
Functions or procedures are used for 2 main reasons:
  1. Common tasks that may be used in many different locations of a program. This saves a lot of codeing/copy pase time. This also allows you to make changes to the task in one spot instead of all over the place.
  2. Helping to make the code easier to understand. By breaking a large/very large section of code into smaller functions and procedures you can make it easier to read and understand how thinks are connected.

To solve this problem you have to break the program up into one of the two categories as described above. This can be done by looking for common tasks that could be extracted or for logical breaks between sections in the code. For example you could have one set of procedures or functions for reading in information from the user, and another set for making the calculations. When breaking this code up, or just in the general creation of functions and procedures there are a couple things that should be taken into account:
  1. What variables are required by the section of code I want to make into a function or procedure. These could be values that should be now passed in as parameters or made to be local variables to the function or procedure.
  2. Does the section of code make changes to variables that are used later by different sections of code. This would indicate that the section of code should be made into a function since it will return/give back information to the caller.
  3. Dealing with issues where the section of code manipulates more then one variables that are used by other sections of code. This could require a change in the data type of the variable or splitting the section of code up into smaller functions or procedures.

Below you will find a step by step explanation of the breaking up of the GradeCalculator program into different procedures and functions.

Here is a link to the file with the solution in it: GradeCalculator2.p


Change 1: extracting code that reads in information from the user

Code

Explanation of changes made

(* ==============================================================================
 *    File: GradeCalculator2.p
 *  Purpose: Simple program to calculate the percentage and letter grade
 *           with procedures and functions
 *  Author: Jordan Kidney ( kidney@cpsc.ucalgary.ca )
 * Created on: Feb 24, 2006
  ============================================================================== *)
program GradeCalculator2 (input,output);

(*----------------------- readTotalMarks ----------------------------------------
 * Purpose: prompts the user to enter in the total marks for the test. This
 *          function also does some basic error checking on the number entered.
 * returns: the number entered by the user as a real
 *------------------------------------------------------------------------------ *)
function readTotalMarks : real;
 begin
   var marks   : real;
   var EndLoop : boolean; (* Used as a flag to end loops for error checking *)


   EndLoop := false;
   repeat
       write('Enter the total number of marks : ');
       readln(marks);
       if (marks <= 0) then
          writeln('Error: The number must be greater then zero.')
       else
          EndLoop := true

   until EndLoop = true;

   readTotalMarks := marks;
 end;
(*----------------------- readUsersMark ----------------------------------------
 * Purpose: prompts the user to enter the mark they got for the test. This
 *          function also does some basic error checking on the number entered.
 * Parameters: TotalMarks - the total number of possible marks on the test. This
 *                          value is used for error checking.
 * returns: the number entered by the user as a real
 * ------------------------------------------------------------------------------ *)
function readUsersMark(TotalMarks : real) :  real;
begin
   var mark    :  real;
   var EndLoop :  boolean; (* Used as a flag to end loops for error checking *)

   EndLoop := false;
   repeat
      write('Enter your mark : ');
      readln(mark);
      if ((mark < 0) or (mark > TotalMarks))  then
      writeln('Error: inncorect input')
      else
      EndLoop := true

    until EndLoop = true;

   readusersMark := mark;
end;

(* ========================== MAIN ============================================== *)
begin
      var Mark        : real; (* The mark the user got *)
      var TotalMarks  : real; (* Total number of possible marks *)
      var Percentage  : real; (* Used to store the calculated percentage value *)
      var LetterGrade : char; (* Used to store the letter cade for the mark *)
      var LetterSign  : char; (* Used to store the sign for the letter grade *)


      TotalMarks := readTotalMarks;
        Mark := readUsersMark(TotalMarks);

      (* Calculate the percentage value for the user *)
      Percentage := (Mark * 100.0)/TotalMarks;

      (* Find the letter grade based upon the percentage value found *)
      if (Percentage < 39.0) then
     begin
        LetterGrade := 'F'; LetterSign  := ' ';
     end
      else if (Percentage < 44.0) then
         begin
           LetterGrade := 'D'; LetterSign  := '-';
     end
      else if (Percentage < 49.0) then
     begin
        LetterGrade := 'D'; LetterSign  := ' ';
     end
      else if (Percentage < 54.0) then
     begin
        LetterGrade := 'D'; LetterSign  := '+';
     end
      else if (Percentage < 59.0) then
     begin
        LetterGrade := 'C'; LetterSign  := '-';
     end
      else if (Percentage < 64.0) then
     begin
        LetterGrade := 'C'; LetterSign  := ' ';
     end
      else if (Percentage < 69.0) then
     begin
        LetterGrade := 'C'; LetterSign  := '+';
     end
      else if (Percentage < 74.0) then
     begin
        LetterGrade := 'B'; LetterSign  := '-';
     end
      else if (Percentage < 79.0) then
     begin
        LetterGrade := 'B'; LetterSign  := ' ';
     end
      else if (Percentage < 84.0) then
     begin
        LetterGrade := 'B'; LetterSign  := '+';
     end
      else if (Percentage < 89.0) then
     begin
        LetterGrade := 'A'; LetterSign  := '-';
     end
      else
     begin
        LetterGrade := 'A'; LetterSign  := ' ';
     end;

      (* Write information to the console *)
      writeln('You got ',
               Percentage:0:2,
          '% on the test which gives you a letter grade of '
          ,LetterGrade,LetterSign);
end.
(* ============================================================================== *)

Here to two tasks of reading in the users marks and and the total marks for a test were extracted into two functions. Functions were created because each section of code will be required to return the information they gathered.

Here local variables in the main that were used in each section of code now need to be declared as local variables in each function. In this case the variable EndLoop is only used in each function, so it can be removed from the main function.


Change 2: extracting code that calculates the percentage

Code

Explanation of changes made

(* ==============================================================================
 *    File: GradeCalculator2.p
 *  Purpose: Simple program to calculate the percentage and letter grade
 *           with procedures and functions
 *  Author: Jordan Kidney ( kidney@cpsc.ucalgary.ca )
 * Created on: Feb 24, 2006
  ============================================================================== *)
program GradeCalculator2 (input,output);

(*----------------------- readTotalMarks ----------------------------------------
 * Purpose: prompts the user to enter in the total marks for the test. This
 *          function also does some basic error checking on the number entered.
 * returns: the number entered by the user as a real
 *------------------------------------------------------------------------------ *)
function readTotalMarks : real;
 begin
   var marks   : real;
   var EndLoop : boolean; (* Used as a flag to end loops for error checking *)


   EndLoop := false;
   repeat
       write('Enter the total number of marks : ');
       readln(marks);
       if (marks <= 0) then
          writeln('Error: The number must be greater then zero.')
       else
          EndLoop := true

   until EndLoop = true;

   readTotalMarks := marks;
 end;
(*----------------------- readUsersMark -----------------------------------------
 * Purpose: prompts the user to enter the mark they got for the test. This
 *          function also does some basic error checking on the number entered.
 * Parameters: TotalMarks - the total number of possible marks on the test. This
 *                          value is used for error checking.
 * returns: the number entered by ther use as a real
 * ------------------------------------------------------------------------------ *)
function readUsersMark(TotalMarks : real) :  real;
begin
   var mark    :  real;
   var EndLoop :  boolean; (* Used as a flag to end loops for error checking *)

   EndLoop := false;
   repeat
      write('Enter your mark : ');
      readln(mark);
      if ((mark < 0) or (mark > TotalMarks))  then
      writeln('Error: inncorect input')
      else
      EndLoop := true

    until EndLoop = true;

   readusersMark := mark;
end;
(*----------------------- computePercentage ---------------------------------------
 *  Purpose: computes the percentage the user got
 *  Parameters: TotalMarks - the total number of possible marks on the test.
 *  returns: the number entered by ther use as a real
 *  ------------------------------------------------------------------------------ *)
function computePercentage(TotalMarks , Mark : real ) : real;
 begin
    computePercentage := (Mark * 100.0)/TotalMarks;
 end;
(* ========================== MAIN ============================================== *)
begin
      var Mark        : real; (* The mark the user got *)
      var TotalMarks  : real; (* Total number of possible marks *)
      var Percentage  : real; (* Used to store the calculated percentage value *)
      var LetterGrade : char; (* Used to store the letter cade for the mark *)
      var LetterSign  : char; (* Used to store the sign for the letter grade *)

      TotalMarks := readTotalMarks;
      Mark := readUsersMark(TotalMarks);

      Percentage := computePercentage(TotalMarks,Mark);

      (* Find the letter grade based upon the percentage value found *)
      if (Percentage < 39.0) then
     begin
        LetterGrade := 'F'; LetterSign  := ' ';
     end
      else if (Percentage < 44.0) then
         begin
           LetterGrade := 'D'; LetterSign  := '-';
     end
      else if (Percentage < 49.0) then
     begin
        LetterGrade := 'D'; LetterSign  := ' ';
     end
      else if (Percentage < 54.0) then
     begin
        LetterGrade := 'D'; LetterSign  := '+';
     end
      else if (Percentage < 59.0) then
     begin
        LetterGrade := 'C'; LetterSign  := '-';
     end
      else if (Percentage < 64.0) then
     begin
        LetterGrade := 'C'; LetterSign  := ' ';
     end
      else if (Percentage < 69.0) then
     begin
        LetterGrade := 'C'; LetterSign  := '+';
     end
      else if (Percentage < 74.0) then
     begin
        LetterGrade := 'B'; LetterSign  := '-';
     end
      else if (Percentage < 79.0) then
     begin
        LetterGrade := 'B'; LetterSign  := ' ';
     end
      else if (Percentage < 84.0) then
     begin
        LetterGrade := 'B'; LetterSign  := '+';
     end
      else if (Percentage < 89.0) then
     begin
        LetterGrade := 'A'; LetterSign  := '-';
     end
      else
     begin
        LetterGrade := 'A'; LetterSign  := ' ';
     end;

      (* Write information to the console *)
      writeln('You got ',
               Percentage:0:2,
          '% on the test which gives you a letter grade of '
          ,LetterGrade,LetterSign);
end.
(* ============================================================================== *)

Here now, a simple function was created to calculate the percentage given the two values entered by the user. The information from the user is now passed in as parameters to the function.


Change 3: extracting code that finds the letter grade

Code

Explanation of changes made

(* ==============================================================================
 *    File: GradeCalculator2.p
 *  Purpose: Simple program to calculate the percentage and letter grade
 *           with procedures and functions
 *  Author: Jordan Kidney ( kidney@cpsc.ucalgary.ca )
 * Created on: Feb 12, 2006
  ============================================================================== *)
program GradeCalculator2 (input,output);

(*----------------------- readTotalMarks ----------------------------------------
 * Purpose: prompts the user to enter in the total marks for the test. This
 *          function also does some basic error checking on the number entered.
 * returns: the number entered by the user as a real
 *------------------------------------------------------------------------------ *)
function readTotalMarks : real;
 begin
   var marks   : real;
   var EndLoop : boolean; (* Used as a flag to end loops for error checking *)


   EndLoop := false;
   repeat
       write('Enter the total number of marks : ');
       readln(marks);
       if (marks <= 0) then
          writeln('Error: The number must be greater then zero.')
       else
          EndLoop := true

   until EndLoop = true;

   readTotalMarks := marks;
 end;
(*----------------------- readUsersMark -----------------------------------------
 * Purpose: prompts the user to enter the mark they got for the test. This
 *          function also does some basic error checking on the number entered.
 * Parameters: TotalMarks - the total number of possible marks on the test. This
 *                          value is used for error checking.
 * returns: the number entered by ther use as a real
 * ------------------------------------------------------------------------------ *)
function readUsersMark(TotalMarks : real) :  real;
begin
   var mark    :  real;
   var EndLoop :  boolean; (* Used as a flag to end loops for error checking *)

   EndLoop := false;
   repeat
      write('Enter your mark : ');
      readln(mark);
      if ((mark < 0) or (mark > TotalMarks))  then
      writeln('Error: inncorect input')
      else
      EndLoop := true

    until EndLoop = true;

   readusersMark := mark;
end;
(*----------------------- computePercentage ------------------------------------
 *  Purpose: computes the percentage the user got
 *  Parameters: TotalMarks - the total number of possible marks on the test.
 *              Mark       - the mark the user got on the test
 *  returns: the number entered by ther use as a real
 *----------------------------------------------------------------------------- *)
function computePercentage(TotalMarks , Mark : real ) : real;
 begin
    computePercentage := (Mark * 100.0)/TotalMarks;
 end;
(*----------------------- findLetterGrade -----------------------------------------
 * Purpose: find the letter grade based upon a given percentage value
 * Parameters: percentage - the percentage used to find the letter grade
 * returns: the letter grade as a string
 *------------------------------------------------------------------------------- *)
function findLetterGrade(percentage :  real ) : string;
 begin
    var LetterGrade :  string[3];

    if (Percentage < 39.0) then LetterGrade := 'F'
    else if (Percentage < 44.0) then LetterGrade := 'D-'
    else if (Percentage < 49.0) then LetterGrade := 'D'
    else if (Percentage < 54.0) then LetterGrade := 'D+'
    else if (Percentage < 59.0) then LetterGrade := 'C-'
    else if (Percentage < 64.0) then LetterGrade := 'C'
    else if (Percentage < 69.0) then LetterGrade := 'C+'
    else if (Percentage < 74.0) then LetterGrade := 'B-'
    else if (Percentage < 79.0) then LetterGrade := 'B'
    else if (Percentage < 84.0) then LetterGrade := 'B+'
    else if (Percentage < 89.0) then LetterGrade := 'A-'
    else LetterGrade := 'A';

    findLetterGrade := LetterGrade;
 end;
(* ========================== MAIN ============================================== *)
begin
      var Mark        : real; (* The mark the user got *)
      var TotalMarks  : real; (* Total number of possible marks *)
      var Percentage  : real; (* Used to store the calculated percentage value *)
      var LetterGrade : string[3]; (* Used to store the letter cade for the mark *)

      TotalMarks := readTotalMarks;
      Mark := readUsersMark(TotalMarks);

      Percentage  := computePercentage(TotalMarks,Mark);
      LetterGrade := findLetterGrade(Percentage);

      (* Write information to the console *)
      writeln('You got ', Percentage:0:2,
          '% on the test which gives you a letter grade of ',LetterGrade);
end.
(* ============================================================================== *)

Finally the code for finding the letter grade is extracted and put into another function. Now in this case since we can only return one variable from a function the data type is change to a string instead of two individual character variables.