Assigned: Tuesday, November 29
Due: Tuesday, December 6 by 10:30pm
Please read the exam procedures page for policies, turn-in procedures, and grading details. If you have any questions about the exam, check the Q&A at the bottom of this page. We will generally spend some time in class every day on questions and answers while the exam is in progress.
While the exam is out, please check back periodically to see if we have reported any new errata.
Complete the exam using the exam4.rkt starter source code. Please rename this file to 000000.rkt, but replace 000000 with your assigned random number.
Topics: Strings, Files, Recursion
A word is “resilient” if you can remove all of the word’s letters one at a time without producing a non-word sequence of characters. For the purposes of this problem, we will treat upper- and lower-case words as equal. As an example, the word “ping” is resilient because we can remove letters in the following sequence:
Note that word resilience is a recursive property; a length n word is resilient if removing one of its letters produces either the empty string or a length n-1 resilient word.
To test whether a word is resilient, we need a dictionary. Luckily, our GNU/Linux machines have one available. The file /usr/share/dict/words contains a long list of words and acronyms, one per line.
Write and document two procedures, (read-dictionary filename) and (is-resilient? word dictionary). The read-dictionary procedure takes a filename as a parameter, which it will open and read into some form that is convenient for you to use in the next procedure. The is-resilient? procedure takes a string word
and a dictionary produced by the read-dictionary procedure and returns true if word is resilient in the context of the given dictionary. There are a few details you should consider as you write your procedures:
is-resilient? procedure should be completely case-insensitive. Upper- and lower-case letters should be treated the same, whether they appear in dictionary, word, or both.is-resilient? procedure.The following lines in DrRacket show an example use of these two procedures:
> (define my-dictionary (read-dictionary "/usr/share/dict/words"))
> (is-resilient? "Ping" my-dictionary)
#t
> (is-resilient? "Myanmar" my-dictionary)
#f
> (is-resilient? "splatters" my-dictionary)
#t
For up to three points of extra credit, write a procedure (longest-resilient-words dictionary-filename) that takes the name of a dictionary file as a parameter and returns a list of the longest resilient words in that dictionary. You do not need to document this procedure.
Topics: Pairs, Deep Recursion
As you may recall, we sometimes refer to the two-element-box created by the cons procedure as a “pair.” It can be very useful to identify just how many pairs appear in a structure, in part because it tells us a bit about the effort required to make the structure.
Write and document a procedure, (count-pairs value), that takes as input a Scheme value (e.g., a pair structure or list) and determines how many pairs (cons cells) appear in that structure.
For example,
> (count-pairs null)
0
> (count-pairs 5)
0
> (count-pairs (cons 5 null))
1
> (count-pairs (list 5))
1
> (count-pairs (cons null 5))
1
> (count-pairs (cons (cons 'a 'b) 'c))
2
> (count-pairs (list 1 2 3 4 5))
5
> (count-pairs (list null null null))
3
> (count-pairs (list (list 1 2 3) (list 4 5)))
7
As the examples above suggest, you need to look within lists to determine if their individual elements themselves contains pairs. (You may find that this happens automatically as you handle non-lists.) However, you need not look within vectors to see if they contain any pair structures.
> (count-pairs (vector (list 1)))
0
Topics: Trees
Write and document two procedures (print-tree! root) and (print-reverse-tree! root) that takes a binary tree root as input.
A binary search tree is a special form of binary tree that follows two specific rules: every child to the left of a node is less than or equal to the node’s value (including nodes further down the tree), and every child to the right of a node is greater than the node’s value (again, including nodes further down the tree).
When given a binary search tree like the ones below, the print-tree! procedure will print the values in the tree in ascending order, and print-reverse-tree! will print the tree’s values in descending order.

> (define tree1 (node 4 (node 2 (leaf 1) (leaf 3)) (node 6 (leaf 5) (leaf 7))))
> (define tree2 (node 4 (node 2 empty (leaf 3)) (node 6 (leaf 5) empty)))
> (print-tree! tree1)
Output! 1 2 3 4 5 6 7
> (print-tree! tree2)
Output! 2 3 4 5 6
> (print-reverse-tree! tree1)
Output! 7 6 5 4 3 2 1
> (print-reverse-tree! tree2)
Output! 6 5 4 3 2
That these procedures work for all trees—not just binary search trees—but they won’t necessarily print values in ascending or descending order; these procedures will print elements from un-sorted trees in un-sorted order. The order that nodes are printed depends on the structure of the tree, not the values in the tree. Consider the example below:

> (define tree3 (node 'a (node 'b (leaf 'd) empty) (leaf 'c)))
> (print-tree! tree3)
Output! d b a c
> (print-reverse-tree! tree3)
Output! c a b d
The printed output of each procedure should be space-separated (and it is OK to have a trailing space at the end of the output). The structure of the trees given are modeled using the procedures in the lab on trees and the relevant procedures are provided in the exam4.rkt file. For debugging and testing purposes, you may find the visualize-tree procedure in the tree-lab code to be helpful.
Topics: Map, Lists, Analyzing Procedures
When we first learned how to use map, we often used the technique of “take a single expression, insert map before each operation, and turn each value into a list, either with make-list or by using something like iota.”
For example, suppose we make one circle as follows.
(define circle-37
(vshift-drawing (* 10 (modulo 37 10))
(hshift-drawing (* 5 37)
(scale-drawing (increment (modulo 37 7))
drawing-unit-circle))))
In the extreme, we may end up writing something like the following.
(define many-circles
(lambda (n)
(map vshift-drawing
(map *
(make-list n 10)
(map modulo
(iota n)
(make-list n 10)))
(map hshift-drawing
(map *
(make-list n 5)
(iota n))
(map scale-drawing
(map increment
(map modulo
(iota n)
(make-list n 7)))
(make-list n drawing-unit-circle))))))
a. Using the techniques from the reading on analyzing procedures and the corresponding lab, update many-circles to count calls to cons, car, cdr, and null?. You can find implementations of all of the main procedures (e.g., map, make-list, and iota) in the exam code file.
b. Find out how many calls there are to cons, car, cdr, and null?. if we are making lists of 5, 10, 20, and 40 circles.
c. Come up with an approximate formula for the number of calls based on n.
d. Rewrite the code so that there are no more than 2*n calls to cons in making a list of n circles. Any helpers you write should be local.
Note: You may want to preview the list of circles with something like (drawing->image (drawing-compose (many-circles 50)) 200 100).
Topics: Vectors, Lists, Higher Order Procedures, Iteration
The following procedure performs a useful operation. While it’s a little difficult to tell what this procedure does, the original author clearly has his ducks in a row. Yes, I realize this is technically a column.
(define daffy
(lambda ( puddles )
(let ([ darkwing (lambda (
donald
huey
count-duckula ) (let ([
louie (vector-ref
donald
huey )][
dewey (vector-ref
donald
count-duckula )]) (vector-set!
donald
huey
dewey )(vector-set!
donald
count-duckula
louie )))]) (map (l-s
apply darkwing )(map list (make-list (quotient (vector-length
puddles ) 2)
puddles )(iota (quotient (vector-length
puddles ) 2))(map (r-s - 1) (map (l-s - (vector-length
puddles )) (iota (quotient (vector-length
puddles ) 2)))))))
puddles ))
Figure out what this procedure does, then do the following.
a. Rename the procedure and parameters so their types and purpose are clear.
b. Reformat the code so it is readable and properly indented. Remember that you can press ctrl+i to auto-indent the code.
c. Eliminate any unnecessary operations or redundant computations to make the code more concise.
d. Document the updated procedure.
Topics: Higher Order Procedures, Numeric Recursion
As you may have noted, sometimes we “nest” repeated calls to the same procedure. For example, to compute x to the eighth power, we might write (square (square (square x))).
Write but do not document a procedure (nest n fun) that returns a new procedure that takes one input and applies fun the specified number of times.
> (define octo (nest 3 square))
> octo
#<procedure>
> (octo 2)
256
> (octo 3)
6561
> (define no-square (nest 0 square))
> (no-square 5)
5
> (define much-redder (nest 6 irgb-redder))
> (irgb->string (much-redder (irgb 0 0 0)))
"192/0/0"
> (irgb->string (much-redder (irgb 10 20 40)))
"202/20/40"
> (map (nest 7 increment) (iota 5))
'(7 8 9 10 11)
We will post answers to questions of general interest here while the exam is in progress. Please check here before emailing questions!
print-tree! procedure displays two spaces between the elements in the tree?Please check back periodically in case we find any new issues.
tree2 definition was the same as tree1 when it should have had the nodes of 1 and 7 removed. (+1/2 point, NJ)tree3 was missing the required spaces. (+1/2 point, GW, HZ)Some of the problems on this exam are based on (and at times copied from) problems on previous exams for the course. Those exams were written by Charlie Curtsinger, Janet Davis, Rhys Price Jones, Samuel A. Rebelsky, John David Stone, Henry Walker, and Jerod Weinman. Many were written collaboratively, or were themselves based upon prior examinations, so precise credit is difficult, if not impossible.
Some problems on this exam were inspired by conversations with our students and by correct and incorrect student solutions on a variety of problems. We thank our students for that inspiration. Usually, a combination of questions or discussions inspired a problem, so it is difficult and inappropriate to credit individual students.