Aquarium Lab Series
Using Arrays
This set of Mini-Lab Exercises is the fourth in a series in which students build a small program with several fish moving around in an aquarium. The set includes the following exercises:
Each section contains an Introduction to a problem or task, (usually) abridged versions of one or more Patterns that will be useful in solving the problem or completing the task, and an Exercise.
In the exercises that precede this one, students will have created three fish that move randomly back and forth in an aquarium, being careful not to hit the sides. Students should be familiar with constructing objects, invoking methods, simple selection statements, basic for loops, and the Random class.
Students should read over the patterns that appear in this document before the lab.
A better alternative would be to ask the user how many fish to place in the aquarium and then store them in a collection object. We can use a Linear Indexed Data Structure to store the collection of fish.
See the complete Linear Indexed Data Structure pattern to see other conditions in which this pattern is appropriate.
Therefore, use a Linear Indexed Data Structure.
Examples include Java arrays, the Java ArrayList class, and the
Java String class. Patterns for building and using these structures
include Fixed-Size Linear Data Structure, Expandable
Linear Indexed Data Structure, Appended Item, and
Linear Indexed Traversal.
Therefore, declare and construct a fixed-size collection, initializing it to the appropriate size as you construct it using the Declare-Construct-Initialize pattern.
For example, the syntax for creating a Java array, deck, that
will contain 52 references to objects of the Card class is:
Card[] deck = new Card[52];
More generally, the syntax for creating a Java array, anArray,
with numItems references to objects of the class T
is:
T[] anArray = new T[numItems];
This will actually construct an array that contains numItems
null references. In order for it to contain numItems references
to objects of type T, you will have to insert valid object references
into the array using Indexed Random Access and Valid
Index (see below).
The various Linear Indexed Data Structure patterns are described more fully in the complete Linear Indexed Data Structure Pattern.
Exercise
|
Of course, we don't see any fish, because we haven't created any. But we do have a container to hold them. When we create fish, we can put them directly into the Linear Indexed Data Structure.
How do we do this? And once we've put them there, how do we display one of them, or ask one for its color? We can no longer refer to each fish by name, but we can use the Indexed Random Access pattern to refer to any individual object in a Linear Indexed Data Structure. Rather than trying to create and display all the fish in the aquarium right away, though, we will start by concentrating on the first and last fish in the collection.
You want to access (retrieve or set) a specified entry in a Linear Indexed Data Structure.
Therefore, access it directly, specifying the index of the desired entry. The index should be a Valid Index for the specific Linear Indexed Data Structure.
For example, if deck is a Java array of playing cards, then
one might use the following statement to display the 1st card in the deck
(provided that there is at least 1 card in the deck):
deck[0].display();
One could display the 6th card just as easily (provided that there are at least
6 cards in the deck):
deck[5].display();
(Note that indexing of Linear Indexed Data Structures in C,
C++, and Java, along with some other languages, begins at 0. See Valid
Index for more details.)
This pattern can also be used to modify an element in the data structure. For example, to swap the 1st and 52nd cards in the deck, the following code might be used:
Card temp = deck[0]; // temp is 1st card
deck[0] = deck[51]; // 1st card is now same as 52nd card
deck[51] = temp; // 52nd card is now old 1st card
You want to access a valid element in a Linear Indexed Data Structure.
Therefore, use an index in the range determined by the programming language and the size of the data structure. In many languages, such as C, C++, and Java, valid indices for a structure with N elements range from 0 to N-1, where 0 is the index of the 1st element in the Linear Indexed Data Structure and N-1 is the index of the last element. In other languages, such as Pascal, valid indices for a Linear Indexed Data Structure with N elements range from 1 to N.
How to determine the size of a data structure (the number of elements in
it) also depends on the language and the particular data structure. For example,
if your data structure myArray is a Java array, then you can
use the expression myArray.length to obtain the number of elements.
Actually, this gives you the capacity, or number of references, in the array.
Whether these are null references or references to meaningful data elements
depends on whether you have initialized the array. If the capacity of the
data structure is larger than the number of meaningful elements put into it,
then the program must keep track of the number of meaningful elements separately.
These patterns are described more fully in the complete Linear Indexed Data Structure Pattern.
Exercise
|
You need to access (set or retrieve) all entries of a Linear Indexed Data Structure exactly once, and, if the order matters, from first to last. You know the number of elements in the Linear Indexed Data Structure.
Therefore, use a FOR statement in which the loop control variable is a Valid Index into the linear data structure. The variable should be initialized to the index of the first element in the linear data structure, the step should increment the index by one, and the test should make sure that the index is not used in the loop body if it goes beyond the last element in the data structure.
The common idiom for Linear Indexed Traversal through arrays in the C-based languages (C, C++, Objective C, Java, etc.) is
for (i = 0; i < N; i++)
{ // body of loop accesses or sets myArray[i]
}
where i is an int, myArray is the linear
indexed data structure and N is the number of elements to be accessed.
If your data structure is a full Java array then you should use myArray.length
for N. If the array's capacity is larger than the actual number
of (meaningful) elements in it, then you should instead keep track of the
number of elements as a separate variable. For example, if hand
is a partially full Java array of playing cards with numCards
cards in it, then one might use the following loop to display the cards in
the hand:
for (int i = 0; i < numCards; i++)
{ hand[i].displayCard();
}
This pattern could also be used to modify elements in the collection. For
example, to replace all the cards in the hand with new cards obtained from
a CardDeck object, deck, the following Java code might be used:
for (int i = 0; i < hand.length; i++)
{ hand[i] = deck.dealCard();
}
Note the condition in the FOR loop: i < N (or, in this case,
i < hand.length). This is the common idiom in C-based languages
to ensure that the index i is always a Valid
Index inside the loop. Another, logically equivalent, condition would
be i <= N-1, but the common idiom is more succinct, possibly
more efficient (depending on your compiler), and draws attention more clearly
to the number of elements in the structure (N).
for (int i = 0; i <= hand.length; i++) INCORRECT!
{ hand[i].displayCard();
}
you will attempt to display one card too many, going beyond the range of the
data structure and, in Java, generating a run-time exception. If, on the other
hand, you write
for (int i = 0; i < hand.length - 1; i++) INCORRECT!
{ hand[i].displayCard();
}
you will not display the last card. To avoid these kinds of errors, consistently
use the common idiom for Linear Indexed Traversal:
(i = 0; i < N; i++)
See Reverse Linear Indexed Traversal if you need to access all the entries of an indexed data structure from last to first. This alternative, and other types of Repetition, are described in the complete Repetition Pattern.
Exercise
|
It seems clear that we will want to loop through the steps in the simulation, as we are already doing. It also seems clear that we will want to loop through all the fish. The question is:
To decide which loop gets nested inside of which, consider the following algorithmic structures in which we assume that we have 25 fish in the aquarium and we want to run the simulation 100 times.
| For each fish in the collection:
Move 100 times and display the aquarium. |
For each step in the simulation:
Move the 25 fish once and display the aquarium. |
We do not want the first fish to move 100 times, followed by the second fish moving 100 times, followed by the third fish moving 100 times. Instead, we want all 25 fish to move once, then all 25 fish to move again. This corresponds to the second solution above.
Another question we have to consider is where the display of the aquarium should be relative to the fish movement. Do we want to display the fish and aquarium in the outer loop (as part of each simulation step), or in the inner loop (as part of processing each fish)? The following table illustrates these two options.
| For each step in the simulation:
For each fish in the collection: Move, possibly changing direction. Display aquarium & fish. |
For each step in the simulation:
For each fish in the collection: Move, possibly changing direction. Display aquarium & fish. |
Which behavior do you wish to implement?
Exercise
|