mt1 - CS 61A Spring 2004 Midterm #1 solutions Note: If your...

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 #1 solutions Note: If your individual score is I points (out of 40) and your group score is G points (out of 13), your overall score will be computed as (max I (+ (* 0.8917 I) (* 0.3333 G))) The theory behind this formula is that 27 of your 40 individual points are not matched by anything in the group exam, and for the 13 points that are matched, we want to weight the exams as 2/3 individual, 1/3 group. So that gives (+ (* 27/40 I) (* 2/3 (* 13/40 I)) (* 1/3 G)), except that if your individual score is better than the combined score by this formula, we'll keep your individual score. But this correction is done at the end of the semester in the final grade-reporting program; GLOOKUP isn't smart enough to report anything but a fixed weighting of points. 1. What will Scheme print? Scoring: One point each, all or nothing, except that we deducted at most one point for leaving out parentheses from any number of otherwise correct answers. We didn't take off points for quotation marks in front of othherwise correct answers, but we will next time -- *you* type quotation marks in front of sentences you want Scheme to treat as data rather than as procedure calls, but the quotation marks are not part of the values, and Scheme doesn't print them. 1) > (se 'a '(a)) (A A) SENTENCE accepts words and/or sentences as arguments, and always returns a sentence. The most common wrong answer was (A (A)), which is not a sentence. That would be the answer if the procedure were LIST rather than SE. 2) > (word 'a '(a)) ERROR The domain of WORD is words, not sentences. It doesn't make sense to make a word by combining sentences! 3) > ((lambda (x) (bf (bl x))) '(abcd efgh)) () (bl '(abcd efgh)) ==> (abcd) (bf '(abcd)) ==> () The key point here is that there's a difference between the word ABCD and the one-word-long sentence (ABCD). BUTFIRST of a word is all but the first letter. BUTFIRST of a sentence is all but the first *word*, and that remains true even if the sentence has only one word. Removing the first word of (ABCD) gives the empty sentence. The most common wrong answer, BCD, comes from taking (butfirst 'abcd) instead of (butfirst '(abcd)). 4) > (every (lambda (x) (bf (bl x))) '(abcd efgh)) (BC FG) Most people got this one. In this case we're taking the butlast and butfirst of words, not of sentences. 5) > (lambda (abcd) (bf (bl abcd))) PROCEDURE Most people got this one. LAMBDA creates a procedure, and nothing is invoking the procedure. The ABCD here is a formal parameter, not an actual argument, so BC would be wrong. 6) > (se (first 'goodbye) (first '(hello))) (G HELLO) Like the third example, this one is about the difference between a word and a one-word-long sentence: (first 'goodbye) ==> G (first '(hello)) ==> HELLO The most common mistake was (G H), ignoring that difference. 7) > (cond (3 4) (5 6) (else 7)) 4 Every value other than #F is considered true by IF and COND. In particular, 3 is true, so the first COND clause is satisfied. The most common wrong answer was ERROR, presumably because these clauses look unusual. You're accustomed to nested parentheses in a COND clause: ((equal? ....) (....)) But the two expressions inside the clause don't have to be procedure calls. 2. Make-closest-value. The recursive use of make-closest-value is a little tricky. The simplest solution is to avoid it by using a recursive helper function. There are several ways to organize the helper. The most elegant uses a recursive process: (define (make-closest-value nums) (define (closest-value num nums) (if (empty? (butfirst nums)) (first nums) (closer num (first nums) (closest-value num (butfirst nums))))) (lambda (num) (closest-value num nums))) Here the recursive call to closest-value provides one of the arguments to closer. It's also not too bad to use an iterative-process helper procedure that takes an extra result-so-far argument: (define (make-closest-value nums) (define (closest-value num sofar rest) (if (empty? rest) sofar (closest-value num (closer num sofar (first rest)) (butfirst rest)))) (lambda (num) (closest-value num (first nums) (butfirst nums)))) Somewhat less elegant is a solution that uses SENTENCE to stick the result-so-far at the front of a smaller sentence: (define (make-closest-value nums) (define (closest-value num nums) (if (empty? (butfirst nums)) (first nums) (closest-value num (sentence (closer num (first nums) (first (bf nums))) (bf (bf nums)))))) (lambda (num) (closest-value num nums))) If you do want to invoke make-closest-value recursively, it's important to remember that it returns a procedure, not a number or a sentence! So you have to invoke that procedure, not just try to put it in a sentence. Any of the solutions above can be turned into one that calls make-closest-value recursively; I'll just show the equivalent to the first one: (define (make-closest-value nums) (lambda (num) (if (empty? (butfirst nums)) (first nums) (closer num (first nums) ((make-closest-value (butfirst nums)) num) )))) ------------------------------------------ The underlined subexpression is (XXX num), where XXX is the procedure returned by the recursive call to make-closest-value. Yet another technique is available to those who read ahead to the higher order function ACCUMULATE defined for lists on page 116: (define (make-closest-value nums) (lambda (num) (accumulate (lambda (x y) (closer num x y)) (first nums) (butfirst nums)))) There were two assumptions about the domain that some students made, which were true in the examples we showed but were not justified by the problem statement: One is that NUMS has length at least two, and the other is that NUMS is sorted. Scoring: 7 correct. 6 trivial errors. For example: - assumes count >= 2; - returns sentence containing a number, instead of a number. 5 has the idea. For example: - assumes NUMS is ordered; - never examines (last nums). 3 has an idea. For example: - function returned by recursive call to make-closest-value isn't invoked. 0 other. For example: - no recursion at all; - tries to use EVERY. 3. Values. The key insight is that this is a KEEP-like recursion, even though there is no explicit sentence of numbers as an argument. So the solution is a cross between the SUM procedure from Chapter 1 and KEEP: (define (values pred from to) (cond ((> from to) '()) ((pred from) (se from (values pred (+ from 1) to))) (else (values pred (+ from 1) to)))) An equivalent solution with a little less typing cleverly uses the fact that SENTENCE just ignores an empty sentence given to it as an argument: (define (values pred from to) (if (> from to) '() (se (if (pred from) from '()) (values pred (+ from 1) to)))) It's possible to use (= from to) as the base case test instead of (> from to), but trickier. You have to remember that the range of VALUES is sentences, not numbers, so it has to return (SE FROM), not just FROM. Some students tried to use KEEP itself. This would be reasonable if we'd allowed the use of helper procedures: (define (values pred from to) ;; would be okay if not disallowed (define (range from to) (if (> from to) '() (sentence from (range (+ from 1) to)))) (keep pred (range from to))) Since we didn't allow helpers, some people crammed this all into one procedure, along these lines: (define (values pred from to) ;; really ugly! (if (> from to) '() (keep pred (se from (values pred (+ from 1) to))))) This works, but it's not a satisfactory solution, because it calls KEEP not just once, which is the way HOFs are supposed to be used, but N times, once in each recursive call to VALUES. So there are Theta(N^2) calls to PRED! In this course you don't have to bend over backwards to be efficient, but you shouldn't bend over backwards to be inefficient either! Why use KEEP at all? Even if you could make only one call to KEEP, you still have to construct two sentences that way, the sentence of all numbers between FROM and TO, and the sentence of those numbers that satisfy the predicate. Scoring: 7 correct. 6 trivial error. For example: - returns number instead of sentence in base case. 5 has the idea. For example: - uses KEEP; - puts (PRED FROM) into the sentence instead of FROM. 3 has an idea. For example: - uses helper procedure. 0 other. 4. Order of growth. The call to COUNT is Theta(N), and the call to Factorial is also Theta(N). Each of those calls happens only once, so the result is still Theta(N). [It's fine to say Theta(2N) but unnecessary.] Strictly speaking, there is no "N" in FACTCOUNT, so the truly correct answer is Theta(COUNT SENT), and indeed some students said that. For our purposes in 61A, all we care about is that you recognize this as linear-time, not quadratic-time. But you should know that in 61B and later courses, they'll probably insist on Theta(COUNT SENT) here; Theta(N) would be marked wrong. The only common wrong answer was Theta(N^2), presumably because you multiplied the order for COUNT by the order for FACTORIAL. That would have been right if, for example, COUNT were invoked *inside* FACTORIAL, like this: (define (funny-fact sent) (if (empty? sent) 1 (* (count sent) (funny-fact (butfirst sent))))) But in the actual problem, FACTCOUNT, which is not itself recursive, invokes COUNT and FACTORIAL exactly once each. Scoring: 2 points, all or nothing. 5. Normal vs. applicative. In Applicative order, which is what Scheme actually uses, the way to evaluate a procedure call is first to evaluate subexpressions, and then substitute the resulting values into the procedure body: 1. Evaluate subexpressions: (+ 1 2) ==> 3 (+ 3 4) ==> 7 (+ 5 6) ==> 11 2. Substitute values into body: (if (= 3 3) 7 11) This returns the value 7. In Normal order, the way to evaluate a procedure call is to substitute the argument *expressions* into the body, then evaluate the result: 1. Substitute: (if (= (+ 1 2) 3) (+ 3 4) (+ 5 6)) 2. Evaluate the IF expression. IF is a special form, so we don't evaluate all its subexpressions at once. We start by evaluating the first one: (= (+ 1 2) 3) ==> (= 3 3) ==> #T Since the result is true, we evaluate the second argument expression, but not the third: (+ 3 4) ==> 7 Reviewing these two processes, we see that (+ 1 2) was evaluated in both Normal and Applicative order, because IF needs its value to know which of the other expressions to evaluate. But (+ 5 6) is evaluated only in applicative order. So the correct answers are: (a) Both normal and applicative order. (b) Applicative order. A common mistake was to think that the question was asking whether, in a standard (applicative-order) Scheme, just the subexpressions (+ 1 2) and (+ 5 6) would be evaluated in normal or applicative order. But that wouldn't make sense, for at least two reasons: (1) The "both" answer couldn't possibly be right if that were the question; (2) when primitive procedures (such as +) are invoked, the argument expressions have to be evaluated in any case. The normal/applicative distinction is about invocations of lambda-defined procedures. Scoring: 2 points each, all or nothing. 6. Iterative vs. recursive process. The moral of this problem is supposed to be that iterative-process code is often really hard to read and understand. (a) The given procedure generates an ITERATIVE process. The recursive helper procedure HELP invokes itself only as the entire action part of two COND clauses. These are tail calls, just as a recursive call in the second or third argument to IF is a tail call. Scoring: 1 point, all or nothing. (b) Here's a trace: (seq 4) (help 4 1 '()) (help 4 2 '(1)) (help 4 3 '(1 2)) (help 4 4 '(1 2 3)) (help 4 5 '(1 2 3 4)) (help 3 1 '(1 2 3 4)) (help 3 2 '(1 2 3 4 1)) (help 3 3 '(1 2 3 4 1 2)) (help 3 4 '(1 2 3 4 1 2 3)) (help 2 1 '(1 2 3 4 1 2 3)) (help 2 2 '(1 2 3 4 1 2 3 1)) (help 2 3 '(1 2 3 4 1 2 3 1 2)) (help 1 1 '(1 2 3 4 1 2 3 1 2)) (help 1 2 '(1 2 3 4 1 2 3 1 2 1)) (help 0 1 '(1 2 3 4 1 2 3 1 2 1)) So the value is (1 2 3 4 1 2 3 1 2 1). Some wrong answers just had the order wrong, e.g., (4 3 2 1 3 2 1 2 1 1); a common really wrong answer was (1 2 3 4). Scoring: 1 point, all or nothing. (c) The most straightforward way to solve this problem is to look at the answer to (b), ignore the complicated procedure, and rewrite it from scratch. The desired value is a sequence of sequences, so it makes sense to use a helper that generates a sequence such as (1 2 3): (define (upto k) (if (= k 0) '() (se (upto (- k 1)) k))) (define (seq n) (if (= n 0) '() (se (upto n) (seq (- n 1))))) It's also possible to start with the given procedure, eliminate the extra result-so-far argument SENT, and change the actions in the COND clauses. But it's important to remember the reversing effect of iterative-process procedures, so (SE SENT K) becomes (SE K <recur>), not (SE <recur> K): (define (seq n) (define (help n k) (cond ((= n 0) '()) ((> k n) (help (- n 1) 1)) (else (se k (help n (+ k 1)))))) (help n 1)) It's also possible to meet the literal requirements of the question, but without gaining the recursive-process benefit of simplifying the code, by just adding a redundant call to SE in the ELSE clause: (define (seq n) (define (help n k sent) (cond ((= n 0) sent) ((> k n) (help (- n 1) 1 sent)) (else (SE (help n (+ k 1) (se sent k)))))) (help n 1 '())) We didn't give that full credit because we thought that it missed the point of the question, even though it happens to work. Scoring: 4 correct. 3 trivial error, e.g., wrong base case; redundant SE as discussed above; otherwise 2-point solution that correctly generates the incorrect but almost-correct solution to part (b). 2 has the idea: recursive process, but not-quite-right sequence. 1 iterative process, correct or almost-correct sequence. 0 other, e.g., generates (1 2 3 4). 7. Debugging. The point of this question is to test debugging skills, which include: 1. Careful observation of program behavior. 2. Inference of *likely* (not just possible) code errors consistent with that behavior. (a) Which arguments produce incorrect behavior? Almost everyone got this: The program gives wrong answers for arguments for which subtraction of a smaller letter-value from a larger one is needed. Some people restricted the incorrect-answer cases to the ones shown in our examples: IV, CD, and XC. Other people decided that the error in the last example was entirely due to one of the subtractions (most often, CD but not XC). They either listed specific cases or attempted to generalize from them, giving rise to answers like the following: It fails only if the small-then-large letters differ by only one position in the MDCLXVI hierarchy. It fails for the first small-then-large pair in the argument. We accepted these, since part (a) is just about observation, not about inference. We rejected similar-looking answers that were inconsistent with the data, such as: It doesn't fail if the small-then-large pair is at the end of the argument word. [Contradicts the MMIV case!] It fails only if there are two small-then-large pairs. [Ditto!] It fails only for the pair IV. [Contradicts MCDXCII!] Some people listed arguments that are not in the program's intended domain, such as non-letters or letters other than MDCLXVI. This was a misunderstanding of the question; we didn't ask for arguments for which the program can't work, but rather for arguments for which the program's behavior doesn't meet its specification -- ones that give rise to wrong answers when a right answer is possible. (b) For those arguments, what wrong answer is produced? The right answer is that for each small-then-large pair, the second (larger) letter is counted twice. In the first example, MMIV, the right answer would be 2004; the given wrong answer, 2009, is 5 more than it should be -- the value of V. The second and third examples work correctly. In the fourth example, MCDXCII, the correct answer is 1492. The given wrong answer, 2092, is too great by 600, which is 500+100 -- the values of D and C. It's probably because the last two digits of the given answer are correct that a lot of students thought, in part (a), that the error is entirely due to the CD pair. But it's hard to think of any way that particular wrong answer would come up, unless the values for every possible small-then-large pair are built into the program explicitly. A few students said that the value for the letter one higher than the larger letter was used, e.g., in the pair IV, use the value for X instead of the value for V. This works in that case because V and X differ by a factor of two, but it fails in a case like XC, because next up from C is D, which is a factor of five larger. Some students said "the result is bigger than it should be" without saying how much bigger. We gave this part credit. (c) What is the probable error in the program code? The second (larger-valued) letter of the pair is counted twice. Most likely, this means that the program is examining it twice. Here is a correct procedure: (define (arabic num) (cond ((empty? (bf num)) (letter-value num)) ((< (letter-value (first num)) (letter-value (first (bf num)))) (+ (- (letter-value (first (bf num))) (letter-value (first num))) (arabic (BF (BF NUM))))) (else (+ (letter-value (first num)) (arabic (bf num)))))) In the second COND clause, dealing with small-before-large pairs, the correct program does the subtraction, then *skips over both letters* using the (BF (BF NUM)) expression capitalized above. The most likely error is that one call to butfirst has been omitted. It wasn't necessary to show actual code; "skips over one letter instead of two" was plenty. Some students suggested that the value of the larger letter was multiplied by two in the procedure, like this: (+ (- (* 2 (LETTER-VALUE (FIRST (BF NUM)))) (letter-value (first num))) (arabic (bf (bf num))))) But why on earth would anyone make this mistake? It's not something you'd be likely to do by accident. We gave no credit for this, as an answer to (c), although it's an acceptable answer to (b) -- it correctly describes the values returned. Scoring: Our original idea was that each of the three parts was worth 2 points. But we had trouble deciding what to do about answers based on the theory of special cases in the code. Many students seemed to be imagining a procedure written like this: (define (arabic num) (cond ((empty? (bf num)) (letter-value (first num))) ((equal? (first-two num) 'IV) (+ 4 (arabic (bf (bf num))))) ((equal? (first-two num) 'IX) (+ 9 (arabic (bf (bf num))))) ((equal? (first-two num) 'XL) (+ 40 (arabic (bf (bf num))))) ... (else (+ (letter-value (first num)) (arabic (bf num)))))) but with some of the numeric values incorrect. Is this a plausible program? Certainly not if the program was written by a competent professional. But it's something a beginning student might write. So the debugging process has to take into account who wrote the program! In the end, we identified several common types of answers, and assigned scores to each. We also read the answers to all three parts at once, because a lot of students gave bad answers to (b) and then put a perfectly good answer to (b) in the space intended for (c). We credited these as (b) answers. Here are the cases: (a) small-before-large (b) larger letter value counted twice (c) skips one letter instead of two This was the answer we wanted; it got 6 points (2+2+2). (a) small-before-large (b) larger letter value counted twice (c) multiplies larger letter by 2 This got 4 points (2+2+0). (a) small-before-large (b) larger letter value counted twice (c) just a restatement of (b) with no basis in the procedure. This got 4 points (2+2+0). (a) Only some small-before-large pairs fail. (b) Anything, if consistent with (a). (c) Specific incorrect pair values built into the program. This got 4 points (2+0+2). (a) small-before-large. (b) Anything inconsistent with the data. (c) Plausible reason for the incorrect (b) behavior. This got 4 points (2+0+2). (a) small-before-large (b) "bigger than it should be" (c) anything that doesn't redeem (b) This got 3 points (2+1+0). (a) small-before-large (b) incorrect (c) incorrect This got 2 points (2+0+0). (a) incorrect (b) correct (c) correct This quite rare combination got 4 (0+2+2). ----------------------------------- 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

Ask a homework question - tutors are online