# Class 37: Tail Recursion

Reading: Tail Recursion. Lab: Tail Recursion. Back to Script-Fu Concluded. On to Tail Recursion, Continued.

Held Monday, April 9, 2001

Summary

Today we revisit recursion and consider a more efficient way to write recursive procedures. This new technique is called tail recursion.

Notes

• Anne is changing tonight's office hours to 7:30-8:30.
• For Wednesday, read Variable-Arity Procedures.
• As some of you have noted, I regularly make PDF files for readings and labs. Does anyone use those? Does anyone want to?
• Are there questions on the exam?
• I'd recommend that you break problem 1 up into parts, and do each part separately.
• You can have a precondition in problem 1 that states the maximum length of a first or last name. (Extra credit for handling an arbitrary width.)
• You may want to review the string procedures for hints on how to do left- and right- justification.
• For problem 2, I want you to change the vector given as a parameter, and not return a new vector.
• Here's what I expected for intersect:
```(define intersect
(lambda (set1 set2)
((remove (complement (right-section member set2))) set1)))
```
• In English: Remove from set1 all values that are not in set 2.
• Why did I give this problem? Because I wanted you to see that the higher-order procedures give us a very different way of thinking about problem solving. Once you've written good helpers, your first inclination no longer has to be one that involves a standard recursive pattern.

Overview

• Kinds of recursion
• Why do tail recursion
• Generating lists tail-recursively
• Lab

## Two Kinds of Recursion

• Recursion in which you do something with the recursive result.
• E.g., definition of `add-to-all`, `factorial`, traditional right-associative `sum`.
• Recursion in which you simply return the recursive result, unchanged.
• E.g., `member?`, the left-associative `sum`.
• The latter kind of recursion is called tail recursion and, in Scheme, is likely to be faster. Why?
• Let's look at the two versions of `sum` (in class).
• Version 1, right associative, not tail recursive.
```(define sumr
(lambda (values)
(if (null? values) 0
(+ (car values) (sumr (cdr values))))))
```
```  + Version 2, left associative, tail recursive.
(define suml
(lambda (values)
(suml-helper values 0)))
(define suml-helper
(lambda (remaining-values partial-sum)
(if (null? remaining-values) partial-sum
(suml-helper (cdr remaining-values)
(+ partial-sum
(car remaining-values))))))
```
• Let's sum the list `(1 2 3 4)` and see if one seems to be easier to deal with.
• Right associative
```(sumr (1 2 3 4))
--> (+ 1 (sumr (2 3 4)))
--> (+ 1 (+ 2 (sumr (3 4))))
--> (+ 1 (+ 2 (+ 3 (sumr (4)))))
--> (+ 1 (+ 2 (+ 3 (+ 4 (sumr ())))))
--> (+ 1 (+ 2 (+ 3 (+ 4 0))))
--> (+ 1 (+ 2 (+ 3 4)))
--> (+ 1 (+ 2 7))
--> (+ 1 9)
--> 10
```
• Left associative
```(suml (1 2 3 4))
--> (suml-helper (1 2 3 4) 0)
--> (suml-helper (2 3 4) 1)
--> (suml-helper (3 4) 3)
--> (suml-helper (4) 6)
--> (suml-helper () 10)
--> 10
```
• Which would you prefer to evaluate by hand?
• In the right-associative case, Scheme needs to remember the things to do once the recursion reaches the base case.
• Remembering that stuff takes extra memory and time.
• Hence, tail-recursive procedures are normally quicker than non-tail-recursive procedures.

## Making Procedures Tail Recursive

• The normal strategy for making procedures tail recursive is the one we used for `suml`:
• Make a helper procedure with one additional parameter.
• The additional parameter accumulates partial solutions. Hence, we often call it an accumulator.
• You can often transform a non-tail-recursive (but recursive) procedure in to a tail-recursive procedure:
• Build that helper with an extra parameter.
• The accumulator normally starts out with the value from the old base case (the one in the non-tail-recursive procedure).
• The operation used to update the accumulator is similar to (but not necessarily identical to) the one used in the standard recursive call.

## Tail Recursion and List Construction

• While tail recursion is generally a good thing, it's a bad idea to spend lots of extra computational effort on building the accumulated partial result.
• The problem appears most frequently in procedures that build lists.
• Consider the problem of adding two to each value in a list.
• Suppose our accumulator contains all the values we've added two to.
• Then at each step, we'll need to put the next value at the end of the accumulator.
• How do you add to the end of a list?
• How much computational effort does it involve?
• You'll find that your procedure spends a lot of time walking through the accumulated result.
• Can we do something better? Yes. Accumulate the reverse of the desired result.
• Updating at each step is each; just cons to the front.
• We do need to spend some extra effort at the end to reverse that result.
• Alternately, we can reverse the parameter before calling the helper.
• See the reading for more details.

## History

Friday, 12 January 2001

• Created generic outline format for class.

Back to Script-Fu Concluded. On to Tail Recursion, Continued.

Disclaimer: I usually create these pages on the fly. This means that they are rarely proofread and may contain bad grammar and incorrect details. It also means that I may update them regularly (see the history for more details). Feel free to contact me with any suggestions for changes.

This page was generated by Siteweaver on Wed May 5 12:15:06 2004.
This page may be found at `http://www.cs.grinnell.edu/~rebelsky/Courses/CS151/2001S/outline.37.html`.
You may validate this page's HTML.
The source was last modified Tue Jan 23 16:01:58 2001.