28. Looping over Linked Lists


Loops and linked lists

Some algorithms on linked lists are conveniently expressed as loops. Let's use conceptual lists to plan a loop to compute the length of a list L. We need to keep track of two variables: count (the number of values skipped over) and p (the remainder of the list). As an example, suppose L = [2, 4, 6, 8].

  p      count
[2, 4, 6, 8]    0
[4, 6, 8]    1
[6, 8]    2
[8]    3
[ ]    4

The loop ends when p is an empty list; variable count is the length of L. Notice that this loop has an invariant: at each line shown,

(length-invariant) count + length(p) = length(L).      

When p is an empty list, length(p) = 0. Substituting 0 for length(p) in (length-invariant) gives

count + 0 = length(L).
That is, when p = [ ], count must be length(L).

Putting that plan into code gives the following definition of length(L).

  int length(ConstList L)
  {
    ConstList p      = L;
    int       count  = 0;
    
    while(p != NULL)
    {
       count++;
       p = p->tail;
    }
    return count;
  }
That can be made shorter by using a for-loop.
  int length(ConstList L)
  {
    ConstList p;
    int       count  = 0;
    
    for(p = L; p != NULL; p = p->tail)
    {
       count++;
    }
    return count;
  }
Look at the for-loop. That is a boilerplate way to loop over all of the cells in a linked list L. Memorize it.


Sum of a list

Suppose sum(L) is intended to return the sum of all of the numbers in linked list L. A scan algorithm will do the job. Here is a plan showing the values of variables p and s for computing sum(L) for L = [2, 4, 6, 8].

 p      s 
[2, 4, 6, 8]    0
[4, 6, 8]    2
[6, 8]    6
[8]    12
[ ]    20

Again there is an invariant: at each line, equation

(sum-invariant) s + sum(p) = sum(L).      

is true. So, when p is an empty list (and sum(p) = 0), s must equal sum(L). Here is a definition of sum in C++.

  int sum(ConstList L)
  {
    ConstList p = L;
    int       s = 0;

    while(!isEmpty(p))
    {
      s += head(p);
      p  = tail(p);
    }
    return s;
  }
Using a for-loop shortens it.
  int sum(ConstList L)
  {
    int s = 0;
    for(ConstList p = L; p != NULL; p = p->tail)
    {
      s += p->head;
    }
    return s;
  }

Reversing a list

Let's define function reverse(L), which returns the reversal of list L. For example, reverse([2, 4, 6, 8]) = [8, 6, 4, 2]. Think of the list as a deck of cards. To reverse the deck, start with it in your hand. Remove a card from the top of the deck and place it in a stack of cards on a table. Keep doing that until there are no more cards in your hand. The deck on the table is the reversed deck. Showing the process just before moving each card gives the following table

 hand      table 
[2, 4, 6, 8]    [ ]
[4, 6, 8]    [2]
[6, 8]    [4, 2]
[8]    [6, 4, 2]
[ ]    [8, 6, 4, 2]
Now it is just a matter of expressing that in C++.
  List reverse(ConstList L)
  {
    ConstList hand  = L;
    List      table = emptyList;

    while(!isEmpty(hand))
    {
      table = cons(head(hand), table);
      hand  = tail(hand);
    }
    return table;
  }

An invariant for the loop in reverse is as follows, where we use x ++ y to indicate the concatenation of lists x and y.

(reverse-invariant) reverse(hand) ++ table = reverse(L).      

What does that tell you about the value of variable table when hand is [ ]?


Exercises

  1. Using a loop, write a C++ definition of function smallest(L), which returns the smallest number in nonempty list L. Answer

  2. Using a loop, write a C++ definition of function firstEven(L), which returns the first even number in list L. If L does not contain an even number, then firstEven(L) should return −1. Answer