University of California, Berkeley
EECS Department - Computer Science Division
CS3L OaW Lecture 7 : Embedded, Tail, and Advanced Recursion
Thanks to Oliver Grillmeyer and Brendan Ferguson for
many of the ideas for these notes
Tail Recursion vs. Embedded Recursion
Short example : 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 near BofA.
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, s
;; REQUIRES : The sentence consist only of integers,
;; : AND the sentence 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? s)
==>
: (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 the sentence 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
==>
==>
==>
==>
Advanced Recursion
Pascal's triangle : pascal
- We'd like to be able to calculate elements of Pascal's triangle
- The triangle is used in many places!
- Combinatorics
- How many ways are there of choosing C things from R possible choices?
- E.g., How many ways are there of choosing C=2 cards from a deck of R=5 cards?
- Answer: look in the table under (C,R) = (2,5) and you see there are 10 ways!
- Polynomial multiplication
- What are the coefficients of the terns (x + y)5?
- 1 x5 + 5 x4y + 10 x3y2 + 10 x2y3 + 5 xy4 + 1 y5
- See any correlation between the bold numbers above and the 5th row (R=5) ?
|
|
r
o
w
s
(R)
|
|
0 |
1 |
2 |
3 |
4 |
5 |
... |
0 |
1 |
|
|
|
|
|
... |
1 |
1 |
1 |
|
|
|
|
... |
2 |
1 |
2 |
1 |
|
|
|
... |
3 |
1 |
3 |
3 |
1 |
|
|
... |
4 |
1 |
4 |
6 |
4 |
1 |
|
... |
5 |
1 |
5 |
10 |
10 |
5 |
1 |
... |
... |
... |
.. |
... |
... |
... |
... |
... |
Pascal's Triangle
- The rule for calculating the elements triangle is:
- Every element is the sum of the two elements directly above (north) and to the immediate upper left (northeast)
- More specifically:
pascal(C,R) = pascal(C-1,R-1) + pascal(C,R-1)
(define (pascal C R)
==>
==>
==>
- Exercise for the ambitious: rewrite pascal to use the formula (! = factorial, you will have to write that also):
pascal(C,R) = R! / ((R-C)! C!)
Pair all prefixes with suffixes : pair-all
- We want to take a sentence of prefixes and one of suffixes and return a sentence with pairings of all the prefixes with all the suffixes
(pair-all '(a b c d) '(1 2 3))
;; ==> (a1 a2 a3 b1 b2 b3 c1 c2 c3 d1 d2 d3)
(pair-all '(to re on) '(ad ward ly))
;; ==> (toad toward toly read reward rely onad onward only)
- Inspiration! Divide the problem into two parts:
- (append-prefix prefix suffixes) ;; append prefix word to all words in suffixes
- (pair-all prefixes suffixes) ;; send all prefixes in prefixes to append-prefix
(define (append-prefix prefix suffixes)
==>
==>
==>
(define (pair-all prefixes suffixes)
==>
==>
==>
==>
==>
==>
==>
- Exercise for the ambitious: Rewrite the code so that instead of looping on prefixes first and suffixes next (e.g., a1 a2 a3 b1 b2 ...) it loops on suffixes first and prefixes next (e.g., a1 b1 c1 d1 a2 b2 ...)
Overview of recursion types
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.
- We saw examples of advanced recursion, and saw how it differs from linear recursion
In Lab this week you'll see...
- Roman Numerals programs
- Recursive programs with two arguments, accumulating recursions and advanced recursion
In Life this week you'll see...
- The nation start to understand what the bailout plan means. (Ans: your grandkids will still be paying it off!)
- Cal (4-1, ranked #25) take on UCLA at home on Saturday