mt2 - CS 61A Fall 2001 Midterm 2 solutions 1. Box and...

Info iconThis preview shows page 1. Sign up to view the full content.

View Full Document Right Arrow Icon
This is the end of the preview. Sign up to access the rest of the document.

Unformatted text preview: CS 61A Fall 2001 Midterm 2 solutions 1. Box and pointer. Note: Please draw actual boxes, as in the book and the lectures, not XX and X/ as in these ASCII-art solutions. Also, please put in the start arrows! Sometimes it's hard to know where your diagrams are supposed to begin, especially if you use leftward and upward pointing arrows. > (list (cons '(0) '(1)) (append '(2) '(3))) (((0) 1) (2 3)) --->XX------------->X/ | | V V XX--->X/ XX--->X/ | | | | V V V V X/ 1 2 3 | V 0 LIST returns a list with as many elements as it has arguments -- in this case, two elements. The top two pairs in the diagram are the ones generated by LIST. CONS generates exactly one new pair. In this problem, the first pair on the second line of the diagram is the one generated by CONS. Its car is the pair on the third line, which is the list (0); its cdr is the second pair on the second line, which is the list (1). The important thing to see here is that the lists (0) and (1) do not play similar roles in the result, because of the way lists are defined. The car of a pair is a list *element*; the cdr of the same pair is a *sublist*. So the value returned by the CONS invocation is a list of two elements, namely (0) and 1 -- not (0) and (1); not 0 and 1. APPEND strings together the elements of its arguments, so in this case, the result of the APPEND invocation is the list (2 3). There were two common wrong answers to this part. One mistake was to put the last element of a sublist in the cdr of a pair, like this: --->XX------------->X/ ; WRONG! | | V V XX--->1 XX--->3 | | V V X/ 2 | V 0 The numbers 1 and 3 are attached to the cdrs of their parent pairs, rather than to the cars. The other common mistake was to leave out the pair just above the zero, like this: --->XX------------->X/ ; WRONG! | | V V XX--->X/ XX--->X/ | | | | V V V V 0 1 2 3 A less common, but more serious, error was to leave out part of the work by having parentheses in the diagram: --->XX------------->X/ ; WRONG! | | V V XX--->(1) XX--->(3) | | V V (0) 2 There are never any parentheses in a box and pointer diagram. The parentheses in the *printed representation* of a list structure are just a notation to represent *pairs* in the actual structure. > (cdadr '((a (b)) (c ((e) d) f) (g))) (((e) d) f) --->XX------------->X/ | | V V XX--->X/ f | | V V X/ d | V e We didn't take off for including other pairs from the large argument in your picture, as long as there was a clear start arrow showing where the return value starts. The quickest way to solve this problem is to remember that CADR means the second element, and to view CDADR as (CDR (CADR ...)) -- all but the first element of the second element of the argument. Alternatively, we can spell it out step by step: (cdr '((a (b)) (c ((e) d) f) (g))) ==> ((c ((e) d) f) (g)) (car '((c ((e) d) f) (g))) ==> (c ((e) d) f) (cdr '(c ((e) d) f)) ==> (((e) d) f) > (append '(list 1 2) '(3)) (list 1 2 3) --->XX--->XX--->XX--->X/ | | | | V V V V list 1 2 3 This was mainly a question to see if you understand what quoting means! Scoring: One point for each printed result; one point for each diagram. 2. DATUM-FILTER This is a question in which it's essential to understand the domain and range of the procedure you're writing, before you start writing it! The arguments to datum-filter are a predicate and a *tree*. What it returns is *not* a tree, but a sequential list whose elements are DATUMs from the tree. So you should expect to use tree selectors (DATUM and CHILDREN), but a list constructor (CONS or LIST or APPEND). There are several ways this can be written. The most straightforward is to use recursion, with a helper for forests: (define (datum-filter pred tree) (if (pred (datum tree)) (cons (datum tree) (forest-datum-filter pred (children tree))) (forest-datum-filter pred (children tree)))) (define (forest-datum-filter pred forest) (if (null? forest) '() (append (datum-filter pred (car forest)) (forest-datum-filter pred (cdr forest))))) Some notes on the above: 1. There's no such thing as an empty tree (this is different from the convention for binary trees), so there's no need for a NULL? check in datum-filter. But we didn't take points off for including one. 2. Why is it CONS in datum-filter, but APPEND in forest-datum-filter? Because in datum-filter we are sticking exactly one datum onto the front of a list of data. But in forest-datum-filter, both of the arguments to APPEND are *lists* of data, not a single datum. We want to combine the elements of the two lists into one big list of those elements. 3. Datum-filter doesn't use CAR or CDR, because its argument is a tree; forest-datum-filter doesn't use DATUM or CHILDREN, because its argument *isn't* a tree! 4. PRED is called to decide whether or not to include a particular datum, but we're including the datum, not the value returned by PRED! 5. Many people threw in unnecessary tests for special cases, such as leaft nodes, nodes with exactly one child, etc. Often this unnecessary code had errors, losing points! Another solution uses higher order functions: (define (datum-filter pred tree) (let ((kids (flatmap (lambda (c) (datum-filter pred c)) (children tree)))) (if (pred (datum tree)) (cons (datum tree) kids) kids))) I used the LET so that I wouldn't have to type the FLATMAP call twice; it's not strictly necessary. If you didn't remember about FLATMAP (SICP page 123) you could say (let ((kids (accumulate append '() (map ...)))) ...) Some people used car/cdr recursion, applying PRED to the words in the tree. Not only is this a data abstraction violation, it also doesn't work! There is no guarantee that the datum in each node is a word; lists are allowed as data, as in the (SAN FRANCISCO) datum in the world tree. You don't want to apply the predicate to the word SAN and separately to the word FRANCISCO. Another common error was (datum-filter pred (children tree)) ; WRONG! This confuses trees with forests. Scoring: 2 points for visiting all the nodes 2 points for selecting with the predicate 2 points for flattening the result correctly 1 point for respecting the data abstraction. In the 2-point categories, it was possible to get one point for code that was clearly trying to do the right thing, but incorrectly. It's possible for one error to lose points in multiple categories. For example, the (datum-filter pred (children tree)) error lost the two points for visiting all the nodes, but in most cases also lost the points for flattening correctly. If forest-datum-filter calls append, but datum-filter also calls append (instead of the correct call to cons), that lost one point. 3. Debug SIMPLIFY-COND. Here are the two bugs: 1. There is no base case test for an atomic (non-list) expression. 2. When a two-clause-cond-expression is found, its pieces are not recursively simplified. The code should look like this: (define (simplify-cond exp) (cond ((ATOM? EXP) EXP) ((two-clauses-cond-exp? exp) (list 'if (SIMPLIFY-COND (car (cadr exp))) (SIMPLIFY-COND (cadr (cadr exp))) (SIMPLIFY-COND (cadr (caddr exp))))) (else (map simplify-cond exp)))) Alternatively, the second clause can be written as ((two-clauses-cond-exp? exp) (SIMPLIFY-COND (list 'if (car (cadr exp)) (cadr (cadr exp)) (cadr (caddr exp))))) That looks strange because it's a recursive call that doesn't seem to be on a smaller argument. But in this situation, EXP is a COND expression, and the recursive call is on an IF expression. So the recursive call will reach the ELSE clause, which will call SIMPLIFY-COND on each piece of the IF expression. Scoring: For each of the two bugs, 4 points for fixing it correctly, or 2 points for understanding the bug but fixing it incorrectly, to a maximum of 7 points. No points for trying to fix a different "bug" that isn't a bug. Common mistakes were adding a NULL? base case test (not necessary, because MAP handles empty lists) and changing the MAP invocation to (cons (car exp) (simplify-cond (cdr exp))) which is doubly wrong; it doesn't solve any problems, and it fails to call simplify-cond recursively on the car of the expression. Students who did this were probably thinking that the car of the expression has to be the name of a procedure or special form, but that's not true: ((cond ((= 2 3) +) (else -)) 9 4) The car of that expression is itself a compound expression, namely a two-clause cond expression! 4. STUDENT data type. (a) The selectors: (define (name student) (CAR STUDENT)) (define (age student) (CADDR STUDENT)) (define (id student) (CADR (CADDDR STUDENT))) We accepted CADADDDR for the last one, although Scheme actually allows only up to four As and Ds in the predefined car/cdr compositions. We also accepted other groupings, such as (cadr (cadr (cddr student))), but this is the most natural way to think about it, because we want the second element of the fourth element of a list, and CADR means second element; CADDR is third element; CADDDR is fourth element. Scoring: One point each. (b) Find a student matching an ID: (define (get-student list-of-students student-id) (cond ((null? list-of-students) #f) ((equal? student-id (id (car list-of-students))) (car list-of-students)) (else (get-student (cdr list-of-students) student-id)))) It can also be done with FILTER, if you remember to return only one student, not a list of them: (define (get-student list-of-students student-id) (let ((ok (filter (lambda (s) (equal? student-id (id s))) list-of-students))) (if (null? ok) #f (car ok)))) Several people returned a new student with the same fields as the one in the list: (make-student (name (car list-of-students)) (age (car list-of-students)) student-id) which is okay, but what's the point? Most people got this correct. One fairly common error was to return the empty list, rather than #F as specified, when the ID is not found in the list. Another common error was to return the name of the student, rather than the student. It's plausible to think that you might want to find a student's name, but you already have a function, NAME, to get a student's name. It's bad design to build that into another function with a different purpose, namely searching the list of students. What if you want to know the student's age? If all you have is the name, you can't find out the age. Scoring: 4 correct. 3 correct except for base case (empty list instead of #F). 2 data abstraction violation, or returns a list of students, or returns the name of the student. 0 worse than that. 5. Data-directed SENTENCE. (a) Table entries. There are two ways to do this. The one we expected was to follow the book's convention in which the first tag is the operation and the second tag is the type signature: (put 'sentence '(word word) list) (put 'sentence '(word sentence) cons) (put 'sentence '(sentence word) (lambda (a b) (append a (list b)))) (put 'sentence '(sentence sentence) append) It's also possible, if your answer to part (b) is written to expect this, to use the two tags in the table as type tags for the two operands: (put 'word 'word list) (put 'word 'sentence cons) (put 'sentence 'word (lambda (a b) (append a (list b)))) (put 'sentence 'sentence append) I like the first approach better, because this implementation of SENTENCE can then be integrated with a general system that also handles the arithmetic operations on different numeric types, and so on. But we accepted either. It's okay to say, e.g., (lambda (a b) (word a b)) instead of just WORD, but it's unnecessary. Scoring: 3 correct. 2 all but one correct. 1 tags correct, functions not. 0 worse. (b) SENTENCE itself: If you use the first form of PUT, you can use the general mechanism for generic operations: (define (sentence a b) (apply-generic 'sentence a b)) If you use the second form of PUT, you have to write your own: (define (sentence a b) ((get (type-tag a) (type-tag b)) (contents a) (contents b))) (In real life you'd include an error check instead of just calling whatever GET returns, but the exam booklet says "assume you will be given arguments of the correct type," so that wasn't required.) Scoring: 3 correct. 2 close to correct. 1 well-motivated, but serious errors. 0 not DDP, or other really bad misunderstandings. The version of SENTENCE we gave you in the exam says (cond ... (else (append arg1 arg2))) instead of checking specifically for the case of two arguments of type SENTENCE. Some people tried to preserve that, using DDP for the word-word, word-sentence, and sentence-word combinations but having a default behavior that calls append for any other combination of types. We accepted that. 6. EVAL-1 modification. The PAIR? cond-clause handles procedure calls. A procedure call is any list that isn't a special form. The special forms are detected in other clauses that come before this one. So, if we move the PAIR? clause to the beginning of the COND, the special forms will not be recognized as being special. Instead, Scheme-1 will try to invoke *primitive procedures* named IF, QUOTE, or LAMBDA, by trying to evaluate those symbols in STk and then apply the returned values as STk procedures. This will result in an STk error. So: 3 ==> 3 + ==> #[closure ...] (The value returned is an STk procedure, not a plus sign!) (if #t 1 2) ==> ERROR ((lambda (x) (* x x)) 3) ==> ERROR (+ 3 4) ==> 7 (lambda (x) (* x x)) ==> ERROR The error cases do *not* result in a "Bad expr" message, as shown in the ELSE clause of the COND; that message is seen when the expression isn't a pair, nor is it a symbol or a constant. The only likely situation in which that would happen is if you try to evaluate the empty list as a Scheme-1 expression. Scoring: One point each. ----------------------------------- If you don't like your grade, first check these solutions. If we graded your paper according to the standards shown here, and you think the standards were wrong, too bad -- we're not going to grade you differently from everyone else. If you think your paper was not graded according to these standards, bring it to Brian or your TA. We will regrade the entire exam carefully; we may find errors that we missed the first time around. :-) If you believe our solutions are incorrect, we'll be happy to discuss it, but you're probably wrong! ...
View Full Document

This note was uploaded on 10/06/2009 for the course CS 61A taught by Professor Harvey during the Fall '08 term at University of California, Berkeley.

Ask a homework question - tutors are online