Title: Recursion and Iteration Let the entertainment begin
1Recursion and IterationLet the entertainment
begin
CS 480/680 Comparative Languages
2No Iteration??
- Yes, its true, there is no iteration in Scheme.
So, how do we achieve simple looping constructs?
Heres a simple example from Cal-Tech
3Summing integers
- How do I compute the sum of the first N integers?
4Decomposing 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
5to 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))))
6Recursion 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?
7Almost
- Whats wrong?
- (define (sum-integers n)
- ( n (sum-integers (- n 1))))
- Gives us
N N-1 1 0 -1 -2
8Debugging
- Fixing the problem
- it doesnt stop at zero!
- Revised
- (define (sum-integers n)
- (if ( n 0)
- 0
- ( n (sum-integers (- n 1)))))
- How does this evaluate?
9Warning
- 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.
10Evaluating
- 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))))
11Evaluating
- Evaluate (if ( 3 0) 0 ( 3 (sum-integers (- 3
1)))) - evaluate ( 3 0)
- evaluate 3 ? 3
- evaluate 0 ? 0
- evaluate ?
- apply to 3, 0 ? f
- since expression is false,
- replace with false clause
- ( 3 (sum-integers (- 3 1)))
- evaluate ( 3 (sum-integers (- 3 1)))
12Evaluating
- 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 ))
13Evaluating
- 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 )
14Evaluating
Pending ( 3 )
- apply (lambda (n)
- (if ( n 0)
- 0
- ( n (sum-integers (- n 1)))))
to 2 - ? (if ( 2 0) 0 ( 2 (sum-integers (- 2 1)))
15Evaluating
Pending ( 3 )
- 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)))
16Evaluating
Pending ( 3 )
- 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
Note pending ( 3 ( 2 ))
17Evaluating
Pending ( 3 ( 2 ))
- 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
Note pending ( 3 ( 2 ( 1 0)))
18Evaluating
Pending ( 3 ( 2 ( 1 0)))
- 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))
19Evaluating
Pending ( 3 ( 2 1))
- 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)
20Evaluating
- Finish pending
- ( 3 3)
- final result 6
21Yeah!!!
- Substitution model
- works fine for recursion.
- Recursive calls are well-defined.
- Careful application of model shows us what they
mean and how they work.
22Recursive 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
23The trick
- Must find the structure of the problem
- How can I break it down into sub-problems?
- What are the base cases?
24List Processing
- Suppose I want to search a list for the max
value? - A standard list
- What is the base case?
- What state do I need to maintain?
lst
3
7
4
1
25More 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?
26Using Recursion
- Write avg.scheme
- See list-position.scheme
- See count_atoms.scheme
- See printtype.scheme
27Mutual 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?
28Recursion 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 ?
29letrec
- 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)))
30Recursion 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.
31Named 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
32Recursion 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
33Stack Frames
- Each call to Fib produces a new stack frame
- 1000 recursive calls 1000 stack frames!
- Inefficient relative to a for loop
pprev
prev
N
pprev
prev
N
34- How can we fix this inefficiency?
- Heres an answer from the Cal-Tech slides
35Example
- Recall from last time
- (define (sum-integers n)
- (if ( n 0)
- 0
- ( n (sum-integers (- n 1)))))
36Revisited (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))))))
37Revisited (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
38Evolution of computation
- (sum-integers 3)
- ( 3 (sum-integers 2))
- ( 3 ( 2 (sum-integers 1)))
- ( 3 ( 2 ( 1 (sum-integers 0))))
- ( 3 ( 2 ( 1 0)))
39What can we say about the computation?
- On input N
- How many calls to sum-integers?
- N1
- How much work per call?
- (sum-integers 3)
- ( 3 (sum-integers 2))
- ( 3 ( 2 (sum-integers 1)))
- ( 3 ( 2 ( 1 (sum-integers 0))))
- ( 3 ( 2 ( 1 0)))
40Linear recursive processes
- 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
- (sum-integers 3)
- ( 3 (sum-integers 2))
- ( 3 ( 2 (sum-integers 1)))
- ( 3 ( 2 ( 1 (sum-integers 0))))
- ( 3 ( 2 ( 1 0)))
41Another strategy
- To sum integers
- add up as we go along
- 1 2 3 4 5 6 7
- 1 12 123 1234 ...
- 1 3 6 10 15 21 28
42Alternate definition
- (define (sum-int n)
- (sum-iter 0 n 0)) start at 0, sum
is 0 - (define (sum-iter current max sum)
- (if (gt current max)
- sum
- (sum-iter ( 1 current)
- max
- ( current sum))))
43Evaluation of sum-int
- (sum-int 3)
- (sum-iter 0 3 0)
- (define (sum-int n)
- (sum-iter 0 n 0))
- (define (sum-iter current
- max
- sum)
- (if (gt current max) sum
- (sum-iter ( 1 current)
- max
- ( current
- sum))))
44Evaluation of sum-int
- (sum-int 3)
- (sum-iter 0 3 0)
- (if (gt 0 3) )
- (sum-iter 1 3 0)
- (define (sum-int n)
- (sum-iter 0 n 0))
- (define (sum-iter current
- max
- sum)
- (if (gt current max) sum
- (sum-iter ( 1 current)
- max
- ( current
- sum))))
45Evaluation of sum-int
- (sum-int 3)
- (sum-iter 0 3 0)
- (if (gt 0 3) )
- (sum-iter 1 3 0)
- (if (gt 1 3) )
- (sum-iter 2 3 1)
- (define (sum-int n)
- (sum-iter 0 n 0))
- (define (sum-iter current
- max
- sum)
- (if (gt current max) sum
- (sum-iter ( 1 current)
- max
- ( current
- sum))))
46Evaluation of sum-int
- (sum-int 3)
- (sum-iter 0 3 0)
- (if (gt 0 3) )
- (sum-iter 1 3 0)
- (if (gt 1 3) )
- (sum-iter 2 3 1)
- (if (gt 2 3) )
- (sum-iter 3 3 3)
- (define (sum-int n)
- (sum-iter 0 n 0))
- (define (sum-iter current
- max
- sum)
- (if (gt current max) sum
- (sum-iter ( 1 current)
- max
- ( current
- sum))))
47Evaluation of sum-int
- (sum-int 3)
- (sum-iter 0 3 0)
- (if (gt 0 3) )
- (sum-iter 1 3 0)
- (if (gt 1 3) )
- (sum-iter 2 3 1)
- (if (gt 2 3) )
- (sum-iter 3 3 3)
- (if (gt 3 3) )
- (sum-iter 4 3 6)
- (define (sum-int n)
- (sum-iter 0 n 0))
- (define (sum-iter current
- max
- sum)
- (if (gt current max) sum
- (sum-iter ( 1 current)
- max
- ( current
- sum))))
48Evaluation of sum-int
- (sum-int 3)
- (sum-iter 0 3 0)
- (if (gt 0 3) )
- (sum-iter 1 3 0)
- (if (gt 1 3) )
- (sum-iter 2 3 1)
- (if (gt 2 3) )
- (sum-iter 3 3 3)
- (if (gt 3 3) )
- (sum-iter 4 3 6)
- (if (gt 4 3) )
- 6
- (define (sum-int n)
- (sum-iter 0 n 0))
- (define (sum-iter current
- max
- sum)
- (if (gt current max) sum
- (sum-iter ( 1 current)
- max
- ( current
- sum))))
49What can we say about the computation?
- on input N
- How many calls to sum-iter?
- N2
- How much work per call?
- (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
50What can we say about the computation?
- on input N
- How many calls to sum-iter?
- N2
- How much work per call?
- constant
- one comparison, one if, two additions, one call
- How many deferred operations?
- none
- (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
51Whats different about these computations?
New
Old
(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)
- (sum-integers 3)
- ( 3 (sum-integers 2))
- ( 3 ( 2 (sum-integers 1)))
- ( 3 ( 2 ( 1 (sum-integers 0))))
- ( 3 ( 2 ( 1 0)))
52Linear iterative processes
- 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
- (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
53Linear 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
54Linear 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
55Tail-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
56Mapping 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)
57Exercises
- 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)