about summary refs log tree commit diff stats
path: root/js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-2.soln.txt
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2023-08-23 07:52:19 -0400
committerelioat <elioat@tilde.institute>2023-08-23 07:52:19 -0400
commit562a9a52d599d9a05f871404050968a5fd282640 (patch)
tree7d3305c1252c043bfe246ccc7deff0056aa6b5ab /js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-2.soln.txt
parent5d012c6c011a9dedf7d0a098e456206244eb5a0f (diff)
downloadtour-562a9a52d599d9a05f871404050968a5fd282640.tar.gz
*
Diffstat (limited to 'js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-2.soln.txt')
-rw-r--r--js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-2.soln.txt484
1 files changed, 484 insertions, 0 deletions
diff --git a/js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-2.soln.txt b/js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-2.soln.txt
new file mode 100644
index 0000000..759cb3b
--- /dev/null
+++ b/js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-2.soln.txt
@@ -0,0 +1,484 @@
+CS 61A		Solutions to sample midterm 3 #2
+
+1.  Box and pointer.
+
+(let ((x (list 1 2 3)))
+  (set-cdr! (car x) 4)
+  x)
+
+prints: *** Error:
+    set-cdr!: wrong type of argument: 1
+
+(CAR X) is 1, not a pair, so it has no cdr to mutate.
+
+
+(let ((x (list 1 2 3)))
+  (set-cdr! x 4)
+  x)
+
+(1 . 4) is printed.
+
+X-->[o|o]--> 4      
+     |      
+     V      
+     1 
+
+Since we are changing the list's cdr to a non-pair, it becomes a "dotted pair."
+
+
+(let ((x (list 1 2 3)))
+  (set-car! (cdr x) x)
+  x)
+
+(1 (1 (1 (1 (1 ...   is printed.
+
+
+      +------+
+      |      |
+      V      |
+X-->[o|o]-->[o|o]-->[o|/]
+     |               |      
+     V               V      
+     1               3
+
+The list's second element is replaced with the list itself, making a circular
+list.  3 is still the third element, but the print procedure never reaches it.
+
+
+(define a ((lambda (z) (cons z z)) (list 'a)))
+(set-cdr! (car a) '(b))
+a
+
+((a b) a b) is printed.
+
+A-->[o|o]
+     | | 
+     | |
+     | |    
+     | |    
+     V V    
+    {o|o}-->[o|/]
+     |       |      
+     V       V      
+     a       b 
+
+Note that the two arrows downward from the first pair point to the same place!
+An arrow to a pair points to the entire pair, not just to its car or its cdr.
+(By contrast, an arrow *from* a pair *does* come from one half of the pair,
+not from the entire pair.)
+
+The pair in {braces} is the one whose cdr is changed (from being the empty
+list) by the set-cdr! invocation.
+
+
+Scoring: One point per printed representation; one point per diagram.
+
+
+
+2.  Assignment, state.
+
+(define (make-player room)
+  (lambda (message)
+    (set! room (next-room room message))
+    room))
+
+That's it!  There's no need to check for specific messages such as South,
+since next-room will take care of that for you.
+
+Scoring:
+
+4  correct
+3  sets room correctly but doesn't return it
+2  domain and range of make-player and of the lambda correct, but
+   something's wrong with setting room.
+1  room is set, but no lambda
+0  even worse
+
+
+3.  Environment diagram.
+
+
+First of all, in this problem there are two procedures created:
+
+P1: parameters X and F, body (IF ...)
+
+P2: parameter Y, body (+ X Y)
+
+
+There are also four environment frames:
+
+G: the global frame
+
+E1: has X bound to 3, F bound to #F
+
+E2: has X bound to 5, F bound to procedure P2
+
+E3: has Y bound to 7
+
+
+Solutions not satisfying the above generally got no points.
+
+The hard part is how to hook these up.  When FOO is defined, a binding
+for FOO is made in the global environment.  Also, the global environment
+is current when the (outer) LAMBDA expression is evaluated, so FOO is P1
+and P1's right bubble points to G.
+
+By the way, that first expression could have been written as
+
+        (define (foo x f) ...)
+
+with exactly the same meaning.
+
+That's all that happens when the first expression is evaluated.
+Procedure P2 and frames E1 through E3 are all created during the
+evaluation of the second expression.
+
+First we call FOO with arguments 3 and #F.  This creates frame E1,
+which extends G, because G is what P1's right bubble points to.
+
+Now we evaluate the body of P1 (a/k/a FOO), using E1 as the current
+environment.  The body is the IF expression.  F is false, so we must
+evaluate (FOO 5 (LAMBDA ...)).
+
+To evaluate a procedure call we first evaluate all the subexpressions.
+In particular, the inner LAMBDA expression is evaluated at this time,
+with E1 as the current environment.  Therefore, P2's right bubble
+points to E1.
+
+Once we've evaluated the subexpressions, we create a new frame, E2,
+binding FOO's parameters to these new arguments.  So in E2 the value
+of X is 5, and the value of F is procedure P2.  Note that this
+binding for F is in E2, but the procedure to which F is bound was
+created in E1.  That's really important, and many groups missed it.
+
+What environment does E2 extend?  Since we are calling P1 (a/k/a FOO),
+we extend the environment in P1's right bubble, namely G.  This, too,
+is a really important point.  If you had E2 extend E1, you are using
+dynamic scope, not lexical scope, which is correct for Scheme.
+
+We now evaluate the body of P1, the IF expression, with E2 current.
+The value of F is true in this environment (since anything other than
+#F is considered true), so we now evaluate (F 7).
+
+To do that, we make a frame, E3, in which Y is bound to 7.  What environment
+does E3 extend?  The procedure we're calling, P2, was created in E1, so E3
+must extend E1 -- not E2, even though that's the current environment.
+
+Since E3 extends E1, the relevant value of X is 3, and so the answer
+that Scheme prints is 10, which is 3+7.
+
+The most common wrong answer was 12, which is 5+7.  (Only a handful of
+papers had any answer other than 10 or 12.)  As it turns out, there were
+two ways of getting the answer 12, with different scores:
+
+  * Many groups thought that procedure P2 was created in environment E2,
+    and so E3 would extend E2 even following the lexical scope rule.
+    This was a relatively mild error, getting 2 points.
+
+  * Many other groups used dynamic scope, getting 1 point.  You were
+    deemed to be using dynamic scope if E3 extended E2 even though P2's
+    right bubble pointed to E1.  You were also deemed to be using
+    dynamic scope if E3 extended E2, and E2 extended E1, regardless of
+    where P2 pointed.
+
+    (A few papers had E2 extending E1, but E3 correctly extending E1,
+    the diagram otherwise correct, and an answer of 10.  These groups
+    were not deemed to be believers in dynamic scope, but merely
+    careless, getting 2 points.)
+
+Several groups got the answer 10 despite incorrect diagrams.  This was a
+surprise, but it turned out that they did it by mixing the substitution
+model with the environment model.  (Since the expressions in the problem
+did not involve mutation, the substitution model does yield the right
+value for (FOO 3 #F) even though it's not what Scheme really does.)
+More specifically, these solutions had (+ 3 Y) instead of (+ X Y) as the
+body of P2.  Such papers got 1 point.
+
+Some papers had P2's right bubble pointing to E3, a frame that didn't
+even exist when P2 was created.  We don't understand what those groups
+were thinking.
+
+Another zero-point solution was to have bindings to expressions rather
+than to values.  For example, some papers had a binding in E2 of the form
+
+	F: (lambda (y) (+ x y))
+
+It's really important to understand that Scheme's job is to translate
+expressions into values, and that only values get into environments.
+(This is what it means to say that Scheme uses applicative order
+evaluation, rather than normal order.)
+
+Scoring:
+
+3  OK
+2  lexical scope, but some error
+1  dynamic scope, or substitution model
+0  gibberish, extra frames, expressions in environments
+
+
+4.  List mutation.
+
+We were surprised at how hard you found this problem.  The solution
+we wanted is pretty short and simple:
+
+(define (merge! a b)
+  (cond ((null? a) b)
+	((null? b) a)
+	((<= (car a) (car b))
+	 (set-cdr! a (merge! (cdr a) b))
+	 a)
+	(else
+	 (set-cdr! b (merge! a (cdr b)))
+	 b)))
+
+This solution DEPENDS CRITICALLY on the fact that merge! RETURNS A VALUE.
+Most of you got in trouble by forgetting to return a value.
+
+The easiest way to think about this problem may be to start by writing
+a functional (non-mutating) version:
+
+(define (merge a b)        ; Not a solution to the exam problem!
+  (cond ((null? a) b)
+	((null? b) a)
+	((<= (car a) (car b))
+	 (cons (car a) (merge (cdr a) b)))
+	(else
+	 (cons (car b) (merge a (cdr b))))))
+
+There's a stream version of this procedure in the book, and in any case
+you should find it easy to invent this.  The mutating version has the
+SAME STRUCTURE, but instead of calling CONS it has to mutate an
+existing pair, and separately return a value.
+
+One of the most important ideas you can learn is the mathematical
+concept of SYMMETRY.  In this case, what that means is that you should
+notice that merge! treats its two arguments equivalently; if you
+interchanged the order of the arguments you should still get the same
+answer.  Therefore, your program should be symmetrical as well; anything
+you do to A should be done in the same way to B.  Many people used
+asymmetrical kludges, and therefore had incredibly long programs that
+didn't work.
+
+Another good idea to learn is QUIZMANSHIP.  Some time around your
+11th or 12th cond clause, you ought to stop and think, "They wouldn't
+assign something this complicated on an exam."  (This is quite
+different from the sort of quizmanship in which you try to guess at
+answers based on irrelevant factors.)
+
+Several solutions had cond clauses with complicated conditions such as
+    (and (< (car a) (car b)) (> (car a) (cadr b)))
+There are a few situations in which you really do have to use this
+degree of complexity, but they're rare.  Really what this means is
+that you're not trusting the recursion to do its job with the second
+elements of the lists.
+
+Several solutions rearranged things in a way that would work only if
+the second-smallest number is in the other list from the smallest
+number.  That is, you hook the two cars together and then recursively
+deal with the two cdrs.  Think about a case like
+   (merge! (list 1 2 3) (list 4 5 6))
+In this example the result should be the same as if you'd appended
+the lists, not the same as if you interleaved them.
+
+A few people did append the lists, and then sort them by insertion
+sort or bubble sort or some such thing.  This can work, and we
+gave full credit for working solutions of this kind, but you should
+be aware that it makes the problem take O(n^2) time instead of
+O(n), and it's more complicated to write correctly, too!  By
+appending the two lists you're losing the valuable information
+that each of them is already ordered.
+
+In a problem about lists, you have to remember that the car and
+the cdr of a pair do NOT play symmetrical roles in the representation
+of a list.  Each car is an element; each cdr is a sublist.  In a
+list of numbers, each car is a number; no cdr is a number.  Therefore,
+any solution that interchanges cars and cdrs or tries to mutate a
+car of something just can't be right:
+   (set-car! (car x) ...)
+   (set-cdr! (car x) ...)
+   (set-cdr! x (car y))
+   (set-car! x (cdr y))
+For solutions of this sort we subtracted three points from what
+the score would otherwise be.
+
+Grading:
+7  correct
+6  base case error
+5  no return value, but it only matters to the ultimate caller
+4  no return value, and so the recursion doesn't work
+4  rearranges pairs with set-cdr! but incorrectly
+3
+2  uses set-car! to rearrange pairs.  (Some set-car! solutions are correct.)
+1
+0  allocates pairs (calls CONS, LIST, APPEND, etc.)
+0  uses set! to rearrange pairs.  (Some uses of set! are okay, but they
+                                    never actually help the solution.)
+
+Then subtract 3 points for the cases mentioned in the previous paragraph.
+
+
+5.  Vectors.
+
+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.
+
+
+6.  Concurrency.
+
+(a)  The only possible value is 10.  If the first process runs first,
+it sets BAZ to 5, and then the second process sets it back to 10.  If the
+second process runs first, it sets BAZ to 20, and then the first process
+sets it back to 10.
+
+(b)	5: P1 computes baz/2 = 5, then P2 runs completely, then P1 stores 5.
+	10: either correct sequential order
+	15: P2 loads BAZ once from memory (getting 10), then P1 runs
+		completely (setting BAZ to 5), then P2 loads BAZ again
+		(getting 5), adds 10+5, stores 15.
+	20: P2 computes baz+baz = 20, then P1 runs completely, then
+		P2 stores 20.
+
+
+Scoring:
+
+3  correct
+2  (a) correct, (b) missing one value and no wrong ones added
+1  (a) correct or (b) correct
+0  neither correct
+
+
+7.  Streams.
+
+(define stream-car car)       ; unchanged from the stream-only version
+
+(define (stream-cdr sl)
+  (if (procedure? (cdr sl))   ; or PROMISE?
+      (force (cdr sl))
+      (cdr sl) ))
+
+;;;;;;;; or
+
+(define (stream-cdr sl)
+  (if (or (pair? (cdr sl)) (null? (cdr l)))
+      (cdr sl)
+      (force (cdr sl)) ))
+
+Scoring: 1 point for stream-car, 3 for stream-cdr.  Part credit in the latter
+for any attempt that shows understanding of the need to distinguish the two
+cases, especially if it's clear that what needs to be distinguished is the
+nature of the cdr rather than of the entire argument.
+
+Comment: Many of you seem to be upset because you never heard of the
+predicate PROCEDURE? and therefore couldn't figure out how to do the
+test.  I'm unsympathetic to this problem, for several reasons:
+
+        1.  You came across PROCEDURE? in the adventure game project,
+        where it is used in the version of PERSON? that you rewrote.
+
+        2.  You own a copy of the Scheme reference manual, which has
+        a perfectly good index.
+
+        3.  If you couldn't think of PROCEDURE? you could have used
+        PAIR? or ATOM? or LIST? to distinguish the two cases.