Previous Table of Contents Next


10.4.1.9. Loops, Arrays, and Files—the Advantage of Subtypes

The next example produces a simple letter-frequency histogram, a horizontal bar graph that shows the number of times each alphabet letter occurs in an input file. For example, given the input

   This is a test of an Ada 95
   simple histogram program.

the program displays

   a | *****
   d | *
   e | **
   f | *
   g | **
   h | **
   i | ****
   l | *
   m | ***
   n | *
   o | ***
   p | **
   r | ***
   s | *****
   t | ***
   A | *
   T | *

Input is taken from the standard input file, which for most operating systems defaults to the terminal keyboard. Input can be “redirected” from an external disk file if the operating system permits this; redirection is an operating system feature and lies outside the Ada standard.

    1 with Ada.Text_IO;
    2 procedure Histogram is
    3
    4   subtype UpperCase is Character range ‘A’..’Z’;
    5   subtype LowerCase is Character range ‘a’..’z’;
    6
    7   Uppers : array(UpperCase) of Natural := (others => 0);
    8   Lowers : array(LowerCase) of Natural := (others => 0);
    9
   10   NextCh   : Character;
   11   PlotChar : constant Character := ‘*’;
   12
   13 begin
   14
   15   while not Ada.Text_IO.End_Of_File loop
   16     while not Ada.Text_IO.End_Of_Line loop
   17
   18       Ada.Text_IO.Get(NextCh);
   19
   20       case NextCh is
   21         when UpperCase =>
   22           Uppers(NextCh) := Uppers(NextCh) + 1;
   23         when LowerCase =>
   24           Lowers(NextCh) := Lowers(NextCh) + 1;
   25         when others =>
   26           null;
   27       end case;
   28
   29     end loop;
   30     Ada.Text_IO.Skip_Line;
   31   end loop;
   32
   33   for C in LowerCase loop
   34
   35     if Lowers(C) /= 0 then
   36       Ada.Text_IO.Put(Item => C & “ | “);
   37       for Count in 1..Lowers(C) loop
   38         Ada.Text_IO.Put(PlotChar);
   39       end loop;
   40       Ada.Text_IO.New_Line;
   41     end if;
   42
   43   end loop;
   44
   45   for C in UpperCase loop
   46
   47     if Uppers(C) /= 0 then
   48       Ada.Text_IO.Put(Item => C & “ | “);
   49       for Count in 1..Uppers(C) loop
   50         Ada.Text_IO.Put(PlotChar);
   51       end loop;
   52       Ada.Text_IO.New_Line;
   53     end if;
   54
   55   end loop;
   56
   57 end Histogram;

Lines 4 and 5 introduce subtypes for the uppercase and lowercase letters, respectively; lines 7 and 8 introduce two arrays to hold the letter frequency counts. An array is an indexed, or subscripted, collection of elements; the index set is given in parentheses. All elements of a given array have the same type, but this type can be arbitrary, chosen for the application needs. Because all the elements have the same type, we say that an array type is homogeneous. In this case, I need nonnegative counters for the letters, so the array elements are of type Natural. The initial expression (others => 0) initializes all elements of both arrays to 0.

Line 11 introduces the constant PlotChar, as the asterisk I use to plot the histogram. A constant must have an initial expression; once the constant is set, it cannot be changed during its lifetime (in this case, the duration of the program execution). A constant value is set at the time its declaration is elaborated; it need not be static but can be computed at that time.

Lines 15 and 16 are the opening lines of the two nested while loops that drive the program. End_Of_Line and End_Of_File are Boolean-returning standard functions. Line 18 is a Get that reads a single character from the standard input file; it is because Get skips over line terminators that I need to test explicitly for End_Of_Line in line 16. Lines 29-31 bracket the loops.

Once a character is read, the case statement in lines 20-27 classifies it and acts accordingly, incrementing the appropriate counter. Note the case form: The first two when clauses refer to subtype ranges UpperCase and LowerCase and the when others covers all other possible values of the character variable. An Ada case statement is safe: All values of the case expression must be covered, using an others clause if necessary.

Once the input file has been completely read, I draw the bar graph. Line 33 starts a for (counting) loop, which is bracketed by the end loop in line 43. The meaning of the for is evident: It covers, in succession, each value in the subtype range of LowerCase.

I choose to plot bars only for those characters that actually appear in the input. The expression Lowers(C) /= 0 (line 35) returns true if the number of occurrences of C was non-zero; /= is Ada’s “unequal” symbol. In line 37, I show a loop expression whose bounds are given explicitly instead of by naming a subtype.

The loop in lines 45-55 just repeats the logic of the earlier loop, plotting bars for the uppercase characters.

The control structures in this example show the value of using subtype ranges. Once a subtype is carefully defined with a range determined by the application, it can be used repeatedly throughout the program. This ensures that appropriate ranges are used in loops and, especially, in loops that run through arrays. Programming without subtype ranges—either in Ada or in languages such as those in the C family that do not support subtypes—often results in runtime errors due to overrunning subscripts; such errors do not occur with carefully designed subtypes.


Previous Table of Contents Next