Exam 4
Assigned: Wednesday, May 3
Due: Tuesday, May 9 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.
You will also need to make a copy of tree-utils.rkt on your Desktop. You will not submit that file.
Prologue
The prologue for this examination will be via email, which you should send to your instructor. Your message should be titled CSC 151.01: Exam 4 Prologue (your name).
For each problem, please include a short note about something that will help you solve the problem. Mostly, we want to see some evidence that you’ve thought about the problem. You might note some similar procedures you’ve written or problems you’ve solved in the past (e.g., in a lab or on a homework assignment). You might note procedures that you expect to use. You might sketch an algorithm. You might pose a question to yourself. (We won’t necessarily read this in a timely fashion, so if you have questions for your instructor, you should ask by email or in person.)
If, when looking at a problem, you think you already know the answer, you can feel free to write something short like “solved” or “trivial”.
After providing a short note on each problem, conclude by answering the question What is an approach that you expect will help you be successful on this exam?
Epilogue
The epilogue for this examination will also be via email, which you should send to your instructor. Your message should be titled CSC 151.01: Exam 4 Epilogue (your name). Include answers to the following questions.
What was the most difficult part of the exam?
What made that part difficult?
What are two recommendations you would give to students in the fall CSC 151 on how to be successful on their exams?
Problem 1: Reversing vectors
Topics: vector mutation, vector recursion, numeric recursion
Document and write a procedure,
(vector-reverse! vec), that reverses a vector in place, swapping the
first and last elements, the second and penultimate elements, and so on and
so forth.
> (define vec1 (vector 'a 'b 'c 'd 'e))
> (define vec2 (list->vector (iota 8)))
> (vector-reverse! vec1)
> vec1
#(e d c b a)
> (vector-reverse! vec2)
> vec2
#(7 6 5 4 3 2 1 0)
> (vector-reverse! vec2)
> vec2
#(0 1 2 3 4 5 6 7)
You may not use the list-based reverse procedure in solving this
problem. You may not create additional vectors or lists in solving
this problem.
Hint: If you are not getting the results you expect, you may find it useful to print out the current position and state of the vector at each recursive call.
(display (list 'pos pos 'vec vec)) (newline)
Problem 2: Making selectors
Topics: higher-order procedures, list recursion
As you may recall from when we first learned recursion, one of the key
reasons we do recursion over lists, rather than relying on something
like map, is so that we can return values other than lists of the
same length.
One of the common activities we do with lists is to select elements that meet some criterion. For example, the following procedure selects all of the odd values in a list.
(define select-odd
(lambda (lst)
(cond
[(null? lst)
null]
[(odd? (car lst))
(cons (car lst) (select-odd (cdr lst)))]
[else
(select-odd (cdr lst))])))
> (select-odd (list 5 2 4 1 8 6 7 13 8))
'(5 1 7 13)
At this point, you know that when you have a common task (e.g.,
selecting elements of a list), you should write a generalized
procedure to help with that task. Here’s the general version
of select-odd.
(define select
(lambda (pred? lst)
(cond
[(null? lst)
null]
[(pred? (car lst))
(cons (car lst) (select pred? (cdr lst)))]
[else
(select pred? (cdr lst))])))
> (select odd? (list 5 2 4 1 8 6 7 13 8))
'(5 1 7 13)
> (select even? (list 5 2 4 1 8 6 7 13 8))
'(2 4 8 6 8)
> (select (l-s <= 6) (list 5 2 4 1 8 6 7 13 8))
'(8 6 7 13 8)
But we can take that a step further. We can write a procedure,
make-selector, that takes only one parameter, a predicate, and
returns a procedure that takes a list as a parameter and selects
only the elements in list for which the predicate holds.
;;; Procedure:
;;; make-selector
;;; Parameters:
;;; pred?, a unary predicate
;;; Purpose:
;;; Create a selector for pred?.
;;; Produces:
;;; selector, a procedure from lists to lists
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; If newlst = (selector lst) then,
;;; Every element of newlst is in lst
;;; (pred? val) holds for every value in newlst
;;; (pred? lav) fails to hold for every value in lst that is not in newlst
;;; The elements of newlst appear in the same order that they
;;; appeared in lst.
(define make-selector
(lambda (pred?)
...))
> (define select-even (make-selector even?))
> (select-even (list 5 1 2 4 3 8 1 6))
'(2 4 8 6)
> (define select-big (make-selector (l-s <= 4)))
> (select-big (list 5 1 2 4 3 8 1 6))
'(5 4 8 6)
a. Implement make-selector. You may not call select directly, but
you can use it to help you think about how to write make-selector.
b. Sometimes, instead of keeping elements for which a predicate holds,
we want to remove the elements for which a predicate holds. As
concisely as possible, write a procedure, (make-rejector pred?),
that creates a procedure that removes the elements for which pred?
holds from a list. You may, but need not, rely on your definition
of make-selector.
> (define remove-odds (make-rejector odd?))
> (remove-odds null)
'()
> (remove-odds (list 1 2 3 4 5))
'(2 4)
> (remove-odds (list 6 1 4 2 8 7 4 2))
'(6 4 2 8 4 2)
> (define remove-big (make-rejector (l-s <= 5)))
> (remove-big (list 6 1 4 2 8 7 4 2))
'(1 4 2 4 2)
Note: The original select-odd procedure will crash when given
a list which contains non-numbers. It is fine if the procedures
returned by make-selector and make-rejector also crash when
the lists they are given contain values to which the predicate
cannot be applied.
Problem 3: Reading words
Topics: files, file recursion, read-char
We have discussed two different ways to read files with Scheme: The read procedure reads a Scheme value (e.g. a string or the number 42) from a file, while the read-char parameter reads a single character. The read-char function allows us to deal with raw text files, this isn’t always convenient; when dealing with raw text files, it is often useful to read them word-by-word rather than one character at a time.
Document and write a procedure (read-word input-port) that reads
an entire word from an open file. A word is a sequence of alphabetic
and numeric characters separated by spaces, punctuation, or other
symbols. You can check if a character is alphabetic or numeric using
char-alphabetic? and char-numeric?, respectively.
The following example uses of read-word may be useful in developing your approach. The contents of somewords.txt are:
Look! This file has the
best words spread out
over 3 lines.
Using this file as input, the following calls to read-word show
how your implementation should combine sequences of characters from
the file into words. Note that the second call to read-word skips
over all non-alphanumeric characters (both the exclamation point
and space) to find the beginning of the next word.
> (define myport (open-input-file "/home/rebelsky/CSC151/somewords.txt"))
> (read-word myport)
"Look"
> (read-word myport)
"This"
> (read-word myport)
"file"
> (read-word myport)
"has"
> (read-word myport)
"the"
> (read-word myport)
"best"
> (read-word myport)
"words"
> (read-word myport)
"spread"
> (read-word myport)
"out"
> (read-word myport)
"over"
> (read-word myport)
"3"
> (read-word myport)
"lines"
> (read-word myport)
#<eof>
> (close-input-port myport)
As the example suggests, when you reach the end of the file, you should return the end-of-file object.
The somewords.txt file is relatively simple. When you
are ready for more of a challenge, try
/home/rebelsky/CSC151/morewords.txt, which contains
the following text.
SamR (who often writes parenthetical remarks)
worries that some
students
will
think
"I can just use the read procedure."
They will be wrong.
Problem 4: Building binary trees
Topics: vectors, divide and conquer, numeric recursion, trees
As we explored the list data type, we found that there were two primary
ways to build lists: We can use the core list building operations
of cons and null or we can use procedures, such as list or
iota that make it much more convenient to build larger lists.
Can we write similar procedures for building binary trees? Certainly. Here’s one common tree-building strategy: We can turn a vector into a tree with a fairly straightforward process.
Use the middle element of the vector as the root of the tree. Build the left half of the tree from the left half of the vector using a similar process. Build the right half of the tree from the right half of the vector using a similar process.
For example, given the vector #("this" "sample" "vector" "contains"
"seven" "string" "values"), we’d note that "contains" is in the
middle position, so it will be the top value in the tree. We’ll
then turn "this", "sample", and "vector" into the left subtree
and "seven", "string", and "values" into the right subtree.
For the left subtree, "sample" will be at the top, with "this"
as its left subtree and "vector" as its right subtree.
Write, but do not document, a procedure,
(vector->tree vec)
that turns a vector into a tree using that strategy.
> (vector->tree (vector "this" "sample" "vector" "contains" "seven" "string" "values"))
'#(node
"contains"
#(node "sample" #(node "this" empty empty) #(node "vector" empty empty))
#(node "string" #(node "seven" empty empty) #(node "values" empty empty)))
> (vector->tree (vector "this" "sample" "vector" "contains" "six" "values"))
'#(node
"vector"
#(node "this" empty #(node "sample" empty empty))
#(node "six" #(node "contains" empty empty) #(node "values" empty empty)))
> (vector->tree (vector))
'empty
> (vector->tree (vector "aardvark"))
'#(node "aardvark" empty empty)
> (vector->tree (vector "aardvark" "baboon"))
'#(node "aardvark" empty #(node "baboon" empty empty))
> (vector->tree (vector "aardvark" "baboon" "chinchilla"))
'#(node "baboon" #(node "aardvark" empty empty) #(node "chinchilla" empty empty))
> (vector->tree (vector "aardvark" "baboon" "chinchilla" "dingo"))
'#(node
"baboon"
#(node "aardvark" empty empty)
#(node "chinchilla" empty #(node "dingo" empty empty)))
> (vector->tree (vector "aardvark" "baboon" "chinchilla" "dingo" "emu"))
'#(node
"chinchilla"
#(node "aardvark" empty #(node "baboon" empty empty))
#(node "dingo" empty #(node "emu" empty empty)))
> (vector->tree (vector "aardvark" "baboon" "chinchilla" "dingo" "emu" "fox"))
'#(node
"chinchilla"
#(node "aardvark" empty #(node "baboon" empty empty))
#(node "emu" #(node "dingo" empty empty) #(node "fox" empty empty)))
Note: Your output may not appear in quite the same form. That’s fine.
Hint: You may want to write a kernel or helper that keeps track of the boundaries of the portion of the vector you are currently converting. For our original vector with seven strings,
- We start with a lower bound of 0 (the least index) and an upper bound of 6 (the greatest index). The middle element is at (0+6)/2, or 3.
- The left subtree will have a lower bound of 0 and an upper bound of 2. (We’ve already used the element at position 3.)
- The right subtree will have a lower bound of 4 and an upper bound of 6.
- When we reach the point that we’re building the right subtree, the middle element is at (4+6)/2 or 5. The left subtree of that is at positions 4 to 4. The right subtree is at positions 6 to 6.
(define vector->tree
(lambda (vec)
(let helper ([left 0]
[right (- (vector-length vec) 1)])
(if ???
empty
(node ??? ??? ???)))))
Hint: You’ll need a bit of care with base cases.
Note: Do not attempt tail recursion. Tail recursion works poorly with trees.
Problem 5: Whatzitdo?
Topics: code reading, HOPs
Sometimes students (and professors) come up with difficult-to-read solutions to problems. Here’s one such solution. Isn’t it beautiful?
(define x (lambda (y z) (apply o (make-list y z))))
a. Clean up this procedure. You should reformat the code (add carriage returns and indent to format clearly); rename the procedure, parameters, and variables; and simplify any unnecessarily complicated or redundant code.
b. Write 6P-style documentation for the procedure.
c. Explain how the procedure achieves its purpose.
Problem 6: Selection sort
Topics: sorting, vectors, numeric recursion, higher-order procedures, extreme values
The famous selection sort algorithm for sorting vectors works by repeatedly stepping through the vector from back to front, swapping the largest remaining thing to the next place in the vector. We might express that algorithm for a vector of strings as
(define vector-selection-sort!
(lambda (vec less-extreme?)
(let kernel [(pos (- (vector-length vec) 1))]
(when (>= pos 0)
(let* ([index (vector-index-of-extreme vec pos less-extreme?)]
[most-extreme (vector-ref vec index)])
(vector-set! vec index (vector-ref vec pos))
(vector-set! vec pos most-extreme)
(kernel (- pos 1)))))))
Of course, we will need the procedure
vector-index-of-extreme, which we might document
as follows.
;;; Procedure:
;;; vector-index-of-extreme
;;; Parameters:
;;; vec, a vector
;;; pos, an integer
;;; less-extreme?, a procedure that compares two values for order
;;; Purpose:
;;; Find the index of the extreme value in the subvector of
;;; vec at positions [0..pos].
;;; Produces:
;;; index-of-extreme, an integer
;;; Preconditions:
;;; pos >= 0.
;;; (vector-length vec) > pos.
;;; less-extreme? can be applied to any two values in vector
;;; less-extreme? represents a transitive operation
;;; Postconditions:
;;; For all i from 0 to pos, inclusive, either
;;; (less-extreme? (vector-ref vec i) (vector-ref vec index-of-extreme))
For example,
> (define claim (vector "computers" "are" "sentient" "and" "malicious" "on" "tv"))
> (vector-index-of-extreme claim 6 string<=?)
6
> (vector-ref claim 6)
"tv" ; "tv" is the alphabetically last word
> (vector-index-of-extreme claim 6 string>=?)
3
> (vector-ref claim 3)
"and" ; "and" is the alphabetically first word
> (vector-index-of-extreme claim 5 string<=?)
2
> (vector-ref claim 2)
"sentient" ; If we ignore the "tv", "sentient" is the alphabetically last
> (vector-selection-sort! claim string<=?)
> claim
'#("and" "are" "computers" "malicious" "on" "sentient" "tv")
> (define shorter? (lambda (x y) (<= (string-length x) (string-length y))))
> (vector-selection-sort! claim shorter?)
> claim
'#("on" "tv" "and" "are" "sentient" "computers" "malicious")
> (define numbers (vector 301 151 161 207 321 211 213))
> numbers
'#(301 151 161 207 321 211 213)
> (vector-selection-sort! numbers <=)
> numbers
'#(151 161 207 211 213 301 321)
> (vector-selection-sort! numbers >=)
> numbers
'#(321 301 213 211 207 161 151)
> (define second-digit (lambda (val) (modulo (quotient val 10) 10)))
> (define smaller-second-digit?
(lambda (x y)
(<= (second-digit x) (second-digit y))))
> (vector-selection-sort! numbers smaller-second-digit?)
> numbers
'#(301 207 211 213 321 151 161)
Implement the vector-index-of-extreme procedure.
Hint: We’ve looked for extreme values before, such as the brightest color or largest number in a list.
Questions and Answers
We will post answers to questions of general interest here while the exam is in progress. Please check here before emailing questions!
General Questions and Answers
- What is a general question?
- A question that is about the exam in general, not a particular problem.
- Do the two sections have the same exam?
- Yes.
- Can we still invoke the “There’s more to life” clause if we spend more than five hours on the exam?
- Yes. However, we really do recommend that you stop at five hours unless you are very close to finishing. It’s not worth your time or stress to spend more effort on the exam. It is, however, worth your time to come talk to us, and perhaps to get a mentor or more help (not on this exam, but on the class). There’s likely some concept you’re missing, and we can help figure that out.
- What do you mean by “implement?”
- Write a procedure or procedures that accomplish the given task.
- Do we have to make our code concise?
- You should strive for readable and correct code. If you can make it concise, that’s a plus, but concision is secondary to readability and correctness. Long or muddled code is likely to lose points, even if it is correct.
- Much of your sample 6P-style documentation has incomplete sentences. Can we follow that model? That is, can we use incomplete sentences in our 6P-style documentation?
- Yes, you can use incomplete sentences in 6P-style documentation.
- You tell us to start the exam early, but then you add corrections and questions and answers. Isn’t that contradictory? Aren’t we better off waiting until you’ve answered the questions and corrected any errors?
- We think you’re better able to get your questions answered early if you start early. Later questions will generally be told “See the notes on the exam.”
- How do we know what our random number is?
- You should have received instructions on how to generate your random number on the day the exam was distributed. If you don’t have a number, ask your professor for one before submitting your exam.
- To show we’ve tested the code informally, would you just like us to just post the inputs we used to test the procedure? If so, how should we list those?
- Copy and paste the interactions pane into the appropriate place in the definitions pane. Select the text. Under the Racket menu, use “Comment out with semicolons.”
- Should we include examples and, if so, how do we include them?
- You should certainly include examples. We would recommend that you copy and paste them from the interactions pane to right below the problem in the definitions pane, and then comment them out with semicolons. (Select and then choose “Comment out with semicolons” from the “Racket” menu. Do not use “Comment out with a box!”
- Should we cite our partner from a past lab or assignment if we use code from a past lab or assignment?
- You should cite both yourself and your partner, although you should do so as anonymously as possible. For example “Ideas taken from the solution to problem 7 on assignment 3 written by student 641321 and partner.”
- If we write a broken procedure and replace it later, should we keep the previous one?
- Yes! This will help us give you partial credit if your final procedure isn’t quite right.
- Do I need to document global and local helper procedures?
- You must document global helpers using the 4Ps. Local helper procedures should have descriptive names, but documentation is not required.
- Even though I put the
tree-utils.rktfile on my Desktop I still get an error. What gives? - Your Desktop might not be “linked” to DrRacket. Open the Terminal application and type in the following.
$ raco link /home/username/DesktopYou should use your MathLAN username instead of
usernameabove.
Problem 2: Making selectors
- Do you really mean to have
lavas a parameter in the postconditions? - Yes. It was intended as a joke.
valis in the result.lav(the opposite ofval) is not. - Can we use the code of
selecteven though we can’t call it directly? - Yes. You can copy, paste, and change, if you find that helpful.
- I recall writing a
negateprocedure at some point. Can I use that? - Yes. Here’s what it looks like.
(define negate
(lambda (pred?)
(lambda (x)
(not (pred? x)))))
Problem 4: Building binary trees
- What do we use for the root if the vector has an even length.
- There are effectively two middle elements in an even-length vector. You can use either of those. Here is an example in which we are using the “left middle”.
> (vector->tree (vector "aardvark" "baboon" "chinchilla" "dingo" "emu" "fox"))
'#(node
"chinchilla"
#(node "aardvark" nil #(node "baboon" nil nil))
#(node "emu" #(node "dingo" nil nil) #(node "fox" nil nil)))
Errata
Please check back periodically in case we find any new issues.
Problem 3: Reading words
- The path of the “somewords.txt” file was incorrect. [+1 point, YC]
Problem 4: Making trees
- In the hint, the wrong indices were used. (They were right in the Markdown, but our Markdown processor seems to like changing numbers.) [+1 point, MJ]
- The
nilin the examples should beempty. [+1 point, DUS]
Problem 5: Whatzitdo?
- The use of “x” inside the procedure should have actually been “z.”
Problem 6: Selection sort
- Minor typo in the “Topics:” section of the problem. [+1 point, YC]
Citations
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, Titus Klinge, 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.