Lists with cursors

One deficiency of the Lists module is that it provides no simple and efficient way for the application program to ``keep its place'' within a list -- for example, to traverse part of the list, then pause for computation, then pick up where it left off. One could store the position number of the last element seen as an Integer and use RecoverByPositionFromList to get each element as it is needed, but this is inefficient because each call to RecoverByPositionFromList starts over at the beginning of the list and counts elements until it reaches the correct one. One would like to be able to simply advance from each element to the next. Alternatively, one could use the FirstOfList and DeleteFirstOfList functions to take off one element at a time for processing; unfortunately, this approach destroys the list as a side effect and so requires the programmer to make a copy of the list first.

The solution to the problem is to add a ``cursor,'' or position marker, to the internal structure of the list and to extend the list interface with procedures and functions for manipulating the cursor. Typically, the cursor has a distinct value for each position in the list, from the first to the last, and in addition a ``null cursor'' value that can act as a sentinel value in loops; advancing the cursor beyond the end of the list makes it a null cursor.

Cursors are used in application programs in many of the ways pointers are used for traversal in standard Pascal; but since they can only be operated on with the procedures and functions exported from the module, application programmers are less likely to make errors with them.

The procedures and functions that operate primarily on the cursor of a list can be specified as follows:

cursor-to-start
Input: operand, a list.
Outputs: none.
Preconditions: none.
Postconditions: If the length of operand is 0, its cursor is the null cursor; otherwise, its cursor is marking its first position.

advance-cursor
Input: operand, a list.
Outputs: none.
Precondition: The cursor of operand is not null.
Postconditions: If the cursor of operand at input marks the last position in operand, then it is a null cursor at output. Otherwise, the cursor of operand at output marks the position following the position that it marked at input.

null-cursor
Input: operand, a list.
Output: result, a Boolean.
Preconditions: none.
Postconditions: result is true if the cursor of operand is null and false if it is not.

element-at-cursor
Input: operand, a list.
Output: result, a value of the element type of operand.
Precondition: The cursor of operand is not null.
Postcondition: result is the element at position marked by the cursor of operand.

assign-at-cursor
Inputs: ls, a list, and new-value, a value of the list's element type..
Outputs: none.
Precondition: The cursor of operand is not null.
Postcondition: The length of ls at output is the length of ls at input. At output, the element at the position marked by the cursor of ls is new-value, and the element at any other position is the same as the element at that position in ls at input.

insert-at-cursor
Inputs: ls, a list, and elm, a value of the list's element type.
Outputs: none.
Preconditions: none.
Postconditions: The length of ls at output is one more than the length of ls at input. At output, elm is the last element of ls if the cursor of ls was null at input; otherwise, it is the element at the position p that was marked by the cursor of ls at input. For every natural number k from one to the length of ls at input, the kth element of ls at input is the kth element of ls at output if the cursor of ls was null at input or if k is less than p, and the (k + 1)st element of ls at output if the cursor of ls was not null at input and k is greater than or equal to p. The cursor of ls is null at output if it was null at input; otherwise, it marks position p + 1.

delete-at-cursor
Inputs: ls, a list.
Outputs: none.
Precondition: The cursor of ls is not null.
Postconditions: The length of ls at output is one less than the length of ls at input. For every natural number k from one to the length of ls at output, the kth element of ls at output is the kth element of ls at input if k is less than the position p marked by the cursor of ls at input and the (k + 1)st element of ls at input if k is greater than or equal to p. The cursor of ls is null at output if p is the last position of ls at input; otherwise, it marks position p at output.

find-next
Inputs: sought and operand, where operand is a list and sought is a value of its element type.
Outputs: none.
Preconditions: none.
Postconditions: Let p be 0 if the cursor of operand was null at input; otherwise, let p be the position it marked. Either the cursor of operand at output is null and sought is not in any position greater than p, or sought is the element at the position q marked by the cursor of operand at output, q is greater than p, and sought is not in any position greater than p and less than q.

find-next-by-test
Inputs: operand, a list, and test, an operation that takes one input, a value of the element type of operand, and yields one output, a Boolean.
Outputs: none.
Preconditions: none.
Postconditions: Let p be 0 if the cursor of operand was null at input; otherwise, let p be the position it marked. Either (1) the cursor of operand at output is null and applying test to the element in any position greater than p yields false, or (2) applying test to the element at the position q marked by the cursor of operand at output yields true, q is greater than p, and applying test to the element in any of the positions greater than p and less than q yields false.

Also, now that lists have cursors, we need to run through the specifications for the previously defined list operations and how cursors are affected in each one. The key here is to make the rules as simple and general as possible, so that the application programmer does not have a hard time keeping track of the cursor's position when performing a series of operations in which it is not used. It turns out that three rules cover all the cases:

  1. Every newly constructed list has a null cursor.
  2. Modifying the structure of a list -- adding, removing, or rearranging elements -- nullifies its cursor (unless the modification is done through one of the cursor-oriented operations listed above).
  3. Any other operation leaves the position of the cursor unchanged.

Adding cursors to the Lists module

In the implementation, of course, a list's cursor is simply an extra pointer field in ListRecord, set to Nil when the cursor is null and otherwise pointing to the appropriate component of the linked list.

Two of the new operations, insert-at-cursor and delete-at-cursor, have difficult special cases. The difficulty is the same both times: When inserting or deleting a component, the most natural way to proceed entails modifying the pointer in the immediately preceding component of the linked list; but there's no easy way to access this component given the cursor -- the pointers point in the wrong direction. If the insertion or deletion is not occurring at the very end of the list, we can use the clever dodge of copying data from one component into another while leaving the pointer structure unchanged; but this will not work at the end of the list, since the pointer in the preceding component must be changed from Nil to non-Nil (in an insertion) or from non-Nil to Nil (in a deletion).

There are a couple of ways of handling this problem, both somewhat unsatisfactory. It would be possible to add yet another pointer field to ListRecord, for a pointer to the component immediately preceding the one the cursor points to; this makes insertion and deletion efficient even at the end of the list, but it slows down all the other operations that affect the cursor. Alternatively, we could just put up with the inefficiency of starting at the beginning of the list and traversing down to the end whenever we had to perform one of the difficult cases of insert-at-cursor and delete-at-cursor. This is the approach adopted below.

Here is the HP Pascal implementation:

{ This module defines an interface for a list data type that includes a
  cursor in each list and implements it for HP 9000 Series 700 workstations
  under HP-UX 9.x, using HP Pascal.

  Programmer: John Stone, Grinnell College.
  Original version: August 17, 1996.
}

$heap_dispose on$

module ListsWithCursors;

$search 'elements.o'$
import Elements;

export

  type
    List = ^ListRecord;         { an opaque type }

  { The MakeEmptyList function creates and returns a newly allocated,
    empty list. }

  function MakeEmptyList: List;

  { The PrependToList procedure takes a list and prepends an element to
    it. }

  procedure PrependToList (Prefix: Element; var Base: List);

  { The FirstOfList function returns the first element of a non-empty
    list. }

  function FirstOfList (Operand: List): Element;

  { The DeleteFirstOfList function takes a non-empty list and removes its
    first element. }

  procedure DeleteFirstOfList (var Operand: List);

  { The EmptyList function determines whether a given list is empty. }

  function EmptyList (Operand: List): Boolean;

  { The LengthOfList function returns the length of a given list. }

  function LengthOfList (Operand: List): Integer;

  { The AppendToList procedure takes a list and appends an element to it. }

  procedure AppendToList (var Base: List; Postfix: Element);

  { The LastOfList function returns the last element of a non-empty list. }

  function LastOfList (Operand: List): Element;

  { The DeleteLastOfList procedure takes a non-empty list and removes its
    last element. }

  procedure DeleteLastOfList (var Operand: List);

  { The CopyList function constructs and returns a copy of a given list,
    in newly allocated storage. }

  function CopyList (Operand: List): List;

  { The CursorToStartOfList procedure adjusts the list's cursor so that it
    marks the first position (or is null, if the list is empty). }

  procedure CursorToStartOfList (var Operand: List);

  { The AdvanceCursorAlongList procedure moves the list's cursor, assumed
    not to be null, to the next position on the list (or makes it null, if
    there are no more positions. }

  procedure AdvanceCursorAlongList (var Operand: List);

  { The NullCursorInList function determines whether a given list's cursor
    is null. }

  function NullCursorInList (Operand: List): Boolean;

  { The ConcatenateList procedure takes two lists and adds the elements
    of the second operand at the end of the first operand. }

  procedure ConcatenateList (var LeftOperand: List; RightOperand: List);

  { The RecoverByPositionFromList function returns the value occupying
    a specified position in a list. }

  function RecoverByPositionFromList (Position: Integer; Ls: List):
    Element;

  { The ElementAtCursorInList function returns the element occupying the
    position marked by the cursor in a given list. }

  function ElementAtCursorInList (Operand: List): Element;

  { The AssignAtPositionInList procedure replaces the value occupying a
    specified position in a list with a new value. }

  procedure AssignAtPositionInList (Position: Integer; var Ls: List;
    NewValue: Element);

  { The AssignAtCursorInList procedure replaces the element occupying the
    position marked by the cursor in a given list with a new value. }

  procedure AssignAtCursorInList (var Ls: List; NewValue: Element);

  { The ElementOfList function determines whether a given value is an
    element of a given list. }

  function ElementOfList (Candidate: Element; Ls: List): Boolean;

  { The LocateInList procedure determines whether a given value is an
    element of a given list and, if so, returns the least position at which
    it occurs. } 

  procedure LocateInList (Sought: Element; Ls: List; var Found: Boolean;
    var Position: Integer); 

  { The FindNextInList procedure advances a given list's cursor to the next
    position occupied by a specified value, leaving it null if there is no
    such position. }

  procedure FindNextInList (Sought: Element; var Ls: List);

  { The Sublist function takes a list and prunes off all of it but a
    section bounded by the positions indicated by Start and Finish. } 

  procedure Sublist (var Ls: List; Start, Finish: Integer);

  { The ReverseList procedure reverses the order of the elements in a given
    list. }

  procedure ReverseList (var Operand: List);

  { The InsertAtPositionInList procedure inserts an element at a specified
    position in a list. }

  procedure InsertAtPositionInList (var Ls: List; Position: Integer;
    Elm: Element);

  { The InsertAtCursorInList procedure inserts a new element into a list
    at the position marked by the list's cursor, or at the end if the
    list's cursor is null. }

  procedure InsertAtCursorInList (var Ls: List; Elm: Element);

  { The DeleteAtPositionInList procedure removes the element at a specified
    position in a list. }

  procedure DeleteAtPositionInList (var Ls: List; Position: Integer);

  { The DeleteAtCursorInList procedure deletes and discards the element
    at the position in a given list that is marked by the list's cursor. }

  procedure DeleteAtCursorInList (var Ls: List);

  { The DeleteValueFromList procedure strips all occurrences of a specified
    value out of a list. }

  procedure DeleteValueFromList (var Ls: List; Delend: Element);

  { The ReplaceInList procedure replaces every occurrence of a specified
    value in a list with a new value. }

  procedure ReplaceInList (var Ls: List; Displacer, Displaced: Element);

  { The FillList function constructs and returns a list consisting
    of a specified number of copies of a given element. }

  function FillList (Length: Integer; Filler: Element): List;

  { The GenerateList function constructs a list of a specified
    length by applying a given function to the positive integers in
    ascending order until that length is reached. }

  function GenerateList (function Generator (N: Integer): Element;
    Length: Integer): List;

  { The TransformList procedure applies a transformation to every element
    of a list. }

  procedure TransformList (var Ls: List;
    function Transformer (E: Element): Element);

  { The ApplyAlongList procedure applies a given procedure to each
    successive element of a given list. }

  procedure ApplyAlongList (Ls: List; procedure Applicand (E: Element));

  { The EveryElementOfList function determines whether every element
    of a given list satisfies a given predicate. }

  function EveryElementOfList (Ls: List;
    function Test (E: Element): Boolean): Boolean;

  { The SomeElementOfList function determines whether at least one
    element of a given list satisfies a given predicate. }

  function SomeElementOfList (Ls: List;
    function Test (E: Element): Boolean): Boolean;

  { The RecoverByTestFromList procedure determines whether any of the
    elements of a given list satisfies a given predicate and, if so,
    returns the one whose position is the least. }

  procedure RecoverByTestFromList (Ls: List;
    function Test (E: Element): Boolean; var Found: Boolean;
    var Sought: Element);

  { The LocateByTestInList procedure determines whether any of the
    elements of a given list satisfies a given predicate and, if so,
    returns the least position occupied by such an element. }

  procedure LocateByTestInList (Ls: List;
    function Test (E: Element): Boolean; var Found: Boolean;
    var Position: Integer);

  { The FindNextByTestInList procedure advances a given list's cursor to
    the next position occupied by an element that satisfies a given
    predicate, leaving it null if there is no such position. }

  procedure FindNextByTestInList (var Operand: List;
    function Test (E: Element): Boolean);

  { The FilterList procedure strips out of a given list any elements that
    do not satisfy a given predicate. }

  procedure FilterList (var Ls: List; function Test (E: Element): Boolean);

  { The DeallocateList procedure recycles the storage associated with
    a given list. }

  procedure DeallocateList (var Operand: List);

implement

import StdErr;

  const
    FirstExceptionCode = 1;

    UninitializedListException = 1;
    FirstOfListException = 2;
    DeleteFirstOfListException = 3;
    LastOfListException = 4;
    DeleteLastOfListException = 5;
    NullCursorException = 6;
    RecoverByPositionFromListException = 7;
    AssignAtPositionInListException = 8;
    SublistException = 9;
    InsertAtPositionInListException = 10;
    DeleteAtPositionInListException = 11;
    FillListException = 12;
    GenerateListException = 13;
    ExceptionException = 14;

    LastExceptionCode = 14;

  type
    Link = ^ListComponent;
    ListComponent = record
                      Datum: Element;
                      Next: Link
                    end;
    ListRecord = record
                   Size: Integer;
                   Head: Link;
                   Cursor: Link
                 end;

  procedure ListExceptionHandler (ExceptionCode: Integer);
  begin
    if (ExceptionCode < FirstExceptionCode) or
                        (LastExceptionCode < ExceptionCode) then
      ExceptionCode := ExceptionException;
    WriteLn (StdErr, 'Exception #', ExceptionCode : 1,
             ' in module Lists:');
    case ExceptionCode of
    UninitializedListException:
      WriteLn (Stderr, 'An operation was applied to an uninitialized ',
               'list. ');
    FirstOfListException:
      WriteLn (StdErr, 'The empty list was passed to the ',
               'FirstOfList function.');
    DeleteFirstOfListException:
      WriteLn (StdErr, 'The empty list was passed to the ',
               'DeleteFirstOfList function.');
    LastOfListException:
      WriteLn (StdErr, 'The empty list was passed to the ',
               'LastOfList function.');
    DeleteLastOfListException:
      WriteLn (StdErr, 'The empty list was passed to the ',
               'DeleteLastOfList function.');
    NullCursorException:
      WriteLn (StdErr, 'A list with a null cursor was passed to a ',
               'procedure or function that required a non-null cursor.');
    RecoverByPositionFromListException:
      WriteLn (StdErr, 'An invalid index was passed to the ',
               'RecoverByPositionFromList function.');
    AssignAtPositionInListException:
      WriteLn (StdErr, 'An invalid index was passed to the ',
               'AssignAtPositionFromList function.');
    SublistException:
      WriteLn (StdErr, 'An invalid index was passed to the Sublist ',
               'function.');
    InsertAtPositionInListException:
      WriteLn (StdErr, 'An invalid index was passed to the ',
               'InsertAtPositionInList function.');
    DeleteAtPositionInListException:
      WriteLn (StdErr, 'An invalid index was passed to the ',
               'DeleteAtPositionInList function.');
    FillListException:
      WriteLn (StdErr, 'A negative Length argument was passed to the ',
               'FillList function.');
    GenerateListException:
      WriteLn (StdErr, 'A negative Length argument was passed to the ',
               'GenerateList function.');
    ExceptionException:
      WriteLn (StdErr, 'The ListExceptionHandler procedure received ',
               'an unknown exception code.')
    end
  end;

  function CopyHelper (Hook: Link): Link;
  var
    OneMore: Link;
  begin
    if Hook = Nil then
      CopyHelper := Nil
    else begin
      New (OneMore);
      OneMore^.Datum := Hook^.Datum;
      OneMore^.Next := CopyHelper (Hook^.Next);
      CopyHelper := OneMore
    end
  end;

  procedure DisposeRest (var Rest: Link);
  begin
    if Rest <> Nil then begin
      DisposeRest (Rest^.Next);
      Dispose (Rest)
    end
  end;

  function MakeEmptyList: List;
  var
    Result: List;
  begin
    New (Result);
    Result^.Size := 0;
    Result^.Head := Nil;
    Result^.Cursor := Nil;
    MakeEmptyList := Result
  end;
  
  procedure PrependToList (Prefix: Element; var Base: List);
  var
    OneMore: Link;
  begin
    Assert (Base <> Nil, UninitializedListException,
            ListExceptionHandler);
    Base^.Size := Base^.Size + 1;
    New (OneMore);
    OneMore^.Datum := Prefix;
    OneMore^.Next := Base^.Head;
    Base^.Head := OneMore;
    Base^.Cursor := Nil
  end;

  function FirstOfList (Operand: List): Element;
  begin
    Assert (Operand <> Nil, UninitializedListException,
            ListExceptionHandler);
    Assert (Operand^.Head <> Nil, FirstOfListException,
            ListExceptionHandler);
    FirstOfList := Operand^.Head^.Datum
  end;

  procedure DeleteFirstOfList (var Operand: List);
  var
    OneLess: Link;
  begin
    Assert (Operand <> Nil, UninitializedListException,
            ListExceptionHandler);
    Assert (Operand^.Head <> Nil, DeleteFirstOfListException,
            ListExceptionHandler);
    Operand^.Size := Operand^.Size - 1;
    OneLess := Operand^.Head;
    Operand^.Head := OneLess^.Next;
    Dispose (OneLess);
    Operand^.Cursor := Nil
  end;

  function EmptyList (Operand: List): Boolean;
  begin
    Assert (Operand <> Nil, UninitializedListException,
            ListExceptionHandler);
    EmptyList := (Operand^.Head = Nil)
  end;

  function LengthOfList (Operand: List): Integer;
  begin
    Assert (Operand <> Nil, UninitializedListException,
            ListExceptionHandler);
    LengthOfList := Operand^.Size
  end;

  procedure AppendToList (var Base: List; Postfix: Element);
  var
    OneMore: Link;

    procedure AttachAtEnd (var Hook: Link);
    begin
      if Hook = Nil then
        Hook := OneMore
      else
        AttachAtEnd (Hook^.Next)
    end;

  begin { procedure AppendToList }
    Assert (Base <> Nil, UninitializedListException, ListExceptionHandler);
    Base^.Size := Base^.Size + 1;
    New (OneMore);
    OneMore^.Datum := Postfix;
    OneMore^.Next := Nil;
    AttachAtEnd (Base^.Head);
    Base^.Cursor := Nil
  end;

  function LastOfList (Operand: List): Element;

    function LastHelper (Hook: Link): Element;
    begin
      if Hook^.Next = Nil then
        LastHelper := Hook^.Datum
      else
        LastHelper := LastHelper (Hook^.Next)
    end;

  begin { function LastOfList }
    Assert (Operand <> Nil, UninitializedListException,
            ListExceptionHandler);
    Assert (Operand^.Head <> Nil, LastOfListException,
            ListExceptionHandler);
    LastOfList := LastHelper (Operand^.Head)
  end;

  procedure DeleteLastOfList (var Operand: List);

    procedure DeleteHelper (var Hook: Link);
    var
      OneLess: Link;
    begin
      if Hook^.Next = Nil then begin
        OneLess := Hook;
        Hook := Nil;
        Dispose (OneLess)
      end
      else
        DeleteHelper (Hook^.Next)
    end;

  begin { procedure DeleteLastOfList }
    Assert (Operand <> Nil, UninitializedListException,
            ListExceptionHandler);
    Assert (Operand^.Head <> Nil, DeleteLastOfListException,
            ListExceptionHandler);
    Operand^.Size := Operand^.Size - 1;
    DeleteHelper (Operand^.Head);
    Operand^.Cursor := Nil
  end;

  function CopyList (Operand: List): List;
  var
    Result: List;
  begin { function CopyList }
    Assert (Operand <> Nil, UninitializedListException,
            ListExceptionHandler);
    New (Result);
    Result^.Size := Operand^.Size;
    Result^.Head := CopyHelper (Operand^.Head);
    Result^.Cursor := Nil;
    CopyList := Result
  end;

  procedure CursorToStartOfList (var Operand: List);
  begin
    Assert (Operand <> Nil, UninitializedListException,
            ListExceptionHandler);
    Operand^.Cursor := Operand^.Head
  end;

  procedure AdvanceCursorAlongList (var Operand: List);
  begin
    Assert (Operand <> Nil, UninitializedListException,
            ListExceptionHandler);
    Assert (Operand^.Cursor <> Nil, NullCursorException,
            ListExceptionHandler);
    Operand^.Cursor := Operand^.Cursor^.Next
  end;

  function NullCursorInList (Operand: List): Boolean;
  begin
    Assert (Operand <> Nil, UninitializedListException,
            ListExceptionHandler);
    NullCursorInList := (Operand^.Cursor = Nil)
  end;

  procedure ConcatenateList (var LeftOperand: List; RightOperand: List);

    procedure ConcatenateHelper (var Hook: Link);
    begin
      if Hook = Nil then
        Hook := CopyHelper (RightOperand^.Head)
      else
        ConcatenateHelper (Hook^.Next)
    end;

  begin { ConcatenateList }
    Assert ((LeftOperand <> Nil) and (RightOperand <> Nil),
            UninitializedListException, ListExceptionHandler);
    LeftOperand^.Size := LeftOperand^.Size + RightOperand^.Size;
    ConcatenateHelper (LeftOperand^.Head);
    LeftOperand^.Cursor := Nil
  end;

  function RecoverByPositionFromList (Position: Integer; Ls: List):
    Element;

    function RecoverHelper (Rest: Link; Current: Integer): Element;
    begin
      if Current = Position then
        RecoverHelper := Rest^.Datum
      else
        RecoverHelper := RecoverHelper (Rest^.Next, Current + 1)
    end;

  begin { function RecoverByPositionFromList }
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    Assert ((1 <= Position) and (Position <= Ls^.Size),
            RecoverByPositionFromListException,
            ListExceptionHandler);
    RecoverByPositionFromList := RecoverHelper (Ls^.Head, 1)
  end;

  function ElementAtCursorInList (Operand: List): Element;
  begin
    Assert (Operand <> Nil, UninitializedListException,
            ListExceptionHandler);
    Assert (Operand^.Cursor <> Nil, NullCursorException,
            ListExceptionHandler);
    ElementAtCursorInList := Operand^.Cursor^.Datum
  end;

  procedure AssignAtPositionInList (Position: Integer; var Ls: List;
    NewValue: Element);

    procedure AssignHelper (Rest: Link; Current: Integer);
    begin
      if Current = Position then
        Rest^.Datum := NewValue
      else
        AssignHelper (Rest^.Next, Current + 1)
    end;

  begin { AssignAtPositionInList }
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    Assert ((1 <= Position) and (Position <= Ls^.Size),
            AssignAtPositionInListException,
            ListExceptionHandler);
    AssignHelper (Ls^.Head, 1)
  end;

  procedure AssignAtCursorInList (var Ls: List; NewValue: Element);
  begin
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    Assert (Ls^.Cursor <> Nil, NullCursorException, ListExceptionHandler);
    Ls^.Cursor^.Datum := NewValue
  end;

  function ElementOfList (Candidate: Element; Ls: List): Boolean;

    function ElementHelper (Rest: Link): Boolean;
    begin
      if Rest = Nil then
        ElementHelper := False
      else if EqualElement (Rest^.Datum, Candidate) then
        ElementHelper := True
      else
        ElementHelper := ElementHelper (Rest^.Next)
    end;

  begin { function ElementOfList }
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    ElementOfList := ElementHelper (Ls^.Head)
  end;   

  procedure LocateInList (Sought: Element; Ls: List; var Found: Boolean;
    var Position: Integer); 

    procedure LocateHelper (Rest: Link; Current: Integer);
    begin
      if Rest = Nil then
        Found := False
      else if EqualElement (Rest^.Datum, Sought) then begin
        Found := True;
        Position := Current
      end
      else
        LocateHelper (Rest^.Next, Current + 1)
    end;

  begin  { procedure LocateInList }
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    LocateHelper (Ls^.Head, 1)
  end;

  procedure FindNextInList (Sought: Element; var Ls: List);
  label
    99;  { Exit when the element is found or the end of the list
           is reached. }
  begin
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    if Ls^.Cursor = Nil then
      Ls^.Cursor := Ls^.Head
    else
      Ls^.Cursor := Ls^.Cursor^.Next;
    while True do
      if Ls^.Cursor = Nil then
        goto 99
      else if EqualElement (Ls^.Cursor^.Datum, Sought) then
        goto 99
      else
        Ls^.Cursor := Ls^.Cursor^.Next;
  99:
  end;  

  procedure Sublist (var Ls: List; Start, Finish: Integer);

    procedure SublistHelper (Rest: Link; Current: Integer);
    begin
      if Current < Start then begin
        SublistHelper (Rest^.Next, Current + 1);
        Dispose (Rest)
      end
      else begin
        if Current = Start then
          Ls^.Head := Rest;
        if Current < Finish then
          SublistHelper (Rest^.Next, Current + 1)
        else begin
          DisposeRest (Rest^.Next);
          Rest^.Next := Nil;
          Ls^.Size := Finish - Start + 1
        end
      end
    end;
        
  begin { function Sublist }
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    Assert ((1 <= Start) and (Start <= Ls^.Size) and
            (1 <= Finish) and (Finish <= Ls^.Size), SublistException,
            ListExceptionHandler);
    if Finish < Start then begin
      DisposeRest (Ls^.Head);
      Ls^.Head := Nil;
      Ls^.Size := 0
    end
    else
      SublistHelper (Ls^.Head, 1);
    Ls^.Cursor := Nil
  end;

  procedure ReverseList (var Operand: List);
  var
    BackLink: Link;

    procedure ReverseHelper (Forwards: Link; var Backwards: Link);
    var
      Temporary: Element;
    begin
      if Forwards <> Nil then begin
        Temporary := Forwards^.Datum;
        ReverseHelper (Forwards^.Next, Backwards);
        Backwards^.Datum := Temporary;
        Backwards := Backwards^.Next
      end
    end;

  begin { procedure ReverseList }
    Assert (Operand <> Nil, UninitializedListException,
            ListExceptionHandler);
    BackLink := Operand^.Head;
    ReverseHelper (BackLink, BackLink);
    Operand^.Cursor := Nil
  end;

  procedure InsertAtPositionInList (var Ls: List; Position: Integer;
    Elm: Element);

    procedure InsertHelper (var Rest: Link; Current: Integer);
    var
      OneMore: Link;
    begin
      if Current = Position then begin
        New (OneMore);
        OneMore^.Datum := Elm;
        OneMore^.Next := Rest;
        Rest := OneMore
      end
      else 
        InsertHelper (Rest^.Next, Current + 1)
    end;

  begin { procedure InsertAtPositionInList }
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    Assert ((1 <= Position) and (Position <= Ls^.Size + 1),
             InsertAtPositionInListException,
             ListExceptionHandler);
    InsertHelper (Ls^.Head, 1);
    Ls^.Size := Ls^.Size + 1;
    Ls^.Cursor := Nil
  end;

  procedure InsertAtCursorInList (var Ls: List; Elm: Element);
  var
    OneMore: Link;
  begin
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    if Ls^.Cursor = Nil then
      AppendToList (Ls, Elm)
    else begin
      New (OneMore);
      OneMore^ := Ls^.Cursor^;
      Ls^.Cursor^.Datum := Elm;
      Ls^.Cursor^.Next := OneMore;
      Ls^.Cursor := OneMore
    end
  end;

  procedure DeleteAtPositionInList (var Ls: List; Position: Integer);

    procedure DeleteHelper (var Rest: Link; Current: Integer);
    var
      OneLess: Link;
    begin
      if Current = Position then begin
        OneLess := Rest;
        Rest := Rest^.Next;
        Dispose (OneLess)
      end
      else
        DeleteHelper (Rest^.Next, Current + 1)
    end;

  begin { procedure DeleteAtPositionInList }
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    Assert ((1 <= Position) and (Position <= Ls^.Size),
            DeleteAtPositionInListException,
            ListExceptionHandler);
    DeleteHelper (Ls^.Head, 1);
    Ls^.Size := Ls^.Size - 1;
    Ls^.Cursor := Nil
  end;

  procedure DeleteAtCursorInList (var Ls: List);
  var
    OneLess: Link;
  begin
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    Assert (Ls^.Cursor <> Nil, NullCursorException, ListExceptionHandler);
    if Ls^.Cursor^.Next = Nil then
      DeleteLastOfList (Ls)
    else begin
      OneLess := Ls^.Cursor^.Next;
      Ls^.Cursor^ := OneLess^;
      Dispose (OneLess)
    end
  end;

  procedure DeleteValueFromList (var Ls: List; Delend: Element);

    procedure DeleteHelper (var Rest: Link; var Tally: Integer);
    var
      OneLess: Link;
    begin
      if Rest <> Nil then begin
        if EqualElement (Rest^.Datum, Delend) then begin
          OneLess := Rest;
          Rest := Rest^.Next;
          Dispose (OneLess);
          Tally := Tally - 1;
          DeleteHelper (Rest, Tally)
        end
        else
          DeleteHelper (Rest^.Next, Tally)
      end
    end;

  begin { procedure DeleteValueFromList }
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    DeleteHelper (Ls^.Head, Ls^.Size);
    Ls^.Cursor := Nil
  end;

  procedure ReplaceInList (var Ls: List; Displacer, Displaced: Element);

    procedure ReplaceHelper (var Rest: Link);
    begin
      if Rest <> Nil then begin
        if EqualElement (Rest^.Datum, Displaced) then
          Rest^.Datum := Displacer;
        ReplaceHelper (Rest^.Next)
      end
    end;

  begin { procedure ReplaceInList }
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    ReplaceHelper (Ls^.Head)
  end;

  function FillList (Length: Integer; Filler: Element): List;
  var
    Filled: List;

    function FillHelper (Remaining: Integer): Link;
    var
      Result: Link;
    begin
      if Remaining <= 0 then
        FillHelper := Nil
      else begin
        New (Result);
        Result^.Datum := Filler;
        Result^.Next := FillHelper (Remaining - 1);
        FillHelper := Result
      end
    end;

  begin { function FillList }
    Assert (0 <= Length, FillListException, ListExceptionHandler);
    New (Filled);
    Filled^.Size := Length;
    Filled^.Head := FillHelper (Length);
    Filled^.Cursor := Nil;
    FillList := Filled
  end;

  function GenerateList (function Generator (N: Integer): Element;
    Length: Integer): List;
  var
    Generated: List;

    function GenerateHelper (Current: Integer): Link;
    var
      Result: Link;
    begin
      if Length < Current then
        GenerateHelper := Nil
      else begin
        New (Result);
        Result^.Datum := Generator (Current);
        Result^.Next := GenerateHelper (Current + 1);
        GenerateHelper := Result
      end
    end;

  begin { function GenerateList }
    Assert (0 <= Length, GenerateListException, ListExceptionHandler);
    New (Generated);
    Generated^.Size := Length;
    Generated^.Head := GenerateHelper (1);
    Generated^.Cursor := Nil;
    GenerateList := Generated
  end;

  procedure TransformList (var Ls: List;
    function Transformer (E: Element): Element);

    procedure TransformHelper (Rest: Link);
    begin
      if Rest <> Nil then begin
        Rest^.Datum := Transformer (Rest^.Datum);
        TransformHelper (Rest^.Next)
      end
    end;

  begin { procedure TransformList }
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    TransformHelper (Ls^.Head) 
  end;

  procedure ApplyAlongList (Ls: List; procedure Applicand (E: Element));

    procedure ApplyHelper (Rest: Link);
    begin
      if Rest <> Nil then begin
        Applicand (Rest^.Datum);
        ApplyHelper (Rest^.Next)
      end
    end;

  begin { procedure ApplyAlongList }
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    ApplyHelper (Ls^.Head)
  end;

  function EveryElementOfList (Ls: List;
    function Test (E: Element): Boolean): Boolean;

    function EveryHelper (Rest: Link): Boolean;
    begin
      if Rest = Nil then
        EveryHelper := True
      else if Test (Rest^.Datum) then
        EveryHelper := EveryHelper (Rest^.Next)
      else
        EveryHelper := False
    end;

  begin { function EveryElementOfList }
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    EveryElementOfList := EveryHelper (Ls^.Head)
  end;

  function SomeElementOfList (Ls: List;
    function Test (E: Element): Boolean): Boolean; 

    function SomeHelper (Rest: Link): Boolean;
    begin
      if Rest = Nil then
        SomeHelper := False
      else if Test (Rest^.Datum) then
        SomeHelper := True
      else
        SomeHelper := SomeHelper (Rest^.Next)
    end;

  begin { function SomeElementOfList }
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    SomeElementOfList := SomeHelper (Ls^.Head)
  end;

  procedure RecoverByTestFromList (Ls: List;
    function Test (E: Element): Boolean; var Found: Boolean;
    var Sought: Element);

    procedure RecoverHelper (Rest: Link);
    begin
      if Rest = Nil then
        Found := False
      else if Test (Rest^.Datum) then begin
        Found := True;
        Sought := Rest^.Datum
      end
      else
        RecoverHelper (Rest^.Next)
    end;

  begin { procedure RecoverByTestFromList }
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    RecoverHelper (Ls^.Head)
  end;

  procedure LocateByTestInList (Ls: List;
    function Test (E: Element): Boolean; var Found: Boolean;
    var Position: Integer);

    procedure LocateHelper (Rest: Link; Current: Integer);
    begin
      if Rest = Nil then
        Found := False
      else if Test (Rest^.Datum) then begin
        Found := True;
        Position := Current
      end
      else
        LocateHelper (Rest^.Next, Current + 1)
    end;

  begin { procedure LocateByTestInList }
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    LocateHelper (Ls^.Head, 1)
  end;

  procedure FindNextByTestInList (var Operand: List;
    function Test (E: Element): Boolean);
  label
    99;  { Exit when the test succeeds or the end of the list is reached. }
  begin
    Assert (Operand <> Nil, UninitializedListException,
            ListExceptionHandler);
    if Operand^.Cursor = Nil then
      Operand^.Cursor := Operand^.Head
    else
      Operand^.Cursor := Operand^.Cursor^.Next;
    while True do
      if Operand^.Cursor = Nil then
        goto 99
      else if Test (Operand^.Cursor^.Datum) then
        goto 99
      else
        Operand^.Cursor := Operand^.Cursor^.Next;
  99:
  end;  

  procedure FilterList (var Ls: List; function Test (E: Element): Boolean);

    procedure FilterHelper (var Rest: Link; var Tally: Integer);
    var
      OneLess: Link;
    begin
      if Rest <> Nil then begin
        if Test (Rest^.Datum) then
          FilterHelper (Rest^.Next, Tally)
        else begin
          OneLess := Rest;
          Rest := Rest^.Next;
          Dispose (OneLess);
          Tally := Tally - 1;
          FilterHelper (Rest, Tally)
        end
      end
    end;

  begin { procedure FilterList }
    Assert (Ls <> Nil, UninitializedListException, ListExceptionHandler);
    FilterHelper (Ls^.Head, Ls^.Size);
    Ls^.Cursor := Nil
  end;

  procedure DeallocateList (var Operand: List);
  begin
    Assert (Operand <> Nil, UninitializedListException,
            ListExceptionHandler);
    DisposeRest (Operand^.Head);
    Dispose (Operand)
  end;

end.

This document is available on the World Wide Web as

http://www.math.grin.edu/~stone/courses/fundamentals/cursors.html

created August 16, 1996
last revised October 28, 1996

John David Stone (stone@math.grin.edu)