<?php require_once "fs_inner_context.inc"; ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
     "http://www.w3.org/TR/html4/loose.dtd">
<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">

<title><?php echo $HTML_TITLE; ?></title>

</head>

<?php echo html_bgcolor_body($BG_COLOR); ?>

<!-- Standard hierarchical header structure -->

<?php echo $CS_HEADER_HTML; ?>

<?php echo html_linked_text("<em>Courses</em>", $CS_COURSES_ROOT_URL); ?>

<?php echo $HTML_HEADER; ?>

<hr><hr>

<?php echo html_header($HTML_HEADER_LEVEL + 1, "Comments after the exam"); ?>

<p>Here is the exam for reference:</p>

<ul>

<li><?php echo html_linked_text("DVI", "final.dvi"); ?></li>

<li><?php echo html_linked_text("PostScript", "final.ps"); ?></li>

<li><?php echo html_linked_text("PDF", "final.pdf"); ?></li>

<li><?php echo html_linked_text("LaTeX source", "final.tex"); ?></li>

</ul>

<?php echo html_header($HTML_HEADER_LEVEL + 2, "The grades"); ?>

<p>Check out
<?php echo html_linked_text("your grades", "../../Grading/grades.html") . " "; ?>
and compare to the
<?php echo html_linked_text("2002 grades", "http://www.classes.cs.uchicago.edu/classes/archive/2002/fall/12500-1/Grading/") . " "; ?>.

<p>I graded this year's final on a 120-point scale, and last year's on
60-points, so you have to scale by 2. That was just an accident---the
grading plan is essentially the same this year as last year. This
year's scores are 112, 101, 100x2, 96, 79, 78, 77, 76, 71, 65, 63, 59,
53, 48, 39, 15 (mean 72.5, median 76).  I alloted 20 points to each of
the numbered problems.</p>

<p>Grade cutoffs this year will probably be similar to 2002. The 2002
class was unusually strong, so that will probably not lead to as large
a number of As. The scores above 70 are very strong, and likely to
lead to Bs and some As.</p>

<?php echo html_header($HTML_HEADER_LEVEL + 2, "The problems"); ?>

<ol>

<li>Part A is exactly the same as the midterm problem 1C.

<br><br>

In part B it's possible to write two separate functions for the sum
and for the product, and then list the two results. But the problem is
to define each function "directly by applications of the higher-order
procedures." I gave 3/5 points for the separate functions.

<blockquote><pre><code>
(define sum_and_product
  
  (eval_binary_tree
   
   (lambda (x y) (list (+ (first  x) (first  y))
                       (* (second x) (second y))
                       )
     )
   
   (lambda (x) (cond ((number? x) (list x x))
                     (else        (list 0 1))
                     )
     )
   )
  )      
</code></pre></blockquote>

<br><br>

Part C exercises a very common algebraic form: accumulation of one
operation (<code>+</code> after another (<code>*</code>) has been
applied pointwise (<code>map</code>ped. The dot product operation on
vectors is probably the most commonly used function of this form. It's
a very good idea to learn to recognize this pattern.

<blockquote><pre><code>
(define (dot_product lst1 lst2)
  
  ((accumulate_lr 0 (lambda (x) x) +) (map * lst1 lst2))
  )
</code></pre></blockquote>

<br><br>

Part D applies the accumulate after map pattern in a slightly more
complicated form.

<blockquote><pre><code>
(define (list_repeats lst1 lst2)
  
  ((accumulate_lr empty (lambda (x) x) append)
   (map (lambda (x y) (cond ((= x y) (list x)) (else empty))) lst1 lst2)
   )
  )
</code></pre></blockquote>

<br><br>

</li>

<li>Many of you provided noniterative solutions. I awarded up to 3/5
points for perfect noniterative solutions, but with the opportunity to
read the instructions a week in advance, you should make sure not to
misunderstand such a question. And it's very important to be able to
distinguish an iterative solution from a noniterative one.

<br><br>There are a number of variant solutions to part A. My favorite
because it's elegant:

<blockquote><pre><code>
(define (sum_diagonal lstlst)
  
  (define (sd sum lstlst)
    
    (cond ((empty? lstlst) sum)
          
          (else            (sd (+ sum (first (first lstlst))) (map rest (rest lstlst))))
          )
    
    )
  
  (sd 0 lstlst)
  )
</code></pre></blockquote>

The most straightforward (and therefore probably the best to use in a
real project):

<blockquote><pre><code>
(define (sum_diagonal lstlst)
  
  (define (select n lst)
    
    (cond ((= n 1) (first lst))
          (else    (select (- n 1) (rest lst)))
          )
    )
  
  (define (sd sum count lstlst)
    
    (cond ((empty? lstlst)
           sum)
          
          (else            
           (sd (+ sum (select count (first lstlst)))
               (+ count 1)
               (rest lstlst)
               )
           )
          )
    )
  
  (sd 0 1 lstlst)
  )
</code></pre></blockquote>

A final exercise, to show that every nested iteration (the previous
solution nests the iteration of <code>select</code> into the iteration
of <code>sd</code>) can be written as a single unnested iteration. In
a conventional procedural programming language, each nested level of
iteration is a <bf>for</bf> loop.

<blockquote><pre><code>
(define (sum_diagonal lstlst)
  
  (define (sd sum countrow countcol lstlst)
    
    (cond ((empty? lstlst)
           sum)
          
          ((= countrow countcol)
           (sd (+ sum (first (first lstlst)))
               (+ countrow 1)
               1
               (rest lstlst)
               )
           )
          
          (else            
           (sd sum
               countrow
               (+ countcol 1)
               (cons (rest (first lstlst)) (rest lstlst))
               )
           )
          )
    )
  
  (sd 0 1 1 lstlst)
  )
</code></pre></blockquote>

<br><br>

In part B, lots of people presented noniterative solutions. There's a
good argument for using the noniterative solution here (unless there's
a very good implementation of <code>append</code>), but the exercise
was to see how to do it iteratively.

<blockquote><pre><code>
(define (merge_lists lst1 lst2)
  
  (define (ma mlst lst1 lst2)
    
    (cond ((empty? lst1)
           (append mlst lst2)
           )
          
          ((empty? lst2)
           (append (mlst lst1))
           )
          
          ((&lt; (first lst1) (first lst2))
           (ma (append mlst (list (first lst1))) (rest lst1) lst2)
           )
          
          (else
           (ma (append mlst (list (first lst2))) lst1        (rest lst2))
           )
          )
    )
  
  (ma empty lst1 lst2)
  )
</code></pre></blockquote>

<br><br>

Part C has <em>exactly</em> the same form as the iterative fast power
operation that we studied. Just replace <code>1</code> with
<code>0</code>, <code>*</code> with <code>+</code>. Well worth
figuring out on your own if you didn't see it during the exam.

<br><br>

</li>

<li>The essence of all problems with the <code>intset</code>
abstraction is that it provides no direct way to get a member of a
set. That's what you should have focused on when studying the
guide. You have to search for members. You can tell when you've found
them all, either by removing them until a set is empty, or by counting
up to the size given by <code>intset_size</code>.

<br><br>

Part A is exactly the solution of the essential problem above. (I
haven't tested these yet, but I think I have them right.) Here's a
sort of minimalist solution, using only <code>empty_intset?</code>,
<code>member_intset?</code>, and <code>remove_intset</code>, keeping
as much information as possible in the <code>intset</code>
representation. There's no particular reason to do it that way, it's
just one way to generate a solution.

<blockquote><pre><code>
(define (intset-&gt;list is)

  (define (list_if_member n is)

    (cond ((member_intset? n is) (list n))
           (else                  empty)
          )
    )

  (define (list_intset_geq-&gt;list lst is n)

    (cond ((empty_intset? is) lst)

          (else        (list_intset_geq-&gt;list
                        (append (list_if_member n       is)
                                (list_if_member (- 0 n) is)
                                lst
                                )
                        (remove_intset n (remove_intset (- 0 n) is))
                        (+ n 1)
                        )
                       )
          )
    )

  (append (list_if_member 0 is)
          (list_intset_geq-&gt;list empty 1 is)
          )
  )
</code></pre></blockquote>

It probably makes more sense to use a nice iterator
(<code>nextint</code>) to produce positive and negative integers in
the standard natural order 0, 1, -1, 2, -2, ...

<blockquote><pre><code>
(define (nextint n)

  (cond ((&lt; n 0) (- 1 n))
        (else       (- 0 n))
        )
  )

(define (intset-&gt;list is)

  (define (list_intset_after-&gt;list lst is n)

    (cond ((empty_intset? is)
           lst
           )

          ((member_intset? n is)
           (list_intset_after-&gt;list (cons n lst)
                                    (remove_intset n is)
                                    (nextint n)
                                    )
           )

          (else
           (list_intset_after-&gt;list lst
                                    (remove_intset n is)
                                    (nextint n)
                                    )
           )
          )
     )

  (list_intset_after-&gt;list empty is 0)
  )
</code></pre></blockquote>

The number of steps is a bit odd. It is not determined by the size of
<code>is</code>. Rather, it is roughly the largest absolute value of a
member of <code>is</code>.

<br><br>

Once you've got <code>intset-&gt;list</code> above, you've cracked the
essence of <code>intset</code> computation, and just need to deploy it
appropriately.

<br><br>

Part B:

<blockquote><pre><code>
(define (intset_union is1 is2)

  (define (add_list_intset lst is)

    (cond ((empty? lst)
           is)

          (else
           (add_list_intset (rest lst)
                            (insert_intset (first lst) is)
                            )
           )
          )
    )

  (add_list_intset (intset-&gt;list is1) is2)
  )
</code></pre></blockquote>

There is an odd asymmetry here. The rough number of steps is the
largest absolute value of a member of <code>is1</code>, and has
nothing to do with <code>is2</code> (although each particular
implementation of <code>intset</code> may give <code>is2</code> an
impact on the number of steps below the abstraction barrier).

<br><br>

Part C:

<blockquote><pre><code>
(define (intset_intersection is1 is2)

  (define (filter_list-&gt;intset lst isin isout)

    (cond ((empty? lst)
           isout)

          ((member_intset? (first lst) isin)
           (filter_list-&gt;intset (rest lst)
                                isin
                                (insert_intset (first lst) isout)
                            )
           )

          (else
           (filter_list-&gt;intset (rest lst)
                                isin
                                isout
                            )
           )

          )
    )

  (filter_list-&gt;intset (intset-&gt;list is1) is2)
  )
</code></pre></blockquote>

Number of steps roughly the largest absolute value of a member of
<code>is1</code>.

<br><br>

You might prefer a symmetric solution:

<blockquote><pre><code>
(define (intset_intersection is1 is2)

  (define (union_intersect_intset_after is1 is2 isout n)

    (cond ((empty_intset? is1)
           isout)

          ((empty_intset? is2)
           isout)

          ((and (member_intset? n is1) (member_intset? n is2))
           (union_intersect_intset_after
            is1 is2
            (insert_intset n isout)
            (nextint n)
            )                          
           )

          (else
           (union_intersect_intset_after
            is1 is2
            isout
            (nextint n)
            )
           )
          )
    )

  (union_intersect_intset_after is1 is2 empty_intset 0)
  )
</code></pre></blockquote>

<br><br>

</li>

<li>Part A:

<blockquote><pre><code>
((eq? (car expr) 'if)
  (if (eval (cadr   expr) env)
      (eval (caddr  expr) env)
      (eval (cadddr expr) env)
      )
  )
</code></pre></blockquote>

Crucial points: <code>(car expr)</code> is not evaluated. <code>(cadr
expr)</code> is evaluated. Exactly one of <code>(caddr expr)</code>
and <code>(cadddr expr)</code> is evaluated.

<br><br>

Part B: I should have moved the star up ahead of the <code>(not (pair?
fn))</code>. The very superficial answer is that we don't have a
pair. The less superficial answer is that the <code>if</code> symbol
would be evaluated, which is an error. But we'd fix that, similarly to
the way we fixed <code>lambda</code> functions. The important robust
answer is that this section of code is for the application of
functions to already evaluated arguments. It's crucial to
<code>if</code> that it controls the evaluation of the arguments. In
<em>LISP</em>/<em>Scheme</em> jargon, <code>if</code> is a <em>special
form</em>, rather than a function.

<br><br>

Part C: <em>m</em> repetitions of <code>eval</code> applied to
<code>(weird 0)</code> evaluates to <code>(weird
<em>m+1</em>)</code>. That is, the list of two things, the first of
which is the symbol <code>weird</code>, and the second of which is the
<em>Scheme</em> integer with the value <em>m+1</em>. I accepted a
variety of notations to describe this as long as they were self
consistent.

</li>

<br><br>

<li>Part A: Most of you got these. If you didn't, figure out what
happened. This exercise gets at the crux of the impact of rough
running time on practical usefulness.

<blockquote><table border>
<tr><td><em>f</em>1</td><td><em>n</em>^16</td></tr>
<tr><td><em>f</em>2</td><td>16<em>n</em></td></tr>
<tr><td><em>f</em>3</td><td>4<em>n</em></td></tr>
<tr><td><em>f</em>4</td><td>2<em>n</em></td></tr>
<tr><td><em>f</em>5</td><td><em>n</em>+4</td></tr>
</table></blockquote>

<br><br>

Part B exercises a very common form of efficiency consideration. It is
almost always faster to sort collections of data than to search
repeatedly in the unsorted forms. <code>same_items1?</code> takes
<em>n</em>^2 steps, while <code>same_items2?</code> takes
<em>n</em>log<em>n</em> steps, which is much faster.

<br><br>

</li>

<li>Part A: <code>(assign x 0)</code>, <code>(frame (bind x) (assign x
0) (f x))</code>. There's no exact replacement for
<code>define</code>. <code>(define x 0)</code> has different results
depending on whether <code>x</code> is bound in the top frame of the
current environment (in which case <code>(assign x 0)</code>) or not
bound in the top frame (in which case <code>(begin (bind x) (assign x
0))</code>. <em>Conspire</em> has no way to test for this pre-existing
binding without running the risk of failure if <code>x</code> is not
bound.

<br><br>

<blockquote><pre><code>
(define (make-incrementer)
  
  (let ((history empty))
    
    (lambda (n)
      
      (cond ((equal? n 'history) history)
            
            (else (begin (set! history (append history (list n))) (+ n 1)))
            )
      )
    )
  )

(define increment (make-incrementer))
</code></pre></blockquote>

You can also define <code>increment</code> directly, substituting in
the code from <code>make-incrementer</code>:

<blockquote><pre><code>
(define increment
  
  (let ((history empty))
    
    (lambda (n)
      
      (cond ((equal? n 'history) history)
            
            (else (begin (set! history (append history (list n))) (+ n 1)))
            )
      )
    )
  )
</code></pre></blockquote>

But you can't succeed in the form

<blockquote><pre><code>
(define (increment n)
  ...
</code></pre></blockquote>

because every use of <code>increment</code> introduces a new frame,
and you need a frame that keeps the same copy of <code>history</code>
around for all uses of <code>increment</code>.

</li>

</ol>

<?php echo html_header($HTML_HEADER_LEVEL + 1, "Study guide"); ?>

<p>There will be some repetition of at least one problem identical or
highly similar to a problem from the past (2002 midterm, 2002 final,
or 2003 midterm).</p>

<ul>

<li><?php echo html_linked_text("DVI", "final_guide.dvi"); ?></li>

<li><?php echo html_linked_text("PostScript", "final_guide.ps"); ?></li>

<li><?php echo html_linked_text("PDF", "final_guide.pdf"); ?></li>

</ul><br><br></li>

</ul>

<?php echo html_footer("index", 0, 1); ?>
<!-- hhmts start -->
Last modified: Fri Dec 12 19:26:43 CST 2003
<!-- hhmts end -->

</body>

</html>
