CS 480/680 – Comparative Languages Recursion and Iteration Let the entertainment begin… No Iteration?? Yes, it’s true, there is no iteration in Scheme. So, how do we achieve simple looping constructs? Here’s a simple example from CalTech: Recursion and Iteration 2 Summing integers How do I compute the sum of the first N integers? Recursion and Iteration 3 Decomposing the sum Sum of first N integers = N + N-1 + N-2 + … 1 N + ( N-1 + N-2 + … 1 ) N + [sum of first N-1 integers] Recursion and Iteration 4 to Scheme Sum of first N integers = N + [sum of first N-1 integers] Convert to Scheme (first attempt): (define (sum-integers n) (+ n (sum-integers (- n 1)))) Recursion and Iteration 5 Recursion in Scheme (define (sum-integers n) (+ n (sum-integers (- n 1)))) sum-integers defined in terms of itself This is a recursively defined procedure ... which is incorrect Can you spot the error? Recursion and Iteration 6 Almost… What’s wrong? (define (sum-integers n) (+ n (sum-integers (- n 1)))) Gives us: N + N-1 + …+ 1 + 0 + -1 + -2 + … Recursion and Iteration 7 Debugging Fixing the problem: • it doesn’t stop at zero! Revised: (define (sum-integers n) (if (= n 0) 0 (+ n (sum-integers (- n 1))))) How does this evaluate? Recursion and Iteration 8 Warning The substitution evaluation you are about to see is methodical, mechanistic, • and extremely tedious. It will not all fit on one slide. Don't take notes, but instead try to grasp the overall theme of the evaluation. Recursion and Iteration 9 Evaluating Evaluate: (sum-integers 3) • evaluate 3 3 • evaluate sum-integers (lambda (n) (if …)) • apply (lambda (n) (if (= n 0) 0 (+ n (sum-integers (- n 1))))) to 3 (if (= 3 0) 0 (+ 3 (sum-integers (- 3 1)))) Recursion and Iteration 10 Evaluating… Evaluate: (if (= 3 0) 0 (+ 3 (sum-integers (- 3 1)))) • evaluate (= 3 0) evaluate 3 3 evaluate 0 0 evaluate = = = to 3, 0 #f • since expression is false, replace with false clause: apply (+ 3 (sum-integers (- 3 1))) • evaluate: (+ 3 (sum-integers (- 3 1))) Recursion and Iteration 11 Evaluating… evaluate (+ 3 (sum-integers (- 3 1))) • evaluate 3 3 • evaluate (sum-integers (- 3 1)) evaluate (- 3 1) ...[skip steps] … 2 evaluate sum-integers(lambda (n) (if …)) Recursion and Iteration 12 Evaluating… evaluate (+ 3 (sum-integers (- 3 1))) • evaluate 3 3 • evaluate (sum-integers (- 3 1)) evaluate (- 3 1) ...[skip steps] … 2 evaluate sum-integers(lambda (n) (if …)) Note: now pending (+ 3 …) Recursion and Iteration 13 Pending (+ 3 …) Evaluating… apply (lambda (n) (if (= n 0) 0 (+ n (sum-integers (- n 1))))) to 2 (if (= 2 0) 0 (+ 2 (sum-integers (- 2 1))) Recursion and Iteration 14 Pending (+ 3 …) Evaluating… evaluate: (if (= 2 0) 0 (+ 2 (sum-integers (- 2 1))) • evaluate (= 2 0) … [skip steps] … #f • since expression is false, replace with false clause • (+ 2 (sum-integers (- 2 1))) • evaluate: (+ 2 (sum-integers (- 2 1))) Recursion and Iteration 15 Pending (+ 3 …) Evaluating… evaluate (+ 2 (sum-integers (- 2 1))) • evaluate 2 2 • evaluate (sum-integers (- 2 1)) evaluate (- 2 1) ...[skip steps] … 1 evaluate sum-integers(lambda (n) (if …)) apply (lambda (n) …) to 1 (if (= 1 0) 0 (+ 1 (sum-integers (- 1 1)))) – evaluate (= 1 0) ...[skip steps] … #f Recursion and Iteration Note: pending (+ 3 (+ 2 …)) 16 Pending (+ 3 (+ 2 …)) Evaluating… Evaluate (+ 1 (sum-integers (- 1 1))) • evaluate 1 1 • evaluate (sum-integers (- 1 1)) evaluate (- 1 1) ...[skip steps] … 0 evaluate sum-integers(lambda (n) (if …)) apply (lambda (n) …) to 0 (if (= 0 0) 0 (+ 1 (sum-integers (- 0 1)))) – evaluate (= 0 0) #t – result: 0 Recursion and Iteration 17 Note: pending (+ 3 (+ 2 (+ 1 0))) Pending (+ 3 (+ 2 (+ 1 0))) Evaluating Back to pending: Know (sum-integers (- 1 1)) 0 [prev slide] Evaluate (+ 1 (sum-integers (- 1 1))) • • • • evaluate 1 1 evaluate (sum-integers (- 1 1)) 0 evaluate + + apply + to 1, 0 1 Note: pending (+ 3 (+ 2 1)) Recursion and Iteration 18 Pending (+ 3 (+ 2 1)) Evaluating Back to pending: Know (sum-integers (- 2 1)) 1 [prev slide] Evaluate (+ 2 (sum-integers (- 2 1))) • • • • evaluate 2 2 evaluate (sum-integer (- 2 1)) 1 evaluate + + apply + to 2, 1 3 Note: pending (+ 3 3) Recursion and Iteration 19 Evaluating Finish pending: (+ 3 3) … final result: 6 Recursion and Iteration 20 …Yeah!!! … Substitution model… …works fine for recursion. Recursive calls are well-defined. Careful application of model shows us what they mean and how they work. Recursion and Iteration 21 Recursive Functions Since there are no iterative constructs in Scheme, most of the power comes from writing recursive functions This is fairly straightforward if you want the procedure to be global: (define factorial (lambda (n) (if (= n 0) 1 (* n (factorial (- n 1)))))) (factorial 5) » 120 Recursion and Iteration 22 The trick Must find the structure of the problem: • How can I break it down into sub-problems? • What are the base cases? Recursion and Iteration 23 List Processing Suppose I want to search a list for the max value? A standard list: lst 3 7 4 1 What is the base case? What state do I need to maintain? Recursion and Iteration 24 More list processing How about a list of X-Y pairs? • What would the list look like? Given X, find Y: • Base case • State • Helper procedures? lst 3 Recursion and Iteration 5 7 2 4 3 1 9 25 Using Recursion Write avg.scheme See list-position.scheme See count_atoms.scheme See printtype.scheme Recursion and Iteration 26 Mutual Recursion (define is-even? (lambda (n) (if (= n 0) #t (is-odd? (- n 1))))) (define is-odd? (lambda (n) (if (= n 0) #f (is-even? (- n 1))))) Note: Scheme has built-in primitives even? and odd? Recursion and Iteration 27 Recursion and Local Definitions Recursion gets more difficult when you want the functions to be local in scope: local-odd? is not yet defined (let ((local-even? (lambda (n) (if (= n 0) #t (local-odd? (- n 1))))) (local-odd? (lambda (n) (if (= n 0) #f (local-even? (- n 1)))))) (list (local-even? 23) (local-odd? 23))) Can we fix this with let* ? Recursion and Iteration 28 letrec letrec – the variables introduced by letrec are available in both the body and the initializations • Custom made for local recursive definitions (letrec ((local-even? (lambda (n) (if (= n 0) #t (local-odd? (- n 1))))) (local-odd? (lambda (n) (if (= n 0) #f (local-even? (- n 1)))))) (list (local-even? 23) (local-odd? 23))) Recursion and Iteration 29 Recursion for loops (letrec ((countdown (lambda (i) (if (= i 0) 'liftoff (begin (display i) (newline) (countdown (- i 1))))))) (countdown 10)) Instead of a for loop, this letrec uses a recursive procedure on the variable i, which starts at 10 in the procedure call. Recursion and Iteration 30 Named let ((let countdown ((i 10)) (if (= i 0) 'liftoff (begin (display i) (newline) (countdown (- i 1))))) This is the same as the previous definition, but written more compactly Named let is useful for defining and running loops Recursion and Iteration 31 Recursion and Stack Frames What happens when we call a recursive function? Consider the following Fibonacci function: int fib(int N) { int prev, pprev; if (N == 1) { return 0; } else if (N == 2) { return 1; } else { prev = fib(N-1); pprev = fib(N-2); return prev + pprev; } } Recursion and Iteration 32 Stack Frames Each call to Fib produces a new stack frame 1000 recursive calls = 1000 stack frames! Inefficient relative to a for loop Recursion and Iteration pprev prev N pprev prev N 33 How can we fix this inefficiency? Here’s an answer from the Cal-Tech slides: Recursion and Iteration 34 Example Recall from last time: (define (sum-integers n) (if (= n 0) 0 (+ n (sum-integers (- n 1))))) Recursion and Iteration 35 Revisited (summarizing steps) Evaluate: (sum-integers 3) (if (= 3 0) 0 (+ 3 (sum-integers (- 3 1)))) (if #f 0 (+ 3 (sum-integers (- 3 1)))) (+ 3 (sum-integers (- 3 1))) (+ 3 (sum-integers 2)) (+ 3 (if (= 2 0) 0 (+ 2 (sum-integers (- 2 1))))) (+ 3 (+ 2 (sum-integers 1))) (+ 3 (+ 2 (if (= 1 0) 0 (+ 1 (sum-integer (- 1 1)))))) Recursion and Iteration 36 Revisited (summarizing steps) (+ 3 (+ 2 (+ 1 (sum-integers 0)))) (+ 3 (+ 2 (+ 1 (if (= 0 0) 0 …)))) (+ 3 (+ 2 (+ 1 (if #t 0 …)))) (+ 3 (+ 2 ( + 1 0))) … 6 Recursion and Iteration 37 Evolution of computation (sum-integers 3) (+ 3 (sum-integers 2)) (+ 3 (+ 2 (sum-integers 1))) (+ 3 (+ 2 (+ 1 (sum-integers 0)))) (+ 3 (+ 2 (+ 1 0))) Recursion and Iteration 38 What can we say about the computation? (sum-integers 3) (+ 3 (sum-integers 2)) (+ 3 (+ 2 (sum-integers 1))) (+ 3 (+ 2 (+ 1 (sum-integers 0)))) (+ 3 (+ 2 (+ 1 0))) Recursion and Iteration On input N: How many calls to sumintegers? N+1 How much work per call? 39 Linear recursive processes (sum-integers 3) (+ 3 (sum-integers 2)) (+ 3 (+ 2 (sum-integers 1))) (+ 3 (+ 2 (+ 1 (sum-integers 0)))) (+ 3 (+ 2 (+ 1 0))) time = C1 + N*C2 Recursion and Iteration This is what we call a linear recursive process Makes linear # of calls • i.e. proportional to N Keeps a chain of deferred operations linear in size w.r.t. input • i.e. proportional to N 40 Another strategy To sum integers… add up as we go along: 1 2 3 4 5 6 7… 1 1+2 1+2+3 1+2+3+4 ... 1 3 6 10 15 21 28 … Recursion and Iteration 41 Alternate definition (define (sum-int n) (sum-iter 0 n 0)) ;; start at 0, sum is 0 (define (sum-iter current max sum) (if (> current max) sum (sum-iter (+ 1 current) max (+ current sum)))) Recursion and Iteration 42 Evaluation of sum-int (define (sum-int n) (sum-iter 0 n 0)) (define (sum-iter current max sum) (if (> current max) sum (sum-iter (+ 1 current) max (+ current sum)))) Recursion and Iteration (sum-int 3) (sum-iter 0 3 0) 43 Evaluation of sum-int (define (sum-int n) (sum-iter 0 n 0)) (define (sum-iter current max sum) (if (> current max) sum (sum-iter (+ 1 current) max (+ current sum)))) Recursion and Iteration (sum-int 3) (sum-iter 0 3 0) (if (> 0 3) … ) (sum-iter 1 3 0) 44 Evaluation of sum-int (define (sum-int n) (sum-iter 0 n 0)) (define (sum-iter current max sum) (if (> current max) sum (sum-iter (+ 1 current) max (+ current sum)))) Recursion and Iteration (sum-int 3) (sum-iter 0 3 0) (if (> 0 3) … ) (sum-iter 1 3 0) (if (> 1 3) … ) (sum-iter 2 3 1) 45 Evaluation of sum-int (define (sum-int n) (sum-iter 0 n 0)) (define (sum-iter current max sum) (if (> current max) sum (sum-iter (+ 1 current) max (+ current sum)))) Recursion and Iteration (sum-int 3) (sum-iter 0 3 0) (if (> 0 3) … ) (sum-iter 1 3 0) (if (> 1 3) … ) (sum-iter 2 3 1) (if (> 2 3) … ) (sum-iter 3 3 3) 46 Evaluation of sum-int (define (sum-int n) (sum-iter 0 n 0)) (define (sum-iter current max sum) (if (> current max) sum (sum-iter (+ 1 current) max (+ current sum)))) Recursion and Iteration (sum-int 3) (sum-iter 0 3 0) (if (> 0 3) … ) (sum-iter 1 3 0) (if (> 1 3) … ) (sum-iter 2 3 1) (if (> 2 3) … ) (sum-iter 3 3 3) (if (> 3 3) … ) (sum-iter 4 3 6) 47 Evaluation of sum-int (define (sum-int n) (sum-iter 0 n 0)) (define (sum-iter current max sum) (if (> current max) sum (sum-iter (+ 1 current) max (+ current sum)))) Recursion and Iteration (sum-int 3) (sum-iter 0 3 0) (if (> 0 3) … ) (sum-iter 1 3 0) (if (> 1 3) … ) (sum-iter 2 3 1) (if (> 2 3) … ) (sum-iter 3 3 3) (if (> 3 3) … ) (sum-iter 4 3 6) (if (> 4 3) … ) 6 48 What can we say about the computation? (sum-int 3) (sum-iter 0 3 0) (sum-iter 1 3 0) (sum-iter 2 3 1) (sum-iter 3 3 3) (sum-iter 4 3 6) 6 Recursion and Iteration on input N: How many calls to sumiter? N+2 How much work per call? 49 What can we say about the computation? (sum-int 3) (sum-iter 0 3 0) (sum-iter 1 3 0) (sum-iter 2 3 1) (sum-iter 3 3 3) (sum-iter 4 3 6) 6 on input N: How many calls to sumiter? N+2 How much work per call? constant one comparison, one if, two additions, one call How many deferred operations? none Recursion and Iteration 50 What’s different about these computations? Old (sum-integers 3) (+ 3 (sum-integers 2)) (+ 3 (+ 2 (sum-integers 1))) (+ 3 (+ 2 (+ 1 (sum-integers 0)))) (+ 3 (+ 2 (+ 1 0))) Recursion and Iteration New (sum-int 3) (sum-iter 0 3 0) (sum-iter 1 3 0) (sum-iter 2 3 1) (sum-iter 3 3 3) (sum-iter 4 3 6) 51 Linear iterative processes • • • • • • • (sum-int 3) (sum-iter 0 3 0) (sum-iter 1 3 0) (sum-iter 2 3 1) (sum-iter 3 3 3) (sum-iter 4 3 6) 6 time = C1 + N*C2 Recursion and Iteration This is what we call a linear iterative process Makes linear # calls State of computation kept in a constant # of state variables • state does not grow with problem size • requires a constant amount of storage space 52 Linear recursive vs linear iterative Both require computational time which is linear in size of input L.R. process requires space that is also linear in size of input • why? L.I. process requires constant space • not proportional to size of input • more space-efficient than L.R. process Recursion and Iteration 53 Linear recursive vs linear iterative Why not always use linear iterative instead of linear recursive algorithm? • often the case in practice Recursive algorithm sometimes much easier to write • and to prove correct Sometimes space efficiency not the limiting factor Recursion and Iteration 54 Tail-call elimination ((let countdown ((i 10)) (if (= i 0) 'liftoff (begin (display i) (newline) (countdown (- i 1))))) Countdown uses only tail-recursion • Each call to countdown either does not call itself again, or does it as the very last thing done Scheme recognizes tail recursion and converts it to an iterative (loop) construct internally Recursion and Iteration 55 Mapping a procedure across a list (map add2 '(1 2 3)) » (3 4 5) (map cons '(1 2 3) '(10 20 30)) » ((1 . 10) (2 . 20) (3 . 30)) (map + '(1 2 3) '(10 20 30)) » (11 22 33) Recursion and Iteration 56 Exercises Include error checking into avg.scheme • Divide by zero error • Non-numeric list items Write a scheme function that accepts a single numeric argument and returns all numbers less than 10 that the argument is divisible by Factor.scheme – described in class Write a scheme function that returns the first n Fibonacci numbers as a list (n is an argument) Recursion and Iteration 57