Functional Problem Solving (CSC 151 2016S) : Assignments

Sample Final Examination

This is a sample examination. It includes problems like those that have appeared on previous in-class final examinations (and, as you'll note, on recent take-home examinations). It also includes the standard examination policies. These problems are not intended to give a comprehensive view of topics. For example, there is likely to be at least one problem on trees. As the sample exam suggests, problems may involve code reading and code analysis in addition to code writing.

Although the sample policies indicate that you may not discuss the actual final exam, you should feel free to discuss this sample final examination with anyone you'd like.

Policies for the Final Examination

1. This is a closed book examination. You may not rely on notebooks, papers, textbooks, computers, colleagues, or anything similar. You may, however, refer to one 8.5 x 11 inch, double sided, hand-written, set of notes that you brought to this exam.

2. This is also a paper examination. Answer all problems in pen or pencil on blank pieces of paper and then staple them together. You may use additional blank pieces of paper as scratch, provided you turn them in at the end of the examination.

3. You have received a cover sheet for this exam with a number on it. Please write your name on the cover sheet. Please write your number (not your name) on each of the non-scratch pages you turn in.

4. There are four problems on this exam. They are not necessarily of equal difficulty.

Four correct or mostly correct solutions will earn you an A. Three correct or mostly correct solutions will earn you a B. Two correct or mostly correct solutions will earn you a C. One correct or mostly correct solution will earn you a D. Zero correct or mostly correct solutions will earn you an F. Failure to attempt the exam will earn you a 0.

Partially correct solutions may or may not earn you a higher grade, at the discretion of the grader.

5. Many of the problems ask you to write Scheme code. Although you need not write correct working Scheme code, your code should be of sufficient quality that the grader can be confident that you would be able to make it work correctly with minimal effort when given access to DrRacket.

6. Other people may be taking this exam (or a similar exam) at another time. You may not discuss this examination with anyone.

7. After you have completed the examination, please write, sign, and date the following statements on the cover sheet:

I have neither received nor given inappropriate assistance on this examination.

I am not aware of any other students who have given or received inappropriate assistance on this examination.

If you do not feel that you can sign either of these statements, please arrange to meet with me as soon as possible.

8. Please arrange the remaining sheets in order by problem number, make sure you've written your number and the page number on the top of each sheet, and then staple them together without the cover sheet.

9. Please turn in this examination, the cover sheet, the stapled pages, your notes (if used), and your scrap paper.

Problem 1: Is it sorted?

Topics: Sorting, vectors, higher-order procedures.

Some of you observed in class that it can be pretty time-consuming to call vector-insertion-sort! on a vector that is already sorted. We might want to test whether a vector is already sorted, before we go to all the effort of sorting it.

Write a predicate, (vector-sorted? vec may-precede?), that determines whether the elements of vec are sorted according to may-precede?.

For example,

> (define fruits (vector "apple" "banana" "orange" "pear" "pineapple"))
> (define numbers (vector 56 73 84 42 128))
> (vector-sorted? fruits string-ci<=?)
> (vector-sorted? numbers <=)

Problem 2: What does it do?

Topics: Code reading, List recursion, Higher-order procedures

Consider the following procedure.

(define proc
  (lambda (lst pred?)
    (let kernel ([rest lst]
                 [left null]
                 [right null])
      ; (display (list 'kernel rest left right)) (newline)
      (if (null? rest)
          (list (reverse left) (reverse right))
          (kernel (cdr rest)
                  (if (pred? (car rest)) (cons (car rest) left) left)
                  (if (pred? (car rest)) right (cons (car rest) right)))))))

a. Suppose we uncommented the line to display intermediate steps. Show the values displayed when evaluating (proc (iota 10) odd?).

b. What does the procedure proc do?

c. What preconditions must be met in order for the procedure proc to work?

Problem 3: Is it efficient?

Topics: Code tracing, List recursion, efficiency

We regularly use the reverse procedure that, given a list, returns a list of the same values, but in the opposite order.

Let's consider two different ways in which the reverse procedure could be defined.

(define list-reverse-1
  (lambda  (lst)
    (if (null? lst)
        (list-append (list-reverse-1 (cdr lst)) (list (car lst))))))

(define list-reverse-2
  (lambda (lst)
    (let kernel ([reversed null]
                 [remaining lst])
      (if (null? remaining)
          (kernel (cons (car remaining) reversed)
                  (cdr remaining))))))

Here are short traces of each procedure in action. (The procedure currently being evaluated is in italics; not every step is shown.)

(list-reverse-1 (list 1 2 3))
=> (list-append (list-reverse-1 (list 2 3)) (list 1))
=> (list-append (list-append (list-reverse-1 (list 3)) (list 2)) (list 1))
=> (list-append (list-append (list-append (list-reverse-1 (list)) (list 3)) (list 2)) (list 1))
=> (list-append (list-append (list-append null (list 3)) (list 2)) (list 1))
=> (list-append (list-append (list 3) (list 2)) (list 1))
=> (list-append (list 3 2) (list 1))
=> (list 3 2 1)

(list-reverse-2 (list 1 2 3))
=> (kernel null (list 1 2 3))
=> (kernel (cons 1 null) (list 2 3))
=> (kernel (list 1) (list 2 3))
=> (kernel (cons 2 (list 1)) (list 3))
=> (kernel (list 2 1) (list 3))
=> (kernel (cons 3 (list 2 1)) null)
=> (kernel (list 3 2 1) null)
=> (list 3 2 1)

The first of these also requires the list-append procedure.

;;; Procedure:
;;;   list-append
;;; Parameters:
;;;   front, a list of size n
;;;   back, a list of size m
;;; Purpose:
;;;   Put front and back together into a single list.
;;; Produces:
;;;   appended, a list of size n+m.
;;; Preconditions:
;;;   front is a list [Unverified]
;;;   back is a list [Unverified]
;;; Postconditions:
;;;   For all i, 0 <= i < n,
;;;    (list-ref appended i) is (list-ref front i)
;;;   For all i, <= i < n+m
;;;;   (list-ref appended i) is (list-ref back (- i n))
(define list-append
  (lambda (front back)
    (if (null? front)
        (cons (car front) (list-append (cdr front) back)))))

Let's trace a sample call to list-append

(list-append (list 0 1 2) (list 3 4 5))
=> (cons 0 (list-append (list 1 2) (list 3 4 5)))
=> (cons 0 (cons 1 (list-append (list 2) (list 3 4 5))))
=> (cons 0 (cons 1 (cons 2 (list-append null (list 3 4 5)))))
=> (cons 0 (cons 1 (cons 2 (list 3 4 5))))
=> (cons 0 (cons 1 (list 2 3 4 5)))
=> (cons 0 (list 1 2 3 4 5))
=> (list 0 1 2 3 4 5)

a. About how many calls to cons do you expect there to be when reversing a list of length 4 with list-reverse-1? Why?

b. About how many calls to cons do you expect there to be when reversing a list of length 4 with list-reverse-2? Why?

c. About how many calls to car do you expect there to be when reversing a list of length 4 with list-reverse-1? Why?

d. About how many calls to car do you expect there to be when reversing a list of length 4 with list-reverse-2? Why?

e. Repeat a-d with a list of length 8.

f. Which version of reverse do you prefer? Why?

Problem 4: What is a nest?

Topics: Higher-order procedures, numeric recursion.

Some of you have observed that it is tedious to use the o to compose the same function with itself several times. For example, it is tedious to write

(image-variant picture
               (o rgb-darker rgb-darker rgb-darker rgb-darker rgb-darker))

to apply the rgb-darker function five times to each pixel in an image.

Write a new procedure, nest, as follows. nest takes two parameters, a unary procedure f and an integer n. The value produced is a new procedure that results from composing together n copies of f.

Note that n must be at least 1 for nest to make sense. Verify this precondition using husk-and-kernel style.

Here are some examples using nest.

> (define plus5 (nest (l-s + 1) 5))
> (plus5 6)
> (define list5 (nest list 5))
> (list5 6)
> (nest list 0)
n must be at least 1, given 0
> (define duplicate (lambda (val n) ((nest (l-s cons val) n) null)))
> (duplicate "hello" 5)
("hello" "hello" "hello" "hello" "hello")