CS136, Lecture 7

  1. Pre & Post-conditions
  2. Vectors
Next assignment: RectGrid class:

Create two-dim'l grid of colored rectangles, be able to determine where clicked and change color of rectangles.

Pre & Post-conditions

Now back to Bailey's text (in red notebook)

You will have noticed that at the top of most of my methods, I have notations like:

/**
    pre: .....
    post: ....
**/

These comments set out a contract for the use of a particular method. For instance, see the following code:

/**
    pre:  0 <= index < this.length()
    post:  returns character at "index" position (starting count from 0) in this
**/
public char charAt(int index)
{...}
The contract expressed by the pre and post-conditions is that the implementer promised that the post-condition will be true after executing the method, as long as the user promises that the pre-condition will be true when it is called. Thus both the caller and the implementer have responsibilities under the contract.

For the above example, the user is required to specify an index which is legal for the string (between 0 and myString.length() - 1, inclusive), and if the user meets that commitment, the implementation promises to return the character in the "index" position of myString.

It is useful having these as comments, but often it is much more useful to have them checked at run-time, as if any fail, it is indication of an error in the program.

Moreover, the location of the failure is more likely to provide a pointer to the source of the error than just getting a wrong answer (or system crash).

Thus, there is a class in the provided "structure" library which contains methods to check these at run-time.

public class Assert {
    // pre: result of precondition test is true or false.
    // post: does nothing if test true, otherwise abort     
    //  w/message
    static public void pre(boolean test, String message)

    // pre: result of postcondition test is true or false.
    // post: does nothing if test true, otherwise abort 
    //  w/message
    static public void post(boolean test, String message)
 
    // pre: result of general condition test is true or false.
    // post: does nothing if test true, otherwise abort 
    //  w/message
    static public void condition(boolean test, String message)

    // post: throws error with message
    static public void fail(String message)
}

Thus if we were writing the code for charAt above, we could write:

public char charAt(int index)
{ Assert.pre(0 <= index && index < length(),
                                "Index out of bounds for string")
    ...
}
Sometimes pre- and post-conditions can't be expressed concisely or efficiently (e.g., how do you say "array is sorted"), so may not be able to put in code. Make sure that don't call the routine recursively to get in non-terminating computation in checking pre- or post-conditions. In these cases, use comments for pre- and post-conditions.

Eiffel is an OO language with built-in support for pre- and post-conditions. It also has compiler switches which can be turned on and off to determine whether or not pre- and post-conditions are checked (default is pre-conditions only checked).

I expect all methods to be decorated with pre and post-conditions in comments, and in code where it makes sense.

Vectors

Last time talked about the use of arrays.

Can create arrays of any fixed size, but can't change size aside from creating an entirely new array.

Built-in class Vector allows user to build something like an array, but can change size dynamically. Can add new elts or delete elts anywhere in vector.

Vector holds elements of type Object, hence must use casts when take out elements to do anything useful with them (unlike arrays!)

Below is listing of many of methods in implementation of Vector in structure library (which mimics the one in java.utilities). (See pp 223-227 of Arnold & Gosling and on-line structures library.)

public class Vector
{
    // post: constructs a vector with capacity for 10 elements
    public Vector()

    // pre: initialExtent >= 0
    // post: constructs an empty vector with initialExtent 
    //  capacity
    public Vector(int initialExtent)

    // pre: initialExtend >= 0, extentIncrement >= 0
    // post: constructs an empty vector with initialExtent 
    //  capacity that extends capacity by extentIncrement, or 
    //  doubles if 0
    public Vector(int initialExtent, int extentIncrement)

    // post: adds new element to end of possibly extended 
    //  vector
    public void addElement(Object obj)
 
    // post: returns true iff Vector contains the value
    //  (could be faster, if orderedVector is used)
    public boolean contains(Object elem)

    // pre: dest has at least size() elements
    // post: a copy of the vector is stored in the dest array
    public void copyInto(Object dest[])

    // pre: 0 <= index && index < size()
    // post: returns the element stored in location index
    public Object elementAt(int index)

    // pre: vector contains an element
    // post: returns first value in vector
    public Object firstElement()
 
    // post: returns index of element equal to object, or -1.  
    //  Starts at 0.
    public int indexOf(Object elem)

    // post: returns index of element equal to object, or -1.  
    //  Starts at index.
    public int indexOf(Object elem, int index)

    // pre: 0 <= index <= size()
    // post: inserts new value in vector with desired index
    //   moving elements from index to size()-1 to right
    public void insertElementAt(Object obj, int index)

    // post: returns true iff no elements in the vector
    public boolean isEmpty()
 
    // pre: vector is not empty
    // post: returns last element of the vector
    public Object lastElement()
  
    // post: returns index of last occurrence of object in the 
    //  vector, or -1
    public int lastIndexOf(Object obj)

    // pre: index >= 0
    // post: returns the index of last occurrence of object at  //  or before index.
    public int lastIndexOf(Object obj, int index)
  
    // post: vector is empty
    public void clear()

    // post: vector is empty
    public void removeAllElements()
 
    // post: remove first element of vector equal to parameter 
    //  Move later elts back to fill space.
    public boolean removeElement(Object element)

    // pre: 0 <= where && where < size()
    // post: indicated element is removed, size decreases by 1
    public void removeElementAt(int where)

    // pre: 0 <= index && index < size()
    // post: element value is changed to obj
    public void setElementAt(Object obj, int index)

    // post: returns the size of the vector
    public int size()
}
Vectors are generally used any time size of an array must change dynamically. One example is in paint programs where new objects added. See SimpleDraw.

How can we implement Vector?

Might try linked list or some sort or array.

Try array implementation. Vector has two fields, array and # elts of array currently in use.
Note distinction btn size of array and # elts in use!

When about to exceed capacity, copy elts into a larger array.
Need efficient strategy for this.

Following is simplification from (more complex) code in structures:

public class Vector 
{
    protected Object elementData[]; // the data
    protected int elementCount;     // # of elts in vec
    protected int capacityIncrement;    // the rate of growth for vector

    // pre: initialExtend >= 0, extentIncrement >= 0
    // post: construct empty vector with initialExtent capacity
    //  that extends capacity by extentIncrement, or doubles if 0
    public Vector(int initialExtent, int extentIncrement)
    {
        Assert.pre(initialExtent >= 0, "Non-negative vector extent.");
        elementData = new Object[initialExtent];
        elementCount = 0;
        capacityIncrement = extentIncrement;
    }
Accessing and modifying elts trivial, adding and deleting more tricky. Method ensureCapacity(int n) ensures there is space for at least n elts in array component of vector. (Will discuss in detail later.)

    // post: add new element to end of possibly extended vector
    public void addElement(Object obj)
    {
        ensureCapacity(elementCount+1);
        elementData[elementCount] = obj;
        elementCount++;
    }

    // pre: 0 <= index <= size()
    // post: inserts new value in vector with desired index
    //   moving elements from index to size()-1 to right
    public void insertElementAt(Object obj, int index)
    {
        int i;
        ensureCapacity(elementCount+1);
        // Must copy from right to left to avoid destroying data
        for (i = elementCount; i > index; i--)
            elementData[i] = elementData[i-1];
        // assertion: i == index and element[index] is available
        elementData[index] = obj;
        elementCount++;
    }
Remove similar (see code on line).

Adding or deleting an element may involve moving up to n elts, if n elts in array (slow!!).

What if run out of space in the array when adding new elts?

Options:

  1. Make new array with 1 (or some fixed # of) more elts and copy old array into new.

  2. Make new array with double (or triple, ...) # of elts and copy old into new.
First option bad, since if start w/ empty takes about n2/2 copies to add n elts to vector.

(0 + 1 + 2 + 3 + 4 + ... + n = n*(n-1)/2 )

Whereas no copies need to be made if allocated space for n elts at beginning.

With second option (where assume n is power of 2 for simplicity), copy

0 + 1 + 2 + 4 + 8 + ... + n/2 = n elts.

While this is overhead, extra copy of n elts, much less painful than n2/2.

Let user decide which strategy to use. If call Vector(int initialExtent) then set capacityIncrement to 0 and use doubling strategy, else always extend by capacityIncrement new elts:

    // post: capacity of this vector is at least minCapacity.
    public void ensureCapacity(int minCapacity)
    {
        if (elementData.length < minCapacity) 
        {           // have less than needed
            int newLength = elementData.length; // initial guess
            if (capacityIncrement == 0) {
                // increment of 0 suggests doubling (default)
                if (newLength == 0) newLength = 1;
                while (newLength < minCapacity) {
                    newLength *= 2;
                }
            } else 
            {
                // increment != 0 suggests incremental increase
                while (newLength < minCapacity)
                    newLength += capacityIncrement;
            }
            // assertion: newLength > elementData.length.
            Object newElementData[] = new Object[newLength];
            // copy old data to array
            for (int i = 0; i < elementCount; i++) 
                newElementData[i] = elementData[i];
            elementData = newElementData;
            // N.B. Garbage collector will pick up elementData
        }
        // assertion: capacity is at least minCapacity
    }