3.4 Repetition (1) -- the WHILE Statement

A number of program examples thus far have employed repetition in the form of the WHILE loop, and there is very little more to say about this Modula-2 construction that the astute reader has not already gleaned from these examples. The general form is:

  WHILE Boolean Expression (* i.e. while "Expression" is TRUE *)
    DO
      Statement Sequence;
    END;

When the END of the loop is reached, program control cycles back to the WHILE and the Boolean expression is checked again. If it is still TRUE, the loop executes once more. If it is FALSE, control passes to the next statement after the END of the WHILE loop.

If the expression happens to evaluate to FALSE when the WHILE is first encountered, the whole statement will be skipped. In the terms used in section 1.9.1, the WHILE loop employs top-of-loop testing. The syntax diagram is in figure 3.5:

Example:

Write a program to read in a sentence and find the average number of letters in each word. (This could be used as one test for the reading level of textual material.)

Discussion:

A somewhat simplified plan will be given for this program, as the WHILE loop has already been well illustrated.

Problem restatement:

Given Input
  a sequence of letters ending with a carriage return
Processing to do
  determine the number of letters
  determine the number of words
  compute the average word length
Desired Output
  inform user of computed average

Problem refinement:

1. Ask the user for a sentence.
2. Read the response in, one character at a time.
	Count the non-blank characters as letters.
	Count blanks as words.
	Add one word (the last word.)
3. Divide letters/words for the average.
4. Inform user of total.
5. Give option of doing it again.
	Recycle if yes.
	End if no.

Entities required:

o Variables:	  currentLetter, answer : CHAR
		 letters, words  : CARDINAL
		 again : BOOLEAN
o Imports:	 WriteString, WriteLn, WriteReal, ReadChar, SkipLine,
	ReadResults, ReadResult (latter two to determine whether at end of line)

Code:

MODULE AverageWordLength;

FROM STextIO IMPORT
  WriteString, WriteLn, ReadChar, SkipLine;

FROM SIOResult IMPORT
  ReadResult, ReadResults;

FROM SRealIO IMPORT
  WriteFixed;

VAR
  currentLetter, answer : CHAR;
  letters, words  : CARDINAL;
  again : BOOLEAN;

CONST
  space = ' ';

BEGIN
  WriteString ("This program computes the average ");
  WriteString ("length of the words in a sentence.");
  WriteLn;
  WriteLn;
  again := TRUE;    (* initialize boolean *)
  WHILE again
    DO
      letters := 0;    (* initialize counters *)
      words := 0;

      (* get data *)
      WriteString ("Type a sentence ending in a return. ");
      WriteLn;
      ReadChar (currentLetter);     (* process first char. *)

      WHILE ReadResult() # endOfLine
      (* continue until end of line *)
        DO  (* process the line *)
          IF currentLetter = space
            THEN
              words := words + 1;
            ELSE
              letters := letters + 1;
            END;    (* if *)

          ReadChar (currentLetter);     (* obtain next char *)
        END;  (* while *)    (* and recycle loop *)
  
      SkipLine;
      WriteLn;
      words := words + 1;     (* add one for last word at end *)
      WriteString ("The average word length was ");
      WriteFixed (FLOAT (letters) / FLOAT (words), 2, 0);
      WriteLn;
      WriteString ("Do you want to do another one? ");
      ReadChar (answer);
      SkipLine;
      WriteLn;
      again := (answer = 'Y') OR (answer = 'y');

    END;     (* first WHILE *)

  (* here we either recycle or fall out to the next statement *)

  WriteString ("AverageWordLength processing concluded.");
  WriteLn;
  WriteString ("Press a key to continue.");
  ReadChar (answer);

END AverageWordLength.

NOTE: The entities ReadResults and ReadResult are the only two items in the library module SIOResults. The first is a variable type that may take on values such as endOfLine and allRight (among others). The second makes an enquiry about the last performed read operation on the standard input/output channel used by such modules as STextIO, SRealIO, and SWholeIO. It then returns a value of the type ReadResults that the main program may compare with the desired outcome.

This program works, as far as it goes, but no output is provided here, for the reader will no doubt have noticed that there are a few flaws in the thinking. One of these is that if two blanks are left after a word, the program will count two words. This could be avoided by having another character variable called, say, lastOne and assigning this the value of currentLetter at the end of each pass through the inner WHILE loop. Then the check for whether one has a word or not at the beginning of the loop could include a comparison of lastOne to space. The code could look like this:

      Read (currentLetter);     (* process first char. *)
      lastOne := currentLetter;
      WHILE ReadResult() # endOfLine
        DO  (* process the line *)
          IF (currentLetter = space) AND (lastOne # space)
            THEN
              words := words + 1;
            ELSE
              letters := letters + 1;
            END;    (* if *)
          lastOne := currentLetter;
          ReadChar (currentLetter);     (* obtain next char *)
        END;  (* while *)    (* and recycle loop *)

Another flaw is that a real sentence ends with a mark like a period and may have quotes, apostrophes, commas, and other punctuation symbols that are not letters. The calculation here counts these as letters. Moreover, it would be nice not to be limited to a single sentence, but to have some means other than the end of line state to determine when processing is complete. In one of the exercises, the reader is asked to rewrite this Module to correct some of these deficiencies.


Contents