There are two parts to each ADT:
Note: There are many possible operations that could be defined for each ADT.
They often fall into these categories:
Introduction
What makes a program good?
To help achieve (2) (which helps with (1)) use
abstraction / modularity / information hiding.
One example is the use of abstract data types (ADTs).
The basic idea of an abstract data type is to separate conceptual objects
and operations from their actual implementation.
The benefits of using ADTs include:
OPERATION | DESCRIPTION |
size | return the number of items in the sequence |
addBefore | add a given item just before the current item, or at the front of the sequence if there is no current item; make the new item the current one |
addAfter | add a given item just after the current item, or at the end of the sequence if there is no current item; make the new item the current one |
removeCurrent | remove the current item (error if there is no current item); if the current item is the last item in the sequence, then after the remove operation there is no current item; otherwise, make the next item the current item |
start | make the first item in the sequence be the current item. |
getCurrent | return the current item (error if there is no current item) |
advance | advance the current item (error if there is no current item) |
isCurrent | return true if there is a current item; otherwise, return false |
lookup | return true if a given item is in the sequence; otherwise, return false |
Many other operations are possible; when designing an ADT, you should try to provide enough operations to make the ADT useful in many contexts, but not so many that it gets confusing. It is not always easy to achieve this goal; it will sometimes be necessary to add operations in order for a new application to use an existing ADT.
Before discussing how to implement sequences, let's make sure you understand what the start, getCurrent, advance, and isCurrent operations are supposed to do. Their purpose is to allow a user of a sequence to iterate through the sequence; i.e., to access each item in the sequence in turn, starting with the first item. Suppose we have the following sequence of words (reading the sequence from left to right):
banana, apple, lemon, mangoAfter invoking the start operation, the sequence would contain the same items, but the first item would be the special "current" item:
banana, apple, lemon, mango ^ | currentThe getCurrent operation would return the current item, "banana", and the advance operation would change the current item to be the second one, "apple":
banana, apple, lemon, mango ^ | currentIf we invoke advance two more times, we will have this situation:
banana, apple, lemon, mango ^ | currentOne more use of advance at this point causes the conceptual current pointer to fall off the end of the sequence:
banana, apple, lemon, mango ^ | currentNow there is no current item; the isCurrent operation will return false, and both the getCurrent and advance operations will cause an error. Similarly, when an empty sequence is first created, there is no current item.
Draw "before" and "after" pictures to illustrate the effect of the addBefore operation (a) on the sequence of numbers: 10, 20, 53, 2, in which 20 is the current item, and (b) on the same sequence of numbers in which there is no current item. In both cases, assume that the item being added to the sequence is the number 6.
Now consider the "private" or "internal" part of the sequence ADT. In other words, how should we actually implement sequences in Java? First, note that Java classes are a good match with the idea of ADTs: the public and privates fields and methods of the class correspond to the public (external) and private (internal) parts of an ADT. So let's use a Java class for our sequence ADT.
Note that we could define different classes for different kinds of sequences (e.g., an IntSequence contains only integers, a StringSequence contains only strings, etc). Another alternative (which we will choose here) is to define the Sequence class as a sequence of Objects; that way, we only need one class definition, and we can use different instances of the class to store different kinds of items (we can even have a sequence that contains more than one kind of item, e.g., both Strings and Integers).
We will consider two ways to implement the Sequence class: using an array, and using a linked list (the former is covered in this set of notes; the latter in a later set of notes).
Here's an outline of the Java definition for the Sequence class,
implemented using an array to store the items in the sequence (the
bodies of the methods are not filled in for now):
Notes:
Now let's think about the actual code.
We'll need two cases, depending on whether there is a current item.
If there is, we'll need to move all of the items after the current item
one place to the right in the array before inserting the new item.
In both cases, the new item becomes the current item, and in both cases,
we need to worry about whether the array is full.
So here's an outline for the code:
Note that the addBefore method will also need code to expand the
array and to move items to the right;
therefore, it seems like a good idea to define (private) methods for
those tasks.
Here's the final code for addAfter, including calls to the
new private methods:
In general, when you write code it is a good idea to think about special
cases.
For example, does addAfter work when the sequence is empty?
When the current item is the first one?
When it is the last one?
You should think through these cases (perhaps drawing pictures to illustrate
what the sequence looks like before the call to addAfter, and
how the call to addAfter affects the sequence).
Decide if the code works as is, or if some modifications are needed.
Question 1.
Question 2.
Array Implementation
public class Sequence {
//*** methods ***
// constructor
public Sequence() { ... }
// add items
public void addBefore(Object ob) { ... }
public void addAfter(Object ob) { ... }
// remove items
public void removeCurrent() { ... } throws NoCurrentException { ... }
// iteration
public void start() { ... }
public Object getCurrent() throws NoCurrentException { ... }
public void advance() throws NoCurrentException { ... }
public boolean isCurrent() { ... }
// other methods
public int size() { ... }
public boolean lookup(Object ob) { ... }
// *** fields ***
private Object[] items; // the items in the sequence
private int current; // the index of the current item
private int numItems; // the number of items in the sequence
}
Implementing addAfter
Now let's think about how to implement some of the Sequence methods.
First, consider the addAfter method.
Recall that it adds a given item immediately after the
current item if there is a current item;
otherwise, it adds the item at the end of the sequence.
In either case, the new item becomes the current item.
Here is a conceptual picture of what addAfter should do when
there is a current item:
BEFORE: item 1 item 2 item 3 ... item n
^
|
current
AFTER: item 1 item 2 NEW ITEM item 3 ... item n
^
|
current
and here's what it should do when there is no current item:
BEFORE: item 1 item 2 item 3 ... item n
AFTER: item 1 item 2 item 3 ... item n NEW ITEM
^
|
current
public void addAfter( Object ob ) {
// if the array is full, expand it
// if there is a current item:
// o move all items after the current item one place to the right
// o increment current (so that it is the index of the space
// in which the new item will be stored)
// if there is no current item:
// o set current to be the index of the first free space at the
// end of the array
// in both cases, store the new item in the space indexed by current
// and increment the count of the number of items in the sequence
}
public void addAfter(Object ob) {
// if the array is full, expand it
if (numItems == items.length) expandArray();
if (isCurrent()) {
// move all items after the current item one place to the right
// and increment current
moveItemsRight(current+1);
current++;
}
else {
// set current to be the index of the first free space at the
// end of the array
current = numItems;
}
// store the new item in the space indexed by current
// and increment the count of the number of items in the sequence
items[current] = ob;
numItems++;
}
Complete method expandArray using the following header.
You should use the method System.arraycopy to copy the values
from the current array to the new array.
System.arraycopy has five parameters:
private void expandArray() {
// allocate an array of twice the size of the "items" array
// copy all values from "items" to the new array
// assign "items" to refer to the new array
}
Complete method moveItemsRight using the following header.
private void moveItemsRight(int start) {
// move all of the items in the sequence, starting with the item with
// index "start", one place to the right (i.e., move items[start] to
// items[start+1], move items[start+1] to items[start+2], etc
}