Exam 3: Recursion and beyond
Assigned: Wednesday, April 12
Due: Tuesday, April 18 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 exam3.rkt starter source code. Please rename this file to 000000.rkt
, but replace 000000
with your assigned random number.
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 3 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 3 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 things you should do differently on the next exam?
Problem 1: Whatzitdo?
Topics: recursion over lists, named let
, code reading, documentation
Consider the following interesting procedure definition.
(define s
(lambda (l)
(let kernel ([a null]
[b null]
[c l])
(cond
[(null? c)
(list (reverse a) (reverse b))]
[(number? (car c))
(kernel (cons (car c) a) b (cdr c))]
[else
(kernel a (cons (car c) b) (cdr c))]))))
Determine what this procedure does and then do the following.
a. Rename the procedure and the parameters so that they will make sense to the reader.
b. Explain why there are two calls to reverse
in the base case.
c. Write 6P-style documentation for the procedure.
Problem 2: Subtle substrings
Topics: Numeric recursion, Strings, Testing
It is often useful to determine whether one string appears within another string. For example, we might want to know whether a faculty member is a rebel by checking for the appearance of the word “rebel” in their name. More generally, once we find that a string appears within another string, we might want to identify the “index” of the substring, the position at which it starts.
Consider the following implementation of index-of-substring
, which
is supposed to achieve that purpose.
;;; Procedure:
;;; index-of-substring
;;; Parameters:
;;; str, a string
;;; substr, a string
;;; Purpose:
;;; Find the index of an instance of substr in str.
;;; Produces:
;;; pos, an integer or #f
;;; Preconditions:
;;; No additional.
;;; Postconditions:
;;; If substr is not contained in str, then pos is #f
;;; If substr is contained within str, then pos is a non-negative
;;; integer and
;;; 0 <= pos < (string-length str)
;;; substr appears as a substring of str, with the initial character
;;; of substr at position pos of str. That is,
;;; (equal? (substring str pos (+ pos (string-length substr))) substr)
;;; If substr appears multiple times within str, then pos may indicate
;;; any of the appearances.
(define index-of-substring
(lambda (str substr)
(let ([substr-len (string-length substr)])
(let kernel ([pos 0]
[finish (- (string-length str) substr-len)])
(cond
[(= pos finish)
#f]
[(equal? substr (substring str pos (+ pos substr-len)))
pos]
[else
(kernel (+ 1 pos) finish)])))))
Here is a simple test suite that someone developed for this procedure.
(define ios-tests
(test-suite
"Tests of string-contains"
(check-equal? (index-of-substring "banana" "ban")
0
"start of string")
(check-equal? (index-of-substring "banana" "nan")
2
"middle of string")
(check-false (index-of-substring "banana" "NAN")
"incorrect capitalization")
(check-false (index-of-substring "banana" "orange")
"mismatched fruit")))
Although the procedure passes the given tests, it is not correctly implemented. In fact, there are at least two different situations in which this procedure gives an incorrect result.
a. Add two distinct checks for which the given implementation of
index-of-substring
fails.
What do we mean by distinct? The relationship between substr
and
str
should be different in each case. As a counterexample, the
following two tests are not distinct because they test essentially
the same thing, a matching substring at the start of the string.
(check-equal? (index-of-substring "banana" "ban")
0)
(check-equal? (index-of-substring "pineapple" "pine")
0)
b. Once you have found additional errors in this implementation, develop a correct implementation. You may find it useful to add even more tests to ensure that your new implementation is correct.
Problem 3: Closest to zero
Topics: list recursion, tail recursion, direct recursion
a. Write a procedure, (closest-to-zero-a values)
, that, given a list of real
numbers (including both positive and negative numbers) as input, returns the
value closest to zero in the list. Your solution must use basic recursion.
Hint: Think about how, given two numbers, you determine which is closer to zero.
Note: If there are multiple values equally close to zero, and all are closer than any other value, you may return any of them.
b. Write a tail-recursive version called closest-to-zero-b
that uses a
local helper. Your helper should likely take closest-so-far
and
remaining
as parameters.
c. Explain which version of closest-to-zero
you prefer and why.
Problem 4: Random turtle Trees
Topics: basic recursion, numeric recursion, turtle graphics
In a recent assignment, you wrote a procedure to draw regular turtle trees. In this problem, you will draw irregular trees, which some people prefer.
Our new procedure has the signature (turtle-random-tree! turtle length
branches levels)
. As you might guess, it should use
the provided turtle
to draw a tree. You will note that we have
replaced the list of branching factors with two new parameters:
branches
and levels
. The branches
parameter indicates the
number of branches at each level. (Yes, we will have the same
number of branches at each level.) The levels
parameter indicates
how many levels we will use in the tree.
What angles should you use when branching? It should be a random angle between -60 and 60 degrees.
How long should the sub-branches be? Let’s go with 75% of the length of the current branch.
On the homework, many of you used turtle-teleport!
to return the turtle to
its starting location before drawing the next branch. Teleportation is
inelegant, so let’s consider two other approaches.
a. We could make a clone of the turtle for each branch. That strategy has the
advantage that in addition to giving each clone a different angle, we might also
set/modify some other characteristic randomly. Write a version of turtle-random-tree!
(which you should call turtle-random-tree-a!
) that builds a clone for
each branch. Here’s a starting point.
(define turtle-random-tree-a!
(lambda (turtle length branches levels)
(turtle-forward! turtle length)
(repeat branches
(lambda ()
(let ([dolly (turtle-clone turtle)])
...)))))
b. As you’ve seen in the past, it is a good strategy to return your
turtle to its starting point and position. Write a version of
turtle-random-tree!
(which you should call turtle-random-tree-b!
)
that uses that strategy. You may not clone the turtle, teleport
the turtle, or make the turtle face a particular direction; the
return to the original location and orientation should come naturally
as part of the code design.
c. Explain which you prefer and why.
You may find the following procedure helpful during your development.
; Create a world, a turtle, set the brush to a fine line, and position the turtle
; at the bottom middle of the image facing up.
(define set-up-turtle
(lambda ()
(let* ([world (image-show (image-new 300 200))]
[tommy (turtle-new world)])
(turtle-turn! tommy -90)
(turtle-teleport! tommy 150 200)
(turtle-set-brush! tommy "2. Hardness 100" 0.15)
tommy)))
After running the set-up code above, you should be able to run the
following examples with a completed turtle-random-tree!
function. While
you should run the tests below, to earn full credit on this problem
you must include three examples of your own. Try varying the depth,
branches, and length to show how your tree procedure responds.
Include the resulting images in your exam file using the Insert
Image option in DrRacket.
The following code should draw a tree with a trunk length of 60, three branches, and three levels.
> (turtle-random-tree! (set-up-turtle) 60 3 3)
This next example draws a four-level tree with five branches per level.
> (turtle-random-tree! (set-up-turtle) 60 5 4)
Here is another possible result of running the turtle-random-tree!
procedure with the same parameters:
> (turtle-random-tree! (set-up-turtle) 60 5 4)
Finally, here is a five-level tree with seven branches per level. This one will take quite a while to finish drawing.
> (turtle-random-tree! (set-up-turtle) 60 7 5)
Problem 5: Verifying the Preconditions
Topics: Verifying preconditions, GIMP tools
Recall that on a previous assignment we asked you to write a procedure (sierpinski-carpet-region! image left top width height small-enough)
that takes an image
, the left
, top
, width
, and height
of a rectangular region within the image, and a small-enough
parameter, and modifies the region of the image to look like the Sierpinski carpet fractal.
Below is an example call to the procedure on a black image.
> (context-set-bgcolor! "black")
'()
> (define c (image-new 100 100))
> (sierpinski-carpet-region! c 0 0 100 100 10)
a. Write 6P documentation for the sierpinski-carpet-region!
procedure, giving special attention to the preconditions of the procedure.
b. Below is an example solution to the sierpinski-carpet-region!
.
(define sierpinski-carpet-region!
(lambda (image left top width height small-enough)
(let* ([area (* width height)]
[w/3 (/ width 3)]
[h/3 (/ height 3)]
[left1 (+ left w/3)]
[left2 (+ left1 w/3)]
[top1 (+ top h/3)]
[top2 (+ top1 h/3)])
(when (>= area small-enough)
(image-select-rectangle! image REPLACE left1 top1 w/3 h/3)
(context-set-fgcolor! "white")
(image-fill-selection! image)
(sierpinski-carpet-region! image left top w/3 h/3 small-enough)
(sierpinski-carpet-region! image left1 top w/3 h/3 small-enough)
(sierpinski-carpet-region! image left2 top w/3 h/3 small-enough)
(sierpinski-carpet-region! image left top1 w/3 h/3 small-enough)
(sierpinski-carpet-region! image left2 top1 w/3 h/3 small-enough)
(sierpinski-carpet-region! image left top2 w/3 h/3 small-enough)
(sierpinski-carpet-region! image left1 top2 w/3 h/3 small-enough)
(sierpinski-carpet-region! image left2 top2 w/3 h/3 small-enough)))))
Rewrite the above procedure using the husk and kernel method with a private helper procedure named kernel
.
The husk of the procedure should approriately verify that all preconditions are met and gracefully exit with an appropriate error message if one precondition is not satisfied.
The kernel should handle the recursion and simplify the code above as much as possible.
Problem 6: Mapping Vectors
Topics: Vectors
The map
procedure has been quite helpful for constructing lists.
Unfortunately, we have not seen an equivalent procedure for vectors.
Write two procedures (vmap proc vec)
and (vmap! proc vec)
.
The vmap
procedure constructs a new vector by applying the procedure proc
to each element of the vector vec
.
(Note that the vector vec
is not changed in this procedure.)
The vmap!
procedure is similar to vmap
except it modifies the original vector vec
using side effects and returns nothing.
> (define vec (vector 1 2 3))
> vec
'#(1 2 3)
> (vmap increment vec)
'#(2 3 4)
> vec
'#(1 2 3)
> (vmap! increment vec)
> vec
'#(2 3 4)
Note that you may not use map
or any other procedures that are similar to vmap
and vmap!
in your solutions.
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.
Errata
Please check back periodically in case we find any new issues.
General
- The
exam3.rkt
file did not requirerackunit
andrackunit/text-ui
. [1 point, NO, LW] - The
exam3.rkt
file included procedures that caused syntactic errors. [1 point, Various] - There was a grammatical error in problem 3. [1 point, JT]
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, 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.