[Instructions] [Search] [Current] [Changes] [Syllabus] [Handouts] [Outlines] [Labs] [Assignments] [Examples] [Bailey Docs] [SamR Docs] [Tutorial] [API]

*This is the first exam I gave to the Fall 1997 Session of CS152.
Students took this exam near the end of seventh week (about 1.5 weeks
later than you will) and had about two hours to complete the exam.
Unfortunately, many of the problems deal with lists, which we haven't
covered. However, your experience with lists in CS151 should make the
general questions clear enough.*

Summarize the modifications you would have to make to your Othello game to implement the following rule changes:

- The game is played on a twenty-by-twenty board.
- There are three colors, not two, which appear in sequence.
You may choose the colors and ordering (
`Black`

,`Red`

, and`White`

is one possible sequence). - Each move flips all neighboring pieces to the next color (not neccesarily to the mover's color). "Neighboring" has the same definition as in Othello.

*Note that you do not need to make the modifications, simply summarize
all the changes to your program you would need to make to get these
extensions to work.*

**Grading:**
Your answer depended on your Othello implementation. In general, I
looked for intelligent solutions that talked about use of constants
for board size, a new (or improved) `Piece`

class, and
so on and so forth.

One of the reasons we have a `current`

field in our
`List`

classes is that it allow us to step through the
list, making modifications as we go. Assume that you're working
with a doubly-linked list and write the following
functions that pertain to current, making sure that you

- have the correct preconditions and postconditions;
- update
`front`

,`current`

, and`back`

appropriately; and - throw exceptions when errors occur.

*The question is intended to test your understanding of lists,
preconditions, postconditions, and exceptions. The logic you employ is
much more important than precise Java or javadoc syntax.*

Implement `void resetCurrent()`

which makes first element
of the list the current element of the list (that is, that resets the
current "pointer" to the front of the list).

**Grading:**
In looking at your solutions, I checked whether you indicated that the
function could throw an exception and whether you actually through one.
I also checked to make sure that you had reasonable pre- and post-conditions.

**Sample Solution:**

/** * Make the current element of the list refer to the front of the list. * pre: The list is nonempty. * post: The current reference refers to the first element. * post: The list is otherwise unchanged. */ public void resetCurrent() throws ListException { // Is the list empty? If so, this will be a problem. if (front == null) { throw new ListException("Can't reset current element in empty list."); } // No exception has been thrown, so we're okay current = front; } // resetCurrent

Write `boolean findElement(Object elt)`

which advances the
`current`

reference to the first
instance of `elt`

after the current current reference, stopping
when it hits the end of the list.
The function returns true if
the element is found, and false otherwise.

For example, if our list were `A,E,B,E,D,F,E`

, and
`current`

were initially at the beginning of the list,
the first call to `findElement(E)`

would move `current`

to the second position,
the next call would move it to the forth position, the next call would
move it to the seventh position, and any subsequent calls would return
false.

**Grading:**
In looking at your solutions, I again checked for exceptions and
pre- and post-conditions. I also checked to make sure that you
used `equals`

for your comparisons and that you met the
specifications of the problem. One particular problem was failing
to specify what happened to the current reference if there were no
further instances of the element.
Many of you also had a number of small or large errors. Some failed
to find the element when it immediately followed the current
element. Some failed to find the element when it came at the end of
the list. Some forgot about the difference between objects and nodes.

**Sample Solution:**

/** * Advance the current pointer to the first instance of elt after * the current instance. If no other instances are found, the * current pointer is returned to the front of the list. * pre: The list is nonempty (not strictly necessary). * pre: The parameter is a comparable object. * pre: The current pointer currently points to some element in the list. * post: The current pointer now points to the next instance of * the element (if such an element exists) or to the front * of the list. * post: The elements in the list have not been modified. */ public boolean findElement(Object element) throws ListException { // Check for errors if (current == null) { throw new ListException("No current element!"); } if (front == null) { throw new ListException("Empty list"); } // Start at the next element current = current.next; // Keep going until we find the right place or reach the // end of the list. We use a return to exit if we've found // the right place. while (current != null) { if (element.equals(current.value)) { return true; } } # while // The element wasn't found, so reset current and return false. current = front; return false; } // findElement

Write `void insertAfterCurrent(Object elt)`

which inserts an
element in the list after the current element of the list list. Make
sure that you think about the various special cases and account for them
in your preconditions, exceptions, and/or code.

**Grading/Problems:**
The biggest problem many of you had was considering all of the special
cases. These include the empty list and the list in which the current
element is the last element. Some of you also had problems with pointer
swapping to correctly insert the element.

**Sample Solution:**

/** * Insert a value after the current element in the list. * pre: The list is nonempty. * pre: There is a current element * post: The size of the list increases by 1 * post: The elements before the current element are in the same * order. * post: The elements after the current element are in the same * order. * post: The new element is directly between the current element * and its previous successor. */ public void insertAfterCurrent(Object elt) throws ListException { // Error checking if (current == null) { throw new ListException("No current element!"); } if (front == null) { throw new ListException("Empty list"); } // Special case: at end of list if (current == back) { appendToEnd(elt); return; } // Normal case, create a node whose previous pointer refers to the // current element and whose next pointer refers to the next element. // Then update the other pointers to accomodate it. Node newelt = new Node(elt,current,current.next); newelt.next.prev = newelt; curent.next = newelt; } // insertAfterCurrent

Here is a typical implementation of `removeAllCopies`

,
which removes all copies of an object from a `Vector`

.

/** * Remove all copies of`elt`

from the current vector. * Returns: The number of elements removed. * Pre: The vector has been initialized. * Post: No copies of`elt`

are in the current vector. * Post: All remaining elements are in the same order that they * were before this method was call. * Post: All original elements not equal to`elt`

are still * in the vector. * Post: The size of the vector is now (old_size - elements_deleted) * [this is implied by the previous postconditions] */ public int removeAllCopies(Object elt) { int i; // The index of the current element we're considering int removed=0;// How many elements have we removed? // Step through the vector, removing each copy with the standard //`removeElementAt`

method. while (i < size()) { // Have we found a copy? if (elt.equals(elementAt(i))) { // Delete that unwanted element deleteElementAt(i); // Update our count of removed elements removed++; // Don't update i, since we may now have another copy at that // position. } // found a copy // We haven't found a copy at the current position, so advance // to the next position. else { i++; } // haven't found a copy } // while // Let the caller know how many elements we removed return removed; } // removeAllCopies

What is the worst-case running time of this algorithm? Express your answer in big-O notation.

**Answer:** The running time of this algortihm is
O(n^2). Why? At worst, there can be O(n) deletions, and each
deletion takes at most O(n) steps. If we wanted to account for
the progressively smaller vectors and deletions, it would be

n-1 + n-2 + ...

which is still O(n^2)

Joe and Jane Student propose that we can improve our algorithm by deleting elements starting from the right, rather than from the left. Their "improved" version of the algorithm appears as:

public int removeAllCopies(Object elt) { int i; // The index of the current element we're considering int removed=0;// How many elements have we removed? // Step through the vector, removing each copy with the standard // <code>removeElementAt</code> method. i = size() - 1; while (i >= 0) { // Have we found a copy? if (elt.equals(elementAt(i))) { // Delete that unwanted element deleteElementAt(i); // Update our count of removed elements removed++; // Don't update i, since we may now have another copy at that // position. } // found a copy // We haven't found a copy at the current position, so advance // to the next position. else { i--; } // haven't found a copy } // while // Let the caller know how many elements we removed return removed; } // removeAllCopies

i. Does this algorithm work? If not, show a counter example.

**Answer:** No the algorithm does not work. If the
element to be removed is the last element of the vector, then after
removing it, there will be an erroneous attempt to reference the
now nonexistant last element of the vector.

**Repair:**
If the index is decreased each time, rather than only when we
fail to match, the algorithm will work.

ii. What is the worst-case running time of this algorithm (in big-O notation)?

**Answer:**
If the list only contained the element we were deleting, the running
time would now be O(n), a significant improvement. However, there
are still cases in which it's O(n^2). In particular, consider the
situation in which the first n/2 elements all match. We end up shifting
n/2 elements n/2 times, which is O(n^2).

iii. In practice, do you think that their algorithm would run faster or slower than the original algorithm?

**Answer:**
It should be faster in all but the single and double match cases,
as it does less copying.

Using at most O(n) more space, write a version of this algorithm
that takes O(n) time on all inputs. If you believe that one of the
algorithms above takes O(n) time, just say so. Note that *n*
is the number of elements in the vector.

**Strategy:**
Create a new vector, and only copy nonmatching elements into that
vector. Then, copy those elements back into the original vector.

**Problems:**
A number of you forgot to ensure that each addition to the new vector
was in constant time (which you can ensure by making it big enough
to begin with). Some of you forgot to copy the elements back. Some
of you forgot to reduce the size after copying the elements back.
Some forgot to return the number of elements deleted.
There were also the standard problems with equals and such.

**Makeup:**
Some of you got the first or second part wrong, and decided that one of
those would be an O(n) algorithm. Those students will be allowed to do
a still-to-be-determined makeup.

**Sample Solution:**

/** * Delete all copies of an object from the current vector. Return * the number of objects deleted. * pre: Comparable object * post: There are no copies of the object left in the vector. * post: The remaining objects are in the same order. */ public int removeAllCopies(Object elt) throws ListException { // Create a new vector to hold the nonmatching elements Vector save = new Vector(size()); // Step through the old vector, copying nonmatching elements for (int i = 0; i < size(); ++i) { if (!elt.equals(elementAt(i))) { save.addElement(elementAt(i)); } // if } // for // Copy back clear(); for (int i = 0; i < save.size(); ++i) { addElement(save.elementAt(i)); } // for } // removeAllCopies

**Analysis:**
The first loop is at most O(n) steps (which may include
`addElement()`

), the second loop is O(n) steps. Each
`addElement()`

takes O(1) steps, so it's an O(n) algorithm.

[Instructions] [Search] [Current] [Changes] [Syllabus] [Handouts] [Outlines] [Labs] [Assignments] [Examples] [Bailey Docs] [SamR Docs] [Tutorial] [API]

**Disclaimer** Often, these pages were created "on the fly" with little, if any, proofreading. Any or all of the information on the pages may be incorrect. Please contact me if you notice errors.

Source text last modified Tue Jan 12 11:43:37 1999.

This page generated on Tue Jan 12 11:47:44 1999 by SiteWeaver.

Contact our webmaster at rebelsky@math.grin.edu