about summary refs log tree commit diff stats
path: root/js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3
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
parent5d012c6c011a9dedf7d0a098e456206244eb5a0f (diff)
downloadtour-562a9a52d599d9a05f871404050968a5fd282640.tar.gz
*
Diffstat (limited to 'js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3')
-rw-r--r--js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-1.pdfbin0 -> 142270 bytes
-rw-r--r--js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-1.soln.txt526
-rw-r--r--js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-2.soln.txt484
-rw-r--r--js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-3.soln.txt459
4 files changed, 1469 insertions, 0 deletions
diff --git a/js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-1.pdf b/js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-1.pdf
new file mode 100644
index 0000000..e79cafe
--- /dev/null
+++ b/js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-1.pdf
Binary files differdiff --git a/js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-1.soln.txt b/js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-1.soln.txt
new file mode 100644
index 0000000..174ff1a
--- /dev/null
+++ b/js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-1.soln.txt
@@ -0,0 +1,526 @@
+CS 61A		Solutions to sample midterm 3 #1
+
+1.  Box and pointer.
+
+(let ((x (list 1 2 3)))
+   (set-car! x (list 'a 'b 'c))
+   (set-car! (cdar x) 'd)
+   x)
+
+((a d c) 2 3) is printed.
+
+X-->[o|o]-->[o|o]-->[o|/]
+     |       |       |      
+     |       V       V
+     |       2       3      
+     V      
+    (o|o)-->{o|o}-->[o|/]
+     |       |       |      
+     V       V       V      
+     a       d       c 
+
+The result of the first set-car! is ((a b c) 2 3); the second set-car! changes
+the B to D.  (car x) is the pair in (parentheses) in the diagram above, so
+(cdar x) is the pair in {braces}.
+
+
+(define x 3)
+(define m (list x 4 5))
+(set! x 6)
+m
+
+(3 4 5) is printed.
+
+M-->[o|o]-->[o|o]-->[o|/]
+     |       |       |      
+     V       V       V      
+     3       4       5
+
+The point of this example is that once (list x 4 5) has been evaluated, it
+doesn't matter that the 3 came from X; the list contains the values, not the
+expressions that produced the values.  So the list M doesn't "know" that X's
+value is later changed.
+
+
+
+(define x (list 1 '(2 3) 4))
+(define y (cdr x))
+(set-car! y 5)
+x
+
+(1 5 4) is printed.
+
+              Y
+              |
+              |
+              V
+X-->[o|o]-->[o|o]-->[o|/]
+     |       |       |      
+     V       V       V      
+     1       5       4
+
+In this example, Y names the same pair as (cdr x).  So changing the car of Y
+changes the cadr of X, replacing (2 3) with 5.
+
+
+
+(let ((x (list 1 2 3)))
+  (set-cdr! (cdr x) x)
+  x)
+
+(1 2 1 2 1 2 1 2 ...    is printed.
+
+     +---------+
+     |         |
+     V         |
+X-->[o|o]-->[o|o]
+     |       |      
+     V       V      
+     1       2
+
+This example creates a circular list.
+
+
+Scoring: One point per printed representation; one point per diagram.
+
+
+
+2.  Local state.
+
+If we call PREV several times, each of the resulting procedures remembers
+its own previous invocation.  In OOP terminology, PREV represents a class, and
+SLOW-SQUARE is an instance of the class.  So OLD-RESULT should be an instance
+variable.
+
+Of the four choices offered, the first is the one in which OLD-RESULT is an
+instance variable, because the LET happens once per call to PREV, i.e., once
+for each instantiation of the class.
+
+In the second choice, the LET happens only once, when PREV is defined, so
+OLD-RESULT would be a class variable.  In the third choice, the LET happens
+every time an *instance* (such as SLOW-SQUARE) is called, so it's not a state
+variable at all, and SLOW-SQUARE would always return #F.
+
+In the fourth choice, PREV doesn't take an argument!  So that can't be right,
+and you don't even have to look at the LET.
+
+Scoring: 4 points, all or nothing.
+
+
+3.  Environment diagram.
+
+There are four frames (including the global one) and three procedures
+in the diagram.
+
+In the global frame, X is bound to 4, BAZ is bound to procedure P1,
+and FOO is bound to procedure P3.
+
+Frame E1 extends the global environment.  In it, X is bound to 30
+and * is bound to procedure P2.
+
+Frame E2 extends E1.  In it, Y is bound to 8.
+
+Frame E3 extends E1.  In it, A is bound to 30, and B is bound to 8.
+
+Procedure P1 is created in the global environment with parameter X
+and body (define ...) (lambda ...)
+
+Procedure P2 is created in E1 with parameters A and B, and body (+ a b).
+
+Procedure P3 is created in E1 with paremeter Y and body (* x y).
+
+---------
+
+The first expression just adds the X binding to the global environment.
+
+The second expression creates P1 and binds BAZ to it in the global
+environment.
+
+The third expression invokes BAZ, so it creates E1 with X bound to 30.
+[A common error was to bind X to 13, thinking that * means addition
+in the expression (* 3 10).  But procedure P2 hasn't even been created
+yet when (* 3 10) is evaluated, let alone the fact that this expression
+is evaluated in the global environment because it was typed at a Scheme
+prompt.]  Then it evaluates the body of BAZ in environment E1.  This
+creates P2, binds * to it in E1, creates P3, and returns P3 as the
+value from BAZ.  Then FOO is bound to that value, P3, in the global
+environment.
+
+The fourth expression computes (* 2 x) in the global environment,
+getting 8, and creates E2 with Y bound to 8.  [A common mistake was
+to use the value 30, in E1, for X and also use P2 for *, thereby
+computing the value 32 for Y.]  Then, with E2 as the
+current environment, it evaluates the body of FOO, which is (* x y).
+Since E2 is the current environment, and it extends E1, the symbol *
+represents procedure P2 in this expression.  Since this is a
+user-defined procedure, not a primitive, this creates E3 with A
+bound to 30 (the value of X found in E1) and B bound to 8 (the value
+of Y found in E2). Then (+ a b) is evaluated in E3, returning the
+value 38, which is the value of the entire expression.
+
+---------
+
+Scoring of common mistakes:
+
+  3  correct
+
+  2  correct return value 38, but...
+      E3 extends E2
+      E3 variables are named X and Y instead of A and B
+      E3 binds A to "X" instead of 30 and B to "Y"
+      E3 left out altogether
+      P3 right bubble points to E2
+
+  1  X=13 in E1
+     Y=32 in E2
+     E2 extends global environment
+     In E1, "(* a b)" is bound to "(+ x y)"
+     The third expression is interpreted as if it were
+	(define (foo) (baz (* 3 10)))
+      thereby making FOO a procedure of no arguments that calls BAZ
+
+  0  P3's right bubble points to the global frame
+     E1 extends E2 (very strange sequence of events!)
+     Y=30, as if it were
+        (define (baz y) (* x y))
+     extra/missing frames or procedures except as noted earlier
+     several errors
+
+
+4.  List mutation.
+
+The easiest way to do a problem like this is to use a LET in which
+you give names to every relevant piece of the list structure.  Then,
+inside the LET, you can mutate the pairs in any old order without
+risk of error:
+
+(define (make-alist! lst)
+  (if (null? lst)
+      'done
+      (let ((car1 (car lst))
+	    (cdr1 (cdr lst))
+	    (car2 (cadr lst))
+	    (cdr2 (cddr lst)))
+	(set-car! lst cdr1)
+	(set-cdr! lst cdr2)
+	(set-car! cdr1 car1)
+	(set-cdr! cdr1 car2)
+	(make-alist! cdr2)))
+  lst)
+
+But that's more work than is really needed.  If you're clever about
+the order in which you do the mutation, you can get by with only one
+temporary variable.  There are several such solutions; here's one:
+
+(define (make-alist! lst)
+  (if (null? lst)
+      'done
+      (let ((tmp (cddr lst)))
+	(set-cdr! (cdr lst) (cadr lst))
+	(set-car! (cdr lst) (car lst))
+	(set-car! lst (cdr lst))
+	(set-cdr! lst tmp)
+	(make-alist! tmp)))
+  lst)
+
+Both of these solutions use the original first pair as the top-left pair
+of the resulting association list; the original second pair becomes the
+bottom-left pair.  Several people noticed that you can rearrange the
+pairs with no temporary variables at all if you reverse those roles,
+so that what used to be the second pair becomes the head of the new
+association list.  Unfortunately, this isn't quite a correct solution,
+because the caller of MAKE-ALIST! probably has some variable that still
+has the original first pair as its value, so the caller will think that
+that first pair is the new a-list.
+
+Neither of these solutions uses the value returned by recursive calls.
+It's also possible to write a solution that does:
+
+(define (make-alist! lst)
+  (if (null? lst)
+      'done
+      (let ((tmp (make-alist! (cddr lst))))
+	(set-cdr! (cdr lst) (cadr lst))
+	(set-car! (cdr lst) (car lst))
+	(set-car! lst (cdr lst))
+	(set-cdr! lst tmp)))
+  lst)
+
+Note that you must check (NULL? LST) *before* you try to take its
+CAR or CDR.
+
+Scoring of typical solutions:
+
+4  OK
+3  Bad/missing base case;
+   rearranges by mutation but the order isn't quite right;
+   leaves second pair on top;
+   uses return value from recursive call but doesn't return a value.
+2  No temporary variable (via LET or helper procedure).
+1  Uses CONS (or LIST or APPEND, etc).
+0  (set! (car lst) ...) or similar confusions.
+
+Solutions using CONS scored lower than other errors because the problem
+explicitly said not to allocate new pairs.  It's important that you 
+understand the difference between allocating a pair and making a new
+pointer to an already-existing pair.  Although it's true that the old
+pairs would be reclaimed, so the total storage requirement of the program
+wouldn't increase with solutions using CONS, sometimes it's important not
+to allow a delay for garbage collection.  For example, if your program is
+creating video animation in real time, a garbage collection might bring
+the animation to a halt for a substantial time, perhaps half a second.
+
+
+5.  Vectors.
+
+To solve this problem it's important to understand what it's about.  The
+result (histogram) vector is separate from the argument (scores) vector; the
+two vectors have different sizes; there is no simple correspondence between
+one element of the result and one element of the argument.  Each element of
+the result depends on examining *every* element of the argument.  The kind of
+higher order function strategy that we often use for lists is not appropriate
+in this problem.
+
+Having passed that hurdle, there is an insight about algorithms that helps
+to simplify the solution if you see it:  Although each element of the result
+depends on every element of the argument, each element of the *argument*
+contributes to *only one* element of the result.  Therefore, a solution that
+works by iterating through the elements of the argument can run in Theta(N)
+time, whereas a solution that iterates through the elements of the result
+will require Theta(N^2) time, and more complicated code.
+
+Here is the Theta(N) solution:
+
+(define (histogram scores)
+  (define (help result i)
+    (if (< i 0)
+	result
+	(begin (vector-set! result
+			    (vector-ref scores i)
+			    (+ (vector-ref result (vector-ref scores i)) 1))
+	       (help result (- i 1)))))
+  (help (make-vector (+ (vector-max scores) 1) 0)
+	(- (vector-length scores) 1)))
+
+It's important that the call to MAKE-VECTOR initializes every element of the
+histogram vector to zero, because we're going to be adding to those values in
+the HELP loop.
+
+You might find it easier to read this slight variant:
+
+(define (histogram scores)
+  (define (help result i)
+    (if (< i 0)
+	result
+	(let ((score (vector-ref scores i)))
+	  (vector-set! result
+		       score
+		       (+ (vector-ref result score) 1))
+	  (help result (- i 1)))))
+  (help (make-vector (+ (vector-max scores) 1) 0)
+	(- (vector-length scores) 1)))
+
+Here's the Theta(N^2) version:
+
+(define (histogram scores)
+  (define (count-score score total j)
+    (cond ((< j 0) total)
+	  ((= (vector-ref scores j) score)
+	   (count-score score (+ total 1) (- j 1)))
+	  (else
+	   (count-score score total (- j 1)))))
+  (define (help result i)
+    (if (< i 0)
+	result
+	(begin (vector-set! result
+			    i
+			    (count-score i 0 (- (vector-length scores) 1)))
+	       (help result (- i 1)))))
+  (help (make-vector (+ (vector-max scores) 1))
+	(vector-max scores)))
+
+And, yes, instead of writing COUNT-SCORE as above, you could use
+(VECTOR-LENGTH (VECTOR-FILTER ...)) to find the count of students with a
+given score.  But that would be pretty inefficient (although only by a
+constant factor), because VECTOR-FILTER examines every element of the
+argument vector twice, and creates a new vector, which you use only long
+enough to count its elements, then throw it away.  VECTOR-FILTER makes a good
+homework problem, but it's not something you would ever really want to use in
+vector-based programming.
+
+It's possible to use the Theta(N^2) algorithm with only one helper procedure,
+instead of two, by having the helper take two index arguments, one for each
+vector.  But it's really, really hard to get that right, and even if you do,
+it's really, really hard for anyone else (or you, next week) to read the
+resulting program.
+
+
+The first index in a vector is 0; the last is (- (vector-length vec) 1).
+Many people lost a point by trying
+	(vector-ref vec (vector-length vec))
+
+People found many ways to make the problem more complicated than necessary,
+such as using
+	(define (helper i)
+	  ...
+	  (set! i (+ i 1))
+	  (helper i)...)
+instead of
+	(define (helper i)
+	  ...
+	  (helper (+ i 1))...)
+This isn't incorrect, but at this late stage in the semester it shows an iffy
+understanding of recursion.
+
+The only two places you can use DEFINE are at the Scheme prompt or at the
+beginning of a procedure body.  You can't, for example, say
+	(cond (some-test
+	       (define ...)
+	       ...)
+	 ...)
+
+
+Scoring:
+
+7  correct.
+
+6  off-by-one error in calculating vector length or end test.
+
+5  in Theta(N) algorithm, doesn't initialize result elements to zero.
+5  computes vector correctly but doesn't return it.
+5  right logic, but a Scheme language error (e.g., misplaced DEFINE).
+
+4  (VECTOR-REF RESULT INDEX) instead of
+   (VECTOR-REF RESULT (VECTOR-REF SCORES INDEX)).
+
+2  No allocation of new vector (result overwrites argument).
+
+0  Uses lists, or takes car/cdr of a vector.
+
+There were other, less common errors; generally "has the idea" got 5, while
+"has an idea" got 2.
+
+
+6.  Parallelism in real life.
+
+For this question we got several essays trying to justify solutions other
+than the obvious ones.  For the most part we didn't accept these -- the
+problem did say "the answer which BEST describes..." -- with one exception
+noted in part (b) below.
+
+(a) People at tables can't get food; people waiting for food can't get
+tables.  This is a potential deadlock, and that was the answer we wanted.
+It's true that the deadlock might not happen, because some people at
+tables may actually have food and might eventually leave, letting the line
+continue moving.  It depends partly on whether the restaurant servers
+believe you when you say "it's okay to serve me because my friend is already
+sitting at a table and saving me a seat."  Even if they do, a deadlock can
+happen if someone comes in alone (so doesn't have a friend to grab a table)
+and when that person gets to the front of the line, all the tables are filled
+with friends of people behind him/her in line.
+
+You may think "it's UNFAIR for people who don't have food yet to hog the
+tables" or "it's INEFFICIENT for people to sit at tables when they don't
+have food yet."  But these are technical terms about parallellism errors,
+and the particular situation described in the problem is technically a
+deadlock.
+
+(b) There are several inspectors serving several lines in parallel, but
+there's only one metal detector, which acts as a single serializer for
+all the inspectors.  This is inefficiency (too much serialization).
+
+Some people argued that the metal detector is best viewed as an actual
+resource, not as a serializer, and so this is correct, if unfortunate,
+serialization of a shared resource.  We accepted that reasoning, so we
+also accepted "none of the above" as a correct answer.
+
+(c) You write while your friend researches.  This is correct parallelism,
+with two processors carrying out parallelizable tasks.
+
+Some people argued that the tasks *aren't* correctly parallellizable
+because the research has to come before the writing.  That idea has some
+merit for a short paper, but you wouldn't be writing a short paper as a
+team anyway; it'd be an individual assignment.  We meant that you were
+writing one part of the paper while your friend was researching a different
+part of the paper.
+
+Some people said "unfairness" because you have to do the boring writing
+while your friend is off doing the interesting research.  One of the
+nice things about working in groups is that sometimes you're lucky and
+your interests complement those of your friend!  But even if you would
+feel an unfairness in this arrangement, it's not unfair in the technical
+sense -- a parallelism unfairness would be if the librarians always get
+books for your friend before they get books for you.
+
+Scoring: One point each.
+
+
+7.  Streams.
+
+The easiest solution is in two steps:  First we make a stream of all possible
+strings of {\tt OVER} and {\tt UNDER}, and then we filter out the ones that
+don't satisfy the condition of having at least one of each:
+
+(define foo
+  (cons-stream '()
+               (interleave (stream-map (lambda (p) (cons 'over p)) foo)
+                           (stream-map (lambda (p) (cons 'under p)) foo))))
+
+(define patterns
+  (stream-filter (lambda (p) (and (member 'over p) (member 'under p)))
+                 foo))
+
+You can't combine these two steps; several people tried this:
+
+(define patterns     ;;; wrong!
+  (stream-filter (lambda ...)
+                 (cons-stream '() (interleave (stream-map ... patterns)
+                                              (stream-map ... patterns)))))
+
+The trouble is that this process will never get started, because the two calls
+to stream-map can only operate on patterns that have previously been
+generated, and there won't be any of those until something makes it through
+the filter.  As a result, the variable PATTERNS hasn't been defined when the
+calls to stream-map try to use it.
+
+Another wrong solution was to try to avoid the need for filtering by "priming
+the pump" with initial patterns other than the empty one, like this:
+
+(define patterns         ;;; wrong!
+  (cons-stream '(over under)
+     (cons-stream '(under over)
+        (interleave ...))))
+
+This indeed gets started correctly, and generates only legal patterns,
+but it doesn't generate all of them.  In particular, it won't generate
+the example shown in the problem:
+
+    (over over under over under under)
+
+because this pattern, although legal, doesn't end with OVER UNDER or
+UNDER OVER.
+
+Some people, recognizing the problem with that last solution, tried to
+get around it by adding words both front and back.  That might possibly
+work, but the people who tried it wanted to add words at the end with
+             (cons p 'over)
+which of course doesn't make a valid list.
+
+Another intriguing method involved the use of random numbers to make
+a randomly chosen pattern for each element of the stream.  It can be
+argued that if the random number generator has perfectly well-distributed
+results, this approach will eventually generate any desired pattern.
+(And, since the number of already-generated patterns is finite, and
+the number of not-yet-generated patterns is infinite, the probability
+of duplication of patterns is zero!  Alas, this doesn't mean it can't
+happen.  :-)  But we didn't accept these solutions because part of the
+idea in an infinte stream is that you have to be able to guarantee that
+any particular element is reachable in bounded time.
+
+
+Scoring:  We graded this one very leniently:
+
+3  correct
+2  interleave correct, filtering missing or incorrect
+1  interleave wrong, but a good try
+0  not close
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.
diff --git a/js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-3.soln.txt b/js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-3.soln.txt
new file mode 100644
index 0000000..20db87b
--- /dev/null
+++ b/js/games/nluqo.github.io/~bh/61a-pages/Volume2/Midterm3/mt3-3.soln.txt
@@ -0,0 +1,459 @@
+CS 61A		Solutions to sample midterm 3 #3
+
+1.  Box and pointer.
+
+(let ((x (list 1 2 3)))
+  (set-cdr! (cdr x) (cddr x))
+  x)
+
+(1 2 3) is printed.
+
+X-->[o|o]-->[o|o]==>[o|/]
+     |       |       |      
+     V       V       V      
+     1       2       3
+
+In this example the SET-CDR! call effectively does nothing, since it
+replaces the cdr of (cdr x) with the cddr -- the cdr of the cdr -- of X.
+The arrow drawn with = instead of - above is the one that's replaced with
+itself.
+
+
+(let ((x (list 1 2 3)))
+  (set-car! x (cddr x))
+  x)
+
+((3) 2 3) is printed.
+
+     +----------------+
+     |                |
+     |                V
+X-->[o|o]-->[o|o]-->[o|/]
+             |       |      
+             V       V      
+             2       3
+
+The set-car! invocation changes (car x), i.e., the first element of X.
+It's changed to (cddr x), which is a *sublist*, not just an element, of X.
+Thus the new first element is not 3, but (3).
+
+
+
+(let ((x (list 1 2 3)))
+  (set-car! (cdr x) (cdr x))
+  x)
+(1 ((((((((( ...  is printed.
+
+             +-+
+             | |
+             | V
+X-->[o|o]-->[o|o]-->[o|/]
+     |               |      
+     V               V      
+     1               3 
+
+Note that the arrow at the top of the diagram points *from the car* of the
+second pair, but points *to the entire pair* (because that's how pointers
+always work), not just to the cdr, even though that's where the arrowhead
+happens to be.
+
+There's still a 3 in this list, but it'll never be printed.
+
+
+
+(let ((x (list 1 2 3 4)))
+  (set-cdr! (cddr x) (cadr x))
+  x)
+
+(1 2 3 . 2) is printed.
+
+X-->[o|o]-->[o|o]-->[o|o]--> 2      
+     |       |       |      
+     V       V       V      
+     1       2       3
+
+In this example we change the cdr of a pair (because we're doing SET-CDR!) to
+point to a non-pair, (CADR X), the second element of X, which is 2.  Thus the
+result is an improper list, so there's a dot in the printed version.
+
+It doesn't matter, in the diagram, whether you have two 2s, as above, or have
+two arrows pointing to the same 2.  It *does* matter when the thing being
+pointed to is a pair; then there's a big difference between two pointer to the
+same pair, and two pairs that have the same values in them.
+
+
+Scoring: One point per printed representation; one point per diagram.
+
+
+2.  Local state.
+
+Does Louis's MEMOIZE give wrong answers?  YES.
+
+Explanation:  It has one table shared among all memoized functions,
+instead of a separate table per function.
+
+Example:
+
+[Louis has defined MEMO-FIB, as shown in the question.]
+
+> (memo-fib 4)
+3
+> (define memo-square (memoize (lambda (x) (* x x))))
+> (memo-square 4)
+3
+
+Since MEMO-FIB and MEMO-SQUARE share the same table, (memo-square 4)
+returns the value previously remembered from (memo-fib 4).
+
+Some students who checked NO explained why the answer is really YES, so
+we figured people misread the question, and went by your explanantion
+rather than by which choice you checked.
+
+Pretty much any explanation with a phrase like "a single table" or
+"universal table" or "global table" was accepted.  (It's not quite right
+to say "a table in the global environment"; the table is actually a local
+state variable of MEMOIZE.)
+
+Explanations that merely restate the form of Louis's change to the program
+were not accepted:  "The program gives wrong answers because Louis has moved
+the LET to outside of the LAMBDA."  You had to explain what *effect* that
+would have on the program's *behavior*.
+
+The example didn't necessarily have to take the form of a specific Scheme
+interaction as above, but did have to assert that two different functions are
+memoized.
+
+Scoring:
+
+4  correct
+2  correct explanation, no/bad example;
+   iffy explanation (like "global environment" discussed above)
+0  bad explanation (including saying that it doesn't give wrong answers)
+
+No odd scores were assigned.
+
+
+3.  Environment diagram.
+
+Bindings A=8 and B=9 go in the global environment.
+
+Then comes the LET, which is equivalent to an invocation of the procedure
+generated by an implicit LAMBDA:
+
+	((lambda (a f) (f 5))
+	 3
+	 (lambda (b) (+ a b)))
+
+As with any procedure invocation, **first all the subexpressions are
+evaluated!**  This evaluation takes place in the global environment -- we
+haven't invoked any procedures yet.
+
+Thus, two procedures are created whose right bubbles point to the global
+environment.  Only then do we invoke the (lambda (a f) ...) procedure, which
+creates a frame in which A is bound to 3, and F is bound to the
+(lambda (b) ...) procedure.  That new frame, of course, extends the global
+environment.
+
+With that frame as (the first frame of) the current environment, we can
+evaluate the body of the LET, which is (F 5).  We find a binding for F in
+the current environment; it's the (lambda (b) ...) procedure.  So we can
+invoke that with the argument 5.
+
+Since F's right bubble points to the global environment, that's what we
+extend with the new frame that binds B=5.  So when we evaluate F's body,
+which is (+ A B), we find the bindings A=8 and B=5.
+
+Thus, the final answer is 13.
+
+
+The overwhelmingly most common mistake was to think that the (lambda (b) ...)
+procedure's right bubble points to the A=3 frame, so the B=5 frame points
+there also, and the final value is 8.  You should know, though, that a LET
+binding can't depend on another binding in the same LET; for example, if the
+problem had been
+
+	(let ((a 3)
+	      (c (+ a b)))
+	  (+ a c))
+
+I hope most people would have known that C would be 8+9, using the global
+bindings for both A and B.  The fact that the computation using A happens
+inside a lambda body doesn't change the fact that the LET's binding for A
+isn't available to it.
+
+A few people drew the environment diagram that would give an answer of 8,
+but instead wrote 13 as their final answer, which means either that they
+copied someone else's final answer or that they *do* know how Scheme
+evaluates expressions, but don't make any connection between that and
+the environment model.  We graded this as if they'd said 8.
+
+A couple of people had procedure F's right bubble correctly pointing to the
+global environment, but nevertheless had the B=5 frame extending the A=3
+frame, using dynamic scope instead of lexical scope.  This gave the same
+wrong final answer of 8.
+
+Scoring:
+
+6  correct.
+3  answer was (or should have been) 8, as discussed above.
+0  even worse (too many frames, no arrows at all -- there weren't many).
+
+
+4.  List mutation
+
+(define a (list 'x))
+(define b (list 'x))
+(define c (cons a b))
+(define d (cons a b)) 
+
+(eq? a b)                    => #F
+
+	Each call to LIST generates new pairs (one new pair, in this case,
+	because each list has only one element).
+
+(eq? (car a) (car b))        => #T
+
+	Both CARs are the symbol X, and equal symbols are always EQ.
+
+(eq? (cdr a) (cdr b))        => #T
+
+	Both CDRs are the empty list, and there's only one empty list.
+
+(eq? c d)                    => #F
+
+	Each call to CONS generates a new pair.
+
+(eq? (cdr c) (cdr d))        => #T
+
+	Both CDRs are the pair named B -- the same pair.
+
+(define p a)
+(set-car! p 'squeegee)
+(eq? p a)                    => #T
+
+	P and A are two names for the same pair.  Mutating the pair
+	doesn't change the variable bindings.
+
+(define q a)
+(set-cdr! a q)
+(eq? q a)                    => #T
+
+	This one looks trickier because the pair now points to itself,
+	but it's really the same as the previous question: Q and A are
+	two names for the same pair.
+
+(define r a)
+(set! r 'squeegee)
+(eq? r a)                    => #F
+
+	This time we didn't mutate a pair; we changed the binding of
+	one of the variables.  So R and A now name two different things;
+	A is still a pair, but R is now a symbol.
+
+Score: 1/2 point each, rounded down to the nearest whole point.
+
+
+5.  Vector programming
+
+(define (ssort! vec)
+  (define (help start)
+    (if (= start (vector-length vec))
+	vec
+	(let ((smallest (subvec-min-start vec start)))
+	  (let ((temp (vector-ref vec smallest)))
+	    (vector-set! vec smallest (vector-ref vec start))
+	    (vector-set! vec start temp)
+	    (help (+ start 1))))))
+  (help 0))
+
+The key point to understand is that you need to walk through the vector,
+bringing the smallest element out to position 0, then the next-smallest to
+position 1, and so on, until you run out of elements.  Thus we have a helper
+procedure with an argument, START, whose value is the position that we're
+up to in the vector; it starts at 0 and is increased by 1 on each recursive
+invocation.
+
+The next important point is that you can't exchange two elements without
+using a temporary variable to remember one of them, hence the LET that
+creates the variable TEMP.  There are two nested LETs because the value of
+SMALLEST is needed to find the value of TEMP in this solution; I could have
+simplified things a little by remembering the other value in the swap instead:
+
+(define (ssort! vec)
+  (define (help start)
+    (if (= start (vector-length vec))
+	vec
+	(let ((smallest (subvec-min-start vec start))
+	      (temp (vector-ref vec start)))
+	  (vector-set! vec start (vector-ref vec smallest))
+	  (vector-set! vec smallest temp)
+	  (help (+ start 1))))))
+  (help 0))
+
+Scoring:
+
+6  correct
+5  trivial error (e.g. base case off by one)
+3  right structure, gets swap wrong
+2  serious algorithm confusion
+0  allocates new vector, uses lists, or incoherent
+
+
+6.  Concurrency
+
+(define (make-mutex)
+  (let ((in-use #f)
+	(s (make-serializer)))
+    (define (the-mutex m)
+      (cond ((eq? m 'acquire)
+	     (if ((s (lambda ()
+		       (if in-use
+			   #t
+			   (begin (set! in-use #t)
+				  #f)))))
+		 (the-mutex 'acquire)))     ; retry
+	    ((eq? m 'release)
+	     (set! in-use #f))))
+    the-mutex))
+
+The structure of the above is just like that of the MAKE-MUTEX in the book,
+except that when an atomic test-and-set operation is needed, it's done by
+including the (IF IN-USE ...) and the (SET! IN-USE #T) within the same
+serialized procedure, rather than by relying on a TEST-AND-SET! primitive.
+
+Many people found the problem deeply confusing.  You learned that
+serialization has three levels of abstraction: the hardware support,
+the critical section mechanism (mutex) based on the hardware, and
+the more abstract level in which procedures are protected rather than
+sequences of instructions.  So what does it mean to define a mutex
+in terms of a serializer?  But actually this could happen.  Suppose
+you are using a language such as Java, with high-level serialization
+built in (and presumably implemented using hardware support), to write
+an operating system that is supposed to provide a mutex capability to
+its users.  You would then write something equivalent to what this
+problem asks for.  What defines an abstraction is its behavior, not
+how it's implemented -- this is the whole idea behind abstraction.
+
+The SICP version uses (LET ((CELL (LIST FALSE))) ...) because their
+TEST-AND-SET! tests and modifies a pair, not a variable, mainly so that
+it can be an ordinary procedure rather than a special form.  In this
+version there's no need to use a pair, although it wouldn't be wrong.
+
+The ugly (IF ((S (LAMBDA () ...))) ...) is necessary because of the way
+serializers work: they take a procedure as argument and return a protected
+version of that procedure, which must then actually be invoked.
+We didn't take off for minor failures in the notation, accepting even
+solutions in which the argument to the serializer was an expression to
+be evaluated atomically: (IF (S (IF IN-USE ...)) ...)  But we didn't
+accept solutions in which the serializer was given data, rather than
+activity, as its argument.  (More on that below.)
+
+The reason for the nested IFs is a little subtle; the recursive call
+to THE-MUTEX to retry the operation if it fails can't be inside the
+serialized procedure, because then it'll block waiting for S, which is
+held by this process itself -- in effect, setting up a deadlock within a
+single process.  But we didn't take off points for missing this subtlety.
+
+Some people used a single global serializer instead of a separate one
+for each mutex.  At first we considered this a serious error, because
+it means that two mutexes can't be acquired at the same time.  But then
+we realized that the serializer just protects the acquiring of the mutex,
+not the critical section that the mutex itself protects, so any mutex
+holds the serializer only for a very short time.  So we allowed a global
+serializer, except in papers that also missed the point in the previous
+paragraph.  If a mutex can get into a deadlock with itself, while holding
+a serializer needed by all the other mutexes, that's serious enough to
+lose a point.
+
+Some solutions never actually tested whether the mutex was in use before
+acquiring it.  Presumably those people thought that the serializer would
+protect the entire critical section, not just the acquiring, so that if
+an acquire operation could get into the serializer the mutex must be free.
+But that's not the case; the mutex returns to its caller once it has
+set its in-use flag, so the caller's critical section is *not* keeping the
+serializer busy.  Such solutions got at most one point.
+
+There were a few common zero-point solutions.  The strangest were the
+ones that called parallel-execute.  The job of a mutex is to protect
+critical sections against other processes, not to create other processes.
+
+Another zero-point solution had expressions such as (S CELL) that showed
+a lack of understanding of the key fact that a serializer protects
+activities, not data.  Similarly, no points for anything that included
+
+        (something . args)       ;; pfui
+
+which indicated mindless copying from the MAKE-SERIALIZER in the book
+without understanding.  Serializers accept as arguments procedures with
+any number of arguments, but any particular use of a serializer isn't
+going to be that complicated -- and none of these solutions found any
+use for the ARGS variable anyway.
+
+Scoring:
+3  OK
+2  at least an atomic test-and-set implementation
+1  no test-and-set, but some idea
+0  way off
+
+
+7.  Streams.
+
+The answer we expected was
+
+(define all-integers
+  (cons-stream 0 (interleave integers
+                             (scale-stream -1 integers))))
+
+The stream generated by this expression looks like this:
+
+        0, 1, -1, 2, -2, 3, -3, 4, -4, ...
+
+You had to understand two key points to get this solution:
+
+1.  You can't have all the integers in size order.  A stream must have
+a definite first element!  There's no smallest negative integer.  Some
+people tried to REVERSE the stream of integers; that's meaningless.
+
+2.  You can't APPEND-STREAMS two infinite streams.  This is explained
+in the handout you got in lecture.  Since the first stream will never
+finish, it's as if the second stream isn't there at all!  (A few people
+used MERGE, which works for two sorted, increasing streams, but not
+in a case like this where one stream is growing up and one growing down.)
+
+There were many other correct solutions, most of which essentially
+reinvented INTERLEAVE.  Special mention for the following beautiful
+solution (which the student unfortunately didn't get quite right):
+
+(define zeros-ones (cons-stream 0 (cons-stream 1 zeros-ones)))
+
+(define all-integers (subtract-streams zeros-ones
+                                       (cons-stream 0 all-integers)))
+
+where subtract-streams is like add-streams with the obvious difference.
+Work this out to convince yourself that it really works!
+
+Scoring:
+
+4  correct
+3  small mistakes, like leaving out zero
+2  append-stream etc.
+1  stream of streams, like (cons-stream negatives positives)
+0  not a stream at all
+
+We heard from several students who thought that the stream-of-streams
+solution was graded too harshly.  Here's the reasoning behind this
+grading policy:  Most errors come from uncertainty about how to
+implement an infinite stream successfully.  But this error indicates
+a misunderstanding about what the stream abstraction is for!  Think
+about the example of testing whether a number is prime.  We create
+a stream containing a range of numbers (range 2 14) and then we use
+that as an argument to FILTER:
+        (filter (lambda (number) (= (remainder 15 number) 0))
+                (range 2 14))
+
+If RANGE returned a stream of streams, then this FILTER wouldn't
+work, because the elements of the stream wouldn't be numbers!  So,
+even though the problem said "a stream containing all the integers"
+instead of "a stream containing all the integers, in which each
+element is a single integer," it's unreasonable to think that a
+stream whose elements aren't numbers would be acceptable.