mt3 - CS 61A Spring 2004 Midterm 3 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 Spring 2004 Midterm 3 solutions 1. Box and pointer. Note: For ease in typing I'm displaying a pair in the form XX, but *you* should draw actual boxes, as in the book, and as I do in lecture! (let ((x (list 1 2 3 4))) (set-car! (cddr x) (cadr x)) x) ------>XX----->XX----->XX-----X/ | | /: | V V / V V / 1 2 <-+ (3) 4 The printed representation is (1 2 2 4). NOT '(1 2 2 4)! Scheme doesn't print quotation marks -- I don't see how people can make that mistake if they've been doing the labs or homework; you don't see quotation marks on the stuff Scheme prints, do you? Anyway, just about everyone got this right. (let ((x (list 1 2 3 4))) (set-car! (car x) (cadr x)) x) ERROR. (CAR X) is the number 1, which isn't a pair. So it can't be the first argument to SET-CAR!, which only works for pairs. The most common error on this part was to treat it as if it said (set-car! x (cadr x)) which results in the answer (2 2 3 4). (let ((x (list (list 1 2) (list 3 4)))) (set-cdr! (cdr x) (cdar x)) x) It's easier to give before and after pictures for this one. Here's before: ------->XX------------->X/ | | V V XX----->X/ XX----->X/ | | | | V V V V 1 2 3 4 (CDR X) is the second (and last) pair in the top row. So to SET-CDR! this pair means to replace the slash (the empty list) in its right half. (CDAR X) is (CDR (CAR X)), which is the second spine pair [CDR] of the first element [CAR] of X, namely the list (1 2). So we are pointing to the pair whose car points to 2: ------->XX------------->XX-----------+ | | | V V | XX----->X/<-+ XX----->X/ | | | | | | | V V | V V | | | 1 2 | 3 4 | | | +----------------+ The worst mistake people made in this problem was to add a new pair to the diagram. There's no CONS in the problem (not even the implicit CONS done by LIST or APPEND). SET-CDR! doesn't allocate any new storage; it just makes an already-existing thing point to another already-existing thing. Another fairly common mistake was to change the car of the pair instead of its cdr, thereby changing the second element of the list to something else. Many people who got this diagram right nevertheless got the print form wrong. What is the spine of the modified list? Start at the top left corner of the diagram, and follow the CDRs of pairs. You'll see the two pairs at the top (the original spine), then the pair whose CAR is 2, and that's it -- the CDR of that third pair is the empty list. So this is now a three-element list. The first two elements are the same as before: (1 2) and (3 4). What's the third element? In other words, what's the CAR of the third spine pair? It's the number 2. So the complete list is ((1 2) (3 4) 2) No parentheses around that last 2! No dot before it, either; that would be if the changed pointer were pointing directly to the 2, rather than to a pair whose car is 2. Scoring: For the first and third part, one point for the diagram and one point for the printed representation. For the second part, two points, all or nothing. 2. Rotate a vector. There are many ways to do this, but they all share two essential points: First, there must be a helper procedure that recursively moves through the vector, changing one element at a time; second, there must be some way to remember one element (generally the original last element) of the vector, either through a LET or through an extra argument to the helper. Here's a straightforward solution: (define (rotate! v) (define (help i) (if (= i 0) 'this-value-doesnt-matter (begin (vector-set! v i (vector-ref v (- i 1))) (help (- i 1))))) (let ((temp (vector-ref v (- (vector-length v) 1)))) (help (- (vector-length v) 1)) (vector-set! v 0 temp) v)) The last line (with just the V) is there so that ROTATE! returns the vector, since the return value from VECTOR-SET! is unspecified. The elements of a vector of N elements are numbered from 0 to N-1, not from 1 to N. That's why the expression (- (VECTOR-LENGTH V) 1) is used to find the index of the rightmost element. It's very important that this program starts at the right end and works down to the left end, so that if we are given (A B C D) to rotate, the intermediate results are (a b c d) (a b c c) (a b b c) (a a b c) (d a b c) If we started at the left end instead, we'd get (a b c d) (a a c d) (a a a d) (a a a a) ; maybe stop here, or (d a a a) ; maybe something like this. There are other ways to organize the program so that left-to-right operation does produce the desired result. Here's one that works from left to right, always swapping the chosen element with the last element: (define (rotate! v) (define (swap v i j) (let ((temp (vector-ref v i))) (vector-set! v i (vector-ref v j)) (vector-set! v j temp))) (define (help i) (if (< i (vector-length v)) (begin (swap v i (- (vector-length v) 1)) (help (+ i 1))))) (help 0) v) When we ran across this solution in the grading, it took us some time to convince ourselves that it really works. Here's a trace of intermediate results: (a b c d) (d b c a) (d a c b) (d a b c) (d a b c) The problem told you to mutate the given vector, not create a new one. That rules out any call to MAKE-VECTOR, either directly or through a helper procedure such as VECTOR-COPY; it also rules out things like (list->vector (list-rotate (vector->list v))) A few students recognized that they should use a LET to save something, but instead of saving one element, tried to save the entire vector with something along the lines of (let ((new-v v)) ...) If this did what they intended, it would be ruled out by the problem statement, but in fact it does *not* make a copy of the vector. It just makes a new variable that's bound to the same (as in EQ?, not EQUAL?) vector as the original variable V. Scoring: 7 correct. 6 indexing off by one (1 to N instead of 0 to N-1). vector rotated, but not returned. 4 one element lost (no LET or equivalent). new vector allocated, but no VECTOR->LIST or LIST->VECTOR. no base case in recursive helper. one value propagated by shifting left-to-right. 2 LIST->VECTOR etc. no recursion (just two values swapped). other messed up reorderings (such as reversal). 0 Total misunderstanding of vectors, such as (CAR V). We did not assign scores 1, 3, or 5. 3. OOP simulation of Instant Messaging. Here's the solution we wanted. (Sorry for giving broadcast-servers a NAME instantiation variable; this is left over from an earlier, more complicated version.) (define-class (client name server) (instance-vars (latest-message '())) (initialize (ASK SERVER 'ACCEPT-CONNECTION SELF)) (method (receive-message msg) (set! latest-message msg)) (method (send-message msg who) (ask server 'send-to-client msg who))) (define-class (server) (instance-vars (clients '())) (method (accept-connection client) (set! clients (cons client clients))) (method (send-to-client msg name) (FOR-EACH (LAMBDA (C) (ASK C 'RECEIVE-MESSAGE MSG)) (FILTER (LAMBDA (C) (EQ? (ASK C 'NAME) NAME)) CLIENTS))) (define-class (broadcast-server) (parent (server)) (method (accept-connection client) (USUAL 'ACCEPT-CONNECTION CLIENT) (ask self 'broadcast (cons (ask client 'name) '(has joined us)))) (method (broadcast msg) (for-each (lambda (c) (ask c 'receive-message msg)) (ASK SELF 'CLIENTS)))) A discussion of each of the four blanks you were asked to fill follows; each of them was worth 2 points. Client initialize: The problem statement says, "Upon instantiation, it [the client] should connect to the given server." The way we connect to a server is to send it a message. The argument to that message is a client, not the NAME of the client, or the CLIENT class; that's why we use SELF, which is the way an object refers to itself. Scoring: 2 if correct, 1 if NAME or CLIENT used instead of SELF, 0 for anything else. Send-to-client method: There are several equivalent ways to do this. Here are a couple of examples: (ask (car (filter (lambda (c) (eq? (ask c 'name) name)) clients)) 'receive-message msg) (for-each (lambda (c) (if (eq? (ask c 'name) name) (ask c 'receive-message msg))) clients) The first of these relies on the assumption that each client has a unique name, so we're expecting FILTER to return a list of length one. Scoring: 2 if correct, 1 if the CAR is omitted (so you're asking a list of objects to do something, instead of asking an object) or if you send a SEND-MESSAGE message instead of a RECEIVE-MESSAGE message, 0 otherwise. Broadcast-server accept-connection: Part (b) is about the idea of inheritance. The crucial point here is that the broadcast-server object has no local list of clients, but instead relies on its parent to keep that information. So we have to make sure the parent's accept-connection method is run. Most people understood that keeping a list of clients was what you had to do. The worst mistake was to try to do it with a LET inside the method: (let ((clients '())) (set! clients (cons client clients))) ; WRONG! This makes a new, empty list of clients every time a new client tries to connect, and the list wouldn't be usable outside of this method anyway. Another wrong idea was to put an INSTANCE-VARS inside the method. This is a better idea, because at least it's a state variable (one that sticks around between method invocations), but you can't put one clause inside another, in a define-class. Those errors got no credit. We gave one point to two other errors: (usual 'accept-connection) ; argument missing and (set! clients (cons client clients)) without a LET around it, attempting to use the CLIENTS variable of the parent object. This wouldn't work, because the child object has no direct access to the parent's variables. But of course we gave no credit to the too-common (set! (ask self 'clients) (cons ...)) ; WRONG!!!! because SET! is a special form, and its first argument can only be a symbol, not a compound expression such as (ASK SELF 'CLIENTS). Scoring: 2 if correct, 1 as discussed in the paragraph above, 0 for anything else. A few people said (ASK USUAL ...) instead of (USUAL ...) in this and the next part, and we deducted only one point for both. Broadcase method: There are two correct answers: (ask self 'clients) or (usual 'clients) Either of these asks the parent (indirectly, in the first case, since this class has no CLIENTS method) for its CLIENTS variable. In our object system, only variables in this object can be used directly, not variables in the parent. Scoring: 2 if correct, 0 otherwise. 4. Scheme-2 environments. The point of the question is that LOOKUP (which finds the values of variables) uses the first binding it finds in the environment. So if PUT is modified to add newer bindings at the end of the list, then LOOKUP will always find the first binding created for a given name. In this case, FOO always has the value 4, even in local environments, because the local environment is an extension of the global one, and so the local binding goes at the end of the global environment, where it's never seen again. So the answers are > (define foo 4) > (define foo 5) > foo ==> 4 > (define sqr (lambda (foo) (* foo foo))) > (sqr 10) ==> 16 > (define foo (lambda (foo) (+ foo 100))) > (foo 20) ==> ERROR Most people got the first one (4). The most common error was to say 100 rather than 16 (4 squared) for the second. The third is an error because if FOO is 4, then (FOO 20) is equivalent to (4 20), which is an attempt to invoke the non-procedure 4. Scoring: 2 points each, all or nothing. 5. Concurrency. It's easier to start with part (b), in which the two threads are protected by the same serializer, so they can't interfere with each other. We don't know which will happen first, though, so two answers are possible: (define x 3) (set! x 100) (set! x (+ x x)) ; 100+100 = 200 in which case the final value of X is 200, or (define x 3) (set! x (+ x x)) ; 3+3 = 6, but then... (set! x 100) so the final value is 100. In part (a), those two values (100 and 200) are still possible, but there are two more possibilities. The two threads are translated into machine language roughly this way: (set! x 100) (set! x (+ x x)) ------------ ---------------- load 100 load x store x add x store x Here are the two ways these could interleave badly: (set! x 100) (set! x (+ x x)) ------------ ---------------- load 100 load x [3] add x [3+3] store x [100] store x [6] and (set! x 100) (set! x (+ x x)) ------------ ---------------- load 100 load x [3] store x [100] add x [3+100] store x [103] So for part (a) there are four answers: 100, 200, 6, and 103. For some reason, several people said 9 instead of 6. We guessed that these people read the plus sign as times (although then they should have said 300 instead of 103, and nobody did that), so we considered it less bad than some other errors. Scoring: One point off per missing value. Two points off per extra or incorrect value, except that 9 instead of 6 lost just one point. No points deducted for the same value mentioned twice. Group problem. Environments. I'm going to describe the result in words, rather than try to draw ASCII-art diagrams. You draw it! The global environment is named G. > (define (inc var) (set! var (+ var 1))) This creates procedure P1, param=VAR, body=(SET! ...), env=G. It creates a binding in G, with INC bound to P1. > (define foo (let ((x 7) (z 100)) (lambda (y) (inc x) (set! z (* z x)) (+ z y)))) Before we can do the DEFINE, we have to do the LET. This creates procedure P2, params=X,Z, body=(LAMBDA (Y)...), env=G. Then the LET invokes P2, creating new environment E1, with bindings X=7, Z=100, extending G. With E1 as the current environment, we evaluate the body of P2, which is a LAMBDA expression. This creates procedure P3, with param=Y, body=(INC...)(SET!...)(+...), env=E1. The value returned by the LET is procedure P3, so, back in the global environment, we can do the DEFINE by adding a binding of FOO to P3. > (foo 10) We look up FOO in G and find procedure P3, so we're going to invoke it with argument 10. To do this, we create E2, with binding Y=10, extending E1 (because that's P3's remembered environment). Now, with E2 as the current environment, we evaluate the three expressions in P3's body: (inc x) We look up INC in E2, and have to follow pointers through E1 to G before we find that its value is P1. We look up X in E2, and follow one pointer back to E1, where we find that X=7. So we are going to invoke P1 with argument 7. To do that, we create environment E3, with binding VAR=7, extending G (**not** extending E2!), because G is where P1 was defined. With E3 as the current environment, we evaluate the body of P1: (set! var (+ var 1)) We first evaluate (+ VAR 1) in E3. Since VAR=7 in E3, this has the value 8. Now we change the binding of VAR in E3, from 7 to 8. Note that nothing else changes; in particular, X is not rebound anywhere! (set! z (* z x)) We're back in E2 as the current environment, because we're back in the body of P3. We don't find bindings for Z and X in frame E2, but we follow the pointer back to E1, where we find bindings Z=100, X=7. So (* Z X) gives the value 700, and we rebind Z to 700. (+ Z Y) We find the binding Y=10 directly in E2, but we have to follow the pointer back to E1 to find that Z=700. So the value that (FOO 10) returns is 710, and that's what should be in the first blank. > x We typed this X at the Scheme prompt, so the current environment is G, where we find the binding X=5, and that's what goes in the second blank. > (foo 20) We look up FOO in G and find procedure P3, so we're going to invoke it with argument 10. To do this, we create E4, with binding Y=10, extending E1 (because that's P3's remembered environment). Now, with E4 as the current environment, we evaluate the three expressions in P3's body: (inc x) We look up INC in E4, and have to follow pointers through E1 to G before we find that its value is P1. We look up X in E4, and follow one pointer back to E1, where we find that X=7. So we are going to invoke P1 with argument 7. To do that, we create environment E5, with binding VAR=7, extending G (**not** extending E4!), because G is where P1 was defined. With E5 as the current environment, we evaluate the body of P1: (set! var (+ var 1)) We first evaluate (+ VAR 1) in E5. Since VAR=7 in E5, this has the value 8. Now we change the binding of VAR in E5, from 7 to 8. Note that nothing else changes; in particular, X is not rebound anywhere! (set! z (* z x)) We're back in E4 as the current environment, because we're back in the body of P3. We don't find bindings for Z and X in frame E4, but we follow the pointer back to E1, where we find bindings Z=700, X=7. So (* Z X) gives the value 4900, and we rebind Z to 4900. (+ Z Y) We find the binding Y=20 directly in E4, but we have to follow the pointer back to E1 to find that Z=4900. So the value that (FOO 10) returns is 4920, and that's what should be in the third blank. The various common errors were indicated on the papers as follows: DS: Dynamic Scope. This was the most common error: E3 extends E2, and E5 extends E4. LX: Local X. The second blank had 6 (which I don't understand) or 7 (which came from looking in E1 instead of G) instead of 5. SX: Set X. The binding for X in E1 is changed from 7 to 8 and then 9, and/or the value in the third blank is 7220. This was usually combined with DS, and was the next most common error. It was considered a serious error. MF: Missing Frames. A few papers left out the frames E3 and E5 altogether, and they were graded like DS, since we couldn't tell what environments they would have extended. FG: Foo Global. Procedure P3 has env=G instead of env=E1. VX: Var is X. Frames E3 and E5 have bindings VAR=X instead of VAR=7. This is a really bad error, using normal order rather than applicative order evaluation. Scoring: 6 correct. 5 LX only. 4 DS or MF only. 3 GX or VX, but not SX. 2 SX only. 1 SX and one other error. 0 SX and two or more other errors, or not even close. ----------------------------------- 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 Berkeley.

Ask a homework question - tutors are online