Functional Problem Solving (CSC 151 2015F) : Assignments

Exam 4: Scheme Structures and Strategies


Assigned: Tuesday, 24 November 2015

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

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

Please read the updated instructions for this exam.

Problems

Problem 1: Translating Trees

Topics: Trees, higher-order procedures

We have used map to apply a procedure to every element in a list, but what if we want to apply a procedure to every element in a different data structure? Unfortunately, map will not help us in this case.

Document and write a procedure, (tree-map proc param-tree), that takes a tree param-tree as input and produces a new tree result-tree with the same structure. Each element in result-tree should be the result of calling the procedure proc on the corresponding value in param-tree. You do not need to support multiple trees the same way map supports multiple lists for procedures that take more than one parameter.

Here are a few examples.

> (tree-map increment empty)
'empty
> (tree-map increment (leaf 11))
'#(node 12 empty empty)
> (tree-map increment (node 7 (leaf 11) empty))
'#(node 8 (node 12 empty empty) empty)
> (tree-map (l-s + 1) (node 3 (node 2 (leaf 1) empty) (node 5 (leaf 4) (leaf 6))))
'#(node
   4
   #(node 3 #(node 2 empty empty) empty)
   #(node 6 #(node 5 empty empty) #(node 7 empty empty)))
> (tree-map (l-s = 5) 
            (node 3 
                  empty 
                  (node 4 empty (node 5 empty (node 6 empty (leaf 7))))))
'#(node
   #f
   empty
   #(node
     #f
     empty
     #(node #t empty #(node #f empty #(node #f empty empty)))))
> (tree-map (lambda (s) (if (equal? s "five") "five" "not five")) 
            (node "three" 
                  empty 
                  (node "four" 
                        empty 
                        (node "five" 
                              empty 
                              (node "six" empty (leaf "seven"))))))
'#(node
   "not five"
   empty
   #(node
     "not five"
     empty
     #(node
       "five"
       empty
       #(node "not five" empty #(node "not five" empty empty)))))

Problem 2: File Utilities

Topics: Files, file recursion, input, output, strings, numeric recursion, repetition.

In our initial explorations with files, we found that some of the procedures we'd like to have are not available. In this problem, you will write some of those procedures.

a. Write, but do not document, a procedure, (read-file file-name), that reads the contents of the given file and returns it as a string.

> (define target (open-output-file "/home/student/Desktop/example.txt" #:exists 'replace))
> (display "Sample text" target)
> (newline target)
> (for-each (lambda (val) (display val target) (newline target)) (iota 5))
> (close-output-port target)
> (read-file "/home/student/Desktop/example.txt")
"Sample text\n0\n1\n2\n3\n4\n"

Hint: You can use repeated calls to read-line along with string-append.

b. Write, but do not document, a procedure, (display-file file-name), that reads the contents of the given file and displays it in the interactions pane.

> (display-file "/home/student/Desktop/example.txt")
Sample text
0
1
2
3
4

Hint: You should use your solution to the previous subproblem.

c. In our exploration with files, you've found that we cannot write to an existing file by default. As you may have noted, many Web browsers handle this situation by creating an alternate version of the file name. For example, for alpha.txt, it will create alpha-1.txt, alpha-2.txt, and so on and so forth.

Write, but do not document, a procedure, (next-version filename), that finds the next available version of a file. For example,

> (file-exists? "/home/student/Desktop/alpha.txt")
#f
> ; Create an empty file and return its name
  (define make-empty-file
    (lambda (filename)
      (let ([target (open-output-file filename #:exists 'replace)])
        (close-output-port target))
      filename))
> (make-empty-file "/home/student/Desktop/alpha.txt")
"/home/student/Desktop/alpha.txt"
> (file-exists? "/home/student/Desktop/alpha.txt")
#t
> (delete-file "/home/student/Desktop/alpha.txt")
> (file-exists? "/home/student/Desktop/alpha.txt")
#f
> (next-version "/home/student/Desktop/alpha.txt")
"/home/student/Desktop/alpha.txt"
> (make-empty-file "/home/student/Desktop/alpha.txt")
"/home/student/Desktop/alpha.txt"
> (next-version "/home/student/Desktop/alpha.txt")
"/home/student/Desktop/alpha-1.txt"
> (file-exists? "/home/student/Desktop/alpha-1.txt")
#f
> (next-version "/home/student/Desktop/alpha.txt")
"/home/student/Desktop/alpha-1.txt"
> (make-empty-file (next-version "/home/student/Desktop/alpha.txt"))
"/home/student/Desktop/alpha-1.txt"
> (file-exists? "/home/student/Desktop/alpha-1.txt")
#t
> (make-empty-file (next-version "/home/student/Desktop/alpha.txt"))
"/home/student/Desktop/alpha-2.txt"
> (make-empty-file (next-version "/home/student/Desktop/alpha.txt"))
"/home/student/Desktop/alpha-3.txt"
> (file-exists? "/home/student/Desktop/alpha-1.txt")
#t
> (make-empty-file (next-version "/home/student/Desktop/alpha-1.txt"))
"/home/student/Desktop/alpha-1-1.txt"
> (file-exists? "/home/student/Desktop/beta")
#f
> (make-empty-file (next-version "/home/student/Desktop/beta"))
"/home/student/Desktop/beta"
> (make-empty-file (next-version "/home/student/Desktop/beta"))
"/home/student/Desktop/beta-1"
> (make-empty-file (next-version "/home/student/Desktop/beta"))
"/home/student/Desktop/beta-2"
> (file-exists? "/home/student/Desktop/CSC151.2015F/gamma.txt")
#f
> (make-empty-file (next-version "/home/student/Desktop/CSC151.2015F/gamma.txt"))
"/home/student/Desktop/CSC151.2015F/gamma.txt"
> (make-empty-file (next-version "/home/student/Desktop/CSC151.2015F/gamma.txt"))
"/home/student/Desktop/CSC151.2015F/gamma-1.txt"
> (make-empty-file (next-version "/home/student/Desktop/CSC151.2015F/gamma.txt"))
"/home/student/Desktop/CSC151.2015F/gamma-2.txt"

> (make-empty-file (next-version "/home/student/Desktop/CSC151.2015F/alpha.txt"))
"/home/student/Desktop/CSC151.2015F/alpha.txt"
> (make-empty-file (next-version "/home/student/Desktop/CSC151.2015F/alpha.txt"))
"/home/student/Desktop/CSC151.2015F/alpha-1.txt"

Hints: Think about how to split the file name into two parts. Use numeric recursion to try potential alternate names. There are some useful procedures in the code file.

d. Write, but do not document, a procedure, (create-file lines filename), that creates the next version of filename and writes each value in lines to that file, one per line. Your procedure should return the name of the created file.

> (create-file (list "; File:" ";   00000.rkt" 
                     "; Contents: " ";   Solutions to exam 4")
               "/home/student/Desktop/00000.rkt")
"/home/student/Desktop/00000.rkt"
> (create-file (list "; File:" ";   00000.rkt" 
                     "; Contents: " ";   More solutions to exam 4")
               "/home/student/Desktop/00000.rkt")
"/home/student/Desktop/00000-1.rkt"
> (display-file "/home/student/Desktop/00000.rkt")
; File:
;   00000.rkt
; Contents: 
;   Solutions to exam 4
> (display-file "/home/student/Desktop/00000-1.rkt")
; File:
;   00000.rkt
; Contents: 
;   More solutions to exam 4
> (create-file (list ";I'm sick of this exam")
               "/home/student/Desktop/00000.rkt")
"/home/student/Desktop/00000-2.rkt"
> (display-file "/home/student/Desktop/00000-2.rkt")
;I'm sick of this exam

Hint: Think about how you can deal with each line in lines.

Note: You can find the documentation for these procedures in the exam code.

Note: You may find some of the following standard Racket procedures useful: (file-exists? path), (read-line input-port), and (number->string number).

Problem 3: Drawing Pair Structures

Topics: Pairs, pair structures, deep recursion

When we first started exploring pairs and pair structures, we found it helpful to sketch the underlying representation of these pairs and pair structures. In sketching those drawings, we followed a fairly straightforward approach.

  • Draw a pair of boxes that represent the car and the cdr.
  • If either the car or the cdr is null, draw a backslash in the appropriate box.
  • If the car is another pair, draw an arrow downward from the left box and repeat the whole process again at the end of the arrow.
  • If the car is anything else, draw a short arrow downward from the left box and write the value at the end of the arrow.
  • If the cdr is another pair, draw an arrow to the right from the right box and repeat the process there.
  • If the cdr is anything else, draw a short arrow downward and write the value at the end of the arrow.

While it was useful to do that work by hand to start getting a better understanding of thse structures, we should now be at the point that we'd rather have the computer follow this algorithm than follow it by hand.

Write, but do not document, a procedure, (render-pair-structure! image structure left top). Your procedure should produce images like the following for the given structures.

(list 'a 'b)
(cons 'a 'b)
(cons null null)
(list (list 'a 'b) 'c (cons 'd 'e))
(list (list (list 'a 'b (list 'c)) 'd 'e 'f) 'g)

You can use the naive approach to this problem. In particular, you do not need to worry about whether or not the elements in a list overlap (as they might if each element is itself a list).

To help you in this endeavor, the exam contains three useful procedures, render-pair!, render-value!, and simple-arrow!. In writing render-pair-structure!, you may assume that each pair is 40 units wide and 20 units high.

Problem 4: Joining Vectors

Topics: Vectors, documentation

Document and write a procedure (vector-join vec1 vec2) that takes two vectors as input and produces a new vector that contains the values of both. In other words, vector-join should join two vectors together. You may not use vector->list or list->vector in your implementation of vector-join.

Here are a few examples.

> (vector-join (vector 1 2 3) (vector 4 5 6))
'#(1 2 3 4 5 6)
> (vector-join (vector "string" 'symbol 1 #t) (vector "another string" 'symbol2 2 #f))
'#("string" symbol 1 #t "another string" symbol2 2 #f)
> (vector-join (vector) (vector 1 2 3))
'#(1 2 3)

Problem 5: Nesting Procedure Calls

Topics: Higher-order procedures, numeric recursion

As you may have noted, sometimes we “nest” repeated calls to the same procedure. For example, to compute x to the eighth power, we might write (square (square (square x))).

Write, but do not document, a procedure, (nest n fun), that returns a new procedure that takes one input and applies fun the specified number of times.

> (define octo (nest 3 square))
> octo
#<procedure>
> (octo 2)
256
> (octo 3)
6561
> (define no-square (nest 0 square))
> (no-square 5)
5
> (define much-redder (nest 6 irgb-redder))
> (irgb->string (much-redder (irgb 0 0 0)))
"192/0/0"
> (irgb->string (much-redder (irgb 10 20 40)))
"202/20/40"
> (map (nest 7 increment) (iota 5))
'(7 8 9 10 11)

Problem 6: Finding the Next Largest Value

Topics: Vectors, binary search

Write, but do not document, a procedure (next-largest val vec) that uses binary search to find the smallest value in the sorted vector vec that is larger than val. Documentation for this procedure is included below:

;;; Procedure:
;;;   next-largest
;;; Parameters:
;;;   val, a real number
;;;   vec, a vector of real numbers
;;; Purpose:
;;;   Find the smallest value in vec that is greater than val
;;; Produces:
;;;   result, a real number or #f.
;;; Preconditions:
;;;   vec is a vector of real numbers sorted from smallest to largest
;;; Postconditions:
;;;   If vec contains a number greater than val, then result is the
;;;     smallest such number.
;;;   If vec does not contain a number greater than val, then result is #f.

Here are a few examples.

> (next-largest 4 '#(2 4 6 8 10 12))
6
> (next-largest 100 '#(1 3 10 29))
#f
> (next-largest 10 '#(7 8 9 10))
#f
> (next-largest 0 '#(100 200 300 400))
100
> (next-largest 225 '#(100 200 300 400))
300

Problem 7: Analyzing Alternate Approaches

Topics: Map, lists, algorithm analysis

When we first learned how to use map, we often used the technique of “take a single expression, insert map before each operation, and turn each value into a list, either with make-list or by using something like iota”.

For example, suppose we make one circle as follows.

(define circle-37
  (vshift-drawing (* 10 (modulo 37 10))
                  (hshift-drawing (* 5 37)
                                  (scale-drawing (increment (modulo 37 7))
                                                 drawing-unit-circle))))

In the extreme, we may end up writing something like the following.

(define many-circles
  (lambda (n)
    (map vshift-drawing
         (map *
              (make-list n 10)
              (map modulo
                   (iota n)
                   (make-list n 10)))
         (map hshift-drawing
              (map *
                   (make-list n 5)
                   (iota n))
              (map scale-drawing
                   (map increment
                        (map modulo
                             (iota n)
                             (make-list n 7)))
                   (make-list n drawing-unit-circle))))))

a. Using the techniques from the reading on analyzing procedures and the corresponding lab, update many-circles to count calls to cons, car, cdr, and null?. You can find implementations of all of the main procedures (e.g., map, make-list, and iota) in the exam code file.

b. Find out how many calls there are to cons, car, cdr, and null?. if we are making lists of 5, 10, 20, and 40 circles.

c. Come up with an approximate formula for the number of calls based on n.

d. Rewrite the code so that there are no more than 2*n calls to cons in making a list of n circles. Any helpers you write should be local.

Note: You may want to preview the list of circles with something like (drawing->image (drawing-compose (many-circles 50)) 200 100).

Extra Credit: Whatzitdo?

This problem is optional. If you complete it correctly, you will earn a few points of extra credit.

Topics: Higher-order procedures, code reading, documentation

Write the 6P documentation for the mystery procedure defined below. Be sure to show at least four example uses of this procedure.

(define mystery (l-s l-s l-s))

Warning! This problem may make your brain hurt.

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.

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. (And no, we don't count errors in the errata section or the question and answer sections.)

  • Missing right paren in vector-join. [PM, 1 point]
  • Internally, exam code says exam4.rkt rather than exam4-sr.rkt or exam4-cc.rkt. (Since you are renaming the file, it doesn't really matter.) [PM, 0 points]
  • In problem 3, “You procedure” should be “Your procedure”. [PM, 1 point]
  • In the prologue, the exam is listed as exam 1, rather than exam 4. [PM, 1 point, SR section only]
  • In the tree-map procedure, some of the output appears incorrect. [GN, 1 point, SR section only]
  • Missing right paren in sample code for problem 3. [GN, 1 point]
  • In problem 7, the topic should be “analysis” not “analyis”. [DC, 1 point]
  • In problem 7, “make-list of ...” should be “make-list or ...”. [PM, 0 points]
  • Problem 7, serving as the host of way too many errors, has the phrase “to to”, even though it is not a dancer. There should probably be only one “to”. [PM, 0 points]
  • For some silly reason, Racket already contains a procedure called silly, so it gets mad when you attempt to redefine it in the definitions pane. (In the example, we've only redefined it in the interactions pane, so this isn't strictly a bug. We have, nonetheless, renamed the procedure in the exam.) [EN, 0 points]
  • We need multiple forms of map. The exam provides only one. [GN, 1 point]
  • There are (were) extra quotation marks in the result from vector-join. [YZ, 1 point]
  • Sam mis-defined make-list. [JO, 1 point, SR section only]

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 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.