CSC 153 Grinnell College Spring, 2005
 
Computer Science Fundamentals
Laboratory Exercise
 

Tail Recursion

Goals

This laboratory provides further analysis of recursion and tail recursion-- giving examples of how non-tail-recursive procedures can be rewritten to become tail recursive.

Finding the sum of numbers on a list

Consider the following two solutions to the problem of finding the sum of a list of numbers.

Solution 1:

The first solution seems to follow a now-familiar recursive format:


(define sum 
   (lambda (ls)
       (if (null? ls)
           0
           (+ (car ls) (sum (cdr ls)))
       )
   )
)

Solution 2:

The second solution adds a running sum parameter.


(define sum 
   (lambda (ls)
       (sum-kernel ls 0)
   )
)
(define sum-kernel
   (lambda (ls running-sum)
       (if (null? ls)
           running-sum
           (sum-kernel (cdr ls) (+ running-sum (car ls)))
       )
   )
)
  1. Change the define statement to trace-define for both versions of sum above. (For the second version, also use trace-define to declare sum-kernel.) Run each version of sum on the list '(1 2 3 4), as in the above example. Comment on how the traces of these versions relate to the call diagrams in the readings for this lab.

Finding the maximum value on a list of numbers

Next consider some solutions for finding the maximum value within a list of numbers.

Solution 1:


(define maximum 
   (lambda (ls)
       (cond ((null? (cdr ls)) (car ls))  
             ((< (car ls) (maximum (cdr ls))) (maximum (cdr ls)))
             (else (car ls))
       )         
   )
)
  1. Change define to trace-define and run the code for (maximum '(2 6 4)). How many times is maximum called? Explain briefly how this number is obtained.

  2. Draw a diagram, such as the ones in the reading, to trace the procedure calls for the procedure call (maximum '(2 6 4)). Be sure your diagram agrees with the count determined from step 2.

Solution 2:

Using an additional parameter in a kernel procedure:


(define maximum 
   (lambda (ls)
      (maximum-kernel (car ls) (cdr ls))
   )
)

(define maximum-kernel
   (lambda (max-so-far lst)
       (cond ((null? lst) max-so-far)
             ((< (car lst) max-so-far) (maximum-kernel max-so-far (cdr lst)))
             (else (maximum-kernel (car lst) (cdr lst)))
       )         
   )
)
  1. As before, change define to trace-define and run the code for (maximum '(2 6 4)). How many times are maximum and maximum-kernel called? Explain briefly how this number is obtained.

  2. Draw a diagram, such as the ones above, to trace the procedure calls for the procedure call (maximum '(2 6 4)).

    Explain why this approach is more efficient that the previous solution.

Solution 3:

The following is a variation of the Solution 2.


(define maximum 
   (lambda (ls)
      (maximum-kernel (car ls) (cdr ls))
   )
)

(trace-define maximum-kernel
   (lambda (max-so-far lst)
       (if (null? lst) 
           max-so-far
           (maximum-kernel (if (< (car lst) max-so-far) 
                               max-so-far
                               (car lst))
                           (cdr lst)
           )
       )         
   )
)
  1. Again, change define to trace-define and run the code for (maximum '(2 6 4)). How many times are maximum and maximum-kernel called? Explain briefly how this number is obtained.

  2. Draw a diagram, such as the ones above, to trace the procedure calls for the procedure call (maximum '(2 6 4)).

  3. Compare the efficiency of Solution 3 with that of Solutions 1 and 2. What conclusions can you make about the various solutions? Which solution do you prefer? Justify your preference briefly.

    Run the traces again, this time for the list '(2 6 4 5)). Explain how the patterns you observed for a 3-element list extend to this list of 4-element list.

Problem -- Average: Find the average of the numbers within a list.

Solution Outline:

Since an average requires both a sum of the items and a count of the number of items present, any solution must do both tasks. A tail recursive approach to this problem uses parameters to keep a running sum and a running count of the number of items processed.

  1. Write a tail recursive solution to this problem, using a separate kernel procedure.

Problem -- Maximum, Minimum, and Average: Find the maximum, minimum, and average of a list of numbers.

Comment:

Since three results are desired, we must decide how these results should be returned. One natural approach would be to place all three results on a single list, with the maximum first, the minimum second, and the average third.

  1. Write a tail recursive solution to this problem, using a single kernel procedure. The kernel procedure may have as many parameters as desired.

Work to be turned in:


This document is available on the World Wide Web as

http://www.cs.grinnell.edu/~walker/courses/153.sp05/labs/lab-tail-recursion.shtml

created 7 March 1997
last revised 16 February 2006
Valid HTML 4.01! Valid CSS!
For more information, please contact Henry M. Walker at walker@cs.grinnell.edu.