000000.rkt
, but replace 000000
with your generated random number. Email this file as an attachment to the instructor when you are finished with your exam.
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
exam03.rkt starter source
code. Please rename this file to 000000.rkt
, but replace
000000
with your generated random number.
The prologue for this examination will be via email, which you should send to your instructor by 10:30 p.m. on Friday evening. 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”.
Which of those problems do you expect to be the most difficult for you to solve? Why?
Conclude by answering the question What is an approach that you expect will help you be successful on this exam? For example, you might suggest that you will work thirty minutes on the exam each day, or work on the exam at 7pm each day, when your brain is most able to process information.
The epilogue for this examination will also be via email, which you should send to your instructor by 10:30 p.m. on the evening after the exam is due. 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 that you would recommend to next semesters’ CSC 151 students to be successful on exams?
Topics: numeric recursion, strings and characters
A string is called a “palindrome” if reversing the characters of
the string results in the same string. A few examples of palindromes
are "radar"
, "civic"
, and "aabaa"
. Write, but do not document,
a procedure (palindrome? str)
that checks to see if
the string str
is a palindrome. You may not use any string
comparison procedures for this problem (e.g. string=?
, string<=?
,
etc.) nor may you build other strings along the way (e.g., you
cannot call string-reverse
or substring
). You must leave the
string as a string. You may not, for example, turn it into a list
of characters.
You will find string-ref
, string-length
, and char=?
useful
procedures for solving this problem.
> (palindrome? "hello")
#f
> (palindrome? "civic")
#t
> (palindrome? "civics")
#f
> (palindrome? "Civic")
#f
> (palindrome? "abccba")
#t
> (palindrome? "")
#t
Hint: Use numeric recursion with the position of the character.
Topics: numeric recursion, divide-and-conquer
Throughout the semester, you’ve used the sqrt
procedure, which computes
the square root of a positive real number. But what if that procedure
did not exist? We’d need to implement it. Fortunately, we can use a
divide-and-conquer algorithm to find square roots.
Start with two guesses: One of which you know is less than the square root of the number (0 is a good start), the other of which is larger than the square root of the number (the number itself is a good start). We repeatedly use the average of those two numbers as a guess and determine whether (a) the guess is close enough to the square root; (b) the guess is smaller than the square root; or (c) the guess is greater than the square root.
How do we know whether it’s close enough? The square root might
be an irrational number, so we can’t expect to compute it exactly.
So, we’ll consider an additional parameter, epsilon
. If the
square of the guess is within epsilon of the original number
, we
allow the computation to stop.
But what if the guess isn’t close enough? If the square of the guess is smaller than the original number, then our guess was too small, so we increase the lower bound. Otherwise, we decrease the upper bound. In both cases, we try again.
For example, let’s find the square root of 2 with an epsilon of 0.001.
Iteration 1
Iteration 2
Iteration 3
The computation continues as follows.
Not a perfect result, but a fairly good estimate. Certainly, the square is within .001 of the expected value.
Now it’s your turn to turn this idea into code.
Document and write a procedure, (square-root n epsilon)
,
that computes the square root of n
using the technique described above.
In writing this procedure, you may assume that n
is at least 2.
Note: You will find it useful to have a kernel that keeps track of the lower and upper bound described above.
Topics: list recursion
As you may recall, on a recent assignment, we wrote two procedures,
(riffle2 lst1 lst2)
, a procedure that riffles two lists together,
and (riffle3 lst1 lst2 lst3)
, a procedure that riffles three
lists together.
For example,
> (riffle2 (list 'a 'b 'c) (list 'x 'y 'z))
'(a x b y c z)
> (riffle2 (list 'a 'b 'c) (range 10))
'(a 0 b 1 c 2 3 4 5 6 7 8 9)
> (riffle2 (range 10) (list 'a 'b 'c)
'(0 a 1 b 2 c 3 4 5 6 7 8 9)
> (riffle2 null (list 'a 'b 'c))
> (riffle3 (list 'a 'b 'c) (list 'p 'q 'r) (list 'x 'y 'z))
'(a p x b q y c r z)
> (riffle3 (list 'a 'b 'c 'd) (list 'e 'f) (list 'g 'h 'i))
'(a e g b f h c i d)
> (riffle3 (list 'a 'b 'c) null (list 'd 'e 'f))
'(a d b e c f)
> (riffle3 (list 'c 'b 'a) (list 'd 'd 'd) (list 'e 'f 'g))
'(c d e b d f a d g)
Here’s a concise definition of riffle2
.
;;; Procedure:
;;; riffle2
;;; Parameters:
;;; lst1, a list
;;; lst2, a list
;;; Purpose:
;;; "Riffles" the two lists together.
;;; Produces:
;;; riffled, a list
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; * Let len be the smaller of (length lst1), (length lst2)
;;; * For all i, 0 <= i < len
;;; * (list-ref riffled (* 2 i)) = (list-ref lst1 i)
;;; * (list-ref riffled (+ 1 (* 2 i))) = (list-ref lst2 i)
;;; * If (length lst1) > len
;;; (drop riffled (* 2 len)) = (drop lst1 len)
;;; * If (length lst2) > len
;;; (drop riffled (* 2 len)) = (drop lst2 len)
(define riffle2
(lambda (lst1 lst2)
(if (null? lst1)
lst2
(cons (car lst1) (riffle2 lst2 (cdr lst1))))))
Here’s a similarly concise definition of riffle3.
;;; Procedure:
;;; riffle3
;;; Parameters:
;;; lst1, a list
;;; lst2, a list
;;; lst3, a list
;;; Purpose:
;;; "Riffles" the three lists together.
;;; Produces:
;;; riffled, a list
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; * Let len be the smaller of (length lst1), (length lst2), (length lst3)
;;; * For all i, 0 <= i < len
;;; * (list-ref riffled (* 3 i)) = (list-ref lst1 i)
;;; * (list-ref riffled (+ 1 (* 2 i))) = (list-ref lst2 i)
;;; * (list-ref riffled (+ 2 (* 2 i))) = (list-ref lst3 i)
;;; * If (length lst1) = len
;;; (drop riffled (* 3 len)) = (riffle2 (drop lst2 len) (drop lst3 len))
;;; * If (length lst2) = len
;;; (drop riffled (* 3 len)) = (riffle2 (drop lst1 len) (drop lst3 len))
;;; * If (length lst3) = len
;;; (drop riffled (* 3 len)) = (riffle2 (drop lst1 len) (drop lst2 len))
(define riffle3
(lambda (lst1 lst2 lst3)
(if (null? lst1)
(riffle2 lst2 lst3)
(cons (car lst1) (riffle3 lst2 lst3 (cdr lst1))))))
As you may have noted, the strategy for writing a concise version of riffle` is fairly straightforward:
Write, but do not document, a recursive procedure, (riffle
list-of-lists)
that takes a list of lists as a parameter and riffles
them together using similar policies.
> (riffle '((1 2 3)))
'(1 2 3)
> (riffle '(() (a b c)))
'(a b c)
> (riffle '((a b c) (x y z)))
'(a x b y c z)
> (riffle '((a b c) (1 2 3 4 5 6)))
'(a 1 b 2 c 3 4 5 6)
> (riffle '((1 2 3 4 5) (a b)))
'(1 a 2 b 3 4 5)
> (riffle '((a b c) (d e) (f) (g h) (i j k)))
'(a d f g i b e h j c k)
> (riffle '((a b c) (d e) (f) (g h) (i j k) (l m n o p)))
'(a d f g i l b e h j m c k n o p)
> (riffle '())
'()
Note: Two basic strategies come to mind. (1) You can use a
generalized version of the strategy we just described. (2) You can
probably do something interesting with filter
and map
(and some
recursion).
Topics: recursion over lists, named let
, code reading, documentation, higher order procedures
Consider the following interesting procedure definition.
(define s (lambda (l) (lambda (p?) (let s ([a null] [b null] [c
p?]) (cond [(null? c) (list (reverse a) (reverse b))] [(l (car c))
(s (cons (car c) a) b (cdr c))] [else (s a (cons (car c) b) (cdr
c))])))))
Determine what this procedure does and then do the following.
a. Reformat the procedure so that it follows reasonable standards. (You need not show this version; we only want to see the version in part b.)
b. Rename the identifiers that they will make sense to the reader.
c. Write 6P-style documentation for the renamed procedure.
d. Explain why there are two calls to reverse
in the base case.
Topics: vectors, vector recursion, higher-order procedures
Document and write a procedure, (vector-tally vec pred?)
,
that counts the number of values in vec
for which pred?
holds.
> (vector-tally (vector 1 2 3 4 5) odd?)
3
> (vector-tally (vector 1 2 3 4 5) even?)
2
> (vector-tally (vector 1 2 3 "four" 'five) odd?)
Error! odd?: contract violation
Error! expected: integer
Error! given: "four"
> (vector-tally (vector 1 2 3 "four" 'five) (conjoin integer? odd?))
2
Note: You may not convert the vector to a list (e.g., with
vector->list
), nor may you generate lists along the way (e.g.,
using map
).
Topics: deep recursion, patterns of recursion, higher-order procedures
As you may recall, we wrote a definition of deep-reverse
that looked
like the following.
;;; Procedure:
;;; deep-reverse
;;; Parameters:
;;; val, a Scheme value
;;; Purpose:
;;; Reverses the value (most frequently, a nested list).
;;; Produces:
;;; reversed, a Scheme value
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; * If val is not a list
;;; reversed is val
;;; * If val is a list
;;; * (length reversed) = (length val)
;;; * For each i, 0 <= i < (length reversed)
;;; (= (deep-reverse (list-ref reversed i))
;;; (deep-reverse (list-ref val (- (length val) i 1))))
(define deep-reverse
(lambda (val)
(if (list? val)
(reverse (map deep-reverse val))
val)))
> (deep-reverse '())
'()
> (deep-reverse '(a b c))
'(c b a)
> (deep-reverse '(a (b (c d) e) (f (((g h) i)))))
'((((i (h g))) f) (e (d c) b) a)
Here’s a definition of deep-largest
that uses a similar structure.
;;; Procedure:
;;; deep-largest
;;; Parameters:
;;; val, a Scheme value
;;; Purpose:
;;; Finds the largest real number in val
;;; Produces:
;;; largest, a real number
;;; Preconditions:
;;; val is either a real number or a deep list of real numbers.
;;; Postconditions:
;;; * largest is one of the values in val.
;;; * If r is a value in val, (>= largest r).
(define deep-largest
(lambda (val)
(if (list? val)
(apply max (map deep-largest val))
val)))
> (deep-largest 4)
4
> (deep-largest '(4 3 2 1))
4
> (deep-largest '(1 2 3 4))
4
> (deep-largest '(-4 -10 -2))
-2
> (deep-largest '((1 2) 4 ((((100) 80) 11) 3) (5 -4)))
100
> (deep-largest '((1 2) 4 ((((80) 11) 3) (5 -4))))
80
Here’s a definition of deep-tally-odd
that uses a similar, but slightly
different, structure.
;;; Procedure:
;;; deep-tally-odd
;;; Parameters:
;;; val, a Scheme value
;;; Purpose:
;;; Counts the number of odd values in val.
;;; Produces:
;;; tally, an integer
;;; Preconditions:
;;; val is either an integer or a deep list of integers
;;; Postconditions:
;;; tally represents the number of odd integers that appear in val.
(define deep-tally-odd
(lambda (val)
(if (list? val)
(apply + (map deep-tally-odd val))
(if (odd? val) 1 0))))
> (deep-tally-odd '((1 2) 4 ((((80) 11) 3) (5 -4))))
4
> (deep-tally-odd '1)
1
> (deep-tally-odd '(1))
1
> (deep-tally-odd '())
0
Here’s yet another procedure that follows a similar pattern, this time to square all the values in a deep structure, leaving the shape of the structure the same.
;;; Procedure:
;;; deep-square
;;; Parameters:
;;; val, a Scheme value
;;; Purpose:
;;; Squares all of the values in val.
;;; Produces:
;;; squared, a Scheme value
;;; Preconditions:
;;; val is either a number of a deep list of numbers
;;; Postconditions:
;;; * squared has the same "shape" as val. That is, if val
;;; is a list, squared is a list of the same length, and
;;; all of the sublists have the same shape.
;;; * Any numbers in val appear as their squares in squared.
(define deep-square
(lambda (val)
(if (list? val)
(map deep-square val)
(sqr val))))
> (deep-square '((1) (2 (((3) 4) 5))))
'((1) (4 (((9) 16) 25)))
> (deep-square '())
'()
> (deep-square '(1 1/2 3.5))
'(1 1/4 12.25)
> (deep-square 5)
25
As you may recall, when we find that we have written similar procedures, we should write a procedure that generalizes the common aspects of those procedures, making any differences parameters to that procedure.
What is similar between all of these procedures? They all check if the parameter is a list and, if so, map the same procedure over that list and then do something with the result. They all do something with the parameter if it’s not a list, even if that “something” is “just leave the parameter as is”.
a. Document a procedure, deep-fun
, that encapsulates the common
aspects of deep-reverse
, deep-largest
, and deep-tally-odd
b. Write the deep-fun
procedure.
c. Rewrite deep-reverse
, deep-largest
, and deep-tally-odd
so
that they use deep-fun
to do most of the work.
d. Using deep-fun
, write (deep-tally pred? val)
, a generalized
version of deep-tally-odd
that tallies the number of values for
which the predicate holds.
We will post answers to questions of general interest here while the exam is in progress. Please check here before emailing questions!
section
?#|
before the pasted material. Put
a |#
after the pasted material.#|
and |#
or semicolons. You might also add a note or two as to
what you were trying to do.lambda
that looks like (define (proc params) body)
. Can I use that?(random 1000000)
. If you end up with a number less than six digits, add zeros to the left side of the number until you have six digits.; STUB
on some of the procedures. What does that mean?val
.all
(as in the reading), we can write
(define all-integer?
(lambda (lst)
(all integer? lst)))
all
encapsulates the common aspects and the new all-integer?
is defined in terms of all
.all
, except for the deep recursion pattern exhibited in those four procedures. You should then rewrite the procedures in terms of deep-fun
, just as we rewrote all-integer?
in terms of all
.reverse
, apply +
, and apply max
inapply +
a procedure?(lambda (lst) (apply + lst))
or as
(section apply + <>)
.Please check back periodically in case we find any new issues.
square-root
. [NC, 1 point]iota
instead of range
. [JM, 1 point]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, Fahmida Hamid, 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.