Rice University - Comp 212 - Intermediate Programming

Spring 2002

Lecture #18 - Immutability vs. Mutability; "Pointers" Manipulations on LRStruct


Let us go back to LRStruct, our mutable linear recursive structure, and contrast it with the Scheme list.  The following visitors will  illustrate the differences.

1. Reversing a list

 

Scheme List

LRStruct

/**
* Reverses the host list by accumulating the reverse
* as one traverses the list.
* This is best done via an anonymous helper visitor. 
* The accumulated reverse list can be
* viewed as a "stack": just stack the first as one
* traverses down the list; by the time the end is
* reached, the accumulated stack is the reverse list.
* The end of a list is marked by the empty list.
*/
public class Reverse implements IListAlgo {
  private IListFactory _fact;
  public Reverse(IListFactory f) {
    _fact = f;
  }


/**
* Reverses the host using local anonymous inner class
* as a helper visitor.
*/
public class Reverse implements IAlgo {
  public static final Reverse Singleton = new Reverse ();

  private Reverse () {
  }

/**
* Returns the host since the reverse of the empty list
* is the empty list.
* @param host
* @param input not used
* @return AList
*/
public Object emptyCase(AList host, Object input) {
  return host;
}
/**
* Does nothing: the empty host is the reverse of itself.
* @return null
*/
public Object emptyCase(LRStruct host, Object input) {
  return null;
}
/**
* Passes to the host's rest the list consisting of the
* host's first as the accumulated reverse of the list
* preceding the host's rest, and asks for help to reverse
* the host.
* @param host
* @param input not used
* @return AList
*/
public Object nonEmptyCase(AList host, Object input) {
  AList accReverse = 
      _fact.makeNEList(host.getFirst(), _fact.makeEmptyList());
  return  host.getRest().execute(new IListAlgo() {
    /**
    * Returns the accumulated reverse because this is the end 
    * of the list.
    * @param h not used
    * @param acc the accumulated reverse of the list preceding h.
    * @return AList
    */
    public Object emptyCase(AList h, Object acc) {
      return acc;
    }

    /**
    * Accumulates the reverse by cons-ing h's first with the
    * accumulated reverse so far, and recur on the h's rest.
    * @param h the rest of the list to be reversed.
    * @param acc the accumulated reverse of the list preceding h.
    * @return AList
    */
    public Object nonEmptyCase(AList h, Object acc) {
      AList stack = _fact.makeNEList(h.getFirst(), (AList)acc);
      return h.getRest().execute (this, stack);
    }

  }, accReverse);
}
}

/**
* Asks for help to recursively move down the host, removes
* one element at a time and inserting it to the front of
* the host. In the end, the host is reversed.
* @return null
*/
public Object nonEmptyCase(final LRStruct host, Object inp) {
  return host.getRest().execute(new IAlgo (){
    /**
    * Does nothing because the end of the original host is reached.
    * From host to h is the reverse of the original list.
    * @param h the remaining tail of the list to be reversed.
    * @param in not used.
    * @return null.
    */
    public Object emptyCase(LRStruct h, Object in) {
      return null;
    }

    /**
    * Removes the current list's first and insert it to the front
    * of the original host and recurs.
    * @param h the remaining tail of the list to be reversed.
    * @param in not used.
    * @return null.
    */
    public Object nonEmptyCase(LRStruct h, Object in) {
      Object hFirst = h.removeFront(); // h has "advanced".
      host.insertFront(hFirst);
      return h.execute(this, null);
    }
  }, null);
}
}

While the Scheme list uses the factory to create a reverse copy, the LRStruct simply performs the appropriate removal and insertion operation to reverse itself!

 

2. Weaving two lists into one list.

Given two lists that do not share any node, write a visitor called Zipper that “zips” them together like a zipper into one list.  For example, when you zip (5, 3, 9) together with (-7, 0, 8, -15), the result is the list (5, -7, 3, 0, 9, 8, -15).  In the case of LRStruct, we want both LRStruct to become (5, -7, 3, 0, 9, 8, -15).  Note that the lists may have different lengths.

Given two distinct LRStruct x and y, we can "assign" y to x by performing the following sequence of operations:

x.insertFront (null);
x.setRest (y);
x.removeFront();

x's head is now pointing to y's head.  We can codify the above as the following visitor.

/**
* Assigns the input list to the host.
* host thus "becomes" the input list.
* In the end, both host and the input list share the same head node.
*/
public class Becomes implements IAlgo {
    public final static Becomes Singleton = new Becomes ();
    private Becomes() {
    }

    /**
     * @param input a LRStruct;
     * @return null
     */
    public Object emptyCase(LRStruct host, Object input) {
        return assign(host, (LRStruct)input);
    }

    /**
     * @param input a LRStruct;
     * @return null
     */
    public Object nonEmptyCase(LRStruct host, Object input) {
        return assign(host, (LRStruct)input);
    }

    final private Object assign (LRStruct lhs, LRStruct rhs) {
        lhs.insertFront (null);
        lhs.setRest (rhs);
        lhs.removeFront();
        return null;
    }
}

We shall use Becomes in the algorithm to "weave" two LRStruct together to obtain a single one.

Scheme List

LRStruct

/**
* Weaves ("Zips") together two distinct lists to yield a 
* single list.
* For example, when we zip (5, 3, 9), the host, together
* with (-7, 0, 8, -15), the input parameter, we get
* (5, -7, 3, 0, 9, 8, -15).
*/
public class Zipper implements IListAlgo {
    private IListFactory _fact;
    public Zipper(IListFactory f) {
        _fact = f;
    }
/**
* Weaves ("Zips") together two distinct LRStructs that do not
* share any node to yield a single LRStruct.
* For example, when we zip (5, 3, 9), the host, together
* with (-7, 0, 8, -15), the input parameter, both lists
* become (5, -7, 3, 0, 9, 8, -15).
*/

public class Zipper implements IAlgo {
    public static final Zipper Singleton = new Zipper();
    private Zipper() {
    }
    /**
     * Weaving an empty list with inp results in inp alone.
     * @param inp the other AList to be weaved with host.
     * @return AList
     */
    public Object emptyCase(AList host, Object inp) {
        return inp;
    }
    /**
     * host is empty, so there is nothing to zip with.
     * Just let host become the input list.
     * @param host empty
     * @param input the other LRStruct.
     */
    public Object emptyCase(LRStruct host, Object input) {
        return host.execute(Becomes.Singleton, input);
    }
    /**
     * Recursively weave inp with host's rest.  Then "cons"
     * host's first with the result.
     * @param inp the other AList to be weaved with host.
     * @return AList
     */
    public Object nonEmptyCase(AList host, Object inp) {
        Object zipRest = ((AList)inp).execute(this, host.getRest());
        return _fact.makeNEList(host.getFirst(), (AList)zipRest);
    }
}
    /**
     * Recursively weave the input LRStruct with host's rest.
     * The result is both input and host'rest share the same
     * head node.  All we need to do next is assign host to
     * input so that input "becomes" the host and share the same
     * head node as host.
     * @param host not empty
     * @param input the other LRStruct.
     */
    public Object nonEmptyCase(LRStruct host, Object input) {
        ((LRStruct)input).execute(this, host.getRest());
        return ((LRStruct)input).execute(Becomes.Singleton, host);
    }
}

 

Algorithms on traditional "non object-oriented" formulations and implementations of the list structure often require extensive "pointer" manipulations.  These pointer manipulations are causes for many bugs and, though  similar in many cases, are not re-usable.  A typical no OO implementation looks something like the following.

public class List {
  private Object _first;
  private List _next; // may be null!
 // a bunch of methods to perform operations on the List.
}

The OO frameworks for our two list structures get all such pointer manipulations done in a few primitive structural methods and hide them away from all visitors.

As illustrated in the above examples, for LRStruct, insertFront(), removeFront(), setRest() are primitive that take care of linking and re-linking different nodes of an LRStruct in the most trivial way.  Yet they are sufficient to support the complicated pointer manipulations in reversing and weaving lists.

You are strongly encouraged to try to write the code to reverse and weave objects of the above class List in order to appreciate the differences.

3. Unweaving a list into two lists.

In class exercise: Given a list, unzip it to obtain two lists.  One list contains all the even indexed elements of the original list; the other list contains all the odd-indexed elements of the original list.  For LRStruct, we want the host to becomes the list that contains only the even-indexed elements, and the return Object of the visitor's methods to be the list holding the odd-indexed elements of the original host list.

 

 


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