Homework 3: OOP, Interfaces and Inheritance

A. Introduction

This homework is intended to reinforce various OOP concepts. You won't have to write much code, and it's unlikely you'll have to do much debugging. However, the problems will be pretty tricky. Make efficient use of your time if you're stuck by working with other students or seeking help.

The online documentation for the abstract class java.io.Reader can be found here. It is used as a general template to define types of objects that have a read operation. The idea is that each time you read from a Reader, it gives you the next character (or characters) from some source; just what source depends on what subtype of Reader you have. A program that takes in a Reader as a parameter doesn't need to know the subtype of the Reader it is reading from; it simply reads from it.

As usual, you can obtain the skeleton with:

$ git fetch shared
$ git merge shared/hw3 -m "Start hw3"
$ git push

B. TrReader

Create a new TrReader class that extends the abstract Reader class and translates the characters from another Reader. That is, a TrReader's source of characters is some other Reader, which is passed into the TrReader's constructor. The translation of the characters given by the Reader object will be translated in the read routine of the TrReader class.

public class TrReader extends Reader {
    /** A new TrReader that produces the stream of characters produced
     *  by STR, converting all characters that occur in FROM to the
     *  corresponding characters in TO.  That is, change occurrences of
     *  FROM.charAt(0) to TO.charAt(0), etc., leaving other characters
     *  unchanged.  FROM and TO must have the same length. */
    public TrReader(Reader str, String from, String to) {
       // FILL IN
    }

    // FILL IN ANY MISSING ABSTRACT METHODS HERE
}

For example, we can define Reader r = new InputStreamReader(System.in); which causes r to point to a Reader whose source of characters is the standard input (i.e., by default, what you type on your terminal, although you can make it come from a file if desired). This means that the following code snippet will simply copy the standard input to the standard output:

while (true) {
   int c = r.read();
   if (c == -1) {
      break;
   }
   System.out.print((char) c);
}

Printing to standard output is just printing as you normally do. However, the following snippet of code will copy the standard input to standard output after first capitalizing all occurrences of the letters a--d. For example, if the input was "bad data structure", the correctly translated output should be "BAD DAtA struCture".

TrReader translation = new TrReader(r, "abcd", "ABCD");
while (true) {
   int c = translation.read();
   if (c == -1) {
      break;
   }
   System.out.print((char) c);
}

If we define a TrReader that does not do any perform any translation at all, then noTrans.read() has the same effect as someReader.read(). As a result, the contents of someReader are copied to standard output with no translation:

/** A TrReader that does no translation. */
TrReader noTrans = new TrReader(someReader, "", "");

while (true) {
   int c = noTrans.read();
   if (c == -1) {
      break;
   }
   System.out.print((char) c);
}

The program above will work even if you only implement the int read(char[], int, int) method of your reader. This is because the default implementation of int read() uses the int read(char[], int, int) method (as can be seen in the source code for the Reader class).

Notes

C. Applying TrReader

Implement the translate function in Translate.java using the TrReader class that you defined in part B. You may use any number of new operations, one other (non-recursive) method call, and nothing else. In addition to StringReader, you are free to use any library classes whose names end with Reader (check the online documentation), but no others. Feel free to create unit tests for your translate method to ensure correctness.

/** This method should return the String S, but with all characters that
 *  occur in FROM changed to the corresponding characters in TO.
 *  FROM and TO must have the same length.
 *  NOTE: You must use your TrReader to achieve this. */
static String translate(String S, String from, String to) {
    /* NOTE: The try {...} catch is a technicality to keep Java happy. */
    char[] buffer = new char[S.length()];
    try {
        throw new IOException(); //TODO: REPLACE THIS LINE WITH YOUR CODE.
    } catch (IOException e) {
        return null;
    }
}

D. WeirdList

Conceptual introduction here

Complete WeirdList.java and WeirdListClient.java. Do not use any if, switch, while, for, do, or try statements, and do not use the ?: conditional operator. The WeirdList class may contain only private fields (except for the public static final WeirdList EMPTY that is already provided in the skeleton code). The methods in WeirdListClient should not use recursion.

DO NOT FIGHT THE PROBLEM STATEMENT! We really do mean to impose all these restrictions in an effort to direct you towards a solution that illustrates object-oriented features. You are going to have to take some time to think, but the solutions themselves are quite short. The skeleton provides some cursory tests. You may refer to IntUnaryFunction.java, but do not modify this file.

Do not modify IntUnaryFunction.java:

/** An IntUnaryFunction represents a function from
 *  integers to integers. */
public interface IntUnaryFunction {
    /** Return the result of applying this function to X. */
    int apply (int x);
}

Here's the code for WeirdList.java:

/** A WeirdList holds a sequence of integers. */
public class WeirdList {
    /** The empty sequence of integers. */
    public static final WeirdList EMPTY =
        null;  // TODO: REPLACE THIS LINE

    /** A new WeirdList whose head is HEAD and tail is TAIL. */
  public WeirdList(int head, WeirdList tail) { /* TODO: FILL IN */ }

    /** Returns the number of elements in the sequence that
     *  starts with THIS. */
    public int length() {
        return 0;  // TODO: REPLACE THIS LINE
    }

    /** Return a string containing my contents as a sequence of numerals
     *  each preceded by a blank.  Thus, if my list contains
     *  5, 4, and 2, this returns " 5 4 2". 
     Note that the toString method of an object is called whenever code attempts to print said object.*/
    @Override
    public String toString() {
        return ""; // TODO: REPLACE THIS LINE
    }

    /** Part 3b: Apply FUNC.apply to every element of THIS WeirdList in
     *  sequence, and return a WeirdList of the resulting values. */
    public WeirdList map(IntUnaryFunction func) {
        return null;  // TODO: REPLACE THIS LINE
    }

    // FILL IN WITH *PRIVATE* FIELDS ONLY.
    // You should NOT need any more methods here.
}

Here's the code for WeirdListClient.java:

/** Functions to increment and sum the elements of a WeirdList. */
class WeirdListClient {
    /** Return the result of adding N to each element of L. */
    static WeirdList add(WeirdList L, int n) { /* FILL IN */ }

    /** Return the sum of all the elements in L. */
    static int sum(WeirdList L) { /* FILL IN */ }
}

HINT: Feel free to create additional classes to use. See IntUnaryFunction.java.

E. Submission

The files you will be turning are:

DO NOT FORGET to commit all the files you created as a part of your solution for section D.

Don't forget to push both your commits and tags for your final submission. As a reminder, you can push your tags by running:

$ git push --tags