CS 3 Spring, 1994 Midterm 2 solutions 1. What's the value? > (map (lambda (x) (+ 3 (cadr x))) '((1 2 3) (4 5 6) (7 8 9))) (5 8 11) This is equivalent to (list (+ 3 (cadr '(1 2 3))) (+ 3 (cadr '(4 5 6))) (+ 3 (cadr '(7 8 9)))) > (append 'for '(no one)) ERROR The arguments to append must be lists, not words. (Some versions of Scheme will let you get away with a word as the last argument to append, but not for anything before the last one. Never mind why, until you take 61A. For our purposes, they all have to be lists.) > ((lambda (a) (cons (cadr a) a)) '(w x y z)) (X W X Y Z) This is equivalent to (cons (cadr '(w x y z)) '(w x y z)) Most people got it, but the common errors were to think it returned a procedure (not noticing that the lambda expression is the first subexpression of a larger expression) or to throw in some extra parentheses, e.g., ((X) W X Y Z). A few people had A as part of their answer; A is a formal parameter, not data! > (first (bf (cadr (cadr '((john lennon) (paul mccartney) (george harrison) (ringo starr)))))) C (cadr '((j l) (p m) (g h) (r s))) ==> (paul mccartney) (cadr '(paul mccartney)) ==> mccartney (bf 'mccartney) ==> ccartney (first 'ccartney) ==> c Scoring: 1/2 point each, rounding up. 2. Geography (define (geography? sent) (cond ((<= (count sent) 1) #t) ((equal? (last (first sent)) (first (first (bf sent)))) (geography? (bf sent))) (else #f))) Scoring: 4 correct, 2-3 has the idea, 1 has an idea, 0 other. Typical 3-point answers: #t/#f backwards, just empty sent as base case, (caadr sent) instead of (first (first (bf sent))) [because you can't take car of a word!], just (first (bf sent)) instead of two firsts. Typical 2-point answers: (first (last sent)) instead of (first (first (bf sent))), (bf (bf sent)) in the recursive call [which would only look at every other word pair], or writing a procedure that returns true if ANY pair of words is okay, rather than if ALL of them are okay. Typical 1-point or 0-point answers aren't predicates (don't return #t or #f) or were just bizarre. Strictly speaking, the program should test for both empty sentences and one-word sentences as base cases. If you only have empty sentences as the base case, your program will blow up when you try to take (first (bf sent)). If you only have one-word sentences, then your program will work fine unless you are actually given an empty sentence as argument, so we didn't take off for that case. 3. Maprest. The solution we wanted is (define (maprest fn lst) (if (null? lst) '() (cons (fn lst) (maprest fn (cdr lst))))) There really is no other good way to do this. Compare the definition of map (page 328 of the text): (define (map fn lst) (if (null? lst) '() (cons (fn (CAR lst)) (map fn (cdr lst))))) The only difference is that for maprest you leave out the CAR that's in capital letters in map above. Think about it -- map applies fn to the car of its argument list; maprest applies fn to the whole list! Many people tried to do it by creating a list of lists and then either using or rewriting map: (define (maprest fn lst) ;; uses HOF (map fn (tails lst))) (define (tails lst) (if (null? lst) '() (cons lst (tails (cdr lst))))) This solution is correct apart from violating the rule about no HOFs, but it does a lot more work than necessary. Many other people had the same idea, but tried to do it all in one procedure: (define (maprest fn lst) ;; wrong!! (if (null? lst) '() (cons (fn (car (tails lst))) (maprest fn (cdr (tails lst)))))) or similar constructions. What these wrong solutions have in common is that maprest expects a list of elements, but you call it recursively with a list of tails. Then a list of tails of lists of tails, etc. If you don't see why this is wrong, trace it! Typical 3-point answer: bf instead of cdr. (We said it should work for lists, not sentences or words.) Typical 2-point answer: Wrong combiner -- list or append or se instead of cons; or correct answer using HOF. Typical 1-point answer: Taking the car of lst, so you essentially reinvented map. 4. Smallest-sum People seemed to have a lot of trouble with this problem. One way to approach a problem like this is to try writing it with HOFs, even though the problem says not to, and then translate. So, the hint says "choose the subtree with the smallest smallest-sum" and that leads me to this procedure: (define (smallest-sum tree) ;; correct but uses HOFs (if (leaf? tree) (datum tree) (+ (datum tree) (reduce min (map smallest-sum (children tree)))))) To eliminate the use of higher-order functions we must write a version with mutual recursion, like the example on page 302. Combining reduce with map is sort of like the Combining Patterns examples on pages 220-221. (define (smallest-sum tree) (if (leaf? tree) (datum tree) (+ (datum tree) (smallest-sum-forest (children tree))))) (define (smallest-sum-forest forest) (if (null? (cdr forest)) (smallest-sum (car forest)) (min (smallest-sum (car forest)) (smallest-sum-forest (cdr forest))))) Many, many people just copied count-leaves from page 302. C'mon, folks, if that program counts the number of leaves in a tree, it's not suddenly going to find the smallest path from the root to a leaf just because you change its name! For example, count-leaves pays no attention to the actual data in the tree. (That is, it doesn't invoke datum anywhere.) That obviously won't work here. How come count-leaves has a + in the forest helper, but smallest-sum does its + in the top-level procedure? Well, draw a picture of a tree and circle the leaves. You'll see that to find the leaves you must move horizontally across the bottom of the tree. Now look at the picture in the exam problem, and you'll see that to get the answer you must move vertically, adding the data along a path from the root to a particular leaf node. (Don't just write down "horizontal=forest, vertical=tree" in your notes! Work through both programs by hand and convince yourself of why they make sense.) If you can't quite see how to get from the HOF version to the one with smallest-sum-forest, you could handle the map and the reduce separately, using the patterns in Chapter 14. (A few people just wrote map themselves, and we let them get away with it, but if you know how to write map you should know how to specialize it to work with a particular function!) (define (smallest-sum tree) (if (leaf? tree) (datum tree) (+ (datum tree) (minimize (all-smallest-sums (children tree)))))) (define (all-smallest-sums forest) (if (null? forest) '() (cons (smallest-sum (car forest)) (all-smallest-sums (cdr forest))))) (define (minimize nums) (if (null? (cdr nums)) (car nums) (min (car nums) (minimize (cdr nums))))) All-smallest-sums is exactly like the EVERY examples on page 216, except that the combiner is CONS, as in the definition of MAP on page 328. (Don't get the idea that you're supposed to flip wildly through the textbook during the test! You're supposed to understand *why* cons is the combiner for map, and be able to figure it out when you have to.) Minimize is just like sent-max on page 20, but for lists instead of sentences. The only tricky part is that the base case isn't an empty list, because there's no identity element for min. See the top of page 220, and all of page 330. Scoring: 5 correct, 3-4 has the idea, 1-2 has an idea, 0 other. Having the idea includes knowing the difference between a tree and a forest, so you don't say things like (smallest-sum (cdr forest)), and actually taking the min of something. An otherwise correct answer using higher-order procedures got 3 points. One way to get zero points was to build the specific example into the program. The worst of these was a program that added 10 (the value at the root of our example tree) to something, but several other people had programs that only worked if every branch node has exactly three children!