Relate missionaries & cannibals to the airplane-path finding: They are both looking for a path in a network. One difference: in find-path we saw the whole map all at once. In miss/cann, we had to generate the portions of the map as we came to them. We also created multiple copies of the same state. (Advanced problem (comp212): how to avoid copies .. efficiently?) Another difference: breadth-first (miss/cann) vs depth-first. Which guarantees finding the shortest path? Both have their uses; see comp212, comp314.
Finish up the class example from last time: we got find-path to work by marking cities previously seen. But -- when we try to run it again, it doesn't find the path!
(define data (vector 'four 'score 'and 'twenty 'years 'ago)) (vector-ref data 2) = 'score (vector-ref data 6) : error (define nums (vector 10 12 14 16 18))Note that in comments, we'll often write data[i] rather than
(vector-ref data i)
.
Write a function:
;; sum-vec: vector(v), natNum(n) --> num ;; Return the sum of the first n elements of v: i.e., v[n-1]...v[0]
When should you use vectors of info, as opposed to lists of info?
Two other ways to construct vectors:
(make-vector [n] [val] ) (build-vector [n] [rule])Ex: Create a vector of 4 elements, so that the i'th element is i^2.
Return exams. See answer key, soln.
A couple notes: waterways (#3) follows template directly.
(cond [(sww? a-ww) ..(first a-ww)..(rest a-ww)..)] ..)
Note that sww?
is a better question than list?
,
because it better reflects the problem.
Note sub-problems: you don't know there is even a first/rest.
Defer list-of-ww-processing to a different function than the one doing
ww-processing.
(make-sww (rest-aww))
,
so they could recur on input of the same type.
This is a bit better, but highly kludgy:
the plumbers don't think of it that way!
They think of one big serial sequence.
(And they might easily have serials in serial:
kitchin might be fridge-sink-disposal-dishwasher in serial;
where fridge is itself intake-dispenser-outgo in serial
(all of which are themselves similar).
Note that this is modeling the problem correctly,
in terms of waterways.
(define (build-vector sz rule) (local [(define ans-vec (make-vector sz #f)) (define (fill-vec! v n rule) ...)] (begin (fill-vec! ans-vec sz rule) ans-vec)))
It just remains to write fill-vec!: vec,natNum,(natNum-->value) --> (void) which sets the first n elements according to "rule":
(define (fill-vec! v n rule) (cond [(zero? n) (void)] [else (begin (vector-set! v (sub1 n) (rule (sub1 n))) (fill-vec! v (sub1 n) rule))]))(Is this tail-recursive? Yep.)
Could we write this going from 0 up to n-1, instead of n downto 0? Sure!
(define (fill-vec! v n rule) (fill-vec-helper! v 0 n rule)) (define (fill-vec-helper! v i n rule) (if (>= i n) (void) (begin (vector-set! v i (rule i)) (fill-vec-helper! v (add1 i) n rule))))Note, there are really two different things here:
Hey, let's separate these two things explicitly.
Example:
Write some code which takes a vector "nums" of numbers,
and triples each value:
(fori= 0 (vector-length nums) (lambda (i) (vector-set! nums i (* 3 (vector-ref nums i))))) ;; fori=: num, num, (num-->(void)) --> (void) ;; (define (fori= start-val stop-val body!) (if (>= start-val stop-val) (void) (begin (body! i) (fori= (add1 start-val) stop-val body!))))
Think about: what if we frequently wanted to do something
to every other number:
should we modify the task (body!), or the control-logic in charge
of applying body! to the right values? Both ways work; the latter is better.
Think about: what if we wanted to cumulate the vector?
What if we want to triple every-other number?
(Or, we wanted to cumulate downward?)
A more general version of fori=
:
give it a starting value, a way to generate the next number,
and a way to know when it's done:
/* WHETHER TO CONTINUE (for C analogy) */
Example: (fori= n zero? sub1 ...body...)
You write: a version which uses this new improved fori= to
call the body with every-other value of i:
What is the stopping condition? How to get the next value?
(fori= 0 (lambda (i) (>= i (vector-length nums))) (lambda (i) (+ i 2)) ...body...) (define (fori= start stop? next body!) (if (stop? start) (void) (begin (body! start) (fori= (next start) stop? next body!))))We'll return to this in a while, to use fori= to even help with non-vectors.
Do we need to use mutation, to make use of a for-loop? No, we actually have been doing stuff all along which didn't: Recall:
(countdown 3) = (list 3 2 1 'blastoff) (triangle-number 3) = 3 + 2 + 1 = 6 (define (countdown n) (fori=-acc 0 n '(blastoff) (lambda (i so-far) (cons (add1 i) so-far)))) (define (triangle-number n) (fori=-acc 1 (add1 n) 0 (lambda (i so-far) (+ i so-far))))Exercise: write a version of
fori=
which separates the work that you're doing
from of doing that work with i, then with i-1, ..., then with 1, then with 0.
(Note here, the sum is initially 0).
(define (fori=-acc strt stop init body ) (cond [(>= stop start) init] [else (fori=-acc (add1 strt) stop body (body strt init))]))Let's see how it actually works: A hand-evaluation quickly shows:
(countdown 3) = (fori=-acc 0 3 '(blastoff) (lambda (i so-far) (cons (add1 i) so-far))) = (fori=-acc 1 3 '(1 blastoff) (lambda (i so-far) (cons (add1 i) so-far))) = (fori=-acc 2 3 '(2 1 blastoff) (lambda (i so-far) (cons (add1 i) so-far))) = (fori=-acc 3 3 '(3 2 1 blastoff) (lambda (i so-far) (cons (add1 i) so-far))) = '(3 2 1 blastoff)We can use this to write, for example, functions from missionaries and cannibals:
;; boatloads<=n: num --> list-of-boatloads ;; Return all boatloads containing n or fewer people. ;; ;; Implementation: ;; Make a boatloads with exactly k people, for all k in [1..n]. ;; (define (boatloads<= n) (fori=-accum 1 (add1 n) empty (lambda (i so-far) (append (boatloads= i) so-far)))) ;; boatloads=: num --> list-of-boatloads ;; Return all boatloads containing exactly k people. ;; ;; Implementation: ;; make a boatload with i missionaries and k-i cannibals, for i in [1..k]. ;; (define (boatloads= k) (fori=-accum 1 (add1 k) empty (lambda (i so-far) (cons (make-boatload i (- k i)) so-far))))
What are the parts in common here? That is, what should be the argumetns to "while"?
(define (while done? generate-next-val curr-val) (cond [(done? curr-val) curr-val] [else (while done? generate-next-val (generate-next-val curr-val))]))For fun, let's use "local" to avoid repeating arguments:
(define (while done? generate-next-val curr-val) (local [(define (while-helper curr) (cond [(done? curr) curr] [else (while-helper (generate-next-val curr))]))] (while-helper curr-val))) ; Example of using while: (define (mergesort-bottomup lolon)
(while length>1? merge-all-pairs lolon))
;; NOTE: BAD EXAMPLE:
Let's try merge-all-pairs as a while-loop:
(define (merge-all-pairs lolon) (while length>1? (lambda (lolon) (cons (merge (first lolon) (second lolon)) (merge-all-pairs (rest (rest lolon))))) lolon))This doesn't turn out so cleanly:
while
does nothing -- exceedingly misleading!)
The problem is that in the generative template,
we created a subproblem and solved that one subproblem
(merging the first two),
but that's not our final answer!
(we need to structurally recur down the entire list).
It suggests that that work is mostly structural, not generative.
It could be worked around of course -- we could have "curr" be not
just a lolon, but a lolon and also the "next stage" being built up.
That's essentially a second accumulator.
Hmmm -- this is actually the generalization of the template for generative recursion! when there is only one sub-problem. You could write super-while, which took in done?, curr, the n functions to create n subproblems (recur on each of those), and one more function to put together everything.
Thus we see that for-loops and while-loops are both specail cases
of recursion: in particular, for loops are more idiomatic for
structural recursion, and while loops more idiomatic for generative
recursion.
(Something I'd never noticed while using languages which have this built-in;
a language which separates these issues outs helps me better understand
programming!)
For Those Who Are Bored:
Okay, end the digression on "while".
;; (no separate code for this lecture, yet.)