Rice University - Comp 212 - Intermediate Programming

Spring 2002

Lecture #13 - More on Linear Recursive Structure Framework


0. Recall

 

1. Code Illustration

Code

Illustration

class EmptyNode extends ANode {
/**
* The owner becomes non-empty and 
* has dat as its first element.
*/
LRStruct insertFront(Object dat, LRStruct owner) {
    return owner.setHead(new NENode(dat, new LRStruct(this)));
}

class NENode extends ANode {
    private Object _dat;
    private LRStruct _tail;

/**
* Initializes this NENode to contain dat and a given tail list.
* @param dat the data object to be stored in this NENode.
* @param tail the LRStruct tail of this NENode.
*/
NENode(Object dat, LRStruct tail) {
    _dat = dat;
    _tail = tail;
}

/**
* Inserts a data object at the front of the LRStruct owner.
* @param dat the object to be inserted at the front.
* @param owner the LRS referencing this NENode.
*/
LRStruct insertFront(Object dat, LRStruct owner) {
    return owner.setHead(new NENode(dat, new LRStruct(this)));
    /* Details:
    LRStruct coOwner = new LRStruct (this);
    NENode newNode = new NENode (dat, coOwner);
    return owner.setHead (newNode);
    "old" style: owner._head = newNode - cannot be done here.
    */
}


}

 

Comment 1: Proper initialization of an object is crucial in program correctness.  The package-private constructor LRStruct( ANode n) { _head = n;} serves to initialize an LRStruct to a particular state.  It  is not absolutely necessary to define such a constructor.  

The call
new LRStruct(n);
can be replaced by a call to LRStruct() followed by a call to setHead(ANode n):
new LRStruct().setHead(n);

However, it is a good programming practice to have an object completely initialized when it is defined, and have all initialization code done in one place.  Such a practice eliminates the prospect of  "forgetting" to initialize and/or improperly initializing an object.

 

 

Comment 2: We intentionally make the code for insertFront(...) for both EmptyNode and NENode look identical.  This begs the question, "why don't we just factor it up to the abstract ANode class, making its insertFront(...) method concrete, and thus eliminating code duplication in the concrete subclass?".

Doing so would make the abstract class ANode "aware" of a specific concrete variant, NENode, and prevent us from hiding (and perhaps changing) the details of implementation from external client code, and thus violating the principle of information hiding.

As a general OO programming principle, never make a super class aware of any of its subclasses.  Doing so is tantamount to saying that the super class is not an abstraction of the subclasses.

 

class NENode extends ANode {

Object removeFront(LRStruct owner) {
    owner.setHead(_tail.getHead()); // owner._head = _tail._head
    return _dat;
}

// other methods elided...
}


 

 

2. LRStruct Visitor

Let us contrast the insert in order algorithms between AList, the immutable list,  and LRStruct, the mutable list.

InsertInOrderWithFactory.java 
import schemeFactory.*;

public class InsertInOrderWithFactory implements IListAlgo {

private IListFactory _listFact;

public InsertInOrderWithFactory(IListFactory lf) {
    _listFact = lf;
}

/**
* Simply makes a new non-empty list with the given 
* inp parameter as first.
* @param host an empty AList.
* @param inp an Integer to be inserted in order into host.
*/
public Object emptyCase(AList host, Object inp) {
    return _listFact.makeNEList(inp, host);
}

/**
* Based on the comparison between first and inp,
* creates a new list or recur!
* @param host a non-empty AList.
* @param inp an Integer to be inserted in order into host.
*/
public Object nonEmptyCase(AList host, Object inp) {
    int n = ((Integer)inp).intValue();
    int f = ((Integer)host.getFirst()).intValue();
    return n < f ?
           _listFact.makeNEList(inp, host):
           _listFact.makeNEList(host.getFirst(),
                         (AList)host.getRest().execute(this, inp));
}
}
InsertInOrderLRS.java 
import lrs.*;

public class InsertInOrderLRS implements IAlgo {

public static final InsertInOrderLRS Singleton 
                                    = new InsertInOrderLRS();

private InsertInOrderLRS() {
}

/**
* Simply inserts the given inp parameter at the front.
* @param host an empty LRStruct.
* @param inp an Integer to be inserted in order into host.
*/
public Object emptyCase(LRStruct host, Object inp) {
    return host.insertFront(inp);
}

/**
* Based on the comparison between first and inp,
* inserts at the front or recurs!
* @param host a non-empty LRStruct.
* @param inp an Integer to be inserted in order into host.
*/
public Object nonEmptyCase(LRStruct host, Object inp) {
    int n = ((Integer)inp).intValue();
    int f = ((Integer)host.getFirst()).intValue();
    if (n < f) {
        return host.insertFront(inp);
    }
    else {
        return host.getRest().execute(this, inp));
    }
}
}
Note that the insert in order algorithm for LRStruct need not creates any new list and thus needs no factory.

 

In class exercise: 

Write an algorithm for LRStruct to remove the last element.  Throw an exception when the list is empty.


dxnguyen@cs.rice.edu
Copyright 2002, Dung X. Nguyen - All rights reserved.