26. More Examples of Functions on Lists


Example: Produce a modified list

Suppose that squares(L) is intended to produce a list of the squares of the numbers in list L. For example,

   squares([2,4,6,8] = [4,16,36,64]
   squares([3,2,1])  = [9,4,1]
   squares([5])      = [25]
   squares([])       = []

Every list is either empty or nonempty. The last example suggests an equation that defines squares([ ]).

(squares.1) squares([]) = []

What about nonempty lists? Break both the parameter list L and the answer list down into head and tail.

  1. Since squares([2, 3, 5, 6]) = [4, 9, 25, 36],

    head(squares([2,3,5, 6])) = 4 = 22.

    In general, whenever L is a nonempty list,

    head(squares(L)) = head(L)2
  2. Since squares([2, 3, 5, 6]) = [4, 9, 25, 36],

    tail(squares([2, 3, 5, 6])) = [9, 25, 36].

    But tail([2, 3, 5, 6]) = [3, 5, 6] and squares([3, 5, 6]) = [9, 25, 36], so

    tail(squares([2, 3, 5, 6])) = squares(tail(2, 3, 5, 6]))

    In general, whenever L is a nonempty list,

    tail(squares(L)) = squares(tail(L))

If you know that a list R has head h and tail t, then R must be h:t. That gives us our second equation for squares(L), which is used when L is not an empty list.

(squares.2) squares(L) = head(L)2 : squares(tail(L))

It is straightforward to convert equations (squares.1) and (squares.2) into a C++ definition of squares.

  List squares(ConstList L)
  {
    if(L == emptyList) 
    {
      return emptyList;
    }
    else
    {
      int h = head(L);
      return cons(h*h, squares(tail(L)));
    }
  }

Example: Select some of the members

Let's write equations that define function evens(L), which yields a list of all members of list L that are even, preserving their order. For example, evens([2, 5, 6, 7, 9, 10]) = [2, 6, 10]. First, an obvious equation.

(evens.1) evens([]) = []
Now suppose that L starts with an even number. Then that even number will be the first value in the result list, and it will be followed by all even numbers in tail(L).
(evens.2) evens(L) = head(L):evens(tail(L))  (when head(L) is even)
Finally, if L does not begin with an even number, just ignore that number.
(evens.3) evens(L) = evens(tail(L))  (when head(L) is odd)
Putting those three equations together defines an algorithm for evens(L).

  List evens(ConstList L)
  {
    if(L == NULL)
    {
      return NULL;
    }
    else {
      int       h = L->head;
      ConstList t = L->tail;
      if(h % 2 == 0)
      {
        return cons(h, evens(t));
      }
      else
      {
        return evens(t);
      }
    }
  }

Example: Prefix test

Let's write equations that define function isPrefix(A, B), which is true if list A is a prefix of list B. For example, isPrefix([1,3], [1,3,5]) is true. Every list is a prefix of itself, so isPrefix([2, 4, 6], [2, 4, 6]) is true, and the empty list is a prefix of every list. Here are equations for isPrefix. "And" indicates logical and (&& in C++).

(isPrefix.1) isPrefix([], B) = true
(isPrefix.2) isPrefix(A, []) = false
                                (when A is not [])
(isPrefix.3) isPrefix(A, B)  = head(A) == head(B) and 
                               isPrefix(tail(A), tail(B))
                                (when A ≠ [] and B ≠ [])
The first two equations should be evident. Equation (isPrefix.1) says that the empty list is a prefix of every list. We assume that the equations are tried in order, so (isPrefix.2) is only relevant when A is not []. Equation (isPrefix.2) says that a nonempty list is not a prefix of an empty list. The third equation should be more obvious through an example.
  isPrefix([3,5], [3,5,7,9])
    = 3 == 3  and  isPrefix([5], [5,7,9])   by (isPrefix.3) 
    = true  and  isPrefix([5], [5,7,9])
    = isPrefix([5], [5,7,9])
    = 5 == 5  and  isPrefix([], [7,9])      by (isPrefix.3)
    = true  and  isPrefix([], [7,9])
    = isPrefix([], [7,9])
    = true                                  by (isPrefix.1)

Here is a C++ definition of isPrefix.

  bool isPrefix(ConstList A, ConstList B)
  {
    if(A == NULL)
    {
      return true;
    }
    else if(B == NULL)
    {
      return false;
    }
    else
    {
      return A->head == B->head && isPrefix(A->tail, B->tail)
    }
  }

Exercises

  1. The following equation about isPrefix is false. Give a counterexample that shows it is wrong. Evaluate the two sides for your counterexample and show that they are not equal.

      isPrefix(h:t, L) = isPrefix(t, L)
    
    Answer

  2. Using equations (isPrefix.1–isPrefix.3), show an evaluation of isPrefix([2,3,4], [2,4,3]) by only replacing expressions by equal expressions. Answer

  3. Using equations (isPrefix.1–isPrefix.3), show an evaluation of isPrefix([2,3], [2]) by only replacing expressions by equal expressions. Answer

  4. Write equations for function sum(L), which yields the sum of all members of list L. For example, sum([2,3,4]) = 2 + 3 + 4 = 9. The sum of an empty list is 0. Make sure that your equations contain enough information to determine the value of sum(L) for any list of integers L. Answer

  5. Using your equations for sum(L) from the preceding question, do an evaluation of sum([3, 5, 7]) by only replacing expressions by equal expressions. Answer

  6. Following your equations, write a C++ definition of sum(L). Answer

  7. Write equations for function smallest(L), which yields the smallest member of nonempty list L. For example, smallest([2, 3, 4]) = 2 and smallest([6, 4, 8, 7]) = 4. Your equations should not try to define smallest([ ]), since the list is required to be nonempty. You can use function min(x, y) to compute the smaller of two integers x and y. Answer

  8. Write a definition of smallest based on your equations. Answer

  9. Write equations for function prefix(L, n), which yields the length n prefix of list L. For example, prefix([2, 4, 6, 8, 10], 3) = [2, 4, 6]. If n is larger than the length of L then prefix(L, n) should return L. For example, prefix([2, 4, 6, 8, 10], 50) = [2, 4, 6, 8, 10]. Answer

  10. Convert your equations for prefix to a C++ definition of prefix(L, n). Answer