Procedures and Functions in Ada 95: Exchange, Maximum, and Generic Binary Search, Papers of Computer Science

Procedures and functions written in ada 95, specifically the exchange procedure for swapping two values, the maximum function for returning the larger of two integers, and a generic binary search procedure. Additionally, there is a discussion on sets and their operations.

Typology: Papers

Pre 2010

Uploaded on 08/18/2009

koofers-user-gs3
koofers-user-gs3 🇺🇸

5

(1)

10 documents

1 / 43

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
Software Construction and Data Structures with Ada 95 Chapter 5 1
from M.B. Feldman, Software Construction and Data
Structures with Ada 95, Addison Wesley, 1996.
CHAPTER 5
Generic Subprograms and Packages
5.1 Ada Structures: Generic Units
5.2 Application: A Generic Sorting Program
5.3 Application: a Generic Binary Search Program
5.4 ADT Design: An Abstract Data Type for Sets
5.5 Application: Music Makers
5.6 ADT Design: A Generic Vector Package
5.7 Summary of Generic Specifications
5.8 ADT Design: Generic Keyed Table Handler
5.9 ADT Design: A Generic Backup Package
5.10 Application: Airline Passenger List
5.11 ADT Design: ADTs versus Abstract Data Objects
This chapter introduces you to an important feature of Ada that makes the language extremely useful for
developing reusable software components: generics. A generic component (package or subprogram) is one
that is parametrized at the level of the types it works with. There are generic formal and actual parameters,
just like the “normal” ones we use with subprograms and variant records. A generic component can be
instantiated or “tailored” to work with a specific type. This means that a very general program or package
can be written whose code is independent of the type it manipulates. Versions of it can be created, using a
single statement for each version, to handle many different types.
This chapter shows you how to create your own generics and tailor them for many interesting
purposes; the remaining chapters of this book introduce many other generic units. Through the careful
design of generic units, an entire industry of reusable, tailorable software components can be built up and
used for a wide range of applications.
5.1 Ada Structures: Generic Units
Ada’s system of types and procedures requires that the type of a procedure’s actual parameter always match
that of the formal parameter. This means that a procedure or function that needs to do the same thing to
values of two different types must be written twice—once for each type. Consider the procedure
Exchange:
copyright © 1995, Michael B. Feldman January 28, 2001
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27
pf28
pf29
pf2a
pf2b

Partial preview of the text

Download Procedures and Functions in Ada 95: Exchange, Maximum, and Generic Binary Search and more Papers Computer Science in PDF only on Docsity!

from M.B. Feldman, Software Construction and Data

Structures with Ada 95, Addison Wesley, 1996.

CHAPTER 5

Generic Subprograms and Packages

5.1 Ada Structures: Generic Units

5.2 Application: A Generic Sorting Program

5.3 Application: a Generic Binary Search Program

5.4 ADT Design: An Abstract Data Type for Sets

5.5 Application: Music Makers

5.6 ADT Design: A Generic Vector Package

5.7 Summary of Generic Specifications

5.8 ADT Design: Generic Keyed Table Handler

5.9 ADT Design: A Generic Backup Package

5.10 Application: Airline Passenger List

5.11 ADT Design: ADTs versus Abstract Data Objects

This chapter introduces you to an important feature of Ada that makes the language extremely useful for

developing reusable software components: generics. A generic component (package or subprogram) is one

that is parametrized at the level of the types it works with. There are generic formal and actual parameters,

just like the “normal” ones we use with subprograms and variant records. A generic component can be

instantiated or “tailored” to work with a specific type. This means that a very general program or package

can be written whose code is independent of the type it manipulates. Versions of it can be created, using a

single statement for each version, to handle many different types.

This chapter shows you how to create your own generics and tailor them for many interesting

purposes; the remaining chapters of this book introduce many other generic units. Through the careful

design of generic units, an entire industry of reusable, tailorable software components can be built up and

used for a wide range of applications.

5.1 Ada Structures: Generic Units

Ada’s system of types and procedures requires that the type of a procedure’s actual parameter always match

that of the formal parameter. This means that a procedure or function that needs to do the same thing to

values of two different types must be written twice—once for each type. Consider the procedure

Exchange:

PROCEDURE Exchange(Value1, Value2: IN OUT Natural) IS TempValue: Natural; BEGIN TempValue := Value1; Value1 := Value2; Value2 := TempValue; END Exchange;

A procedure to exchange two Float values would have the same sequence of statements, but the type

references would be different:

PROCEDURE Exchange(Value1, Value2: IN OUT Float) IS TempValue: Float; BEGIN TempValue := Value1; Value1 := Value2; Value2 := TempValue; END Exchange;

Obviously, we could modify the first version to give the second version by using an editor. Because we

are likely to need the Natural version again, we modify a copy of it. This gives two versions of a

procedure that are almost the same; because of overloading, the two can both be called Exchange.

Carrying this to its extreme, we could build up a large library of Exchange programs with our editor and

be ready for any eventuality. Exchange could even be made to work with array or record structures,

because Ada allows assignment for any type.

There is a problem with this approach: It clutters our file system with a large number of similar

programs. Worse still, suppose that a bug turns up in the statements for Exchange or in another program

with more complexity. The bug will have turned up in one of the versions; the same bug will probably be

present in all of them, but we will probably forget to fix all the others! This is, in miniature, a problem

long faced by industry: multiple versions of a program, all similar but not exactly alike, all requiring

debugging and other maintenance.

Returning to our simple example, it would be nice if we could create one version of Exchange, test

it, and then put it in the library. When we needed a version to work with a particular type, we could just tell

the compiler to use our pretested Exchange but to change the type it accepts. The compiler would make

the change automatically, and we would still have only a single copy of the procedure to maintain.

It happens that Ada allows us to do exactly this. The solution to this problem is generics. A generic

unit is a recipe, or template, for a procedure, function, or package. Such a unit is declared with formal

parameters that are types, and sometimes are procedure or function names. An analogy can be drawn with an

unusual recipe for a layer cake: All the elements are there except that the following items are left as

variables to be plugged in by the baker:

  • The number of layers
  • The kind of filling between the layers
  • The flavor of the cake itself
  • The flavor of the icing

This recipe was pretested by the cookbook author, but before we can use it for a three-layer yellow cake

with marshmallow filling and chocolate icing, we need to (at least mentally) make all the changes necessary

to the list of ingredients. Only after this instance of the recipe has been created does it make sense to try to

make a cake using it.

Program 5.

A Test of the Generic Swap Procedure

WITH Swap_Generic; WITH Ada.Text_IO; WITH Ada.Integer_Text_IO; PROCEDURE Test_Swap_Generic IS


--| Test program for Swap_Generic --| Author: Michael B. Feldman, The George Washington University --| Last Modified: September 1995


X : Integer; Y : Integer;

A : Character; B : Character;

PROCEDURE IntegerSwap IS NEW Swap_Generic (ValueType => Integer); PROCEDURE CharSwap IS NEW Swap_Generic (ValueType => Character);

BEGIN -- Test_Swap_Generic

X := 3; Y := -5; A := 'x'; B := 'q';

Ada.Text_IO.Put("Before swapping, X and Y are, respectively "); Ada.Integer_Text_IO.Put(Item => X, Width => 4); Ada.Integer_Text_IO.Put(Item => Y, Width => 4); Ada.Text_IO.New_Line;

IntegerSwap(Value1 => X,Value2 => Y);

Ada.Text_IO.Put("After swapping, X and Y are, respectively "); Ada.Integer_Text_IO.Put(Item => X, Width => 4); Ada.Integer_Text_IO.Put(Item => Y, Width => 4); Ada.Text_IO.New_Line; Ada.Text_IO.New_Line;

Ada.Text_IO.Put("Before swapping, A and B are, respectively "); Ada.Text_IO.Put(Item => A); Ada.Text_IO.Put(Item => B); Ada.Text_IO.New_Line;

CharSwap(Value1 => A,Value2 => B);

Ada.Text_IO.Put("After swapping, A and B are, respectively "); Ada.Text_IO.Put(Item => A); Ada.Text_IO.Put(Item => B); Ada.Text_IO.New_Line;

END Test_Swap_Generic;

Generic Subprogram Parameters

Sometimes a generic recipe needs to be instantiated with the names of functions or procedures. To continue

the food analogy, a certain fish recipe can be prepared by either baking or broiling; the rest of the recipe is

independent. Thus, the action “desired cooking method” would be a parameter of that recipe.

Consider the function Maximum, which returns the larger of its two Integer operands:

FUNCTION Maximum (Value1, Value2: Integer) RETURN Integer IS

Result: Integer;

BEGIN

IF Value1 > Value2 THEN Result := Value1; ELSE Result := Value2; END IF;

RETURN Result;

END Maximum;

We would like to create a function that returns the larger of its two operands regardless of the types of

these operands. As in the case of Generic_Swap, we can use a generic type parameter to indicate that an

instance can be created for any type. This is not enough, however. The IF statement compares the two

input values. Suppose the type we use to instantiate does not have an obvious, predefined “greater than”

operation? Suppose the type is a user-defined record with a key field, for example? “Greater than” is not

predefined for records! We can surely write such an operation, but we need to tell the compiler to use it;

when writing a generic, we need to reassure the compiler that all the operations used in the body of the

generic will exist at instantiation time. Let us indicate in the generic specification that a comparison

function will exist.

Program 5.4 is the desired generic specification. The WITH syntax shown here takes getting used to,

but it works.

Program 5.

Specification for Generic Maximum Function

GENERIC

TYPE ValueType IS PRIVATE; WITH FUNCTION Compare(L, R : ValueType) RETURN Boolean;

FUNCTION Maximum_Generic(L, R : ValueType) RETURN ValueType;

--| Specification for generic maximum function --| Author: Michael B. Feldman, The George Washington University --| Last Modified: September 1995


The body of the generic function, presented as Program 5.5, looks similar to the one just given for

Maximum.

Program 5.

Body of Generic Maximum Function

FUNCTION Maximum_Generic(L, R : ValueType) RETURN ValueType IS

--| --| Body of generic maximum function --| --| Author: Michael B. Feldman, The George Washington University --| Last Modified: September 1995

BEGIN -- Test_Maximum_Generic

Ada.Text_IO.Put("Maximum of -3 and 7 is "); Ada.Integer_Text_IO.Put(Item => Maximum(-3, 7), Width=>1); Ada.Text_IO.New_Line; Ada.Text_IO.Put("Minimum of -3 and 7 is "); Ada.Integer_Text_IO.Put(Item => Minimum(-3, 7), Width=>1); Ada.Text_IO.New_Line(Spacing => 2);

Ada.Text_IO.Put("Maximum of -3.29 and 7.84 is "); Ada.Float_Text_IO.Put (Item => Maximum(-3.29, 7.84), Fore=>1, Aft=>2, Exp=>0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("Minimum of -3.29 and 7.84 is "); Ada.Float_Text_IO.Put (Item => Minimum(-3.29, 7.84), Fore=>1, Aft=>2, Exp=>0); Ada.Text_IO.New_Line(Spacing => 2);

Ada.Text_IO.Put("Maximum of 23.65 and 37.49 is "); Currency.IO.Put (Item => Maximum(MakeCurrency(23.65), MakeCurrency(37.49))); Ada.Text_IO.New_Line; Ada.Text_IO.Put("Minimum of 23.65 and 37.49 is "); Currency.IO.Put (Item => Minimum(MakeCurrency(23.65), MakeCurrency(37.49))); Ada.Text_IO.New_Line(Spacing => 2);

END Test_Maximum_Generic;

Generic Array Parameters

An important use for generics, combined with unconstrained array types, is building very general

subprograms to deal with arrays. For a generic to be instantiated for many different array types, we need to

specify formal parameters for the index and array types.

Program 5.7 is a specification for a function Maximum_Array_Generic that returns the “largest”

of all the elements in an array, regardless of the index or element type. We place “largest” in quotation

marks because we already know that we can make it work as a minimum-finder as well.

Program 5.

Specification for Generic Array Maximum Function

GENERIC

TYPE ValueType IS PRIVATE; -- any nonlimited type TYPE IndexType IS (<>); -- any discrete type TYPE ArrayType IS ARRAY(IndexType RANGE <>) OF ValueType; WITH FUNCTION Compare(L, R : ValueType) RETURN Boolean;

FUNCTION Maximum_Array_Generic(List: ArrayType) RETURN ValueType;

--| Specification for generic version of array maximum finder --| Author: Michael B. Feldman, The George Washington University --| Last Modified: September 1995


The syntax of the specification for IndexType means “any discrete type is acceptable as an actual

parameter.” Recalling that discrete types are the integer and enumeration types and subtypes, this is exactly

what we need for the index type of the array. The specification for ArrayType looks like a type

declaration, but it is not. Rather, it is a description to the compiler of the kind of array type that is

acceptable as an actual parameter. In this case, the array type must be indexed by IndexType (or a

subtype thereof) and must have elements of type Valuetype (or a subtype thereof).

The body of Maximum_Array_Generic is shown in Program 5.8.

Program 5.

Body of Generic Array Maximum Function

FUNCTION Maximum_Array_Generic(List: ArrayType) RETURN ValueType IS

--| Body of generic array maximum finder --| Author: Michael B. Feldman, The George Washington University --| Last Modified: September 1995


Result: ValueType;

BEGIN -- Maximum_Array_Generic

FOR WhichElement IN List'Range LOOP IF Compare(List(WhichElement), Result) THEN Result := List(WhichElement); END IF; END LOOP;

RETURN Result;

END Maximum_Array_Generic;

You can write a test program for it as an exercise. As a hint, consider the following declarations:

TYPE FloatVector IS ARRAY(Integer RANGE <>) OF Float; TYPE RationalVector IS ARRAY (Positive RANGE <>) OF Rational;

and instantiate the generic as follows:

FUNCTION Maximum IS NEW Maximum_Array_Generic(ValueType=>Float, IndexType=>Integer, ArrayType=>FloatVector, Compare=>">");

FUNCTION Minimum IS NEW Maximum_Array_Generic(ValueType=>Rational, IndexType=>Positive, ArrayType=>RationalVector, Compare=>"<");

5.2 Application: A Generic Sorting Program

Let us continue our study of generics with the development of a generic sort procedure that uses much of

what we have done in this chapter. We develop a sort procedure that will work correctly for any variable of

any unconstrained array type, regardless of its bounds, index type, or element type.

In Program 4.2 we developed SelectSort, which works for any array of a particular unconstrained

array type. We need only to modify it to make it generic. We also have our procedure Swap_Generic,

which we can instantiate and use to handle exchanges.

Program 5.9 is the specification for the generic sort routine. This is similar to

Maximum_Array_Generic from Program 5.7.

Program 5.

Specification for Generic Sort Procedure

GENERIC

Using the Generic Sort to Order an Array of Records

Sort_Generic can be especially useful in sorting arrays of records. Consider the following declarations:

MaxSize : CONSTANT Positive := 250; MaxScore : CONSTANT Positive := 100;

SUBTYPE StudentName IS String(1..20); SUBTYPE ClassIndex IS Positive RANGE 1..MaxSize; SUBTYPE ClassRange IS Natural RANGE 0..MaxSize; SUBTYPE ScoreRange IS Natural RANGE 0..MaxScore;

TYPE ScoreRecord IS RECORD Name: StudentName; Score: ScoreRange; END RECORD;

TYPE ScoreArray IS ARRAY (ClassIndex RANGE <>) OF ScoreRecord;

Here is a “compare” function that tells us whether one record is “less than” another (in the sense that

one score is lower than the other):

FUNCTION ScoreLess(Score1, Score2 : ScoreRecord) RETURN Boolean IS BEGIN RETURN Score1.Score < Score2.Score; END ScoreLess;

This function compares the score fields of the two records, returning True if the first record is “less than”

the second and False otherwise. We could have named this function "<", of course, but chose not to do

so in the interest of clarity. Given Sort_Generic, it takes only a single instantiation statement to create

a sort that will order an array of score records in ascending order:

PROCEDURE SortUpScores IS NEW Sort_Generic (ElementType => ScoreRecord, IndexType => ClassIndex, ListType => ScoreArray, Compare => ScoreLess);

Given variables Scores and ClassSize as follows:

Scores: ScoreArray(ClassIndex'First..ClassIndex'Last); ClassSize: ClassRange;

we see that Scores can hold up to 250 records, and ClassSize can be used to determine the actual

number of records read from a file into the array. The array can easily be put into ascending order by score,

just by calling SortUpScores with the appropriate array slice:

SortUpScores(List => Scores(1..ClassSize));

Program 5.11 demonstrates the sort for two entirely different array types: an array of float values and an

array of phone call records like the one we used in Section 4.3.

Program 5.

Test of Generic Sort Procedure

WITH Ada.Text_IO; WITH Ada.Integer_Text_IO;

WITH Ada.Float_Text_IO;WITH Sort_Generic;

PROCEDURE Test_Sort_Generic IS

--| Demonstrates Sort_Generic using two unrelated kinds of lists; --| this is not a realistic application, but rather just shows that --| many instances of a generic can occur within one client program. --| Author: Michael B. Feldman, The George Washington University --| Last Modified: September 1995


SUBTYPE Index IS Integer RANGE 1..10; TYPE FloatVector IS ARRAY(Index RANGE <>) OF Float;

V1 : FloatVector(1..10);

SUBTYPE DayRange IS Natural RANGE 0..6; SUBTYPE Weekdays IS DayRange RANGE 0..4; SUBTYPE Weekend IS DayRange RANGE 5..6;

TYPE Days IS (Mon, Tue, Wed, Thu, Fri, Sat, Sun); TYPE CallRecord IS RECORD DayOfWeek : Days; NumberOfCalls: Natural; END RECORD;

TYPE Callers IS ARRAY(DayRange RANGE <>) of CallRecord;

PACKAGE Days_IO IS NEW Ada.Text_IO.Enumeration_IO(Enum => Days);

ThisWeek: Callers(DayRange);

-- if we are going to sort CallRecords, -- we need to know how to compare them

FUNCTION "<" (L, R: CallRecord) RETURN Boolean IS BEGIN RETURN L.NumberOfCalls < R.NumberOfCalls; END "<";

FUNCTION ">" (L, R: CallRecord) RETURN Boolean IS BEGIN RETURN L.NumberOfCalls > R.NumberOfCalls; END ">";

-- local procedures to display the contents of two kinds of lists

PROCEDURE DisplayCallers (List: Callers) IS BEGIN -- DisplayCallers FOR Count IN List'Range LOOP Days_IO.Put (Item=>List(Count).DayOfWeek, Width=>3); Ada.Integer_Text_IO.Put (Item=>List(Count).NumberOfCalls, Width=>4); Ada.Text_IO.New_Line; END LOOP; Ada.Text_IO.New_Line; END DisplayCallers;

PROCEDURE DisplayFloatVector (V: FloatVector) IS BEGIN FOR Count IN V'First..V'Last LOOP Ada.Float_Text_IO.Put(Item=>V(Count), Fore=>4, Aft=>2, Exp=>0); END LOOP;

DisplayCallers(List => ThisWeek);Ada.Text_IO.New_Line;

SortUpCallers(List => ThisWeek); Ada.Text_IO.Put(Item=> "Here is ThisWeek after upward sorting."); Ada.Text_IO.New_Line; DisplayCallers(List => ThisWeek); Ada.Text_IO.New_Line;

SortDownCallers(List => ThisWeek); Ada.Text_IO.Put(Item=> "Here is ThisWeek after downward sorting."); Ada.Text_IO.New_Line; DisplayCallers(List => ThisWeek); Ada.Text_IO.New_Line;

END Test_Sort_Generic;

5.3 Application: A Generic Binary Search Program

Sorting and searching are both very important applications in computing. Having examined a simple

generic sorting program, we will now look at a generic version of binary search, which was discussed in

Section 3.2. Recall that it is meaningful to use binary search only on a table of sorted values, and that the

“big O” of binary search is log N.

We choose to show an iterative version, rather than the recursive one given earlier, both for the sake of

variety and also because the iterative version does not require O(log N ) levels of recursion. For large N , this

would be a large number of recursive calls; it is better to avoid these, especially because the iterative

algorithm is as easy to understand as the recursive one.

Specification of the Generic Binary Search

Program 5.12 shows the generic specification for the generic binary search.

Program 5.

Specification for Generic Binary Search Procedure

GENERIC

TYPE KeyType IS PRIVATE; TYPE ElementType IS PRIVATE; TYPE IndexType IS (<>); TYPE ListType IS ARRAY (IndexType RANGE <>) OF ElementType; WITH FUNCTION "<"(Left, Right: KeyType) RETURN Boolean; WITH FUNCTION KeyOf (Element: ElementType) RETURN KeyType;

PROCEDURE Binary_Search_Generic (List : IN ListType; Target : IN KeyType; Location: OUT IndexType; Found : OUT Boolean);


--| Performs an iterative binary search of an ordered array of

--| keys with bounds List'First..List'Last.

--| Pre : Target and List are defined, and List is sorted upward --| Post: If Target is found in array List, returns True in Found --| and the location in Location; otherwise, --| returns False in Found and returns in Location --| the location in which to insert Target

--| Raises: Ada will raise Constraint_Error--| if List'Last = IndexType'Last and Target would be

--| inserted beyond List'Last. --| Author: Michael B. Feldman, The George Washington University --| Last Modified: October 1995


Let’s discuss the six generic parameters. Here, we take a slightly different approach from that of the

sort program. In general, we will be searching arrays of records, usng some field in the record as the search

key. Let’s call the key and record types KeyType and ElementType, respectively. As in the sort

program, we also specify the index and the list types. The last two parameters are given by

WITH FUNCTION "<"(Left, Right: KeyType) RETURN Boolean; WITH FUNCTION KeyOf (Element: ElementType) RETURN KeyType;

The formal comparison parameter, shown here as "<", compares keys , not elements. We therefore

provide a sixth parameter, KeyOf, so that the client can specify exactly how to find the key field of an

element.

This binary search is, in another way, more general than the simple one given in Section 3.2. That

program returns 0 if the search is unsuccessful, whereas this one returns, in the OUT parameter Location,

the location into which to insert this value. The client can then choose whether or not to actually perform

the insertion. This generality will make it possible to use binary search in the generic table handler we

present in Section 5.8.

Before we examine the body of the binary search procedure, let’s see how it might be instantiated and

used. Program 5.13 shows a test of the generic search procedure, instantiating for a record consisting of a

name and a test score.

Program 5.

A Test of Generic Binary Search

WITH Ada.Text_IO; WITH Ada.Integer_Text_IO; WITH Binary_Search_Generic; PROCEDURE Test_Binary_Search IS


--| Test of generic binary search, array of records --| Author: Michael B. Feldman, The George Washington University --| Last Modified: October 1995


Success: Boolean; WhereFound: Positive;

SUBTYPE NameType IS String(1..10); SUBTYPE ScoreType IS Natural RANGE 0..100;

TYPE ScoreRecord IS RECORD Name: NameType; Score: ScoreType := 0; END RECORD;

TYPE ScoreArray IS ARRAY(Positive RANGE <>) OF ScoreRecord;

Test1: ScoreArray(1..9);

FUNCTION NameOf (Item: ScoreRecord) RETURN NameType IS BEGIN RETURN Item.Name; END NameOf;

END IF;

BinarySearch(Test1, "Mary ", WhereFound, Success); IF Success THEN Ada.Text_IO.Put(Item => "Mary is at location "); Ada.Integer_Text_IO.Put(Item => WhereFound, Width => 1); Ada.Text_IO.New_Line; ELSE Ada.Text_IO.Put(Item => "Mary would be at location "); Ada.Integer_Text_IO.Put(Item => WhereFound, Width => 1); Ada.Text_IO.New_Line; END IF;

BinarySearch(Test1, "Zachary ", WhereFound, Success); IF Success THEN Ada.Text_IO.Put(Item => "Zachary is at location "); Ada.Integer_Text_IO.Put(Item => WhereFound, Width => 1); Ada.Text_IO.New_Line; ELSE Ada.Text_IO.Put(Item => "Zachary would be at location "); Ada.Integer_Text_IO.Put(Item => WhereFound, Width => 1); Ada.Text_IO.New_Line; END IF;

END Test_Binary_Search;

To specify how to find the key part of a score record, we write

FUNCTION NameOf (Item: ScoreRecord) RETURN NameType IS BEGIN RETURN Item.Name; END NameOf;

and use this function as an actual parameter in the instantiating statement

PROCEDURE BinarySearch IS NEW Binary_Search_Generic(ElementType => ScoreRecord, KeyType => NameType, IndexType => Positive, ListType => ScoreArray, "<" => "<", KeyOf => NameOf);

You should examine the various cases used in the test program and try to predict the response for each

case.

Now suppose the array we are searching contains simple integer elements.

TYPE IntegerArray IS ARRAY(Positive RANGE <>) OF Integer;

In this case, there is no actual key, because there is no record whose field is the key. However, the

binary search procedure requires a KeyOf function to fill that parameter. In this case, we can write a

“dummy” function that simply returns the value of its integer input.

FUNCTION Identity (Value: Integer) RETURN Integer IS BEGIN RETURN Value; END Identity;

Now we can instantiate Generic_Binary_Search, as follows:

PROCEDURE BinarySearch IS NEW Binary_Search_Generic(ElementType => Integer, KeyType => Integer, IndexType => Positive, ListType => IntegerArray, "<" => "<", KeyOf => Identity);

Modifying the test program to fit this case is left as an exercise.

Body of the Generic Binary Search

Look now at Program 5.14, the body of the search procedure.

Program 5.

Body of Generic Binary Search Procedure

PROCEDURE Binary_Search_Generic (List : IN ListType; Target : IN KeyType; Location: OUT IndexType; Found : OUT Boolean) IS


--| Body of Generic Binary Search Procedure --| Author: Michael B. Feldman, The George Washington University --| Last Modified: October 1995


Middle : IndexType; -- the subscript of the middle element Success: Boolean; Left : IndexType; Right : IndexType;

BEGIN -- Binary_Search_Generic

Left := List'First; Right := List'Last; Success:= False;

IF Target = KeyOf(List(Left)) THEN Found := True; Location := Left; ELSIF Target < KeyOf(List(Left)) THEN -- Target goes in pos. 1 Found := False; Location := Left; ELSIF Target = KeyOf(List(Right)) THEN Found := True; Location := Right; ELSIF NOT (Target < KeyOf(List(Right))) THEN -- Target beyond end Found := False; Location := IndexType'Succ(Right); ELSE WHILE (Left <= Right) AND (NOT Success) LOOP

Middle := IndexType'Val((IndexType'Pos(Left)

  • IndexType'Pos(Right)) / 2);

IF Target = KeyOf(List(Middle)) THEN Success := True; ELSIF Target < KeyOf(List(Middle)) THEN Right := IndexType'Pred(Middle); -- search lower subarray ELSE

members. A set is said to be empty if it has no members. In cases where there is no ordering, it also makes

no difference if we name a member twice, so {a, b, a} = {b, a, b} = {a, b}.

Operations on Sets

What are the important operations associated with sets? Certainly inserting a member in a set and deleting a

member from a set are essential; so are testing a set to see whether a given element is a member of it and

testing a set to see whether it is empty. The last two operations are predicate or inquiry selector operations;

they return true/false values. The most important dyadic constructor operations are

  • The union of two sets S and T (usually written as ST ), which returns the set containing all of S ’s

members and all of T ’s members

  • The intersection of S and T ( ST ), which returns the set containing all elements which are members

of both S and T

  • The difference ST , which returns the set containing all elements which are members of S but not of

T

An often-used monadic constructor is the complement - S , which returns the set containing all elements

in the universe that are not members of S.

We will use “+” and “*” to represent the union and intersection, respectively, because the union and

intersection symbols are not part of the normal Ada character set. For example, if the universe is the letters

a..k inclusive and S = {a, d, e, g} and T = {b, c, d, e, k}, then

S + T = {a, b, c, d, e, g, k}

S * T = {d, e}

S - T = {a, g} and T - S = {b, c, k}

  • S = {b, c, f, h, i, j, k}

Finally, two more inquiry operations are commonly used:

  • The improper subset operation ( S C T ) which returns True if and only if all members of S are also

members of T

  • The proper subset operation ( S C T ) which returns True if and only if S C T and S /= T ; that is, at

least one member of T is not a member of S.

Because the subset symbols are also missing from the Ada character set, we use < and < for improper

and proper subset, respectively. For example {b, c} < {a, b, c, d, e} and {b, c} < {a, b, c, d, e} but is not a

subset of {c, e}. Also, {a, b} < {a, b}.

Specifying the Generic Set ADT

Mathematically, sets can be infinite (all the integers, for example). In programming applications, however,

it is finite sets that are most interesting. Therefore we confine ourselves to representing finite sets—

specifically, to sets taken from finite universes of integers or enumeration values. As we shall see, it is

easy to use Ada’s generic facility to build a package providing a good but more flexible approximation to

the predefined set facility of Pascal.

A universe is either an integer subtype or an enumeration type; this means that a universe also happens

to be a valid index range for arrays. Choosing a universe, we implement a set as a one-dimensional array of

Boolean values, with index range corresponding to that universe. Given a set S represented as an array S, if

a given member of the universe is a member of S , we let the corresponding element of S be True;

otherwise we let that element be False. This representation is often called the characteristic function or bit

map of a set, and is an especially compact way to represent a large set. For example, suppose we choose the

universe a ..g. Every set over this universe is represented as a Boolean array indexed 'a'..'g'; the set S

= {a, d, e, g}, specifically, is represented as

True False^ False^ True True^ False True

a b c d e f g

Now let us devise a generic Ada package for this ADT. A framework for the generic part of the

specification is

GENERIC

TYPE Universe IS (<>);

PACKAGE Sets_Generic IS

... END Sets_Generic;

The second line specifies a generic parameter that can match any discrete type—that is, any

enumeration type or integer subtype. This is exactly what we need for our finite, discrete universes!

Program 5.15 gives the desired specification, complete with PRIVATE part defining the type Set.

Making sets a PRIVATE type allows client programs to copy sets and check them for inequality using the

predefined assignment, equality, and inequality operations, but denies clients direct access to the

implementation of sets. This leaves the package writer the flexibility to change the implementation of sets

without requiring any code changes in client programs.

Program 5.

Specification for Generic Set Package

GENERIC

TYPE Universe IS (<>); -- any integer or enumeration type

PACKAGE Sets_Generic IS

--| Specification for sets over discrete universes --| Author: Michael B. Feldman, The George Washington University --| Last Modified: October 1995


TYPE Set IS PRIVATE; Phi: CONSTANT Set; -- empty set

-- constructors

FUNCTION "+" (S: Set; E: Universe) RETURN Set; FUNCTION "-" (S: Set; E: Universe) RETURN Set; -- Pre: S and E are defined -- Post: returns S with E inserted or deleted respectively;

-- "+" has no effect if IsIn(S,E); "-" has none if NOT IsIn(S,E)

FUNCTION Singleton(E: Universe) RETURN Set; FUNCTION "+" (E1, E2: Universe) RETURN Set; -- Pre: E, E1, and E2 are defined