Skip to main content

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)

A depth three random tree with three branches per level and a trunk length of 60

This next example draws a four-level tree with five branches per level.

> (turtle-random-tree! (set-up-turtle) 60 5 4)

A four-level tree with five branches per level and a trunk length of 60

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)

Another four-level tree with five branches per level and a trunk length of 60

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)

A five-level tree with seven branches per level and a trunk length of 60

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 black 100x100 Sierpinski carpet with one more level than the previous image

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 require rackunit and rackunit/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.