Class 21: Tail Recursion

Back to More Algorithm Analysis. On to Searching.

Held: Monday, 24 February 2003

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

Related Pages:

Notes:

• You may have thought it was silly that I essentially spent ten hours in a plane for a one day meeting with nothing else. Wait until I tell you about my colleagues.
• I've graded Exam 1. Although you all did well, we'll spend some time discussing issues I identified as problematic.
• Are there questions on homework 2?
• We won't do a lab today because I think the other topics will take up most of today's class.
• There's no reading for tonight.

Overview:

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

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

Thursday, 15 January 2003 [Samuel A. Rebelsky]

• Created as a mostly-blank outline.

Monday, 24 February 2003 [Samuel A. Rebelsky]

• Wrote introductory material.
• Dropped lab.

Back to More Algorithm Analysis. On to Searching.

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

This document was generated by Siteweaver on Tue May 6 09:20:37 2003.
The source to the document was last modified on Mon Feb 24 09:14:34 2003.
This document may be found at `http://www.cs.grinnell.edu/~rebelsky/Courses/CS153/2003S/Outlines/outline.21.html`.

You may wish to validate this document's HTML ; ; Check with Bobby

Samuel A. Rebelsky, rebelsky@grinnell.edu