Exam 4: Advanced Scheming


Assigned: Tuesday, 3 May 2016

Due: The due dates for various tasks are as follows.

  • 10:30 p.m., Tuesday, 10 May 2016 (electronic)

Important! This examination has an initial code file that you should copy and use as the starting point for your work.

Please read the exam procedures.

Problems

Problem 1: Removing Items from Association Lists

Topics: Association lists, list recursion, algorithm analysis, lists

As you may recall, an association list is a list of key/value pairs. It's easy to add a key/value pair to an association list. It's a bit harder to remove key/value pairs.

a. Write a procedure, (assoc-list-remove alist key), that removes all of the key/value pairs with a matching key from the association list.

;;; Procedure:
;;;   assoc-list-remove
;;; Parameters:
;;;   alist, an association list
;;;   key, a value
;;; Purpose:
;;;   Remove *all* entries with the specified key from the list.
;;; Produces:
;;;   newlist, an association list
;;; Preconditions:
;;;   alist has the standard form of an association list, a list
;;;     of key/value pairs.
;;; Postconditions:
;;;   For all k not equal to key, (assoc k newlist) = (assoc k alist)
;;;   (assoc key newlist) = #f.

Here's an example.

> (define sample
    (list (list "TUT100" "Joy")
          (list "CSC151" "Functional Problem Solving")
          (list "CSC207" "Algorithms and Objects")
          (list "TEC154" "Evolution of Technology")
          (list "TUT100" "Time")
          (list "TUT100" "Failure")))
> (assoc "TUT100" sample)
'("TUT100" "Joy")
> (assoc "TUT100" (cdr sample))
'("TUT100" "Time")
> (assoc-list-remove sample "TUT100")
'(("CSC151" "Functional Problem Solving")
  ("CSC207" "Algorithms and Objects")
  ("TEC154" "Evolution of Technology"))
> (assoc-list-remove sample "CSC151")
'(("TUT100" "Joy")
  ("CSC207" "Algorithms and Objects")
  ("TEC154" "Evolution of Technology")
  ("TUT100" "Time")
  ("TUT100" "Failure"))

b. How many calls will your procedure make to cons, car, and cdr for each of the following?

i. A list of N elements in which the key does not appear.

ii. A list of N elements in which the key appears only in the first element.

iii. A list of N elements in which the key appears only in the last element.

iv. A list of N elements in which the key appears in every element.

v. A list of N elements in which the key appears in every-other element.

Problem 2: Removing Items from Associative Vectors

Topics: Association lists, vectors, vector recursion, algorithm analysis

An associative vector is much like an association list, except that we store the key/value pairs in a vector, rather than a list, that we permit the vector to contain null values at the end, and that we can change the vector “in place”.

Here's a sample session with an associative vector.

> (define sample
    (vector (list "TUT100" "Joy")
            (list "CSC151" "Functional Problem Solving")
            (list "CSC207" "Algorithms and Objects")
            (list "TEC154" "Evolution of Technology")
            (list "TUT100" "Time")
            (list "TUT100" "Failure")
            null
            null
            null))
> sample
'#(("TUT100" "Joy")
   ("CSC151" "Functional Problem Solving")
   ("CSC207" "Algorithms and Objects")
   ("TEC154" "Evolution of Technology")
   ("TUT100" "Time")
   ("TUT100" "Failure")
   ()
   ()
   ())
> (avec-get sample "CSC151")
'("CSC151" "Functional Problem Solving")
> (avec-get sample "TUT100")
'("TUT100" "Joy")
> (avec-get sample "CSC161")
#f
> (avec-set! sample "CSC161" (list "Imperative Problem Solving"))
#t
> (avec-get sample "CSC161")
'("CSC161" "Imperative Problem Solving")
> sample
'#(("TUT100" "Joy")
   ("CSC151" "Functional Problem Solving")
   ("CSC207" "Algorithms and Objects")
   ("TEC154" "Evolution of Technology")
   ("TUT100" "Time")
   ("TUT100" "Failure")
   ("CSC161" "Imperative Problem Solving")
   ()
   ())
> (avec-get sample "CSC207")
'("CSC207" "Algorithms and Objects")
> (avec-set! sample "CSC207" (list "Algorithms and Object-Oriented Design"))
#t
> (avec-get sample "CSC207")
'("CSC207" "Algorithms and Object-Oriented Design")
> sample
'#(("TUT100" "Joy")
   ("CSC151" "Functional Problem Solving")
   ("CSC207" "Algorithms and Object-Oriented Design")
   ("TEC154" "Evolution of Technology")
   ("TUT100" "Time")
   ("TUT100" "Failure")
   ("CSC161" "Imperative Problem Solving")
   ()
   ())

It is fairly straightforward to set and get elements from an associative vector.

;;; Procedure:
;;;   avec-set!
;;; Parameters:
;;;   avec, an associative vector
;;;   key, a Scheme value
;;;   value, a Scheme value
;;; Purpose:
;;;   Makes sure that the entry for key returns value.
;;; Produces:
;;;   success, a Boolean value
;;; Preconditions:
;;;   avec has the standard form for associative vectors.
;;; Postconditions:
;;;   If avec contains any null entries, then success is true.
;;;   If success is true, then (avec-get avec key) returns (cons key value).
;;;   Otherwise, (avec-get avec key) returns #f.
(define avec-set!
  (lambda (avec key value)
    (let kernel ([i 0])
      (cond
        [(equal? i (vector-length avec))
         #f]
        [(null? (vector-ref avec i))
         (vector-set! avec i (cons key value))
         #t]
        [(equal? key (car (vector-ref avec i)))
         (vector-set! avec i (cons key value))
         #t]
        [else
         (kernel (+ i 1))]))))

;;; Procedure:
;;;   avec-get
;;; Parameters:
;;;   avec, an associative vector
;;;   key, a value
;;; Purpose:
;;;   Finds the first entry in avec with the given key.
;;; Produces:
;;;   entry, either a key/value pair or #f
;;; Preconditions:
;;;   avec has the standard form for associative vectors.
;;; Postconditions:
;;;   If there is an i s.t. (equal? key (car (vector-ref avec i))),
;;;     then entry is (vector-ref avec i)
;;;   Otherwise, entry is #f
(define avec-get
  (lambda (avec key)
    (let kernel ([i 0])
      (cond
        [(equal? i (vector-length avec))
         #f]
        [(null? (vector-ref avec i))
         #f]
        [(equal? key (car (vector-ref avec i)))
         (vector-ref avec i)]
        [else
         (kernel (+ i 1))]))))

Since we've just figured out how to remove elements from an association list, we should figure out something similar for an associative vector.

a. Write a procedure, (avec-remove! vec key), that removes all of the key/value pairs with a matching value from the vector.

;;; Procedure:
;;;   avec-remove!
;;; Parameters:
;;;   avec, an associative vector
;;;   key, a value
;;; Purpose:
;;;   Remove *all* entries with the specified key from the list.
;;; Produces:
;;;   [Nothing; called for the side effect.]
;;; Preconditions:
;;;   avec has the standard form of an associative vector.
;;; Postconditions:
;;;   For all k not equal to key, (avec-get avec k) remains the same
;;;   (avec-get avec key) = #f.

Here's an extended example that shows removal in the middle, at the front, at the back, and at multiple positions.

> (define sample
    (vector (list "CSC105" "The Digital Age")
            (list "TUT100" "Joy")
            (list "CSC151" "Functional Problem Solving")
            (list "CSC207" "Algorithms and Objects")
            (list "TEC154" "Evolution of Technology")
            (list "TUT100" "Time")
            (list "TUT100" "Failure")
            (list "MAT115" "Statistics")
            null
            null
            null))
> sample
'#(("CSC105" "The Digital Age")
   ("TUT100" "Joy")
   ("CSC151" "Functional Problem Solving")
   ("CSC207" "Algorithms and Objects")
   ("TEC154" "Evolution of Technology")
   ("TUT100" "Time")
   ("TUT100" "Failure")
   ("MAT115" "Statistics")
   ()
   ()
   ())
> (avec-remove! sample "TEC154")
> sample
'#(("CSC105" "The Digital Age")
   ("TUT100" "Joy")
   ("CSC151" "Functional Problem Solving")
   ("CSC207" "Algorithms and Objects")
   ("TUT100" "Time")
   ("TUT100" "Failure")
   ("MAT115" "Statistics")
   ()
   ()
   ()
   ())
> (avec-remove! sample "CSC999")
> sample
'#(("CSC105" "The Digital Age")
   ("TUT100" "Joy")
   ("CSC151" "Functional Problem Solving")
   ("CSC207" "Algorithms and Objects")
   ("TUT100" "Time")
   ("TUT100" "Failure")
   ("MAT115" "Statistics")
   ()
   ()
   ()
   ())
> (avec-remove! sample "TUT100")
> sample
'#(("CSC105" "The Digital Age")
   ("CSC151" "Functional Problem Solving")
   ("CSC207" "Algorithms and Objects")
   ("MAT115" "Statistics")
   ()
   ()
   ()
   ()
   ()
   ()
   ())
> (avec-remove! sample "CSC105")
> sample
'#(("CSC151" "Functional Problem Solving")
   ("CSC207" "Algorithms and Objects")
   ("MAT115" "Statistics")
   ()
   ()
   ()
   ()
   ()
   ()
   ()
   ())
> (avec-remove! sample "MAT115")
> sample
'#(("CSC151" "Functional Problem Solving")
   ("CSC207" "Algorithms and Objects")
   ()
   ()
   ()
   ()
   ()
   ()
   ()
   ()
   ())

b. How many calls will your procedure make to vector-ref, and vector-set! for each of the following?

i. A vector of N elements in which the key does not appear.

ii. A vector of N elements in which the key appears only in the first element.

iii. A vector of N elements in which the key appears only in the last element.

iv. A vector of N elements in which the key appears in every element.

v. A vector of N elements in which the key appears in every-other element.

Problem 3: Whatzitdo?

Topics: Higher-order procedures, recursion, code reading, documentation

Consider the following procedure definition.

(define iowa
  (lambda (illinois michigan)
    (letrec ([indiana (lambda (south-dakota north-dakota)
                        (if (illinois south-dakota north-dakota) south-dakota north-dakota))]
             [minnesota (lambda (wisconsin)
                    (cond [(null? (cdr wisconsin)) (list (list (car wisconsin) (car wisconsin)))]
                          [(null? (cddr wisconsin)) (list wisconsin)]
                          [else (cons (list (car wisconsin) (cadr wisconsin))
                                      (minnesota (cddr wisconsin)))]))])
      (if (null? (cdr michigan))
          (car michigan)
          (iowa illinois (map (l-s apply indiana) (minnesota michigan)))))))

Figure out what this procedure does, and then do the following.

a. Rename the procedure and the parameters so they will make sense to the reader, and write 6P-style documentation for this procedure.

b. Explain how this procedure works. A correct explanation will describe the high-level steps to compute a result, not just restate the code in prose.

Problem 4: 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.

Write and document 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/curtsinger/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/Desktop/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 5: Generalized List Reduction

Topics: List recursion, higher-order procedures.

As you may recall, when we first considered recursive helper procedures, our motivation was to correctly subtract lists of numbers from left to right. In particular, we wrote a procedure similar to the following:

(define difference
  (lambda (numbers)
    (let kernel ([difference-so-far (car numbers)]
                 [remaining (cdr numbers)])
         (if (null? remaining)
	     difference-so-far
	     (kernel (- difference-so-far (car remaining)) (cdr remaining))))))

This pattern is useful for more than just computing differences. It is so useful that computer scientists have generalized it to a procedure often called left-reduce. left-reduce takes two parameters: proc, a binary procedure that combines each element of the list with the answer so far, and lst, the list itself.

Here are some examples of left-reduce in action. We've shown the sequence of calls to the kernel so you can see how it would work.

> (left-reduce + (list 100 4 5))
(kernel 100 (4 5))
(kernel 104 (5))
(kernel 109 ())
109
> (left-reduce - (list 100 4 5))
(kernel 100 (4 5))
(kernel 96 (5))
(kernel 91 ())
91
> (left-reduce - (list 10 4 3 2 1 ))
(kernel 10 (4 3 2 1))
(kernel 6 (3 2 1))
(kernel 3 (2 1))
(kernel 1 (1))
(kernel 0 ())
0
> (left-reduce (lambda (s1 s2) (string-append s1 " " s2)) (list "My" "bonnie" "lies" "over" "the" "ocean"))
(kernel "My" ("bonnie" "lies" "over" "the" "ocean"))
(kernel "My bonnie" ("lies" "over" "the" "ocean"))
(kernel "My bonnie lies" ("over" "the" "ocean"))
(kernel "My bonnie lies over" ("the" "ocean"))
(kernel "My bonnie lies over the" ("ocean"))
(kernel "My bonnie lies over the ocean" ())
"My bonnie lies over the ocean"
> (left-reduce (lambda (s1 s2) (string-append s2 " " s1)) (list "My" "bonnie" "lies" "over" "the" "ocean"))
(kernel "My" ("bonnie" "lies" "over" "the" "ocean"))
(kernel "bonnie My" ("lies" "over" "the" "ocean"))
(kernel "lies bonnie My" ("over" "the" "ocean"))
(kernel "over lies bonnie My" ("the" "ocean"))
(kernel "the over lies bonnie My" ("ocean"))
(kernel "ocean the over lies bonnie My" ())
"ocean the over lies bonnie My"

Implement, but do not document, left-reduce.

Problem 6: Counting Pairs

Topics: Pairs, deep recursion

As you may recall, we sometimes refer to the two-element-box created by the cons procedure as a “pair”. It can be very useful to identify just how many pairs appear in a structure, in part because it tells us a bit about the effort required to make the structure.

Define and test a procedure, (count-pairs value), that takes as input a Scheme value (e.g., a pair structure or list) and determines how many pairs (cons cells) appear in that structure.

For example,

> (count-pairs null)
0
> (count-pairs 5)
0
> (count-pairs (cons 5 null))
1
> (count-pairs (list 5))
1
> (count-pairs (cons null 5))
1
> (count-pairs (cons (cons 'a 'b) 'c))
2
> (count-pairs (list 1 2 3 4 5))
5
> (count-pairs (list null null null))
3
> (count-pairs (list (list 1 2 3) (list 4 5)))
7

As the examples above suggest, you need to look within lists to determine if their individual elements themselves contains pairs. (You may find that this happens automatically as you handle non-lists.) However, you need not look within vectors to see if they contain any pair structures.

> (count-pairs (vector (list 1)))
0

Some Questions and Answers

Here we will post answers to questions of general interest. Please check here before emailing your questions!

General Questions

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?
More or less.
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 tutor 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.
If we get more than 70 points, does the “There's more to life” clause drop our grade to 70?
No! The “There's more to life” clause provides a minimum grade for people who do appropriate amounts and type of work and provide evidence of some mastery.
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?
That's one of the reasons we give extra credit to those who work on the exam early. But you're also better able to get your questions answered early if you start early (or at least we think you are). 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 one in class. If you need a new one, there's a stack in the back of our classroom.
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”.
What is this STUB comment that appears in the code file?
We typically use the term “STUB” to indicate that we've put in a piece of code to get the program to run, but that the code is intended only as a placeholder until we write something correct.
I know that you prefer that lines be under 80 characters. But what if I'm citing a Web page and the URL is longer than 80 characters?
In that particular case, it's fine that the line is longer than 80 characters.

Problem 4

Can't I just use read?
No. The read procedure sometimes reads more than you'd like, for example if there is a quotation mark or an open parenthesis.

Errata

Here you will find errors of spelling, grammar, and design that students have noted. Remember, each error found corresponds to a point of extra credit for everyone. We usually limit such extra credit to five points. However, if we make an astoundingly large number of errors, then we will provide more extra credit. (Sam's record is nine points of extra credit; Charlie's is about five.) Note that we do not count errors in the errata section, the question and answer sections, or the separate code file.

  • Sam forgot to post the link to the code for the exam. [EP, 1 point, SR section only]
  • The parameter to the avec-get and avec-set procedures was sometimes called avec and sometimes called aarray. [ATP, 1 point]
  • Sam forgot that he renamed avec-lookup to avec-get. [TO, 1 point]
  • In the documentation for avec-get and avec-remove, Sam wrote “for for” instead of “form for”. [EP, 1/2 point]
  • The difference procedure in problem 5 used parens and square brackets in a way inconsistent with tradition. [EP, 1/2 point]

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.

If there's a photograph of a kitten in the exam, that photograph was released for public use at http://public-photo.net/displayimage-2485.html. It appears that site is now defunct.