Fundamentals of CS I (CS151 2001S)

Exam 2

Distributed: Friday, 6 April 2001
Due: 9 a.m., Monday, 16 April 2001
Those with extenuating circumstances may request an extension on the exam. Such requests must be received by 9 a.m. Thursday, 12 April 2001.

This page may be found online at


There are three problems on the exam. Each problem is worth the same number of points. Some problems may be broken up into subproblems. The point value associated with a problem does not necessarily correspond to the complexity of the problem or the time required to solve the problem.

This examination is open book, open notes, open mind, open computer, open Web. However, it is closed person. That means you should not talk to other people about the exam. Other than that limitation, you should feel free to use all reasonable resources available to you. As always, you are expected to turn in your own work. If you find ideas in a book or on the Web, be sure to cite them appropriately.

This is a take-home examination. You may use any time or times you deem appropriate to complete the exam, provided you return it to me by the due date. It is likely to take you about five to ten hours, depending on how well you've learned topics and how fast your work. I would appreciate it if you would write down the amount of time each problem takes. I expect that someone who has mastered the material and works at a moderate rate should have little trouble completing the exam in a reasonable amount of time. Since I worry about the amount of time my exams take, I will give three points of extra credit to the first two people who honestly report that they've spent at least eight hours on the exam. (At that point, I may then change the exam.)

Over the past few weeks, I've designated a few talks (some CS talks; some convocation talks) as extra credit. Please indicate which, if any, of those talks you've attended.

You must include both of the following statements on the cover sheet of the examination. Please sign and date each statement. Note that the statements must be true; if you are unable to sign either statement, please talk to me at your earliest convenience. Note also that ``inappropriate assistance'' is assistance from (or to) anyone other than myself or our teaching assistant.

1. I have neither received nor given inappropriate assistance on this examination.
2. I am unaware of any other students who have given or received inappropriate assistance on this examination.

Because different students may be taking the exam at different times, you are not permitted to discuss the exam with anyone until after I have returned it. If you must say something about the exam, you are allowed to say ``This is among the hardest exams I have ever taken. If you don't start it early, you will have no chance of finishing the exam.'' You may also summarize these policies. You may not tell other students which problems you've finished.

Answer all of your questions electronically and turn them in in hardcopy. That is, you must write all of your answers on the computer and print them out. You should also email me a copy of your exam by copying your exam and pasting it into an emal message. Put your answers in the same order that the problems appear in. Make sure that your solution confirms to the format for laboratory writeups

In many problems, I ask you to write code. Unless I specify otherwise in a problem, should write working code and include examples that show that you've tested the code.

You should fully document all of the primary procedures (including parameters, purpose, value produces, preconditions, and postconditions). If you write helper procedures (and you may certainly write helper procedures) you should document those, too, although you may opt to write less documentation. When appropriate, you should also include short comments within your code. You should also take care to format your code carefully.

Just as you should be careful and precise when you write code, so should you be careful and precise when you write prose. Please check your spelling and grammar.

I will give partial credit for partially correct answers. You ensure the best possible grade for yourself by emphasizing your answer and including a clear set of work that you used to derive the answer.

I may not be available at the time you take the exam. If you feel that a question is badly worded or impossible to answer, note the problem you have observed and attempt to reword the question in such a way that it is answerable. If it's a reasonable hour (before 10 p.m. and after 8 a.m.), feel free to try to call me in the office (269-4410) or at home (236-7445).

I will also reserve time at the start of classes next week to discuss any general questions you have on the exam.


Problem 1: Grading

One of the tasks faculty frequently encounter is the assignment of a letter grade based on the numeric grades that students have received over the course of a semester. Grading rubrics differ greatly. Some faculty simply average grades; others sum grades; others give different aspects different weights.

Suppose we've decided to store all the grades for a class in a file. One mechanism would be to have one list for each student in the class. The list will have the form

(last-name first-name email grade1 grade2 ... graden)

For example, here is the list of grades for a fictitous class in which students have received four grades. You can find the list in the file samplegrades.

("Aardvark"  "Arnie" "" 100 50 80 90)
("Beetle"    "Betty" "" 85 85 90 85)
("Chihuahua" "Chuck" "" 90 90 90 0)
("Dingo"     "Donna" "" 95 95 95 80)
("Elephant"  "Ernie" "" 5 0 0 0)
("Fox"       "Fiona" "" 80 80 80 100)

How will we process the list of grades? By reading each entry from the file, calling an appropriate grading procedure, and doing something with the result. For example, we might write the results to a file.

The procedure we write for processing grades needs at least two parameters: the name of the input file and the name of the output file. Are there others? To make our procedure as general as possible, we can also make the grading procedure a parameter. For example, if a faculty member's rubric is ``average the grades'', the procedure can be

(lambda (grades) (divide (sum grades) (length grades)))

Similarly, if a faculty member's rubric is ``average the grades after dropping the lowest'', the procedure can be

(lambda (grades)
  (divide (- (sum grades) (min grades))
          (- (length grades) 1))

For the more complex ``best two of three exams count 50%; final counts 50%'', we might write

(lambda (grades)
  (let* ((sedarg (reverse grades))
         (final (car sedarg))
         (exams (cdr sedarg)))
    (+ (divide final 2)
       (divide (- (sum exams) (min exams)) 4))))

Note that for computing grades, we don't care about the name or email; we care only about the numbers.

Problem: Document, write, and test a procedure, (process-grades infile outfile gradeproc), that reads a sequence of grade entries from infile, processes each entry with gradeproc, and writes a neatly-formatted grade report to outfile. For example, for the example above with the rubric of ``use the best grade'', the output file might contain

---------  ----------  -----
Aardvark   Arnie         100
Beetle     Betty          90
Chihuahua  Chuck          90
Dingo      Donnna         95
Elephant   Ernie           5
Fox        Fiona         100

In grading this problem, I will look at how well you process the input file, how well you generate the output file, how you deal with procedures as parameters, and how you format the output. If you can't do everything right, you should attempt to show your ability to do some aspects by simplifying some parts of the problem (e.g., by reading grades from a list rather than a file, by generating a list rather than a file, by hardcoding the grading procedure, or by ignoring formatting in the output file).

Problem 2: Mapping with Vectors

You should be quite familiar with the map procedure. In its standard form, map takes two parameters, proc, a procedure of one argument, and values, a list of values. The map procedure builds a new list by applying proc to each element of values.

As you may recall from our discussion, Scheme provides vectors as an alternative to lists. Vectors differ from lists in a few key ways: vectors have a fixed size, vectors provide indexed access, and vectors are mutable.

It therefore makes sense to design a variant of map for vectors. Rather than building a new vector, we'll have the variant change the elements in place. Hence, we call the modified version map!. Here are some examples of the desired use.

> (define grades (vector 80 66 92 50 23))
> grades
#(80 66 92 50 23)
> (map! (lambda (val) (* 2 val)) grades)
> grades
#(160 132 184 100 46)
> (map! (right-section - 40) grades)
> grades
#(120 92 144 60 6)

Document, write, and test map!. Note that I will look for tests that go beyond my simple examples.

Problem 3: Currying

You may have noted that we permit procedures to have zero, one, two, or more parameters. Some time ago, a Mathematician named Haskell B. Curry suggested that all procedures be designed to take exactly one parameter. Is that possible? Certainly. One technique would be to have procedures take a list as a parameter. However, it is also possible to simulate procedures of multiple arguments by having the single-argument procedures return other single argument procedures. We call this process currying.

In general, instead of defining a procedure of n parameters as

(define proc
  (lambda (param1 param2 ... paramn)

we use

(define proc
  (lambda (param1)
    (lambda (param2)
        (lambda (paramn)
          body) ...)))

Similarly, instead of calling that procedure with

(proc arg1 arg2 ... argn)

we use

((...((proc arg1) arg2) ...) argn)

For example, here is a curried version of max

(define cmax
  (lambda (a)
    (lambda (b)
      (if (> a b) a b))))

We would use it as follows:

> ((cmax 5) 6)
> ((cmax 2) 1)

Similarly, here are curried versions of add and expt.

;;; Procedure:
;;;   cadd
;;; Parameters (curried):
;;;   a, a number
;;;   b, a number
;;; Purpose:
;;;   Adds a and b.
;;; Produces:
;;;   sum, a number
;;; Preconditions:
;;;   a and b are numbers.
;;;   a+b is computable.
;;; Postconditions:
;;;   sum = a+b
(define cadd
  (lambda (a)
    (lambda (b)
      (+ a b))))

;;; Procedure:
;;;   cexpt
;;; Parameters (Curried):
;;;   base, a number
;;;   power, a natural numbers
;;; Purpose:
;;;   Multiplies power copies of base.
;;; Produces:
;;;   result, a number
;;; Preconditions:
;;;   base and power are numbers.
;;;   power is a non-negative integer
;;;   The result is computable.
;;; Postconditions:
;;;   result = base * base * ... * base, where there are 
;;;     power copies of base.
(define cexpt
  (lambda (base)
    (lambda (power)
      (expt base power))))

We can even write a curried version of map

;;; Procedure:
;;;   cmap
;;; Parameters (Curried):
;;;   proc, a procedure
;;;   lst, a list
;;; Purpose:
;;;   Applies proc to each element of lst.
;;; Produces:
;;;   newlst, a list.
;;; Preconditions:
;;;   proc takes one parameter
;;;   proc can be legally applied to each element of lst.
;;; Postconditions:
;;;   newlst is of the same length of lst
;;;   Each element, n, of newlst, is the result of applying
;;;    proc to the corresponding element of lst.
(define cmap
  (lambda (proc)
    (lambda (lst)
      (if (null? lst)
          (cons (proc (car lst))
                ((cmap proc) (cdr lst)))))))

What are the advantages of currying? For the implementer, it is often easier to design a system in which all procedures have only one parameter. However, there are also advantages for the programmer. In particular, it's very easy to form the left section of any curried procedure. All you do is give it the first parameter!

For example, the procedure that adds two to any argument can be written as (cadd 2) (rather than the more cumbersone (left-section + 2). Similarly, the procedure that adds two to all members of a list can be written (cmap (cadd 2)).

a. Using cmap and cexpt, compute the list of the first eleven powers of 2 (that is, 1, 2, 4, 8, 16, ..., 512, 1024).

b. Document, write, and test csub and cmult, curried procedures of two parameters that respectively subtract and multiply their parameters.

c. Document, write, and test cmember?, a curried procedure of two parameters that checks whether the first parameter (a value) is a member of the second parameter (a list).

d. Document, write, and test c-right-section, a curried version of right section that expects a curried two-parameter procedure as its first parameter and a value as a second parameter. Your procedure should then fill in the second parameter of the two-parameter procedure.

The following note was added on 10 April 2001

For example, given the cexpt defined above, you might write

(define square ((c-right-section cexpt) 2))

When you applied square to 3, you would get 9.

e. One of the most commonly used higher-order procedures is compose. Compose takes two single-parameter procedures as parameters and creates a procedure that applies the second and then the first. For example, if we wanted a procedure to multiply a number by 5 and then add 2, we might write

(compose (cadd 2) (cmult 5))

or, in curried form

((ccompose (cadd 2)) (cmult 5))

For example,

> (define scale (compose (cadd 2) (cmult 5)))
> (scale 3)
> (scale 1/5)

Document, write, and test ccompose.



Wednesday, 4 April 2001

Thursday, 5 April 2001

Friday, 6 April 2001

Wednesday, 11 April 2001


Disclaimer: I usually create these pages on the fly. This means that they are rarely proofread and may contain bad grammar and incorrect details. It also means that I may update them regularly (see the history for more details). Feel free to contact me with any suggestions for changes.

This page was generated by Siteweaver on Thu May 3 23:10:03 2001.
This page may be found at
You may validate this page's HTML.
The source was last modified Mon Apr 16 08:48:12 2001.