• No results found

ADTs Unsorted List and Sorted List

In Chapter 2, we defined an abstract data type and showed how all data can be viewed from three perspectives: from the logical perspective, the implementation perspective, and the application perspective. The logical perspective is the abstract view of whatthe ADT does. The implementation perspective offers a picture of howthe logical operations

are carried out. The application perspective shows whythe ADT behaves as it does—that

is, how the behavior can be useful in a real-world problem.

In this chapter, we look at an ADT that should be familiar to all of us: the list. We all know intuitively what a “list” is; in our everyday lives we use lists constantly—gro- cery lists, lists of things to do, lists of addresses, lists of party guests. Lists are places where we write down things that we want to remember.

3.1

Lists

In computer programs, lists are very useful abstract data types. They are members of a general category of abstract data types called containers, whose purpose is to hold other objects. In some languages, the list is a built-in structure. In Lisp, for example, the list is the main data structure provided in the language. In C++, while lists are provided in the Standard Template Library, the techniques for building lists and other abstract data types are so important that we show you how to design and write your own.

From a theoretical point of view, a list is a homo- geneous collection of elements, with a linear relation-

ship between elements. Linear means that, at the

logical level, each element in the list except the first one has a unique predecessor, and each element except the last one has a unique successor. (At the implementation level, a relationship also exists between the elements, but the physical relationship may not be the same as the logical one.) The number of items in the list, which we call the lengthof the list, is a property of a list. That is, every list has a length.

Lists can be unsorted—their elements may be

placed into the list in no particular order—or they can be sorted in a variety of ways. For instance, a list of numbers can be sorted by value, a list of strings can be sorted alphabetically, and a list of grades can be sorted numerically. When the elements in a sorted list are of composite types, their logical (and often physical) order is determined by one of the members of the structure, called the keymember. For example, a list of students on the honor roll can be sorted alphabetically by name or numerically by student identifi- cation number. In the first case, the name is the key; in the second case, the identifica- tion number is the key. Such sorted lists are also called key-sorted lists.

If a list cannot contain items with duplicate keys, it is said to have unique keys. This chapter deals with both unsorted lists and lists of elements with unique keys, sorted from smallest to largest key value.

Linear relationship Each element except the first has a unique predecessor, and each element except the last has a unique successor

Length The number of items in a list; the length can vary over time

Unsorted list A list in which data items are placed in no particular order; the only relationships between data ele- ments are the list predecessor and successor relationships Sorted list A list that is sorted by the value in the key; a semantic relationship exists among the keys of the items in the list

Key A member of a record (struct or class) whose value is used to determine the logical and/or physical order of the items in a list

3.2

Abstract Data Type Unsorted List

Logical Level

Programmers can provide many different operations for lists. For different applications we can imagine all kinds of things users might need to do to a list of elements. In this chapter we formally define a list and develop a set of general-purpose operations for creating and manipulating lists. By doing so, we build an abstract data type.

In the next section we design the specifications for a List ADT where the items in the list are unsorted; that is, no semantic relationship exists between an item and its predecessor or successor. Items simply appear next to one another in the list.

Abstract Data Type Operations

The first step in designing any abstract data type is to stand back and consider what a user of the data type would want it to provide. Recall that there are four kinds of opera- tions: constructors, transformers, observers, and iterators. We begin by reviewing each type and consider each kind of operation with respect to the List ADT. We use a hand- writing font for operation names at the logical level and change to a monospaced font when we refer to specific implementation.

Constructors A constructor creates an instance of the data type. It is usually implemented with a language-level declaration.

Transformers Transformers are operations that change the structure in some way: They may make the structure empty, put an item into the structure, or remove a specific item from the structure. For our Unsorted List ADT, let’s call these transformers

MakeEmpty

,

InsertItem

, and

DeleteItem

.

MakeEmpty

needs only the list, no other parameters. As we implement our opera- tions as member functions, the list is the object to which the function is applied.

InsertItem

and

DeleteItem

need an additional parameter: the item to be inserted or removed. For this Unsorted List ADT, let’s assume that the item to be inserted isnotcur- rently in the list and the item to be deleted isin the list.

A transformer that takes two sorted lists and merges them into one sorted list or appends one list to another would be a binary transformer.The specification for such an operation is given in the exercises, where you are asked to implement it.

Observers Observers come in several forms. They ask true/false questions about the data type (Is the structure empty?), select or access a particular item (Give me a copy of the last item.), or return a property of the structure (How many items are in the structure?). The Unsorted List ADT needs at least two observers:

IsFull

and

LengthIs

.

IsFull

returns true if the list is full;

LengthIs

tells us how many items appear in the list. Another useful observer searches the list for an item with a particular key and returns a copy of the associated information if it is found; let’s call it

RetrieveItem

.

If an abstract data type places limits on the component type, we could define other observers. For example, if we know that our abstract data type is a list of numerical val-

ues, we could define statistical observers such as

Minimum

,

Maximum

, and

Average

.

Here, we are interested in generality; we know nothing about the type of the items on the list, so we use only general observers in our ADT.

In most of our discussions of error checking to date, we have put the responsibility of checking for error conditions on the user through the use of preconditions that pro- hibit the operation’s call if these error conditions exist. In making the client responsible for checking for error conditions, however, we must make sure that the ADT gives the user the tools with which to check for the conditions. In another approach, we could keep an error variable in our list, have each operation record whether an error occurs, and provide operations that test this variable. The operations that check whether an error has occurred would be observers. However, in the Unsorted List ADT we are speci- fying, let’s have the user prevent error conditions by obeying the preconditions of the ADT operations.

Iterators Iterators are used with composite types to allow the user to process an entire structure, component by component. To give the user access to each item in sequence, we provide two operations: one to initialize the iteration process (analogous to Reset or Open with a file) and one to return a copy of the “next component” each time it is called. The user can then set up a loop that processes each component. Let’s call these operations

ResetList

and

GetNextItem

. Note that

ResetList

is not an iterator itself, but rather an auxiliary operation that supports the iteration. Another type of iterator takes an operation and applies it to every element in the list.

Declarations and Definitions

In general programming terminology, adeclarationassociates an identifier with a data object, an action (such as a function), or a data type. C++ terminology distinguishes between a declaration and a defini- tion. A declaration becomes adefinitionwhen it binds storage to the identifier. Hence, all definitions are declarations, but not all declarations are definitions. For example, a function prototype is a declara- tion, but a function heading with a body is a function definition. On the other hand, declarations such astypedefcan never be definitions, because they are not bound to storage. Because of the way that C++ treats classes, their specification is also a definition. Because the ISO/ANSI C++ standard uses the term “definition” rather than “declaration” when referring to a class, we do the same here.

Generic Data Types

A generic data type is one for which the operations are defined but the types of the items being manipulated are not. Some programming languages have a built-in mechanism for defining generic data types; others lack this feature. Although C++ does have such a mechanism (called a template), we postpone its description until the next chapter. Here we present a simple, general-purpose way of

Generic data type A type for which the operations are defined but the types of the items being manipulated are not

simulating generics that works in any programming language. We let the user define the

type of the items on the list in a class named ItemTypeand have our Unsorted List ADT

include the class definition.

Two of the list operations (

DeleteItem

and

RetrieveItem

) will involve the comparison of the keys of two list components (as does

InsertItem

if the list is sorted by key value). We could require the user to name the key data member “key” and compare the key data members using the C++ relational operators. However, this approach isn’t a very satisfactory solution for two reasons: “key” is not always a meaningful identifier in an application program, and the keys would be limited to values of simple types. C++ does

have a way to change the meaning of the relational operators (called overloading

them), but for now we present a general solution rather than a language-dependent one.

We let the user define a member function

ComparedTo

in the class

ItemType

. This

function compares two items and returns LESS, GREATER, or EQUAL depending on

whether the key of one item comes before the key of the other item, the first key comes after it, or the keys of the two items are equal, respectively. If the keys are of a simple

type such as an identification number,

ComparedTo

would be implemented using the

relational operators. If the keys are strings, function

ComparedTo

would use the string- comparison operators supplied in <string>. If the keys are people’s names, both the last name and the first name would be compared. Therefore, our specification assumes that

ComparedTo

is a member of

ItemType

.

Our ADT needs one more piece of information from the client: the maximum num- ber of items on the list. As this information varies from application to application, it is logical for the client to provide it.

Let’s summarize our observations in two CRC cards: one for

ItemType

and the other

for

UnsortedType

. Note that

UnsortedType

collaborates with

ItemType

.

ItemType

Responsibilities Collaborations

Provide

ComparedTo (item) returns RelationType

. . .

Class Name: Superclass: Subclasses:

MAX_ITEMS