Fundamentals of Computer Science I: Media Computing (CS151.02 2007F)
Distributed: Friday, 30 November 2007
Due: Friday, 7 December 2007
There are four problems on the exam. Some problems have subproblems. Each problem is worth twenty-five (25) points. 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 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.
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. In particular, this exam is likely to take you about four to six hours, depending on how well you've learned topics and how fast you work. You should not work more than eight hours on this exam. Stop at eight hours and write “There's more to life than CS” and you will earn at least 80 points on this exam.
I would also appreciate it if you would write down the amount of time each problem takes. Each person who does so will earn two points of extra credit. Since I worry about the amount of time my exams take, I will give two points of extra credit to the first two people who honestly report that they've spent at least five hours on the exam or completed the exam. (At that point, I may then change the exam.)
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 as restricted by 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.
Although you may use the Web for this exam, you may not post your answers to this examination on the Web. And, in case it's not clear, you may not ask others (in person, via email, via IM, by posting a please help message, or in any other way) to put answers on the Web.
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. You may not tell other students how long you've spent on the exam.
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. You need not reveal the particulars of the dishonesty, simply that it happened. Note also that inappropriate assistance is assistance from (or to) anyone other than Professor Rebelsky (that's me) or Professor Davis.
You must present your exam to me in two forms: both physically and electronically. That is, you must write all of your answers using the computer, print them out, number the pages, put your name on the top of every page, and hand me the printed copy. You must also email me a copy of your exam. You should create the emailed version by copying the various parts of your exam and pasting them into an email message. In both cases, you should put your answers in the same order as the problems. Failure to name and number the printed pages will lead to a penalty of two points. Failure to turn in both versions may lead to a much worse penalty.
In many problems, I ask you to write code. Unless I specify otherwise in a problem, you should write working code and include examples that show that you've tested the code. Do not include images; I should be able to regenerate those.
Unless I explicitly ask you to document your procedures, you need not write introductory comments.
Just as you should be careful and precise when you write code and documentation, so should you be careful and precise when you write prose. Please check your spelling and grammar. Since I should be equally careful, the whole class will receive one point of extra credit for each error in spelling or grammar you identify on this exam. I will limit that form of extra credit to five points.
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.
Higher-order procedures, files,
As you may recall,
map is one of the most popular
and most useful higher-order procedures. In its typical form,
map takes two parameters, a unary procedure and
map then creates a new list by applying the
procedure to each element of the original list.
You may also recall that files are useful
when we have collections of values that will not
necessarily fit in memory or that we want to preserve.
If we are collecting our data in files, it makes
equal sense to write a version of
map that works
with files, rather than with lists.
Write a procedure,
(, that reads each value from the
file named by the string
proc, and writes the result to the file named
For example, suppose the file
rational-numbers.txt contains rational numbers
between 0 and 1, like so:
0.1410427672 0.7246121521 0.2468009653 0.3293669477 0.1280985349 0.5556000506 0.3700089885 0.2961479627 0.641676477 0.36518387
We want to convert each fraction into a percentage rounded to the
nearest point. We should be able to do so using the
(file.map "rational-numbers.txt" (lambda (n) (round (* 100 n))) "percentages.txt")
This should produce the following output in the file
14 72 25 33 13 56 37 30 64 37
It is not acceptable to write this
procedure by reading all the values from the file into a list, using
map, and then writing the list back to a file.
Your goal is to read, modify, and write each value in turn.
to read each value from the source file. Recall that
read returns the special end-of-file object
eof-object?) when you attempt to
read beyond the end of a file.
Topics: Higher-order procedures, vectors, trees, tallying, recursion, documentation, testing
As you may recall, there are a number of instances in which we have wanted to tally, or count, the number of values in a list that meet some criteria. In the past, we've tallied odd numbers and bright colors, among other things.
The principle of writing general procedures, along with the technique
of higher-order order programming, suggests that we should consider writing
a more general
tally procedure that tallies all the
values that meet a particular predicate. (Of course, we should not name
tally, since it is then unclear whether it tallies
values in a list, vector, or tree.)
a. Document a procedure,
tallies all the values in
lst for which
pred? holds. Pay particular attention to preconditions
and postconditions. [5 points]
list.tally. [5 points]
c. Give a call to
list.tally to tally all the
even integers in a list,
some-values. Note that
some-values may contain values of any type; you should
just tally the integers. [5 points]
d. Implement a procedure,
tallies all of the values in
vec for which
pred? holds. [5 points]
e. Implement a procedure,
tallies all of the values in the tree
tree for which
pred? holds. [5 points]
Topics: Sorting, keys and keyed values
As you may recall, we learned at least three forms of the generalized insertion sort procedure.
list.insertion-sorttakes two parameters: a list to sort and a
may-precede?predicate that compares two values and determines whether the first may precede the second in the sorted list.
list.keyed-insertion-sorttakes three parameters: a list to sort, a
get-keypredicate that extracts keys from values in the list, and a
may-precede?predicate that compares two keys and determines whether the first may precede the second.
vector.insertion-sort!takes two parameters: a vector to sort and a
may-precede?predicate. In this case, the sorting is in place, modifying the vector to be sorted.
Suppose we have a list of named objects, such as the following:
(define drawing (list (list "circ1" "ellipse" "red" 10 10 80 80) (list "thin" "ellipse" "blue" 10 80 300 10) (list "tall" "rectangle" "green" 80 5 100 2) (list "ys1" "rectangle" "yellow" 0 50 10 10) (list "ys2" "rectangle" "yellow" 0 50 20 20) (list "ys3" "rectangle" "yellow" 0 55 30 30) (list "ys4" "rectangle" "yellow" 0 60 40 40) (list "ys5" "rectangle" "yellow" 0 65 50 50) (list "ys6" "rectangle" "yellow" 0 70 60 60) (list "rc" "ellipse" "red" 100 100 30 30) (list "oc" "ellipse" "orange" 90 110 30 30) (list "yc" "ellipse" "yellow" 80 120 30 30) (list "gc" "ellipse" "green" 80 130 30 30) (list "bc" "ellipse" "blue" 90 140 30 30) (list "ic" "ellipse" "indigo" 100 150 30 30) (list "vc" "ellipse" "violet" 110 160 30 30) (list "last" "rectangle" "white" 0 0 1 1)))
Sorting this list by name is fairly easy with
list.keyed-insertion-sort. We grab the
car and compare names with
(list.keyed-insertion-sort drawing car string-ci<=?)
It's a bit more complex to sort this list by name using
the more general
list.insertion-sort. In this
case, we need to make the
car part of the
(list.insertion-sort drawing (lambda (o1 o2) (string-ci<=? (car o1) (car o2))))
Similarly, sorting this list by left margin is relatively easy using
list.keyed-insertion-sort. In this case, the key
is the left margin (element 3) and we compare keys numerically.
(list.keyed-insertion-sort drawing (lambda (object) (list-ref object 3)) <=)
We might also express this a bit more concisely
r-s (our shorthand for
(list.keyed-insertion-sort drawing (r-s list-ref 3) <=)
But what if we want to sort using more complex criteria? That's your goal to figure out for this problem.
a. Write a command (that you could type in the interactions window) to
list.keyed-insertion-sort to sort
by the size of an object, from smallest to largest. For simplicity,
assume that we compute the size of an object by multiplying its width
and height. [10 points]
(list.keyed-insertion-sort drawing (lambda (obj) _____) ____)
b. Write a command (that you could type in the interactions window)
list.insertion-sort to sort
alphabetically by color name. If multiple objects have the same color,
they should be ordered by type (ellipses before rectangles), and then
by name. [10 points]
(list.insertion-sort drawing (lambda (obj1 obj2) _____))
As the description suggests, you need to first compare by color,
then by type, and finally by name.
("alpha" "rectangle" "black")
("zeta" "ellipse" "white") because
"black" alphabetically precedes
("alpha" "rectangle" "white") comes
("zeta" "ellipse" "white") because the two colors
are the same, and ellipses come before rectangles. Finally,
("alpha" "ellipse" "white") comes before
("zeta" "ellipse" "white") because the colors and shapes
are the same, and
Note: You may find it useful to define a helper
procedure to do the comparison. It is certainly fine to do so during
development. However, in the end, you should answer this questions
with a single command (which may involve a
c. Write a command (that you could type in the interactions window)
drawing by brightness of color, from darkest to
lightest. (Note that you're starting with color names, so you'll have
to convert those to RGB colors and then compute the brightness of those
colors.) [5 points]
Topics: Files, representing images
As you may recall, we devised, but did not implement, a technique for more efficiently storing images. In this technique, which is called run-length encoding, when you have a sequence of pixels of the same color, you write one entry for all the pixels, rather than a separate entry for each pixel. That is, suppose the first five pixels in an image are purple. We would write the color purple and then the number 5. To read the image back from the file, we repeatedly read the color and the number of repetitions, and fill in that many pixels.
For example, consider a mostly-black 5x5 image with a single white pixel in the center. This image has 12 black pixels in sequence (five in the first row, five in the second row, two in the third row), one white pixel, and then another 12 black pixels (two more in the third row, five in the forth row, and five in the fifth row). We might represent this image as
5 5 0 0 0 12 255 255 255 1 0 0 0 12
Of course, nothing (other than our desire to produce the smallest file possible) says that we have to continue pixels from one row to the next. Hence, we could also represent the image as
5 5 0 0 0 5 0 0 0 5 0 0 0 2 255 255 255 1 0 0 0 2 0 0 0 5 0 0 0 5
In fact, we could even represent each pixel separately (which gives us a larger file than we would have otherwise).
5 5 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 255 255 255 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1
How can we read such a file back into an image? We might use a procedure like the following.
;;; Procedure: ;;; image.read-compressed ;;; Parameters: ;;; filename, a string ;;; Purpose: ;;; Read a compressed image from the given file. ;;; Produces: ;;; image, an image ;;; Preconditions: ;;; filename names a valid file. ;;; The file has the following form: ;;; (1) an integer that specifies the width of the image ;;; (2) an integer that specifies the height of the image ;;; (3) a sequence of four-integer sequences, each of which specifies ;;; red, green, blue, and repetitions. For example, "255 0 0 10" means ;;; "the next ten pixels are red". ;;; Postconditions: ;;; The image corresponds to the description in the file. ;;; Note: ;;; Internally, positions are represented as (col.row) pairs. (define image.read-compressed (lambda (filename) (let* ((inport (open-input-file filename)) (width (int.read inport)) (height (int.read inport)) (image (image.new width height)) ; nextpos takes a (col.row) pair and produces the next ; (col.row) pair in the image. If there is no next pair, ; nextpos returns #f. (nextpos (lambda (pos) (cond ; If we're not yet at the last column, advance ; to the next column. ((< (car pos) (- width 1)) (cons (+ 1 (car pos)) (cdr pos))) ; We're at the last column. If we're not at the ; last row, advance to the beginning of the next ; row ((< (cdr pos) (- height 1)) (cons 0 (+ 1 (cdr pos)))) ; Well, it appears we've run out of rows and cols. (else #f))))) (let kernel ((pos (cons 0 0)) (color (rgb.read inport)) (count (int.read inport))) (cond ; No positions left? We're done. ((not pos) (close-input-port inport) image) ; No colors left? Read another one. ((zero? count) (kernel pos (rgb.read inport) (int.read inport))) ; Otherwise, set the color in the current position and move ; on to the next. (else (image.set-pixel! image (car pos) (cdr pos) color) (kernel (nextpos pos) color (- count 1))))))))
How would we write these files? It depends on whether we want to be as concise as possible or are willing to let the file be larger. If we're willing to write an uncompressed version, using a count of 1 per color, we can use a technique like that we've previously used to write images. If we prefer to do a row at a time, we might keep track of the current color on the row and the number of pixels. If we want the most compressed version, we need to work with multiple rows. Again, we keep track of the most recent color encountered and the number of pixels with that color. We then look at the next pixel, whether or not it's on the same row. If the next pixel is the same color, we increment the count for that color and go on. If the next pixel is a different color, we write the previous color and count, and continue with the new color and a count of 1. When we run out of pixels to write, we write the final color and count.
a. Write a procedure,
that writes an image to a file using run-length encoding with the
naive technique (that is, write each pixel separately with a count
of 1). This procedure should create files that can be read by
image.read-compressed. [15 points]
b. Write a procedure,
(, that writes
an image to a file using run-length encoding with the row-by-row technique.
c. Write a procedure,
(, that writes
an image to a file using run-length encoding with the multi-row
technique. [5 points]
In each case, you'll benefit from
rgb.read, defined as follows:
(define rgb.write (lambda (color port) (int.write (rgb.red color) port) (int.write (rgb.green color) port) (int.write (rgb.blue color) port))) (define rgb.read (lambda (port) (let* ((red (int.read port)) (green (int.read port)) (blue (int.read port))) (if (eof-object? blue) blue (rgb.new red green blue)))))
During testing, you may want to use the following versions of
(define int.write (lambda (int port) (write int port) (display " " port))) (define int.read (lambda (port) (read port)))
However, in the end, we will use the versions of these procedures that produce a more compact representation.
(define int.write (lambda (int port) (write-char (integer->char (+ int 1)) port))) (define int.read (lambda (port) (let ((ch (read-char port))) (if (eof-object? ch) ch (- (char->integer ch) 1)))))
Where have we seen the tally procedure before?
We wrote a test suite for a version of
For problem 2(c), my version of
crashes when I
even? as the predicate. Is it
list.tally isn't broken---it is doing
exactly what you would expect it to do.
even? always crashes when it is given
a parameter that is not an integer. You will need to write a new
predicate that doesn't crash when it is given values other than
Is there a specific procedure you want us to use to compute brightness?
(define rgb.brightness (lambda (color) (+ (* 0.30 (rgb.red color)) (* 0.59 (rgb.green color)) (* 0.11 (rgb.blue color)))))
Here you will find errors of spelling, grammar, and design that students have noted. Remember, each error found corresponds to a point of extra credit for everyone. I usually limit such extra credit to five points. However, if I make an astoundingly large number of errors, then I will provide more extra credit.
Copyright © 2007 Janet Davis, Matthew Kluber, and Samuel A. Rebelsky. (Selected materials copyright by John David Stone and Henry Walker and used by permission.)
This material is based upon work partially supported by the National Science Foundation under Grant No. CCLI-0633090. Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the National Science Foundation.
This work is licensed under a Creative Commons
Attribution-NonCommercial 2.5 License. To view a copy of this
or send a letter to Creative Commons, 543 Howard Street, 5th Floor,
San Francisco, California, 94105, USA.