Skip to main content

Exam 2 - Expanding Our Vocabulary

Assigned: Wednesday, 1 March 2017

Prologue Due: Friday, 3 March 2017 by 10:30pm

Exam Due: Tuesday, 7 March 2017 by 10:30pm

Epilogue Due: Wednesday, 8 March 2017 by 10:30pm

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 exam2.rkt starter source code. Please rename this file to 000000.rkt, but replace 000000 with your generated random number.

Problem 1: Determining types

Topics: types, conditionals

Write, but do not document, a procedure, (type-of val), that takes one parameter, an arbitrary Scheme value, and returns the type of the value. Since colors and images are represented as integers, you need not handle colors and images.

Here are some examples.

> (type-of 1/2)
'exact-rational
> (type-of "Hello")
'string
> (type-of (make-list 10 5))
'list
> (type-of type-of)
'procedure
> (type-of 3.0+4i)
'inexact-complex

If you encounter a type you don’t know (and yes, there are a few we have not yet encountered), you can return 'unknown.

*Note: You should probably do the next problem (write tests for type-of) before you do this problem.

Problem 2: Testing type-of

Topics: testing, types

Write a series of tests for type-of. You need not put them into a test suite. Rather just list them individually, as in

(check-equal? (type-of 1) 'exact-integer "Small exact integer")
(check-equal? (type-of type-of) 'procedure "Self reference")

We are likely to run your tests on some non-working versions of type-of. Your tests should catch any reasonable errors.

You should have at least two tests for each non-numeric type. For each numeric type, you should have between four and eight tests so that you consider edge and corner cases.

Problem 3: Whatzitdo?

Topics: code reading, conditionals, local bindings

Sometimes students (and professors) come up with difficult-to-read solutions to problems. Here’s one such solution. Isn’t it beautiful?

(define rect (let* ([image 100] [red (lambda (green) (* image 1/255
(+ (* 0.2126 (irgb-red green)) (* 0.7152 (irgb-green green)) (*
0.0722 (irgb-blue green)))))] [blue (lambda (num) (let ([blue (*
num 255 (/ 1 image))]) (irgb blue blue blue)))] [circle (blue (*
image 1/8))] [square (blue (* image 3/8))] [diamond (blue (* image
5/8))] [oval (blue (* image 7/8))]) (lambda (file) (let ([center
(red file)] [right (* image 1/4)] [top (* image 1/2)] [middle (*
image 3/4)]) (or (and (< center right) circle) (and (>= center
right) (< center top) square) (and (>= center top) (< center middle)
diamond) oval))))) 

a. Clean up this procedure. You should reformat the code (add carriage returns and indent to format clearly); rename the procedure, parameters, and variables; and simplify any unnecessarily complicated or redundant code.

b. Write 6P-style documentation for the procedure.

c. Explain how the procedure achieves its purpose.

Hint: Start by getting the code into a form you can understand. Then, figure out the type of each input and change it to a more sensible name. For example, if something seems to be a color, you should probably name it something like color. (Using Racket’s rename option is a great way to rename things.) You may find it helpful to check the helpers separately, but they should stay local to the procedure.

Problem 4: Radial blends

Topics: computing images, color blends, computing circles

In the reading on building images, you learned how to compute an image of circles using the distance of each image pixel from a center point thresholded by the specified radius. In the corresponding lab, you learned how to generalize a linear (horizontal) blend from one color component’s value to another. In this problem we will combine those two approaches.

Write, but do not document, a procedure (radial-green-blend width height center-col center-row radius initial final) that generates an image containing a radial color blend where the value of the color’s green component is initial at (center-col, center-row) and final at all points of a distance of radius from the center of the circle, while the red and blue components remain zero; the pixels between the center and the radius should blend smoothly from initial to final. Outside the circle, pixels should be black.

;;; Procedure:
;;;   radial-green-blend
;;; Parameters:
;;;   width, a positive integer
;;;   height, a positive integer
;;;   radius, a positive integer
;;;   center-col, a non-negative integer
;;;   center-row, a non-negative integer
;;;   initial, an integer
;;;   final, an integer
;;; Purpose:
;;;   Create a width-by-height image that contains a green radial blend
;;;   centered at (center-col, center-row).
;;; Produces:
;;;   blend, an image
;;; Preconditions:
;;;   0 <= center-col < width
;;;   0 <= center-row < height
;;;   0 <= initial <= 255
;;;   0 <= final <= 255
;;; Postconditions:
;;;   (image-width blend) = width
;;;   (image-height blend) = height
;;;   For any position (i,j) in the image
;;;     (irgb-red (image-get-pixel blend i j)) = 0
;;;     (irgb-blue (image-get-pixel blend i j)) = 0
;;;   (irgb-green (image-get-pixel blend center-col center-row)) = initial
;;;   For any position (i,j) that is radius away from (center-col,center-row),
;;;     (irgb-green (image-get-pixel blend i j)) = final
;;;   For any other position less than radius away from the center, the 
;;;     green component is appropriately scaled between initial and final.

In writing such a procedure, you may find the euclidean-distance procedure from the reading helpful.

A radially-blended green circle of radius 128, centered at (64,64), with a dark center and a bright edge (radial-green-blend 256 256 128 64 64 0 255)

A radially-blended green circle of radius 128, centered at (64,64), with a bright center and a dark edge (radial-green-blend 256 256 128 64 64 255 0)

Hint: Your computation of the green component should look very similar to horiz-blue-blend. However, instead of transitioning from column zero to width, the calculation will span a distance (from the center) of zero up to the radius.

Hint: Locally bind the distance of a given pixel from the center and use that distance both in a conditional test and in the calculation of the color’s green component.

Hint: You may find it helpful to first write procedures in which you just blend from a green component of 0 to a green component of 255, and vice versa.

Problem 5: Picture grids

Topics: Gimp tools, code clarity, code reading

Students who have studied the Pop art movement of the 1960’s know that Andy Warhol, among others, explored the power of making grids of the same (or similar) images. We will do the same in this problem.

Write a procedure, (grid! image) that takes an image as input and updates the image to create a two-by-two grid.

A two-by-two grid of kittens

> (define kitten (image-show (image-load "/home/rebelsky/Desktop/kitten.jpg")))
> (grid! kitten)

Your procedure should work for any image, even those with different sizes or aspect ratios than our friendly kitten.

How will you get started with this problem? Here’s some guidance. Gimp provides a variety of tools for working with images, including some for copying and pasting. Since we’ve been scripting Gimp, you might expect that we can programmatically copy and paste parts of images.

Here are the normal steps one uses.

  • Select the area you want to copy in the source image. You can use image-select-rectangle! and image-select-ellipse! and any similar procedure.
  • Call (gimp-edit-copy-visible source), where source is the number that identifies the source image.
  • Select the area you want to paste into in the target image.
  • Call (gimp-edit-paste (image-get-layer target) 1) where target is the number that identifies the target image.
  • Take note of the number that is returned. You can do so manually, or you can write code that does so. (define pasted (car (gimp-edit-paste (image-get-layer target) 1)))
  • Call (image-select-nothing! target) to make sure nothing is currently selected.
  • Transform the pasted material as appropriate with gimp-layer-scale, gimp-item-transform-rotate, and other related procedures.
  • Call (gimp-image-flatten target).

Since that’s a lot of repeated work, we often write helper procedures for the common portions. Here is a procedure that deals with the steps after you copy.

;;; Procedure:
;;;   paste-and-scale!
;;; Parameters:
;;;   target, an image id
;;;   left, an integer
;;;   top, an integer
;;;   width, a positive integer
;;;   height, a positive integer
;;; Purpose:
;;;   Paste whatever has been copied into the specified region,
;;;   flattening the image afterwards.
;;; Returns:
;;;   target, the same image id 
;;; Preconditions:
;;;   Something has been copied (from this image or from another).
;;; Postconditions:
;;;   The copied portion has been pasted into the appropriate area
;;;     of target.
;;;   No other portion of target has changed.
(define paste-and-scale!
  (lambda (target left top width height)
    (image-select-rectangle! target REPLACE left top width height)
    (let ([pasted (car (gimp-edit-paste (image-get-layer target) 1))])
      (image-select-nothing! target)
      (gimp-layer-scale pasted width height 1)
      (gimp-image-flatten target)
      target)))

Here’s an example for you to play with.

> (define kitten (image-show (image-load "/home/rebelsky/Desktop/kitten.jpg")))
> kitten
19
> (image-select-rectangle! kitten REPLACE 0 0 100 100)
19
> (gimp-edit-copy-visible kitten)
'(1)
> (paste-and-scale! kitten 100 100 200 50)
19
> (paste-and-scale! kitten 350 0 50 200)
19

Note: If you’d like more information about copying and pasting, we have prepared a longer description.

Problem 6: Rewriting the plot

Topics: code clarity, lists, map, drawings as values

In a recent assignment, you were asked to write a procedure, make-plot, that illustrated a function by plotting multiple points. Here are the instructions from that assignment, which we have implicitly cited.

Write and document a procedure (make-plot fun) that produces a drawing of a function fun over a range of x values from 0 to 200, and y values from -50 to 50. Your make-plot procedure should plot at least 20 different points for the function between x values of 0 and 200. For each point, compute a y value with (fun x-value) and place a small circle in the drawing. A y value of -50 should be at the bottom of your drawing, and a y value of 50 should be at the top. Color each point blue if the y value is positive and red if the y value is negative.

Here is a procedure that we might use as we experiment with make-plot.

;;; Procedure:
;;;   show-plot
;;; Parameters:
;;;   plot, a drawing 
;;; Purpose:
;;;   Show the plot created by plot
;;; Produces:
;;;   image, an image
;;; Preconditions:
;;;   plot was created by make-plot or one of its variants
;;; Postconditions:
;;;   image is 200x100
;;;   image contains a rendering of plot
;;;   image is shown
(define show-plot
  (lambda (plot)
    (image-show (drawing->image plot 200 100))))

Here is a typical solution to that problem.

;;; Procedure:
;;;   point-color
;;; Parameters:
;;;   y-value, a real number
;;; Purpose:
;;;   Determines the color of the point associated with y
;;; Produces:
;;;   color, a string that names a color
(define point-color
  (lambda (y-value)
    (cond
      [(< y-value 0)
       "red"]
      [(> y-value 0)
       "blue"]
      [else
       "black"])))

;;; Procedure:
;;;   make-plot
;;; Parameters:
;;;   fun, a one-parameter procedure from real numbers to real numbers
;;; Purpose:
;;;   Illustrate fun by plotting a series of points
;;; Produces:
;;;   plot, a drawing
;;; Preconditions:
;;;   fun should work with any input between 0 and 200, inclusive.
;;; Postconditions:
;;;   Plot contains at least 20 points.
;;;   Plot, when rendered, should represent values in fun.
(define make-plot
  (lambda (fun)
    (drawing-compose
      (map recolor-drawing
           (map point-color (map fun (map * (make-list 20 10) (iota 20))))
           (map vshift-drawing
                (map - (make-list 20 50)
                       (map fun (map * (make-list 20 10) (iota 20))))
                (map hshift-drawing
                     (map * (make-list 20 10) (iota 20))
                     (map scale-drawing
                          (make-list 20 5)
                          (make-list 20 drawing-unit-circle))))))))

As you may note, that solution makes lots of calls to map and lots of calls to make-list. Those repeated calls are both inefficient (in that they build a lot of unnecessary lists along the way) and inelegant (in that they start to get hard to read). It’s also not general, in that the number of points (20) is hardcoded.

a. Rewrite this procedure so that (i) it has no calls to make-list (ii) it has only one call to iota; and (iii) it has no calls to other procedures that you have defined. Do your best to avoid repetitive code. Your solution should have the form

(define make-plot
  (lambda (fun)
    (drawing-compose
     (map (lambda (i)
            ...)
          (iota 20)))))

b. It would also be nice if we could specify the number of points. Generalize your answer to accommodate the number of points. You need not document this new version.

(define make-plot-n
  (lambda (fun num-points)
    (drawing-compose
     (map (lambda (i)
            ...)
          (iota num-points)))))

c. Instead of making all of the circles the same size, we might use another function to determine the size of the circle. Rewrite the function to take two parameters, yfun, which determines the y position, and sizefun, which determines the size of the circle to plot. (Use 1 + the absolute value of (sizefun x) for the size.)

You should also use the size function to determine the color. If that function gives a positive value, color the circle blue. If that function gives a negative value, color the circle red and make the size the absolute value plus 1.

You need not document the new procedure.

(define make-multi-plot
  (lambda (yfun sizefun num-points)
    (drawing-compose
     (map (lambda (i)
            ...)
          (iota num-points)))))

Here are a few examples.

A graph of the function x/2 - 50 in which the points decrease in size and then increase in size

(show-plot (make-multi-plot (lambda (x) (- (/ x 2) 50))
                            (lambda (x) (- (/ x 10) 10))
                            30))

A graph of the function x/2 - 50 in which the points increase in size

(show-plot (make-multi-plot (lambda (x) (- (/ x 2) 50))
                            (section / <> 20)
                            30))

A graph of the function x/2 - 50 in which the points cyclically increase and decrease in size

(show-plot (make-multi-plot (lambda (x) (- (/ x 2) 50))
                            (lambda (x) (* 10 (sin (* pi 1/50 x))))
                            30))

A graph of the function 10 * six(x * pi/50) in which the size of the points increase linearly

(show-plot (make-multi-plot (lambda (x) (* 10 (sin (* pi 1/50 x))))
                            (lambda (x) (- (/ x 2) 50))
                            30))

A graph of the function 10 * six(x * pi/50) in which the size of the points decrease then increase linearly

(show-plot (make-multi-plot (lambda (x) (* 10 (sin (* pi 1/50 x))))
                            (o (r-s - 10) (r-s / 10))
                            30))

A graph of the function 10 * six(x * pi/50) in which the size of the points depends on the value of the function

(show-plot (make-multi-plot (lambda (x) (* 10 (sin (* pi 1/50 x))))
                            (lambda (x) (* 10 (sin (* pi 1/50 x))))
                            30))

Questions and Answers

We will post answers to questions of general interest here while the exam is in progress. Please check here before emailing questions!

General Questions and Answers

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?
Yes.
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 mentor 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.
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?
We think you’re better able to get your questions answered early if you start early. 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 instructions on how to generate your random number on the day the exam was distributed. If you don’t have a number, ask your professor for one before submitting your exam.
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.” Alternately, use RackUnit for testing.
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.”
If we write a broken procedure and replace it later, should we keep the previous one?
Yes! This will help us give you partial credit if your final procedure isn’t quite right.
Should we include images in our answers?
No. We can rerun your code.
How should I fill out the time log?
Something like the following.
; Time Log:
;   Date        Start   Finish  Spent   Activity
;   2017-03-01  14:00   14:15   0:15    Tried to understand problem, wrote prelim docs
;   2017-03-02  18:00   18:20   0:20    Finished documenting
;   2017-03-02  19:00   19:20   0:20    Draft of procedure.  Works for most cases, but not black and white.
;   2017-03-05  01:00   01:10   0:10    Woke up in the middle of the night with the solution
;   2017-03-05  09:00   09:05   0:05    Minor cleanup

; Time Spent: 1:10

Problem 1

Do we have to handle drawings as values?
No.
Will you tell me what types to cover?
No, that’s part of the problem.
Do we have to indicate whether numbers are positive or negative?
No.
Do we need to indicate whether numbers are exact or inexact?
Yes.
I know about integers and reals. What other kinds of numbers are
there?
We discussed most of the important ones in the reading on numbers.
Is there a difference between real and rational?
In the real world, yes.
In DrRacket, no.
Which should we use?
I prefer real for inexact and rational for exact.

Problem 2

Do we have to do problem 2 before 1?
No, but it will help you think about how to solve problem 1.
Is problem 2 just lots and lots and lots and lots of calls to check-equal?
or check-=?
Yes
What’s that last parameter of check-equal??
What it says if the test fails. It can be helpful in explaining to ourselves or the reader what we are testing.

Problem 3

I recall that there’s a way to see a link between a variable and its
definition. How do I see that link?
Click Check Syntax in DrRacket.

Problem 5

Can we assume that the top-left of the original image is (0,0)?
Yes.
What are width and height doing in paste-and-scale?
They are the width and height of the section we paste into.
Can paste-and-scale change the aspect ratio?
Yes.
What size should the result be?
The same size as the original image. (It should actually be the same image, now modified, but we will allow you to return a new image of the same size.)

Problem 6

Can we use let?
Yes.
Can we use make-list in our let?
No.

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

Problem 4

The documentation of euclidean-distance in the exam2.rkt file had a minor typo in the Purpose specification. [MS & JT, 1 point]

Problem 6

The instructions for how to use the sizefun to determine the scale for each drawing-unit-circle was inconsistent. You should use the absolute value of sizefun plus 1. [IX, 1 point]

Miscellaneous

  1. Sam set up the exam2.rkt file incorrectly. [RN, 1 point]
  2. A few minor grammatical mistakes in the collage reading. [YC, 1 point]

In Sam’s class, there was a link to the incorrect set of instrutions. [JTL, 1 point, Rebelsky section only]

In problem 4, in the code file, the Euclidean distance documentation mistakenly refers to (col2,row) instead of (col2,row2). [Various folks, 0 points]

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.