CMPS 144 (Computer Science 2)
Dr. R. McCloskey
The (Positional) List ADT/Collection Class

The Concept
Applications
Implementation/Data Representation

The Concept

The list is a generalization of both stacks and queues. Stacks and queues are sequences from which we can remove items and into which we can insert items. But we are very restricted as to where these insertions and removals may occur. In a stack, they occur at the same end, called the top, which gives rise to a LIFO ("last-in-first-out") insertion/removal pattern. In queues, insertions occur at one end, the rear, and removals at the other end, the front, which gives rise to a FIFO ("first-in-first-out") insertion/removal pattern. In the most restrictive versions of stacks and queues, we are also limited as to which items are observable: in a stack, only the item at the top can be observed; in a queue, only the item at the front.

If we lift these restrictions so as to allow insertion and deletion anywhere, plus the capability of navigation (i.e., "moving around" among the items), we get the concept of a list. Different varieties of lists are obtained by making different choices as to exactly which operations are included to provide these capabilities. The kind of list we present here represents only one such choice, which we refer to as the Positional List with Cursors.

We can view a list as a sequence of nodes, in each of which is stored a data item. Each node has a predecessor and a successor, with the exception of the first and last nodes. If we consider the first node to be the successor of the last, we arrive at the concept of a circular list or ring, as opposed to a grounded list. In this document, we will focus upon grounded lists.

Here is one possible depiction of a (grounded) list of animals:

+---+    +---+    +---+    +---+    +---+    +---+    +---+
| C |    | D |    | B |    | A |    | O |    | Y |    | C |
| A |----| O |----| U |----| N |----| W |----| A |----| O |----★
| T |    | G |    | G |    | T |    | L |    | K |    | W |    ^
+---+    +---+    +---+    +---+    +---+    +---+    +---+    |
  ^                                                            |
  |                                                            |
  |                                                            |
front                                                         rear

In (most versions of) lists, observation and mutation may occur not only at the two ends but also in the middle. Thus, we need some mechanism for maneuvering (or navigating) through the structure and for viewing what's there. For this purpose, we introduce the concept of a cursor, which can be positioned at any of a list's nodes or at the special rear position that follows the list's last node. (Note that the front position corresponds to a list's first node, unless the list is empty (i.e., has no nodes), in which case the front and rear positions coincide.)

Most of the operations that we define on positional lists are done with respect to a cursor. For example, to access the contents of a node, we position a cursor at that node and then invoke the cursor's getItem() operation (which, as the name suggests, retrieves the data in the node at which the cursor is positioned). Similarly, to remove a node, we position a cursor there and invoke its remove() operation.

Hence, to express our specification for Positional List with Cursors in Java, we use two interfaces, one for cursors and one for positional lists.

Specification:

PositionalListWithCursors interface
public interface PositionalListWithCursors<T> {

   int lengthOf();                       // returns # nodes in the list
   PositionalListCursor<T> getCursor();  // returns a cursor into the list
}

PositionalListCursor interface
public interface PositionalListCursor<T> {

   /**  observers  **/

   boolean atFront();   // is cursor at front of list?
   boolean atRear();    // is cursor at rear of list?
   T getItem();         // returns (reference to) item at cursor's position

   // returns (a reference to) the list to which the cursor is associated
   PositionalListWithCursors<T> getList() 

   // Note: precondition of getItem() is !atRear()

   /**  navigation  **/

   // For the client's convenience, each navigation operation not only
   // positions the cursor but also returns it.

   PositionalListCursor<T> toFront(); // positions cursor at front of list
   PositionalListCursor<T> toRear();  // positions cursor at rear of list
   PositionalListCursor<T> toNext();  // moves cursor one place towards rear
   PositionalListCursor<T> toPrev();  // moves cursor one place towards front

   // positions cursor at same position as c
   PositionalListCursor<T> setTo(PositionalListCursor<T> c); 

   // Note : precondition of toNext() is !atRear()
   //        precondition of toPrev() is !atFront()
   
   void dispose(); // "kills" this cursor

   /**  list mutation  **/

   T remove();              // removes node at which cursor is positioned
   void replace(T newItem); // replaces item at cursor's position
   void insert(T newItem);  // inserts new node, containing the specified item,
                            //  as predecessor of cursor's position

   // Note : precondition of remove() and replace() is !atRear()
}

Arrays vs. Lists:

Some authors present a version of the list concept that is really a hybrid between the list and the array. But one should not confuse the two. Among the distinctions between lists and arrays is that the number of elements in an array is fixed, while in a list it is not. You can place a value into a specified location within an array, which has the effect of replacing the element that had occupied that location. But you can't literally insert a new element into an array, nor can you remove one. You can only replace. With lists, this restriction does not apply, which gives them an advantage.

On the other hand, in a list, retrieving (or replacing) the k-th element, for a specified value of k, is not —in contrast to arrays— a "primitive" operation in the sense that locating the specified element is, generally, not a trivial task.

Java's java.util.ArrayList class is a good example of a hybrid structure combining the traditional capabilities of arrays and lists. Suppose, for example, that aList is a reference to an ArrayList object. Then the call aList.get(4) retrieves the 4-th element of aList and the call aList.set(4,x) replaces the 4th element by the value of x. These operations are analogous to retrieving a value from, and replacing a value in, an array, respectively, where the element's location is specified via an index value. But with the ArrayList, one can also make the calls aList.add(4,y) and aList.remove(4), the former of which inserts the value of x into position 4 (and bumps the elements formerly at positions 4, 5, 6, etc., to positions 5, 6, 7, etc.) and the latter of which removes the value at position 4 (and bumps those at positions 5, 6, 7, etc., into positions 4, 5, 6, etc.).

A natural question to ask is why we should ever choose to use an array (or a list) when we could choose to use a more versatile structure, such as an ArrayList. The overarching, vague answer is: because you can't get something for nothing! Somewhat more precisely, there is a tradeoff between performance and versatility: given two interfaces A and B, where A includes all the operations provided by B, plus more, the likelihood is that an implementation of B will exhibit superior performance (in terms of running times and/or memory usage) to an (equally good) implementation of A. Regarding the ArrayList, as compared to the list, the ability to retrieve elements via index value imposes a performance penalty upon the task of inserting and removing them.


Applications of Lists

Having sketched a (rather large, compared to the ADT's we've already seen) collection of operations, let's do a few list "applications".

Problem #0: Develop a method that finds the length of (i.e., the number of nodes in) a list. (Ignore the fact that we stipulated the existence of a lengthOf() operation!)
Solution:

public int lengthOf(PositionalListWithCursors list)   {
 
   int cntr = 0;
   PositionalListCursor c = list.getCursor();
   c.toFront();

   // loop invariant: cntr = # of nodes preceding c's position
   while ( !c.atRear() )  {
      cntr = cntr + 1;
      c.toNext();
   }
   c.dispose();
   return cntr;
}

Problem #1: Develop a method that finds the sum of the values in a list of items from the wrapper class Integer.
Solution:

public static int sumOf(PositionalListWithCursors<Integer> list) {
 
   int sumSoFar = 0;
   PositionalListCursor<Integer> c = list.getCursor();
   c.toFront();

   // loop invariant: 
   //   sumSoFar == sum of list elements at nodes preceding c's position
   while ( !c.atRear() )  {
      sumSoFar = sumSoFar + c.getItem(); 
      c.toNext();
   }
   c.dispose();
   return sumSoFar;
} 

Here we see an algorithmic pattern that is quite common: traversing a list, examining each node, and, in doing so, performing some operation that helps in the accumulation of some result.

Trivial modifications to the method yield solutions to similar problems such as, say, counting the number of nodes containing positive values. Here, we would replace the assignment statement inside the loop with this if statement:

if (c.getItem() > 0)
   { cntr = cntr + 1; } 
where cntr is a local variable whose value would, after termination of the loop, be returned as the method's result.

Problem #2: For each node in a given list of items from the (wrapper) class Integer, increment its value if it is positive, decrement it if it is negative, and don't change it if it is zero.
Solution:

public static void incOrDec( PositionalListWithCursors<Integer> list ) {

   PositionalListCursor<Integer> c = list.getCursor();
   c.toFront();
   while ( !c.atRear() )
      int y = c.getItem();
      if (y < 0)  
         { c.replace(y-1);  }
      else if (y > 0) 
         { c.replace(y+1); }
      else  // y = 0
         {  }

      c.toNext();
   }
   c.dispose();
}

Problem #3: Remove from a list any node containing the same value (as determined by the equals() method) as its predecessor. That is, the final state of the list should be such that it contains the same sequence of values as originally, but with no consecutive duplicates.
Solution:

public static <T> void removeAdjacentDuplicates(PositionalListWithCursors<T> list)
{
   if (list.lengthOf() < 2)  
      {  }
   else  {
      PositionalListCursor<T> pred, crrnt;
      pred = list.getCursor().toFront();
      crrnt = list.getCursor().toFront().toNext();

      /* loop invariant: there are no adjacent duplicates in the
      *  portion of list preceding crrnt &&
      *  pred and crrnt are at adjacent positions, the latter being
      *  the successor of the former
      */
      while ( !crrnt.atRear() )   {
         if (pred.getItem().equals(crrnt.getItem()))
            { crrnt.remove(); }
         else { 
            pred.toNext();    // or, equivalently, pred.setTo(crrnt);
            crrnt.toNext();
         }
      }
      pred.dispose();  crrnt.dispose();
   }
}

Problem #4: Assuming that the items in list are in ascending order with respect to the Comparator comp, insert a new item into its rightful place.
Solution:

/* pre:  list is in ascending order (with respect to comp)
** post: list is still in ascending order, and is the same as before,
**       except that a new node containing obj appears in it
*/
public static <T> void orderedInsert(PositionalListWithCursors<T> list, 
                                     Comparator<T> comp,
                                     T obj)  { 

   PositionalListCursor<T> cur = list.getCursor();
   cur.toFront();
   /* loop invariant:
   ** All nodes preceding cur contain values less than obj
   */
   while (!cur.atRear()  &&  comp.compare(obj, cur.getItem()) > 0)
      { cur.toNext(); }

   // assertion: either
   // (1) cur.atRear() and obj is larger than every item in list, or
   // (2) !cur.atRear() and the node at which cur is positioned is the
   //      first one containing a value greater than or equal to obj
   cur.insert( obj );
   cur.dispose();
}

Problem #5: Sorting. Given an array of objects and a comparator comp that defines a total ordering upon them, construct an ascending-ordered list containing the objects in the array.
Solution:

public static <T> PositionalListWithCursors<T> sort(T[] a, Comparator<T> comp)
{ 
   PositionalListWithCursors<T> list = new PositionalListWithCursorsViaX<T>();  

   for (int i=0; i != a.length; i = i+1) {
      orderedInsert(list, comp, a[i]);    // from previous problem
   }
   return list;
}

Suppose that the objects in the array were in "random" order. What would be the expected asymptotic running time of this sorting algorithm, as a function of the length of the array? Well, on average we would expect each call to orderedInsert() to traverse half the list (that has been constructed so far) before finding the right place to insert the new node. The list grows by one in length each time a new item is inserted. Thus, we should expect that the running time would be proportional to (1/2)(0+1+2+ ... +(N-1)), which is about N2/4 and thus in O(N2).


Problem #6: Partitioning. Given a list of objects, a Comparator by which to compare them, and a "pivot" object, rearrange the items in the list so that, upon completion, all those less than the pivot precede those not less than the pivot. The returned value corresponds to the position at which the not-less-than-the pivot objects begin. Note that this solution requires that the equals() method on cursors yield true iff two cursors are at the same position (within the same list!) and thus does not necessarily correspond to ==.


Solution #1:

public static <T> void partition1(PositionalListWithCursors<T> list,
                                  Comparator<T> comp,
                                  T pivot)  { 

   int compareResult;
   PositionalListWithCursors<T> cLeft = list.getCursor();
   PositionalListWithCursors<T> cRight = list.getCursor();
   cLeft.toFront();
   cRight.toRear();
   
   /* loop invariant: The set of items in the list is fixed  &&
   **    all nodes preceding cLeft are less than the pivot  &&
   **    all nodes at or following cRight are not less than the pivot.
   */
   while (!cLeft.equals(cRight)) {
      compareResult = comp.compare(cLeft.getItem(), pivot); 
      if (compareResult >= 0) {
         cRight.insert(cLeft.getItem());
         cRight.toPrev();
         cLeft.remove();
      }
      else {
         cLeft.toNext();
      }
   }
   cRight.dispose();
   return cLeft;
}

We next illustrate the method above using an example in which the list contains Integer values, the pivot is 27, and the Comparator implements the standard ordering on numbers.

Before first loop iteration:
    7  15  2  35  42  10  50  29  8  *
    ^                                ^
    |                                |
    |                                |
 cLeft                            cRight
After three iterations, during each of which cLeft advances past a number that is less than 27 (the pivot):
    7  15  2  35  42  10  50  29  8  *
               ^                     ^
               |                     |
               |                     |
            cLeft                 cRight
After one more iteration, during which the node pointed to by cLeft (35) is removed but a node containing the same value is inserted in front of cRight, which is then positioned at that new node:
    7  15  2  42  10  50  29  8  35  *
               ^                  ^
               |                  |
               |                  |
            cLeft               cRight
After one more iteration, which takes similar actions as the previous one:
    7  15  2  10  50  29  8  42  35  *
               ^              ^
               |              |
               |              |
            cLeft           cRight
The next iteration advances cLeft past another value (10) smaller than the pivot:
    7  15  2  10  50  29  8  42  35  *
                   ^          ^
                   |          |
                   |          |
                cLeft       cRight
After another iteration that removes a node (50) but inserts a new one containing the same value:
    7  15  2  10  29  8  50  42  35  *
                   ^      ^
                   |      |
                   |      |
                cLeft   cRight
Yet another iteration that removes a node (29) and inserts a new one:
    7  15  2  10  8  29  50  42  35  *
                  ^   ^
                  |   |
                  |   |
               cLeft  cRight
The last iteration advances cLeft past a value (8) smaller than the pivot, at which point cLeft and cRight are at the same place, and so the loop terminates with both cursors positioned at the first list element that is greater than or equal to the pivot:
    7  15  2  10  8  29  50  42  35  *
                      ^
                      |
                      |
                   cRight
                   cLeft


Solution #2: (A bit better, in that it uses the replace() method rather than remove() or insert().)

public static <T> void partition2(PositionalListWithCursors<T> list,
                                  Comparator<T> comp,
                                  T pivot)  { 

   int compareResult;
   PositionalListWithCursors<T> cLeft = list.getCursor();
   PositionalListWithCursors<T> cRight = list.getCursor();
   cLeft.toFront();
   cRight.toRear();
   
   /* loop invariant: The set of nodes and items in list is fixed
   **    (although some pairs of nodes have had their items swapped) &&
   **    all nodes preceding cLeft are less than the pivot  &&
   **    all nodes at or following cRight are not less than the pivot.
   */
   while (!cLeft.equals(cRight)) {
      if (comp.compare(cLeft.getItem(), pivot) < 0) {
         cLeft.toNext();
      }
      else {
         cRight.toPrev();
         if (!cLeft.equals(cRight)) {
            if (comp.compare(cRight.getItem(), pivot) >= 0) {
               cRight.toPrev();
            }
            else {
               // swap data items at the two cursor positions
               T temp = cLeft.getItem(); 
               cLeft.replace(cRight.getItem());
               cRight.replace(temp);
            }
         }
      }
   }
   cRight.dispose();
   return cLeft;
}

Implementation/Data Representation

Click
here to read about an array-based representation.