Algorithms and OOD (CSC 207 2014S) : Assignments

Exam 2: ADTs, Algorithms, and Data Structures


Assigned: Wednesday, 12 March 2014

Electronic and printed versions due: 10:00 a.m., Friday, 4 April 2014.

Preliminaries

Exam Format

This is a take-home examination. You may use any time or times you deem appropriate to complete the exam, provided you return it to me by the due date.

There are four problems on this examination. You must do your best to answer all of them. The problems are not necessarily of equal difficulty. Problems may include subproblems. If you complete four problems correctly or mostly correctly, you will earn an A. If you complete three problems correctly or mostly correctly, you will earn a B. If you complete two problems correctly or mostly correctly, you will earn a C. If you complete one problem correctly or mostly correctly, you will earn a D. If you complete fewer than one problem correctly or mostly correctly, you will earn an F. If you do not attempt the examination, you will earn a 0. Partially correct solutions may or may not earn you a partial grade, depending on the discretion of the grader.

Read the entire examination before you begin.

I expect that someone who has mastered the material and works at a moderate rate should have little trouble completing the exam in a reasonable amount of time. In particular, this exam is likely to take you about six hours, depending on how well you've learned the topics and how fast you work. (When I do the problems, I will report how long each one took me.)

Academic Honesty

This examination is open book, open notes, open mind, open computer, and open Web. However, it is closed person. That means you should not talk to other people about the exam. Other than as restricted by that limitation, you should feel free to use all reasonable resources available to you.

As always, you are expected to turn in your own work. If you find ideas in a book or on the Web, be sure to cite them appropriately. If you use code that you wrote for a previous lab or homework, cite that lab or homework and the other members of your group. If you use code that you found on the course Web site, be sure to cite that code. You need not cite the code provided in the body of the examination.

Although you may use the Web for this exam, you may not post your answers to this examination on the Web. (You certainly should not post them to GitHub.) And, in case it's not clear, you may not ask others (in person, via email, via IM, via IRC, by posting a “please help” message, or in any other way) to put answers on the Web.

Because different students may be taking the exam at different times, you are not permitted to discuss the exam with anyone until after I have returned it. If you must say something about the exam, you are allowed to say “This is among the hardest exams I have ever taken. If you don't start it early, you will have no chance of finishing the exam.” You may also summarize these policies. You may not tell other students which problems you've finished. You may not tell other students how long you've spent on the exam.

You must include both of the following statements on the cover sheet of the examination.

  1. I have neither received nor given inappropriate assistance on this examination.
  2. I am not aware of any other students who have given or received inappropriate assistance on this examination.

Please sign and date each statement separately. Note that the statements must be true; if you are unable to sign either statement, please talk to me at your earliest convenience. You need not reveal the particulars of the dishonesty, simply that it happened. Note also that inappropriate assistance is assistance from (or to) anyone other than Professor Rebelsky (that's me).

Presenting Your Work

You must present your exam to me in two forms, physically and electronically. If you fail to turn in both versions, you are unlikely to receive credit for the examination.

Physical copy: You must write all of your answers using the computer, print them out, number the pages; put your name on the top of every page, write, sign, and date each of the academic honesty statements (provided you are able to do so); and hand me the printed copy or put it under my office door. If you fail to name and number the printed pages, you may suffer a penalty. If you fail to turn in a legible version of the exam, you are also likely to suffer some sort of penalty.

Electronic copy: You must also submit an electronic copy of your exam. You should create the electronic version by making a tarball of any relevant code and emailing me the tarball. Here are the steps for making a tarball.

  1. Remove any cruft (needless files) from your directory structure. I don't want to see .class files, editor backups, or anything similar.
  2. Switch to the parent directory of your exam directory.
  3. Issue the command tar cvzf username.tgz directory.
  4. If you have done things correctly, you should see the file username.tgz.
  5. Make sure that the tar file contains the appropriate contents using the command tar tvf username.tgz. For example, if I were to check my tarball, I might see something like the following.
    > tar tvf rebelsky.tgz
    rebelsky/
    rebelsky/.git/
    rebelsky/.gitignore
    rebelsky/Problem1/
    rebelsky/Problem2/
    rebelsky/Problem3/
    rebelsky/Problem4/
    rebelsky/README.md
    rebelsky/Problem1/.classpath
    rebelsky/Problem1/.project
    rebelsky/Problem1/src/
    rebelsky/Problem1/src/Quicksorter.java
    rebelsky/Problem1/src/SamTest.java
    rebelsky/Problem2/.classpath
    rebelsky/Problem2/.project
    rebelsky/Problem2/src/
    reblesky/Problem2/src/Example1.java
    reblesky/Problem2/src/Example2.java
    reblesky/Problem2/src/Example3.java
    ...
    

Code: In many problems, I ask you to write code. Unless I specify otherwise in a problem, you should write working code and include examples that show that you've tested the code. You should do your best to format that code to the class formatting standards. I am likely to subtract at least half the value of any problem in which your code does not adhere closely to the standards.

Documentation: You should document classes, interfaces, fields, and methods using Javadoc-style comments. You should specify preconditions and postconditions for each method.

Care: Just as you should be careful and precise when you write code and documentation, so should you be careful and precise when you write prose. Please check your spelling and grammar. Since I should be equally careful, the whole class will receive one point of extra credit for each error in spelling or grammar you identify on this exam. I will limit that form of extra credit to five points.

Partial Credit: I may give partial credit for partially correct answers. I am best able to give such partial credit if you include a clear set of work that shows how you derived your answer. You ensure the best possible grade for yourself by clearly indicating what part of your answer is work and what part is your final answer.

Getting Help

I may not be available at the time you take the exam. If you feel that a question is badly worded or impossible to answer, note the issue you have observed and attempt to reword the question in such a way that it is answerable. You should also feel free to send me electronic mail at any time of day.

I will also reserve time at the start of each class before the exam is due to discuss any general questions you have on the exam.

Preparation

Clone the repository using the following command, which will help ensure that you have the correct directory structure. Note that I've set things up so that you can treat each problem as a separate Eclipse project. Please do not make one project for the whole exam.. Note also that the command will clone the repository into a directory that corresponds to your user name.

$ git clone https://github.com/Grinnell-CSC207/exam2-2014S *username*

Problems

Problem 1: Dutch Quicksort

Topics: Sorting. Quicksort. the Dutch National Flag Algorithm.

Quinn and Sol are a bit frustrated by our current implementation of Quicksort. They say “Quicksort seems to do a lot of extra work when the pivot appears multiple times in the array. Why can't we just group all of the elements equal to the pivot together, and only recurse on the values that are strictly smaller and strictly larger?

Duff, Nat, and Fran say “That seems to be a task for the Dutch National Flag algorithm, which can partition an array into three sections: red, white, and blue, or smaller, equal, and larger.

Write a version of Quicksort that uses this approach. That is, it should partition the array into three parts and only recurse on the left and right subparts.

Note: You may find it difficult to make the partition process a separate method, since, in addition to rearranging the items in the subarray, it now has to return two values: the lower bound and upper bound of the middle part of the subarray. Here are three options for dealing with that issue: (1) You can move the partitioning process into the middle of Quicksort. (2) You can write a helper procedure that, given the start of the middle subarray, finds the end. (3) You can write a helper class that holds two integers and return an object in that class. I tend to use the first approach.

Problem 2: Filtering

Topics: Iterators. Predicates. Iteration. Anonymous inner classes. Generics. Object-oriented design.

Your classmates, Phil and Holden, have enjoyed many aspects of object oriented design, but also like to think functionally. They've started to look at approaches to working with iterators and predicates.

They note that it's relatively simple to print out all of the values for which a predicate holds. For example,

import java.io.PrintWriter;

import java.util.Arrays;
import java.util.Iterator;

/**
 * An example of iteration and filtering with predicates by Phil and Holden.
 */
public class Example1
{
  public static void main(String[] args)
    throws Exception
  {
    PrintWriter pen = new PrintWriter(System.out, true);
    Integer[] values = new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8 };
    Iterator<Integer> it = Arrays.asList(values).iterator();
    Predicate<Integer> even = new Predicate<Integer>()
        {
          public boolean test(Integer val)
          {
            return (val % 2) == 0;
          } // test(Integer)
        }; // new Predicate<Integer>()
        
    while (it.hasNext())
      {
        Integer i = it.next();
        if (even.test(i))
          pen.println(i);
      } // while

    pen.close();
  } // main(String[])
  
} // class Example1

But they'd like to play a bit more with the design. Phil starts by noting that it would be useful to combine an iterator with a predicate to make what he calls a “filtered iterator”. A filtered iterator works much like an iterator, except that it only returns the values in the iteration for which the predicate holds.

import java.io.PrintWriter;

import java.util.Arrays;
import java.util.Iterator;

/**
 * An example of iteration and filtering with predicates by Phil and Holden.
 */
public class Example2
{
  public static void main(String[] args)
    throws Exception
  {
    PrintWriter pen = new PrintWriter(System.out, true);
    Integer[] values = new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8 };
    Iterator<Integer> it = new FilteredIterator<Integer>(
            Arrays.asList(values).iterator(),
            new Predicate<Integer>()
                {
                  public boolean test(Integer val)
                  {
                    return (val % 2) == 0;
                  } // test(Integer)
                });
        
    while (it.hasNext())
      {
        pen.println(it.next());
      } // while

    pen.close();
  } // main(String[])
  
} // class Example2

Holden suggests that if we're going to make such use of predicates, we really should have tools for making new predicates. He suggests ways to negate predicates and to combine predicates with and and or. He also suggests making simple numeric predicates. He even creates a handy-dandy code sketch.

/**
 * Helpers for working with predicates.
 */
public class Pred
{
  // +-----------+-------------------------------------------------------
  // | Constants |
  // +-----------+

  /**
   * A method that accepts any parameter.
   */
  public static Predicate<Object> ACCEPT = new Predicate<Object>()
         {
           public boolean test(Object val)
           {
             return true;
           }
         }; // new Predicate<Object>()

  /**
   * A method that rejects any parameter.
   */
  public static Predicate<Object> REJECT = new Predicate<Object>()
         {
           public boolean test(Object val)
           {
             return false;
           } // test(Object)
         }; // new Predicate<Object>

  // +----------------+--------------------------------------------------
  // | Static Methods |
  // +----------------+

  /**
   * Create a new predicate that holds when both of two predicates hold.
   *
   * @return
   *   pred a predicate
   * @post
   *   pred.test(val) holds if and only if left.test(val) holds
   *   and right.test(val) holds.
   */
  public static <T> Predicate<T> and(final Predicate<? super T> left, 
      final Predicate<? super T> right)
  {
    // STUB
    return munge(ACCEPT);
  } // and(Predicate<T>, Predicate<T>)

  /**
   * Create a new predicate that holds when another predicate does not
   * hold, and vice versa.
   *
   * @return
   *   pred a predicate
   * @post
   *   pred.test(val) holds if and only if derp.test(val) does not hold.
   */
  public static <T> Predicate<T> not(final Predicate<T> derp)
  {
    // STUB
    return munge(ACCEPT);
  } // not(Predicate<T>)

  /**
   * Create a new predicate that holds when either or both of two
   * predicates hold.
   *
   * @return
   *   pred a predicate
   * @post
   *   pred.test(val) holds if and only if left.test(val) holds or
   *   right.test(val) holds.
   */
  public static <T> Predicate<T> or(final Predicate<? super T> left, 
          final Predicate<? super T> right)
  {
    // STUB
    return munge(ACCEPT);
  } // or(Predicate<T>, Predicate<T>)

  /**
   * Convert a predicate that works on a superclass to a predicate
   * that works on a subclass.
   */
  public static <T> Predicate<T> munge(final Predicate<? super T> pred)
  {
    return new Predicate<T>() 
        {
          public boolean test(T val)
          {
            return pred.test(val);
          } // test(T)
        }; // new Predicate<T>
  } // munge(Predicate)
} // class Pred

Holden admits that this approach is a bit less object-oriented than one might hope. After all, shouldn't and be a method of the Predicate interface, with each predicate able to combine itself with a parameter predicate? But they've decided that static methods can still provide a relatively object-oriented approach, and making every predicate implement and seems excessive.

They've also come up with a host of utility classes, example, experiments, and tests that you can find on Github.

Unfortunately, Phil and Holden are better at concept and experiments than they are at details, so they've turned to you for the implementation.

a. Implement the FilteredIterator class.

b. Implement the methods in Pred.java.

Problem 3: Sorted Lists

Topics: Linked structures. Skip lists. Iterators. ADT design.

Soren and Lisa claim to have enjoyed our exploration of lists. However, they note

We thought that when we first started talking about lists, someone said that “lists are ordered collections”. If they're ordered, shouldn't they use a comparator to specify the ordering?

With some effort, you explain to them that the ordering is determined by the client, rather than a separate iterator. They reply,

Fine. If you refuse to follow a sensible design, we'll come up with our own ADT. A sorted list is a collection of values that are iterated from smallest to largest. We use sorted lists when we need to prioritize things (e.g., we can process things from most important to least important by iterating the list) and when we need to quickly identify the “best” few values (e.g., the top five scoring students). Sorted lists should provide an add(T value) method and a method for getting an iterator. Clients remove elements with the iterator's remove method. We'll leave the complexities of full list iterators to another day.

As you might expect, your instructor, Hugh DeWitt, wants you to implement their design. Fortunately, you have a working version of skip lists which Lisa and her friend Skippy developed previously. Hence, your primary goal is to create a subclass that adds an iterator to skip lists. You should not change the skip list code. You will, however, probably need to have your iterator reference the fields of the skip list class. (Let's hope that your classmates chose the right protection levels.)

Note that the iterator's hasNext and next methods should all be O(1). (Yes, you can pretend the height of a skip list is constant.) The iterator's remove method should be at least as efficient as the skip list remove method.

Problem 4: The Kth Smallest Value

Topics: Linked structures. Doubly-linked lists. Quicksort. List iterators.

Your classmates, Kay and Smilla, were intrigued by the efficient median algorithm we developed in class. In case you've forgotten, the algorithm looks something like the following:

lb = 0
ub = values.length
while (true)
  int p = partition(values, lb, ub, order)
  if (p == k)
    return values(k)
  else if (p < k)
    lb = p + 1
  else
    ub = p

However, Kay and Smilla are disappointed that the algorithm worked only on arrays. “After all”, they say, “isn't the whole reason we have iterators is that we want to be able to do clever things with lists as well as arrays.” They suggest that we rewrite our algorithm to work with structures that have a listIterator method.

Amazingly, Hugh DeWitt is a little bit concerned about their observation. Hugh notes

That's a great idea. Unfortunately, to do something like partition in lists, we need to be able to not only iterate forward and backward, but also keep track of where in the list the lower bound and upper bound are. We could use integer indices, but that's potentially inefficient. If the folks at Sun/Oracle had been sensible, they might have made ListIterators cloneable, but they didn't.

Kay and Smilla, not to be deterred, quickly whip up a CloneableListIterator interface and rewrite our DoublyLinkedList class so that its iterators are cloneable.

import java.util.ListIterator;

/**
 * List iterators that we can clone.
 *
 * Many of the sorting algorithms benefit from being able to create
 * a second iterator at the same location as an existing iterator.
 * For example, when we're partitioning in Quicksort, we want to be
 * able to keep track of the bounds of the subarray of interest, but
 * we also want to be able to iterate that subarray from back to front
 * and front to back.
 */
public interface CloneableListIterator<T>
  extends Cloneable, ListIterator<T>
{
  public CloneableListIterator<T> clone();
} // interface CloneableListIterator<T>
import java.util.Iterator;
import java.util.ListIterator;

/**
 * Very simple lists.
 */
public interface SimpleList<T>
    extends
      Iterable<T>
{
  public Iterator<T> iterator();

  public CloneableListIterator<T> listIterator();
} // interface SimpleList<T>
import java.util.Comparator;

/**
 * Utilities for simple lists.
 */
public class SimpleListUtils
{
  /**
   * Find the kth smallest element of a simple list.
   *
   * @pre
   *   No two values in the list are equal.  We have this precondition
   *   mostly to make it easier to state the postcondition.
   * @pre
   *   k < length of lst
   * @pre
   *   lst is not empty
   * @post
   *   There are exactly k values smaller than val in the list.  A
   *   value is "smaller" than val if order.compare(value, val) < 0.
   * @post
   *   The elements of the list may have been rearranged.
   * @post
   *   No elements have been added to or removed from the list.
   */
  public static <T> T kthSmallest(SimpleList<T> lst, Comparator<T> order, int k)
  {
    // STUB
    return lst.iterator().next();
  } // kthSmallest(SimpleList<T>, Comparator<T>, int)
} // class SimpleListUtils

They are about to start work on the kthSmallest operation when Hugh DeWitt turns to you and say “Kay and Smilla already did enough of the work. They shouldn't have to implement this method. Why don't you do it?

Implement the kthSmallest algorithm for lists that support cloneable iterators. Note that your implementation should have an expected running time of O(n), so you may not sort the list. However, you may rearrange the elements of the list. You must also do the work with only a constant amount of extra memory, so you may not copy the list into an array.

Extra Credit

You can earn a small amount of extra credit if you report on three interesting techniques or ideas you discover from reading my unit tests.

Include a file at the top level of your directory structure with your comments on those techniques or ideas.

You may also earn a small amount of extra credit if you show good git behavior (other than pushing your work to a public repository). Make sure to do relatively small commits and to include good commit message.

Questions and Answers

Problem 1

How long did this problem take you?

When I first tried it (a few semesters ago), it took between fifteen and twenty minutes. About five minutes to write the invariant. (Yes, I do write invariants when I'm solving things.) About ten minutes to write the loop and the recursive calls. A few minutes to fix the one bug in my code, which involved backwards comparisons. Since I'd written DNF and Quicksort examples that semester, I seemed pretty quick, even though I did not refer explicitly to the examples.
When I redid the problem this semester, it took ten minutes. Amazingly, my first solution was correct.

What are the most common student errors on this problem?

Some students forget that the pivot will move when they rearrange the array, and refer to it is values[mid] or something similar. The traditional partition method moves the value to the beginning of the subarray so that we know where it is. For DNF, you might just store it in a value.
I sometimes see some off-by-one errors.

Problem 2

How long did this problem take you?

I forgot to record the time. I'll solve it again and let you know. (Yes, enough time has passed that I'll be doing it “from scratch” as it were.) Okay, the second time took fifteen minutes, including a bit of playing with options.

Can we assume that every call to next is immediately preceded by a call to hasNext?

No. Someone who “knows” the expected number of elements might just call next the appropriate number of times. Or someone might just keeping calling next until they get an exception.

Can we assume that next is called at least once between any two calls to hasNext?

No. A client might call hasNext, decide not to do anything immediately, and then call hasnext another time elsehwere in the code.

Will you write some unit tests for the remove method?

I'll try.

Problem 3

How long did this problem take you?

The basic implementation (using the underlying skip list remove method) took me under ten minutes.
Adding support for “fail fast” took less than five additional minutes. (“fail fast” is optional. I just wanted to see how hard it was to add.)
I have not yet tried implementing remove directly.

Can we use our own implementation of skip lists?

Yes. However, you do have to make it generic.

For the iterator's remove method, can we call the skip list remove method?

Yes, provided that you still keep the iterator at the right position in the list.

Do we need to worry about invalidating iterators when the list is mutated?

You are not required to invalidate iterators.

Soren and Lisa said “We'll leave the complexities of full list iterators to another day.”. But you write that “your primary goal is to create a subclass that adds an iterator to skip lists”. I'm confused. Don't the two statements contradict each other?

No. The full list iterator (well, ListIterator) interface includes methods to move in both directions and to set the current element. You don't need to do those additional operations, just the basic iterator (well, Iterator) methods: hasNext, next, and remove.

Since we have to iterate elements in order, do we need to keep track of the last value we returned and search for the next smallest value, as we did for one implementation of priority queues?

Such an implementation is unlikely to be O(1). You should think more about the underlying data structure that we're using.

Problem 4

How long did this problem take you?

It took me forty minutes. I was overconfident, and did not draw the pictoral loop invariants. Since I didn't draw those invariants, I had an “off by one” error that took some time for me to catch.

Why did you give us both SimpleList and DoublyLinkedList? Do we have to implement kthSmallest for both?

I want you to write code to an interface, rather than to an implementation. However, in order to experiment with your code, you needean implementation. Hence, I provide DoublyLinkedList as an implementation of SimpleList that you could use. (I also thought you'd want to see a full implementation of doubly-linked lists.) So, you only need to implement kthSmallest once, for SimpleList. The wonders of polymorphism guarantee that it will also work for DoublyLinkedList.

Is this zero-based or one-based? That is, is the smallest element what you get from kthSmallest(0) or kthSmallest(1).

The postconditions say “There are exactly k values smaller than val in the list.”. That implies that it's zero-based.

You say that you should have drawn loop invariants. Any hints on drawing those invariants?

The conceptual model of iterators suggests that they fall between elements. Hence, I'd make sure to draw a picture in which the iterators that mark the lower bound and upper bound are between elements. Presumably, the lower bound will be before the first element in the range of values left to process and the upper bound will be after the last element in that range.
Since the set method uses the last value returned by the iterator, and list iterators are bidirectional, I'd also find a way to mark those elements. (Such marking may not be useful for the invariant, but it will be useful for analyzing how the procedure is supposed to work.)
So, maybe something like the following. The slash and backslash indicate the element just returned.
... val - val - val ... val - val - val ... val - val - val ... val - val ...
        ^                  \^                   ^/                  ^
        |                   |                   |                   |
        lb                small               large                 ub

Miscellaneous

Errata

Here you will find errors of spelling, grammar, and design that students have noted. Remember, each error found corresponds to a point of extra credit for everyone. I usually limit such extra credit to five points. However, if I make an astoundingly large number of errors, then I will provide more extra credit.

I will not accept corrections for credit until after I have taken the examination out of draft mode.

I will not credit corrections for text in the Q&A and errata sections, since those are often written quickly to get information out to students. I may not credit corrections to grammatical mistakes in code.

  • Gremlins changed "1" to "-2" in the comparator for Point objects. [VB. 2 points]
  • Shouldn't use use” should be “Shouldn't they use”. [SZ, 1 point]
  • Sam neglected to remove the disclaimer at the start of the exam. [KN, 1 point].
  • Sam used “and” and “or” as verbs, which abuses the English language. (As in “to or two predicates”.) [GB, 1 point].
  • Sam spelled “list” as “ilst”. [EW, 0 point, spelling correction in code].

Copyright (c) 2013-14 Samuel A. Rebelsky.

Creative Commons License

This work is licensed under a Creative Commons Attribution 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by/3.0/ or send a letter to Creative Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA.