This is the final exam that was given to CS1321X students in
Fall 2002.
CS 1321X
Final Exam
Fall 2002
Please write your name on the upper right hand corner of this page. There
are 170 points possible on this exam. You have 170 minutes to complete it
and turn it in. Put only what you want to be graded on the front sides of
the pages, and use the back sides of the pages as scratch paper. If you
run out of room on the front side of the page, you can put part of your
answer on the back of that same page, but make sure you note on the front
of the page that there's stuff to be graded on the back. And please strive
for legibility, because we don't give credit for stuff we can't read.
All programs are to be written in Scheme, unless the instructions for a
specific problem say to write the program in Python.
For any problem, make sure that you stay within the constraints and follow
the specifications given in that problem. There are a couple of pages at
the end of this exam to supplement your memory about iteration and vectors
in Scheme. You may remove these pages from the rest of the exam. You don't
have to comment the code that you write for this exam.
1. (10 points) Given the creation of this constant:
(define *code-list*
'((a n) (b o) (c p) (d q) (e r) (f s) (g t) (h u) (i v) (j w) (k x) (l y) (m z)
(n a) (o b) (p c) (q d) (r e) (s f) (t g) (u h) (v i) (w j) (x k) (y l) (z m) (_ _)))
create a Scheme function called replace-char which takes two arguments.
The first argument should be either a letter of the alphabet or an
underscore. The second argument should be the constant defined above.
Your replace-char function should lookup the character that was passed as
the first argument in the association list that was passed as the second
argument and return whatever character is associated with the character
that was passed as the first argument. In other words, replace-char
works like this:
> (replace-char 'a *code-list*)
N
> (replace-char 'b *code-list*)
O
> (replace-char 'c *code-list*)
P
>
:
:
> (replace-char 'y *code-list*)
L
> (replace-char 'z *code-list*)
M
> (replace-char '_ *code-list*)
_
>
You don't need to worry about what happens if replace-char is passed a
character that isn't in the association list.
2. (10 points) Assume that you now have a working version of replace-char.
Don't rewrite replace-char in the space below, but use replace-char and
augmenting recursion to write a little encryption program called crypto-1
that takes a message represented as a list of characters and the association
list defined in Problem 1 and returns an encrypted message like this:
> (crypto-1 '(b e e f c a k e) *code-list*)
(O R R S P N X R)
> (crypto-1 '(p _ q h z c f _ o v t _ p b e r) *code-list*)
(C _ D U M P S _ B I G _ C O R E)
>
Also, you are to define only one new function. Don't bother with
abstraction here. Just write one new function.
3. (10 points) Rewrite your encryption program from Problem 2 so that it
uses tail recursion instead of augmenting recursion. You can call
replace-char again, but don't rewrite it here, regardless of how you
implemented it in Problem 1. Call your new encryption program crypto-2.
Also, you are to define no more than two new functions in the space below,
including the one called crypto-2. You can ignore abstraction again.
4. (10 points) OK, this is the last time with the encryption thing.
Rewrite your encryption program one more time, and this time call it
crypto-3. The new wrinkle here is that you are to use map as your control
structure. And don't call replace-char this time. Instead, use a lambda
function that does the same thing as replace-char. As in the previous
problems, forget about abstraction here. Just define one new function,
which of course is called crypto-3.
5. (10 points) Scheme provides a function that takes a list as an argument
and converts that list to a vector as shown:
> (list->vector '(a b c d e))
#5(a b c d e)
> (list->vector '())
#0()
>
Using Scheme, create your own version of list->vector and call it
my-list->vector.
6. (10 points) Scheme provides a function that takes a vector as an
argument and converts that vector to a list as shown:
> (vector->list #5(a b c d e))
(a b c d e)
> (vector->list #0())
()
>
Using Scheme, create your own version of vector->list and call it my-vector->list.
7. (10 points) Here are some questions you might be able to answer (with
no more than two sentences each, please) even if you can't write an iota
of Scheme code:
a) What's functional programming?
b) What's a side effect?
c) What's abstraction?
d) What's reference?
e) What's synthesis?
f) What's the difference between a procedure and a process?
g) What's the "shape" of a process?
h) How does the difference between tail recursion and augmenting recursion
affect the shape of a process?
8. (10 points) Let's say that Dr. Scheme doesn't have a built-in and
operator. Would the following function be a good substitute for and
(assuming it was sufficient for and to accept only two arguments)? Explain.
(define (and operand1 operand2)
(cond [(not operand1) #f]
[(not operand2) #f]
[else #t]))
9. (10 points) You may not believe it, but some of you aren't really
expending much effort at using meaningful, helpful names, comments, or
abstraction when constructing the Scheme functions that we have to grade.
Now it's time for us to turn the tables. Take a good look at the
following four functions:
(define (lista list1)
(listb list1 () () ()))
(define (listb list1 list2 list3 list4)
(cond ((null? list1) (list list2 list3 list4))
(else (listc (cdr list1)
(append list2 (list (car list1))) list3 list4))))
(define (listc list1 list2 list3 list4)
(cond ((null? list1) (list list2 list3 list4))
(else (listd (cdr list1)
list2 (append list3 (list (car list1))) list4))))
(define (listd list1 list2 list3 list4)
(cond ((null? list1) (list list2 list3 list4))
(else (listb (cdr list1)
list2 list3 (append list4 (list (car list1)))))))
Now tell us what will be returned when the following call to lista is
evaluated (and think of what your grader goes through when you turn in
code that looks like this):
> (lista '(s g r p h u a o l c s e e t s))
Below is a highly-abstracted map of routes between some selected cities
in Georgia, including estimated driving distances between these cities.
[** sorry, the map isn't reproduced here in this sample exam, but you
could recreate it from the information below **]
Here's an association list that encodes the information contained in the
map above:
(define *map*
'((atlanta (macon 82) (perry 108) (butler 90))
(macon (atlanta 82) (perry 25))
(butler (atlanta 90) (albany 77))
(perry (atlanta 108) (macon 25) (tifton 76))
(tifton (valdosta 50) (perry 76) (albany 43))
(valdosta (tifton 50))
(albany (tifton 43) (butler 77))
)
)
10. (20 points): Using Scheme, construct a function which takes as
parameters the names of two cities on the map from the previous page,
along with the association list bound to the variable *map*. This
function uses depth-first search to find one path without cycles which
connects one city to the other along with the total driving distance for
that path. Call this function find-one-path. One example of a function
invocation would be this:
> (find-one-path 'atlanta 'valdosta *map*)
and it would return one of the following results:
(233 (atlanta macon perry tifton valdosta)) or
(234 (atlanta perry tifton valdosta)) or
(260 (atlanta butler albany tifton valdosta))
(Which result is actually returned? It would depend on how you implement
your search; it doesn't really matter which one is returned, just so long
as one of these is returned.)
11. (10 points) Explain what a state space search is. Describe the three
pieces of information that should be passed to a state space search
procedure for the search to be successful. Finally, explain why it's
useful for computer science types like us to know about state space searches.
12. (10 points) In the context of object-oriented programming, explain
what a class is and what an object is. How are they related? And while
you're at it, explain what inheritance is and why it's useful.
13. (20 points) Forget vending machines. I've decided to follow a
different career path. I want to set up my own Internet casino. Once
again, however, I'd like to simulate its operation before I fully commit
to the venture. So as I explore my online blackjack possibilities, I'll
need a generator function (i.e., lexical closure) that will deal cards
for blackjack. Define a function called make-card-dealer which is passed
one argument, a list representing a deck of cards, and returns a lexical
closure which simulates the dealing of cards from the deck of cards by
returning symbols representing the cards, one card at a time each time the
lexical closure is called. If there are no more cards in the deck, the
symbol 'no_more_cards is returned. Use Scheme when defining this function.
Here's how the whole thing should work. First, I'll call make-card-dealer
and pass it a very small deck of cards (as a list) containing the Ace of
Hearts, the 3 of Clubs, the 9 of Diamonds, and the Queen of Spades. I'll
give the lexical closure that is returned its own unique name, deal-card.
Then each time deal-card is called, it returns a card like this:
> (deal-card)
ah
> (deal-card)
3c
> (deal-card)
9d
> (deal-card)
qs
> (deal-card)
no_more_cards
>
You may use side effects here, and thanks in advance for helping me with my
project.
14. (10 points) As you might guess, many students complain about the
plethora of parentheses found in Scheme. So that we can help these
weaklings recover from severe brain strain, I'd like you to write a
Scheme function that, when passed a list as an argument, will return
the number of parentheses found in the list. Call this function
count-parens. Don't do any error-checking, and don't worry about
dotted pairs. You can assume that anything passed to your functions
will be a valid, well-formed list. You want examples? Here they are:
> (count-parens '() )
2
> (count-parens '(()) )
4
> (count-parens '(a (b (c)) d) )
6
> (count-parens '(define (sqr x) (* x x)) )
6
> (count-parens '(a (b (c (d (e) f) g) h) (i (j (k) l) m) n) )
16
>
15. (10 points) Here's my definition of the Coke Machine class, written
in Python. We talked about it in class on Thursday, and it's also
discussed in the lecture notes:
class CokeMachine:
def __init__(self):
totalmachines = 0
CokeMachine.totalmachines = CokeMachine.totalmachines + 1
self.numcans = 20
print "Adding another Coke machine to your empire"
def BuyCoke(self):
if self.numcans > 0
self.numcans = self.numcans - 1
print "Have a nice frosty Coke!"
print "%3d frosty cans of Coke remaining." % (self.numcans)
else
print "Sorry, out of Coke."
def LoadCoke(self loadcans):
self.numcans = self.numcans + loadcans
print "%3d cans of Coke added" % (loadcans)
print "%3d cans of Coke now available" % (self.numcans)
OK, I lied. It's not exactly like what we talked about in class and in
the notes, because this version is broken. As we discussed, I want it to
add more Cokes to the machine when I invoke LoadCoke, and I want it to
simulate the purchase of a Coke when I invoke BuyCoke, and I want it to
keep track of how many Coke machines I have in my empire. But things
aren't working the way I want them to. Explain what's wrong and how to
fix it. (Hint: there's more than one problem with this program.)
----------
Hints on do:
Scheme provides you with one slightly complicated iterative form.
It's just called "do", and it looks like this:
(do (([var-1] [init-expr-1] [update-expr-1])
([var-2] [init-expr-2] [update-expr-2])
:
:
([var-n] [init-expr-n] [update-expr-n]))
([test-expr] [action-expr-1] ... [action-expr-m])
:
[zero or more expressions]
:
)
Here's how "do" works:
1. each [var-i] is bound to its corresponding [init-expr-i]
(in "parallel", just like with "let"---that is, there's
no guarantee of the order the bindings are completed,
so don't encode dependencies of any kind in this part
of the "do" form).
2. [test-expr] is evaluated. If the result is non-#f, then
the [action-expr-1] through [action-expr-m] are evaluated
in left-to-right fashion. The "do" form returns the value
of [action-expr-m] and the iteration is terminated. If
there are no action expressions, what the "do" form
returns is unspecified (!).
3. if evaluating [test-expr] returns #f, then execute the
body of code.
4. when the body of code has been executed, update each
[var-i] by binding it to the value obtained by evaluating
the corresponding [update-expr-i]. The [update-expr-i]
are optional, so if it's not there, then the various
[var-i] are never changed. Also, these bindings are done
automatically by "do", so there's no need to include your
own "set!" in the update expressions. Finally, these
updates, as well as the initializations, are done in
an unspecified order, so you can't count on any
dependencies (i.e., this happens before that). You
can however count on some protection in that Scheme binds
the variables to fresh locations, does the updates to
those fresh locations, and then binds the updated values
to the variable.
5. go to 2
-----
Hints on vectors:
> (define x (vector 10 100 90 50 45))
> x
#5(10 100 90 50 45)
> (define y (make-vector 6))
> y
#6(0)
> (define y (make-vector 6 'foo))
> y
#6(foo)
> (vector-ref x 2)
90
> (vector-length x)
5
> (vector-set! x 4 65)
> x
#5(10 100 90 50 65)
> (vector-set! x 2 '(foo bar))
> x
#5(10 100 (foo bar) 50 65)
> (vector-ref x 5)
vector-ref: index 5 out of range [0, 4] for vector: #5(10 100 (foo bar) 50 65)
Copyright (c) 2003 by Kurt Eiselt. All rights reserved, with
the exception of stuff that belongs to somebody else.
Last revised: December 2, 2003