meeting -*- Outline -*- * tail recursion (1.2.3) important for efficiency ** motivation from "Scheme and the Art of Progamming" by Springer and Friedman, 4.6 ------------- FIBONACCI NUMBERS Each pair of rabbits has 2 kids. Mate every month Bear young 2 months after birth # of pairs of rabbits: 1 ; end of month 1 1 ; end of month 2 2 = 1 + 1 ; month 3 3 = 1 + 2 ; month 4 5 = 2 + 3 ; month 5 8 = 3 + 5 ; month 6 13 = 5 + 8 ; month 7 ------------- Q: How many at the end of month 8? Note: the fib function starts at 1, shouldn't ask about month 0, but we define it as 0 for month 0 Italian "Son of Bonacci" = filis Bonacci. Mathematician. ------------------------------------------ FULLY RECURSIVE VERSION (deftype fib (-> (number) number)) (define fib (lambda (n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))) ------------------------------------------ try to compute on-line (fib 5) (fib 10) then (fib 100) while that's running... yawn, check your watch, ** definition ------------------------------------------ FULL vs. TAIL RECURSION def: a procedure is tail recursive if full recursion trace: (list-sum '(3 5 7)) = (+ 3 (list-sum '(5 7))) = (+ 3 (+ 5 (list-sum '(7)))) = (+ 3 (+ 5 (+ 7 (list-sum '())))) = (+ 3 (+ 5 (+ 7 0))) = (+ 3 (+ 5 7)) = (+ 3 12) = 15 tail recursion trace: (list-sum '(3 5 7)) = (list-sum-iter '(3 5 7) 0) = (list-sum-iter '(5 7) 3) = (list-sum-iter '(7) 8) = (list-sum-iter '() 15) = 15 ------------------------------------------ ... it has no pending computations when makes a recursive call point out the *pending computations* in the fully recursive version Q: Which of these is more like a while loop? This is how you design a tail recursive program, by designing the sequence of iterates. ------------------------------------------ FULL vs. TAIL RECURSION (deftype list-sum (-> ((list-of number)) number)) (define list-sum ; fully recursive (lambda (lon) (if (null? lon) 0 (+ (car lon) (list-sum (cdr lon)))))) (deftype list-sum (-> ((list-of number)) number)) (define list-sum ; tail recursive (lambda (lon) ------------------------------------------ ... (list-sum-iter lon 0))) (deftype list-sum-iter (-> ((list-of number) number) number) (define list-sum-iter (lambda (lon acc) (if (null? lon) acc (list-sum-iter (cdr lon) (+ (car lon) acc))))) ------------------------------------------ TAIL RECURSIVE VERSION OF FIB How would we design fib to be tail recursive? ------------------------------------------ Need to design a sequence of numbers that will compute it. - since need previous 2 fib numbers, need to use *two* accumulators. Let's play with this for a while, how to start? pick the first 2 fib numbers What could we do? Well, we get the next number by adding them but then if we want to do this again, need to save the previous one. So 1 step of the iteration - replace last with old value of prev (remains 1 behind) - replace prev with last + prev have to do this simultaneously. (advantage of recursion over assignment here) Emphasize that this sequence has to be designed!!!! how? E.g., for (fib-iter 7 0 1)... n prev last 7 0 1 6 1 1 5 1 2 4 2 3 3 3 5 2 5 8 1 8 13 end condition is n = 1, so have to test in main for n = 0 - write it out on the board in code ------------------------------------------ FOR YOU TO DO (IN PAIRS) Write a tail-recursive procedure for: (reverse '()) ==> () (reverse '(a b c)) ==> (c b a) (reverse '(x y z h)) ==> (h z y x) ------------------------------------------ Hint: use an accumulator, work out the trace emphasize that non-tail solutions won't do. Note: the results come out backwards, which is what you want in this case, but not in general, sometimes need to reverse accumulator at end, or process it in some other way. Q: do our questions still work? yes, but you also have to think about: - using an iterator - designing the sequence of iterates ** When to use tail recursion ------------------------------------------ WHEN TO USE TAIL RECURSION 1. When working with vectors or strings (deftype vector-sum (-> ((vector-of number)) number)) (define vector-sum (lambda (von) (partial-vector-sum von (vector-length von) 0))) (deftype partial-vector-sum (-> ((vector-of number) number number) number))) (define partial-vector-sum (lambda (von n acc) (if (zero? n) (partial-vector-sum von (- n 1) (+ (vector-ref von (- n 1)) acc))))) ------------------------------------------ ... acc ------------------------------------------ WHEN TO USE TAIL RECURSION 2. To return directly to caller (since no pending computations) (deftype list-product (-> ((list-of number)) number)) (define list-product (lambda (lon) (prod-iter lon 1))) (deftype (-> ((list-of number) number) number)) (define prod-iter (lambda (lon acc) (if (null? lon) acc (if (zero? (car lon)) 0 (prod-iter (cdr lon) (* acc (car lon))))))) ------------------------------------------ ** correspondence to loops Q: What in C/C++ is like a tail recursion? while loop, for loop... ------------------------------------------ CORRESPONDENCE TO WHILE LOOP struct Cons {int car; Cons *cdr}; // C++ int list_product(Cons *lon) { int acc = 1; while (!(lon == NULL)) { if (lon->car == 0) { return 0; } else { acc = acc * lon->car; lon = lon->cdr; } } return acc; } (define list-product ; Scheme (lambda (lon) (prod-iter lon 1))) (define prod-iter (lambda (lon acc) (if (null? lon) acc (if (zero? (car lon)) 0 (prod-iter (cdr lon) (* acc (car lon))))))) ------------------------------------------ ** when to use an accumulator ------------------------------------------ WHEN YOU NEED AN ACCUMULATOR When working with vectors, strings (deftype vector-sum (-> ((vector-of number)) number)) (define vector-sum (lambda (von) (partial-vector-sum von (vector-length von)))) (deftype partial-vector-sum (-> ((vector-of number) number) number) (define partial-vector-sum (lambda (von n) (if (zero? n) 0 (+ (vector-ref von (- n 1)) (partial-vector-sum von (- n 1)))))) ------------------------------------------ more generally, if the data can't be decomposed and you need to pass a "placeholder" or "pointer", etc. ** summary Q: What are the key ideas for designing tail recursion? correspondence to while design the iteration (sequence of values of variables) Q: When should you use tail recursion? for vectors when need to return directly to caller for other efficiency reasons (less stack, etc.) Q: When shouldn't you use tail recursion? when you don't need to, it may be simplier to use full recursion e.g. append Q: How do you design a fully recursive program? think of examples, how to get answer from cdr of problem's ans Q: What are the questions to ask? what's the first step, what's the rest, how to combine...