CSC 151.01, Class 23: Naming local procedures
Overview
- Preliminaries
- Notes and news
- Upcoming work
- Extra credit
- Questions
- Review of reading
- Lab
- Debrief
Preliminaries
News / Etc.
- New partners!
- Happy pi day!
- We will have one mentor session this week at 7pm on Thursday. The intent is more of an opportunity to talk broadly about CS than about the quiz. (A panel of mentors.)
- RIP Stephen Hawking.
- I hope to distribute an answer key to HW6 tonight.
- You are awesome.
Upcoming work
- Lab writeup for class 23: Exercise 2. Due before class Friday..
- Reading for Friday
- No homework over break.
- Flash Cards due TONIGHT.
- Quiz Friday: Identify your classmates.
Extra credit (Academic/Artistic)
- Visit the two exhibits at the Faulconer Gallery.
Extra credit (Peer)
Extra credit (Recurring peer)
- Listen to KDIC Wednesdays at 6pm - Witty banter with other
personalities and/or co-host. Also Indian, Arabic, and Farsi music.
(Up to two units of extra credit.) - Peer editing with SS. Talk to SS about the details. Make your English Lit more literate.
Extra credit (Misc)
- Host one or more prospective students.
Other good things
Questions
How do I decide whether to use direct recursion or helper recursion?
Use helper recursion if (a) your analysis of the problem suggests that an extra parameter will be useful; (b) you find it particularly natural to think about recursion in terms of building up a solution as you go; or (c) the “draw a table” approach seems productive to you.
Use direct recursion if the solution to a “simpler” version of the problem leads naturally to a solution to the whole problem. “Trust the magic recursion fairy.”
When I’m trying to build lists that appear in the same order as the original, I am more likely to use direct recursion.
Can we do an example?
Sure. We did
tally-oddsandiotaon the board. Here’s our incorrect version ofiota
(define iota
(lambda (n)
(iota-helper '() 0 n)))
(define iota-helper
(lambda (so-far i n)
(if (>= i n)
so-far
(iota-helper (cons so-far i)
(+ 1 i)
n))))
That one didn’t work so well.
(define iota
(lambda (n)
(iota-helper '() 0 n)))
(define iota-helper
(lambda (so-far i n)
(if (>= i n)
so-far
(iota-helper (cons so-far i)
(+ 1 i)
n))))
We realized that
constakes a value and a list, not a list and a value.
(define iota
(lambda (n)
(iota-helper '() 0 n)))
(define iota-helper
(lambda (so-far i n)
(if (>= i n)
so-far
(iota-helper (cons i so-far)
(+ 1 i)
n))))
When we tried it, we found that we got the values in reverse order.
> (iota 5)
'(4 3 2 1 0)
So we added a call to
reverse
(define iota
(lambda (n)
(reverse (iota-helper '() 0 n))))
We tried using
appendinstead.
(define iota
(lambda (n)
(iota-helper '() 0 n)))
(define iota-helper
(lambda (so-far i n)
(if (>= i n)
so-far
(iota-helper (append so-far (list i))
(+ 1 i)
n))))
This version worked fine. But Sam said “I don’t like the use of
appendin recursive procedures.”
Sam also noted that this may be a case in which direct recursion is better.
I hear that you don’t like append. Why?
It’s similar to the
(= (length lst) 1)issue.appendturns out to be an expensive operation. Let’s look at how we might write it.
(define join-two-lists (lambda (lst1 lst2) ...))
I’m building a list, so I likely want to try direct recursion. I’ll need to figure out which list to simplify.
We recurse over the first list. If the recursive case works, we can then use
consto add the first thing back in.
When are we done? When the first list is empty.
(join-two-lists null some-list)gives backsome-list
; Just like append, but we defined it ourselves
(define join-two-lists
(lambda (lst1 lst2)
(if (null? lst1)
lst2
(cons (car lst1)
(join-two-lists (cdr lst1) lst2)))))
Why does this work?
(join-two-lists '(3 1 4) '(1 5 9))
=> (cons 3 (join-two-lists '(1 4) '(1 5 9)))
=> (cons 3 (cons 1 (join-two-lists '(4) '(1 5 9))))
=> (cons 3 (cons 1 (cons 4 (join-two-lists '() '(1 5 9)))))
=> (cons 3 (cons 1 (cons 4 '(1 5 9))))
=> (cons 3 (cons 1 '(4 1 5 9)))
=> (cons 3 '(1 4 1 5 9))
=> '(3 1 4 1 5 9)
Now let’s go back to
iotaand assume we wrote it with append.
(append '() (list 0))- Fast. One call to cons to build(list 0)
(append '(0) (list 1))- One call to cons to build(list 1)One recursive call in append gives another cons. Two calls to cons.
(append '(0 1) (list 2))- Three calls to cons
(append '(0 1 2) (list 3))- four calls to cons
To build
'(0 1 2 3)took ten call tocons.
To build
'(0 1 2 3 4)took fifteen calls tocons.
To build
'(0 1 2 3 4 5)took twenty-one calls tocons.
To build
(iota 100)will take approximately 5050 calls tocons. In contrast, our model withreversewill take approximately 200 calls. That’s a big difference.
So I should avoid “expensive” operations in the middle of recursive procedures?
In general, yes. Sometimes it’s unavoidable.
Lab
Can we use all and any? Certainly.
Writeup: Exercise 2.
What’s going on with named let?
(let NAME ([PARAM1 VAL1]
[PARAM2 VAL2]
...)
BODY)
Named let is a different way to define/run procedures. It defines a procedure with the given name, parameters, and body. It also calls the procedure with the given values.
Debrief
Next class, we’ll talk a bit more about named let.
Next class, we’ll look at the different ways people approached problem 2, particularly the different places they put the kernel.
(define ff0
(letrec ([kernel ...])
(lambda (lst)
(cond
...))))
(define ff0
(lambda (lst)
(letrec ([kernel ...])
(cond
...))))
(define ff0
(lambda (lst)
(cond
...
[else
(letrec ([kernel ...])
...)])))
Which do you prefer? Why?
Which is easiest to change to named let?
Q from student: Can’t I just use an internal define? Nope.