Skip to main content

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.rkt file 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/Desktop

You should use your MathLAN username instead of username above.

Problem 2: Making selectors

Do you really mean to have lav as a parameter in the postconditions?
Yes. It was intended as a joke. val is in the result. lav (the opposite of val) is not.
Can we use the code of select even though we can’t call it directly?
Yes. You can copy, paste, and change, if you find that helpful.
I recall writing a negate procedure 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 nil in the examples should be empty. [+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.