Copyright 1997 by Robert M. Keller, all rights reserved.
The following source files are referenced herein.2 general notions:
"open" lists -
invite sharing
"closed" lists -
don't
L1 [a,b,c,d]
L2 [c,d]
could share [c,d]
Abstractions:
first
rest
cons
isEmpty (called null in rex)
nil empty list
rest([A|L]) ==> L
cons(A,L) ==> [A | L]
isEmpty([]) ==> true
isEmpty([A|L]) ==> false
nil == []
list = reference to first cell of list
[ ] = null (reference)
This works best with static methods (functions).
class openList { // two components of a list: Object First; // the first element openList Rest; // the rest of the elements static openList nil = null; // null list openList(Object First, openList Rest) // constructor for class { this.First = First; this.Rest = Rest; } static openList cons(Object First, openList Rest) // create new list { return new openList(First, Rest); } // essential functions (static methods) static Object first(openList L) // return first element { return L.First; } static openList rest(openList L) // return all but first { return L.Rest; } static boolean isEmpty(openList L) // check if empty { return L == null; } static openListEnum elements(openList L) { return new openListEnum(L); } }
1. We use Object as the type of element, but any fixed type could be used.
Using Object allows nesting.
2. Calling first(nil) or rest(nil) will throw a nullPointerException.
static openList reverse(openList L) { openList result = nil; while( !isEmpty(L) ) { result = cons(first(L), result); L = rest(L); } return result; }
Write append using method 1.
Link to a source file containing openList definitions and append.
first(L)
rest(L)
isEmpty(L)
e.g. L.isEmpty()
can't apply method to null
We define a class Cell ;
List is a wrapper for a reference to the first Cell.
Cell is similar to openList without the static methods.
class Cell { Object First; Cell Rest; Cell(Object First, Cell Rest) { this.First = First; this.Rest = Rest; } } class openList { Cell firstCell; // reference to the first cell in the list static openList nil = new openList(null); openList(Cell firstCell) { this.firstCell = firstCell; } // essential functions static openList cons(Object First, openList Rest) // create new list { return new openList(new Cell(First, Rest)); } Object first() // return first element { return firstCell.First; } openList rest() // return all but first { return firstCell.Rest; } boolean isEmpty() // check if empty { return firstCell == null; }
etc.
}Link to a source file containing Method 2 definitions.
This is the method currently used in Poly.
can throw EmptyListException
Calls to them on a List must be within try...catch.
Calls to them on a NonEmptyList do not.
Catching a List to a NonEmptyList can generate a ClassCastException if the original was an EmptyList.
.elements()
returns an Enumeration which can be used to traverse the list without try...catch...
Doing nextElement() can throw a NoSuchElement exception.
static List reverse(List L) { List result = nil; for( Enumeration e = L.elements(); e.hasMoreElements(); ) { result=cons(e.nextElement(),result); } }
Defining an Enumeration class for open lists
Enumeration in Java is an "interface", sort of like an abstract class definition.
However, classes don't inherit from an interface; they implement the interface.
class openListEnum implements Enumeration { openList remainder; openListEnum(openList initial) { remainder = initial; } public Object nextElement() throws NoSuchElementException { if(remainder.isEmpty()) throw new NoSuchElementException(); Object result = remainder.first(); remainder = remainder.rest(); return result; } public boolean hasMoreElements() { return !remainder.isEmpty(); } }
class openList { : : Enumeration elements() { return new openListEnum(this); } }
Create an enumeration class for an array of Object.
class arrayEnum implements Enumeration { arrayEnum(Object a[]) // constructor { } Object nextElement() // methods required by interface def'n { } boolean hasMoreElements() { } }
Ideally pass static method as argument.
However this is not legal in Java;
Bummer!
Can pass Objects as arguments.
Simulate passing a function by passing an object with the function as a method.
This method is given a standard name such as
Object apply(Object ob)
abstract class Applicable { abstract Object apply(Object ob); }
static List map(Applicable fun, List L) { if(L.isEmpty()) { return nil; } return cons(fun.apply(L.first()), map(fun,L.rest())); }
Square each element of a list
First define function
class Square extends Applicable { Object apply(Object ob) { double value = ((Double)ob).doubleValue(); return new Double(value*value); } }Apply instance of function:
map(new Square(), someList) // returns List
Scale a list by a factor c
class Scaler extends Applicable { double c; Scaler(double c) { this.c = c; } Object apply(Object ob) { double value = ((Double)ob).doubleValue(); return new Double(c*value); } }Apply instance of function:
map(new Scaler(100.), someList) // returns List
Example:
push method treats list as a stack
class List { : : void push(Object ob) { firstCell = new Cell(ob, firstCell); } Object pop() { Object result = firstCell.First; firstCell = firstCell.Rest; return result; }
i.e. without creating new cells.
void reverseInPlace() { if(firstCell == null) return; Cell previous = null; while( firstCell.Rest != null) { Cell temp = firstCell; firstCell = temp.Rest; temp.Rest = previous; previous = temp; }
after temp=firstCell;
firstCell=temp.Rest;
temp.Rest=previous;
previous=temp;
Just like at top!
Similarly, a closed list can be
appended to destructively.
but defines
enqueue
dequeue
instead of push/pop:
push ~ dequeue
pop removes youngest item in stack
dequeue removes oldest item in queue
Implement class Queue, with methods:
void enqueue(Object ob)
Object dequeue()
boolean isEmpty()
Permit traversal in either direction
Cost: 1 extra reference per cell
Develop abstraction of bi-directional lists. Then implement the abstraction.
Closed Lists are best if
destructive modification is desired.
inserting new elements
deleting new elements
splicing in lists
We need some way to say where to do these things.
A nice abstraction is that of a cursor.
Cursor is seperate from list; refers to list.
Multiple cursors could be on one list.
List in the abstract
A B C ... Zcursor positions
insert inserts element where cursor is
remove removes value after cursor (say)
splice splices a whole list at the cursor
forward moves cursor forward
backward moves cursor backward
after returns value after
before returns value before
class Cursor { closeList myList; Cell before,after; Cursor(closedList myList { this.myList = myList; before = null; after = myList.firstCell; }
etc.