We have written programs that accumulate the sum of all input data items in a single variable. Often, we have different categories of data items, and we might want to accumulate a separate total for each category rather than lump all items together. The problem that follows uses an array to accomplish this. The problem illustrates both random and sequential access of array elements.
PROBLEM SPECIFICATION
Your parents want a program that keeps track of their monthly expenses in each of several categories. The program should read each expense amount, add it to the appropriate category total, and print the total expenditure by category. The input data consists of the category number and amount of each purchase made during the past month.
ANALYSIS
Your parents have selected these budget categories: entertainment, food, clothing, rent, tuition, insurance, and miscellaneous. Seven separate totals are to be accumulated; each total can be associated with a different element of a seven-element array. The program must read each expenditure, determine to which category it belongs, and then add that expenditure to the appropriate array element. When done with all expenditures, the program can print a table showing each category and its accumulated total. As in all programs that accumulate a sum, each total must be initialized to zero. The problem inputs and outputs follow.
Data Type
TYPE BudgetCat IS ( Entertainment, Food, Clothing, Rent, Tuition, Insurance, Miscellaneous);
Problem Inputs
each expenditure and its category
Problem Outputs
the array of seven expenditure totals (Budget
)
DESIGN
Algorithm
1. Initialize all category totals to zero.
2. Read each expenditure and add it to the appropriate total.
3. Display the accumulated total for each category.
The structure chart in Figure 8.8 shows the relationship between the three steps.
Figure 8.8
The array Budget
is manipulated by all three procedures in the
program solution. Procedure Initialize
sets up the array;
procedure Post
stores expenditure information in this array (in
accounting terminology, registering a transaction is often called
posting); this information is displayed by procedure Report
.
TEST PLAN
Testing the program with the following transactions:
C 25.00 M 25.00 C 15.00 E 675.00 Qshould give the following output:
Category Expense ENTERTAINMENT 675.00 FOOD 0.00 CLOTHING 40.00 RENT 0.00 TUITION 0.00 INSURANCE 0.00 MISCELLANEOUS 25.00Also you need to design more test cases; we leave it to you to develop and justify these.
IMPLEMENTATION
The main program is shown, along with several procedures in Program 8.9.
Program 8.9
WITH Ada.Text_IO; WITH Ada.Float_Text_IO; WITH Screen; PROCEDURE Home_Budget IS ------------------------------------------------------------------------ --| Prints a summary of all expenses by budget category. --| Author: Michael B. Feldman, The George Washington University --| Last Modified: July 1995 ------------------------------------------------------------------------ MaxExpense : CONSTANT Float := 10_000.00 -- max expense amount TYPE Categories IS (Entertainment, Food, Clothing, Rent, Tuition, Insurance, Miscellaneous); TYPE Commands IS (E, F, C, R, T, I, M, Q); PACKAGE Category_IO IS NEW Ada.Text_IO.Enumeration_IO(Enum => Categories); PACKAGE Command_IO IS NEW Ada.Text_IO.Enumeration_IO(Enum => Commands); SUBTYPE Expenses IS Float RANGE 0.00..MaxExpense; -- expense type TYPE BudgetArray IS ARRAY (Categories) OF Expenses; -- array type Budget : BudgetArray; -- array of ten totals PROCEDURE Initialize (Budget : OUT BudgetArray) IS -- Pre: None -- Post: Each array element Budget(Category) is 0.00 BEGIN -- Initialize Budget := (OTHERS => 0.00); END Initialize; PROCEDURE DisplayTitles IS -- Pre: None -- Post: Displays a list of expense categories with abbreviations WhichRow: Screen.Depth; BEGIN Screen.ClearScreen; Screen.MoveCursor(Row => 3, Column => 20); Ada.Text_IO.Put(Item => "Expense Categories"); Ada.Text_IO.New_Line; Ada.Text_IO.New_Line; WhichRow := 5; FOR C IN Commands'First..Commands'Pred(Commands'Last) LOOP Screen.MoveCursor(Row => WhichRow, Column => 20); Command_IO.Put(Item => C, Width => 3); Category_IO.Put(Item => Categories'Val(Commands'Pos(C))); WhichRow := WhichRow + 1; END LOOP; Screen.MoveCursor(Row => WhichRow, Column => 20); Command_IO.Put(Item => Commands'Last, Width => 3); Ada.Text_IO.Put(Item => "when data entry is completed"); END DisplayTitles; PROCEDURE GetCommand(Command: OUT Commands) IS -- Pre: None -- Post: A valid Command is returned in Command BEGIN LOOP BEGIN -- exception handler block Screen.MoveCursor(Row => 18, Column => 15); Ada.Text_IO.Put("Please enter first letter of category > "); Command_IO.Get(Item => Command); Screen.MoveCursor(Row => 19, Column => 15); Ada.Text_IO.Put("Category accepted, thank you"); EXIT; EXCEPTION WHEN Ada.Text_IO.Data_Error => Screen.Beep; Screen.MoveCursor(Row => 19, Column => 15); Ada.Text_IO.Put("Sorry, invalid category! "); Ada.Text_IO.Skip_Line; END; -- exception handler block END LOOP; -- assert: valid command input received END GetCommand; PROCEDURE GetExpense(Expense: OUT Expenses) IS -- Pre: None -- Post: a valid Expense is returned in Expense BEGIN LOOP BEGIN -- exception handler block Screen.MoveCursor(Row => 20, Column => 15); Ada.Text_IO.Put ("Please enter expense as floating point number > "); Ada.Float_Text_IO.Get(Item => Expense); Screen.MoveCursor(Row => 21, Column => 15); Ada.Text_IO.Put("Expense accepted, thank you"); EXIT; EXCEPTION WHEN Ada.Text_IO.Data_Error => Screen.Beep; Screen.MoveCursor(Row => 21, Column => 15); Ada.Text_IO.Put("Sorry, invalid expense! "); Ada.Text_IO.Skip_Line; END; -- exception handler block END LOOP; -- assert: valid expense received END GetExpense; PROCEDURE Post (Budget : IN OUT BudgetArray) IS -- Pre: Each array element Budget(c) is 0.0 -- Post: Each array element Budget(c) is the sum of expense -- amounts for category c. Sentinel : CONSTANT Commands := Q; -- sentinel command NextCommand : Commands; -- command NextCategory : Categories; -- expenditure category NextExpense : Expenses; -- expenditure amount BEGIN -- Post LOOP -- invariant: -- no prior value of NextCommand is Sentinel GetCommand(Command => NextCommand); EXIT WHEN NextCommand = Sentinel; NextCategory := Categories'Val(Commands'Pos(NextCommand)); GetExpense(Expense => NextExpense); Budget(NextCategory) := Budget(NextCategory) + NextExpense; END LOOP; END Post; PROCEDURE Report (Budget : IN BudgetArray) IS -- Pre: Each array element Budget(c) is assigned a value. -- Post: Each array element Budget(c) is displayed. WhichRow: Screen.Depth; BEGIN -- Report Screen.ClearScreen; Screen.MoveCursor(Row => 3, Column => 20); Ada.Text_IO.Put(Item => "Category Expense"); Ada.Text_IO.New_Line; Ada.Text_IO.New_Line; WhichRow := 5; FOR Category IN Categories LOOP Screen.MoveCursor(Row => WhichRow, Column => 20); Category_IO.Put(Item => Category, Width=>13); -- Print row Ada.Float_Text_IO.Put(Item=>Budget(Category), Fore=>7, Aft=>2, Exp=>0); WhichRow := WhichRow + 1; END LOOP; Screen.MoveCursor(Row => 23,Column => 1); END Report; BEGIN -- Home_Budget -- prepare terminal screen for data entry DisplayTitles; -- Initialize array Budget to all zeros. Initialize (Budget); -- Read and process each expenditure. Post (Budget); -- Print the expenditures in each category. Report (Budget); END Home_Budget;
The
array Budget
(type BudgetArray
) appears in each
parameter list shown in
Program
8.9 and is passed between each procedure and the main program. When passing
an entire array, no subscript is used. We will have more to say about the use
of arrays as parameters later.
In procedure Initialize
, the array Budget
is set
to zeroes using an aggregate. In procedure Report
, the statements
Category_IO.Put(Item=>Category, Width=>13); Ada.Float_Text_IO.Put(Item=>Budget(Category), Fore=>10, Aft=>2, Exp=>0);are used to print each category and its associated total.
Category_IO
is, as usual, an instantiation of Ada.Text_IO.Enumeration_IO
.
Procedure Post
must read each expenditure and add it to the
appropriate array element. To read a category, we create an enumeration type
for the first letters of the categories so that the user does not have to type
the entire category name:
TYPE Commands IS (E, F, C, R, T, I, M, Q);where the type includes an extra command
Q
which will signal the user's
desire to quit the program. Post
uses the procedures
GetCommand
and GetExpense
to read valid commands and
expenditure values into NextCommand
and NextExpense
,
respectively. The package Screen
is used to position the screen
cursor properly to require the user to enter input at the bottom of the screen
and to send a beep in case of invalid input.
A valid command (other than Q
) needs to be converted into a
category in order to determine the proper array element to update. This is done
with the statement
NextCategory := Categories'Val(Commands'Pos(NextCommand));The assignment statement
Budget(NextCategory) := Budget(NextCategory) + Expense;adds the expense amount to whichever element of array
Budget
is
selected by the subscript NextCategory
.
TESTING
As illustrated in the test transactions, it is not necessary for the data to be in order by category. Otherwise, we leave testing to you as an exercise.
PROGRAM STYLE
Allowing for Array Expansion; Designing for Reuse
Categories
and Commands
are
used throughout
Program
8.9 to represent the budget categories. This enables us to extend the
program easily to handle more budget categories, or to change the entire set of
categories, by just rewriting the two types and recompiling the program.
Nothing else in the program needs to be changed.Program 8.9 illustrates two common ways
of selecting array elements for processing. Often,
we need to manipulate all elements of an array in some uniform manner (as in
Example 8.8). In situations like this, it
makes sense
to process the array
elements in sequence (sequential access), starting with the first and ending
with the last. In procedures Initialize
and Report
,
this is accomplished by using a FOR
loop whose loop control
variable is also the array subscript.
In procedure Post
the order in which the array elements are
accessed is completely dependent on the order of the data. The value assigned
to NextCategory
determines which element is incremented. This is
called random access because the order is not predictable beforehand.
Copying and Comparing Arrays
A third way of manipulating an array--accessing the entire array at once--is
provided by the assignment (:=
), equality (=
), and
inequality (/=
) operations. As in the case of records, it is
possible to assign the entire contents of one array to another array provided
the arrays are compatible. Arrays follow the same compatibility rules as
scalars and records do: Two arrays are compatible if their type names are
identical or they are subtypes of the same type. Given the declarations
MaxSize : CONSTANT Positive := 100; SUBTYPE Index IS Positive RANGE 1..MaxSize; TYPE TestArray IS ARRAY (Index) OF Float; W : TestArray; X : TestArray; Y : TestArray;the assignment statement
X := Y;copies each value in array
Y
to the corresponding element of array
X
(i.e. Y( 1)
is copied to X( 1)
,
Y( 2)
to X( 2)
, etc.). Furthermore, the use of
=
and /=
in the fragment
IF X = Y THEN Ada.Text_IO.Put(Item => "Arrays X and Y are equal"); ELSIF W /= Y THEN Ada.Text_IO.Put(Item => "Arrays W and Y are unequal"); END IF;is quite correct. The additional declaration
Z : ARRAY (Index) OF Float;happens to be correct Ada, although we recommend against its use.
Z
is
declared directly as an array, instead of as a variable of an array type
as X
and Y
were. The Ada compiler, which tries to
establish the type name of every variable, will give Z
an internal
type name, which our program cannot know. The assignment statements
Z := Y; -- invalid array copy X := Z; -- invalid array copyand the
IF
fragment
IF Z = X THEN ...are illegal and result in compilation errors. Even though array
Z
has
the same structure as arrays X
and Y
, the type of
array Z
is anonymous and is not compatible with the type of arrays
X
and Y
(type TestArray
). Note that the
elements of Z
are compatible with the elements of
X
and Y
(they are all Float
), and
therefore assignments such as
Z( 3) := Y( 5); X( 9) := Z( 1);and comparisons such as
IF X( 9) = Z( 1) THEN... IF Y( 3) /= Z( 5) THEN ...are legal.
PROGRAM STYLE
Avoiding Anonymous Array Types
If several elements of an array are being manipulated by a procedure, it is generally better to pass the entire array of data instead of individual array elements. In Program 8.9, the procedure call statements
Initialize ( Budget); Post ( Budget); Report ( Budget);pass the entire array
Budget
to each procedure. Budget
is
declared as an OUT
parameter in procedure Initialize
,
an IN OUT
parameter in Post
, and an IN
parameter in procedure Report
.
The rules for manipulating array parameters in a procedure are similar to those
for manipulating scalar parameters: IN
parameters may not be
altered by the procedure.
The next two examples illustrate the use of arrays as parameters assuming the declarations below.
MaxSize : CONSTANT Positive := 5; SUBTYPE Index IS Positive RANGE 1..MaxSize; TYPE TestArray IS ARRAY (Index) OF Float; X, Y, Z : TestArray;
Example 8.10 Although it is possible to use a single assignment statement to copy one array to another, no arithmetic on entire arrays is defined in Ada. For example, the assignment statement
Z := X + Y; -- illegal addition of arrays
is
invalid because Ada has no predefined operator +
that acts on
array operands.. Procedure AddArray
in
Program
8.10 can be used to add two arrays of type TestArray
.
Program 8.10
Procedure AddArray
PROCEDURE AddArray ( A, B : TestArray; C : OUT TestArray) IS -- Stores the sum of A( I) and B( I) in C( I). Array elements -- with subscripts in the range Index are summed, element by element. -- Pre: A( I) and B( I) ( I in range Index) are assigned values -- Post: C( I) := A( I) + B( I) ( I in range Index). BEGIN -- AddArray -- Add corresponding elements of each array FOR I IN Index LOOP C( I) := A( I) + B( I); END LOOP; END AddArray;The parameter correspondence established by the procedure call statement
AddArray ( A => X, B => Y, C => Z);
is shown in Fig. 8.9.
Figure 8.9
Parameter Correspondence for AddArray( X, Y, Z)
Formal parameter arrays A
and B
in the procedure
correspond to actual arrays X
and Y
; formal parameter
array C
corresponds to actual array Z
. The procedure
results are stored in array Z
. After execution of the procedure,
Z( 1)
will contain the sum of X( 1)
and
Y( 1)
, or 3.5
; Z( 2)
will contain
6.7
, and so on. Arrays X
and Y
will be
unchanged.
More on the Rules of Parameter Passing
Recall from Section 6.6 that the values of actual
scalar IN
OUT
parameters are always copied into the corresponding formal
parameters in the procedure's data area, and that all the OUT
and
IN OUT
results are copied back into the calling program just
before the procedure terminates normally and returns to the caller. The rules
for structured parameters are somewhat different. The Ada standard allows
structured parameters to be copied, as for scalars, but also allows them to be
passed more efficiently, simply by copying the address of the actual
parameter into the location of the corresponding formal parameter in the
subprogram's data area. The Ada compiler writer can choose which method to use.
If the latter method (usually called call by reference) is used, a
modification to an IN OUT
parameter will be effective immediately
in the calling program, instead of waiting until the procedure returns to its
caller. This is because in the latter method, the formal parameter and the
actual parameter refer to exactly the same set of locations. Why are the rules
different for the scalar and structured cases?
Suppose that an exception is raised in the execution of the procedure and is
not handled by an exception handler in that procedure. Ada requires that the
procedure terminate abnormally and that the exception be propagated
(passed back) to the calling program, which could then handle it with its own
handler. In this case the scalar OUT
or IN OUT
parameters will not be copied back, because the procedure didn't terminate
normally. This is usually a good thing: The program writer is sure that the
original parameter values, not the new ones, remain in the calling program if
the procedure does not run to normal completion.
Why not do the same thing with array and record parameters? Arrays and records can be large, and so copying them to and from subprograms could take a large amount of time and space. Ada therefore gives the compiler the option of just passing the address of a structured parameter. Most Ada compilers pass structured parameters by reference; some compilers pass small ones by copying and large ones by reference to try to get the best of both worlds. Although in theory a user does not know which method is being used, it is reasonably safe to assume that a compiler will use reference passing for large structured parameters, and therefore:
Procedure Exchange
in
Program
8.11 exchanges the values of its two type Float
parameters.
Program 8.11
Procedure Exchange
PROCEDURE Exchange (P, Q : IN OUT Float) IS -- Exchanges the values of P and Q. -- Pre: P and Q are assigned values. -- Post: P has the value passed into Q and vice-versa. Temp : Float; -- temporary variable for the exchange BEGIN -- Exchange Temp := P; P := Q; Q := Temp; END Exchange;
The procedure call statement
Exchange ( X( 1), X( 2));
uses
this procedure to exchange the contents of the first two elements (type
Float
) of array X
. The actual parameter
X(1)
corresponds to formal parameter P
; the actual
parameter X(2)
corresponds to formal parameter Q
.
This correspondence is shown in Fig. 8.10 for the array X
.
Figure 8.10
Parameter Correspondence for Exchange(X(1),X(2))
It is not permitted to use a subscripted variable as a formal parameter. For example, the procedure declaration
PROCEDURE Exchange ( X( i), X( j) : IN OUT Float);would cause a compilation error.
PROGRAM STYLE
Choosing the Best Parameter Mode
In Pascal, programmers often pass arrays by reference (VAR
parameters) to save time and space, even if the array is not going to be
modified and is used as a read-only parameter. Therefore, programmers who have
Pascal experience often think they should pass all Ada arrays as IN
OUT
, to be sure they are passed by reference. This is a misunderstanding
that leads to poor design: In Ada, arrays are (almost always) passed by
reference even if they are IN
parameters, and scalars are never
passed by reference even if they are IN OUT
.
Because of this rule, there is absolutely no efficiency gain in passing an
array as an IN OUT
parameter when its use is as an IN
parameter. It is therefore best to choose the parameter mode that best
describes the use of the parameter, not the method by which it is
passed.
True
to element
I
of the output array if element I
of one input array
has the same value as element I
of the other input array;
otherwise assign a value of False
. If the input arrays have
subscript type IndexType
, the output array should have the type
below:
TYPE BoolArray IS ARRAY(IndexType) OF Boolean;
Copyright © 1996 by Addison-Wesley Publishing Company, Inc.