University of California, Berkeley
EECS Department - Computer Science Division
CS3 Lecture 9
Tail Recursion
Thanks to Oliver Grillmeyer and Brendan Ferguson for
many of the ideas for these notes
Overview of today's lecture
Review
Recursion & Patterns
- Mapping
- Takes a sentence and does something to every word in the
sentence and returns a sentence of the same size
- e.g., rate-eateries, give a rating (1-10) for a
sentence of Cal dining halls
- Finding (like member)
- Finding the first element of a sentence that satisfies test,
and return either that element or the rest of the sentence starting
with that element, #f otherwise.
- e.g., find-good-eatery, return the rest of the sentence
starting with a dining hall whose rating exceeds some number,
#f ottherwise.
- Counting (like count)
- Counting the number of elements that satisfy a test
- e.g., count-good-eateries, return the number of
dining halls whose rating is above a set threshold
- Filtering (like remove)
- Takes a sentence and for each element, decides to either
keep or discard it.
- e.g., remove-bad-eateries, remove all the dining
halls whose rating is below a certain threshold.
- Testing
- Predicate which checks something about every or any
element of the sentence, returns whether it's true or not
- e.g., all-good-eateries? which returns #t if all
dining halls are "good".
- Combining / Accumulating
- Combine the elements of the sentence in some way
- e.g., eatery-average, which gets the average eatery
rating
Tail Recursion vs. Embedded Recursion
Quickie review : sum-0-to-n
- We have seen that recursion, as we know it, almost
always leaves us with work to do.
;; sum-0-to-n
(define (sum-0-to-n n)
(if (zero? n)
0
(+ n (sum-0-to-n (- n 1)))))
: (model (sum-0-to-n 3))
(+ 3 (sum-0-to-n 2))
(+ 3 (+ 2 (sum-0-to-n 1)))
(+ 3 (+ 2 (+ 1 (sum-0-to-n 0)))) ;; base case
(+ 3 (+ 2 (+ 1 0))) ;; now going back up, "cleaning up"
(+ 3 (+ 2 1))
(+ 3 3)
6
- Which means we had to work our way down to the base case,
then work our way back up to the top.
This is called embedded recursion.
(it's also called linear recursion, because
it only uses one recursive call in the recursive case)
- Analogy: Let's say you were commissioned to measure a large
distance, like the distance from here to the best New York-Style
Pizza in Berkeley, Arinell's pizza on Shattuck south of Addison.
Solving the problem using embedded recursion:
- Check if you are there yet
- If not, take a step, put a mark on a piece of paper, and
restart the problem (but now you're one step closer)
- When you are done, then you have to count up all the marks!
Let's rethink the problem : sum-0-to-n-tail
- Would it be possible to work our way down and somehow keep
track of the answer as we go, and when we get to the bottom (base)
case, return that answer?
This is called tail recursion, or iteration.
- Using our analogy, we'd instead write down how many steps
we'd taken so far as a running total, and when we were done,
the answer would be there already!
- Official definition: the recursive call is not embedded
within an expression that needs to wait for the completion of
the call
- Unofficial definition: there's nothing to the left
of the recursive call, like a +, se, or and
Often it is easier to solve a recursive problem using
tail recursion!
...some find this the more intuitive approach.
- How would we rewrite sum-0-to-n so that it keeps
track of the sum?
;; sum-0-to-n-tail
;;
;; INPUTS : A single number, n
;; REQUIRES : The number be a positive integer
;; SIDE-EFFECTS : None
;; RETURNS : The sum of all the integers from 0 to n.
;; EXAMPLE : (sum-0-to-n-tail 3) ==> 6 ;; 0 + 1 + 2 + 3 = 6
: (define (sum-0-to-n-tail n)
==>
: (define (sum-0-to-n-tail-helper n sum-so-far)
==>
==>
==>
: (model (sum-0-to-n-tail 3))
(sum-0-to-n-tail-helper 3 0)
(sum-0-to-n-tail-helper 2 3)
(sum-0-to-n-tail-helper 1 5)
(sum-0-to-n-tail-helper 0 6) ;; base case
6 ;; ...done! No need to "clean up"
Another example: my-count
vs. my-count-tail
- Recall embedded recursion version of my-count:
(define (my-count s)
(if (empty? s)
0
(+ 1 (my-count (bf s)))))
: (model (my-count '(a b c)))
(+ 1 (my-count '(b c)))
(+ 1 (+ 1 (my-count '(c))))
(+ 1 (+ 1 (+ 1 (my-count '())))) ;; base case
(+ 1 (+ 1 (+ 1 0))) ;; cleaning up
(+ 1 (+ 1 1))
(+ 1 2)
3
- Now let's write a tail recursive my-count-tail:
: (define (my-count-tail s)
==>
: (define (mct-helper s count-so-far) ;; shortened name for convenience
==>
==>
==>
: (model (my-count-tail '(a b c)))
(mct-helper '(a b c) 0)
(mct-helper '(b c) 1)
(mct-helper '(c) 2)
(mct-helper '() 3) ;; base case, no clean up needed!
3
More examples: count-even-and-odds
;; count-even-and-odds
;;
;; INPUTS : A sentence of integers, s
;; REQUIRES : The sentence consist only of integers
;; SIDE-EFFECTS : None
;; RETURNS : A two-word sentence, wherein the first word
;; : is the number of even numbers in the sentence, and
;; : the second word is the number of odd numbers
;; EXAMPLE : (count-even-and-odds '(1 2 3 0 -4 8 5)) ==> (4 3)
;; : (count-even-and-odds '(7 9 5 795)) ==> (0 4)
;; : (count-even-and-odds '(2 4 999998)) ==> (3 0)
;; : (count-even-and-odds '()) ==> (0 0)
: (define (count-even-and-odds s)
==>
: (define (ceao-helper
==>
==>
==>
==>
==>
More examples: all-increasing?
;; all-increasing?
;;
;; INPUTS : A sentence of integers, s
;; REQUIRES : The sentence consist only of integers, and be non-empty
;; SIDE-EFFECTS : None
;; RETURNS : #t if the sentence is in strictly increasing order, else #f
;; EXAMPLE : (all-increasing? '(-50 1 2 3 5)) ==> #t
;; : (all-increasing? '(1 2 3 0)) ==> #f
: (define (all-increasing? L)
==>
: (define (ai-helper?
==>
==>
==>
==>
More examples: longest-win-streak
;; longest-win-streak
;;
;; INPUTS : A sentence of w's and l's, s
;; REQUIRES : The sentence consist only of w's and l's, and be non-empty
;; SIDE-EFFECTS : None
;; RETURNS : The count of the team's longest winning streak
;; EXAMPLE : (longest-win-streak '(l l l l)) ==> 0
;; : (longest-win-streak '(l w w w l w w l l l l w)) ==> 3
: (define (longest-win-streak s)
==>
: (define (lws-helper
==>
==>
==>
==>
Summary
- We saw examples of tail recursion, and learned how
it implements iteration.
- Sometimes it's easier to think of a problem and solve it
this way.
Next Time
- We'll look at advanced recursion.
Puzzle : Salad Dressing ["Puzzlegrams"
by Pentagram, Fireside Publishing, 1989]
- In the course of making a salad dressing, a glass of oil
is placed next to a glass of vinegar.
- Someone takes a spoonful of vinegar and stirs it into the
oil.
- Then a spoonful is taken from the oil glass and returned
to the vinegar glass.
- At the end of this operation, which of the two glasses is
the more contaminated?
Game : Horseshoe ["Pentagames"
by Pentagram, Fireside Publishing, 1990]
- Each player starts with two counters set out as shown on
the right.
- The first player starts by moving one of her counters along
a line to the empty point in the center.
- The second player then moves one of her counters along a
line to the vacant point.
- The objective of the game is to block your opponent so that
she cannot move any of her counters.