Functional Problem Solving (CSC 151 2014F) : Labs
Primary: [Front Door] [Schedule] - [Academic Honesty] [Disabilities] [Email] - [FAQ] [Teaching & Learning] [Grading] [Rubric] - [Calendar]
Current: [Assignment] [EBoard] [Lab] [Outline] [Reading]
Sections: [Assignments] [EBoards] [Examples] [Handouts] [Labs] [Outlines] [Readings]
Reference: [Setup] [VM] [Errors] - [Functions A-Z] [Functions By Topic] - [Racket] [Scheme Report (R5RS)] [R6RS] [TSPL4]
Related Courses: [Davis (2013F)] [Rebelsky (2014S)] [Weinman (2014F)]
Misc: [Submit Questions] - [SamR] [Glimmer Labs] [CS@Grinnell] [Grinnell] - [Issue Tracker (Course)]
Summary:
In this laboratory, you will explore three of Scheme's primary
conditional control operations, if
,
when
, and cond
.
If expressions typically have the form
(if
test
consequent
alternative
)
The Scheme interpreter first evaluates the test. If the value of the
test is false (that is, #f
), the interpreter evaluates the
alternative and returns the value. If the value of the test is truish
(that is, anything that is not false), the interpreter evaluates the
consequent and returns its value.
When expressions typically have the form
(when
test
exp1
...expn
)
The Scheme interpreter first evaluates the test. If the value of the
test is false (that is, #f
), the interpreter ignores
the rest of the expression. If the value of the test is truish,
the interpreter evaluates the expressions in turn.
Cond expressions typically have the form
(cond
[test0
exp0
] [test1
exp1
] ... [testn
expn
] [elsealternative
])
Scheme evaluates each test in turn until one is truish. It then evaluates the corresponding expression and returns its value. If none of the tests is truish, then it evaluates the alternative and returns its value.
a. Add the following to your definitions pane to make it easy to create and show a variety of sample images.
(define grid (image-show (image-new 3 3))) (define canvas (image-show (image-new 200 200))) (define picture (image-load "/home/rebelsky/MediaScheme/Images/kitten.png"))) (image-set-pixel! grid 0 0 (irgb 192 0 0)) (image-set-pixel! grid 1 0 (irgb 192 0 192)) (image-set-pixel! grid 2 0 (irgb 0 0 192)) (image-set-pixel! grid 0 1 (irgb 192 192 0)) (image-set-pixel! grid 1 1 (irgb 0 192 192)) (image-set-pixel! grid 2 1 (irgb 255 255 255)) (image-set-pixel! grid 0 2 (irgb 0 192 0)) (image-set-pixel! grid 1 2 (irgb 0 0 0)) (image-set-pixel! grid 2 2 (irgb 192 192 192))
b. Save your definitions pane as conditionals-lab.rkt
.
c. Open the reference page for drawings in a separate window or tab.
Recently, we've considered a number of activities that required us to work with the brightness of colors. We typically compute brightness using an expression which can be encapsulated in a procedure as follows:
;;; Procedure: ;;; irgb-brightness ;;; Parameters: ;;; color, an integer-encoded RGB color ;;; Purpose: ;;; Computes the brightness of color on a 0 (dark) to 100 (light) scale. ;;; Produces: ;;; brightness, an integer ;;; Preconditions: ;;; [No additional] ;;; Postconditions: ;;; If color1 is likely to be perceived as lighter than color2, ;;; then (irgb-brightness color1) > (irgb-brightness color2). ;;; 0 <= brightness <= 100 (define irgb-brightness (lambda (color) (round (* 100 (/ (+ (* 0.30 (irgb-red color)) (* 0.59 (irgb-green color)) (* 0.11 (irgb-blue color))) 255)))))
a. Add irgb-brightness
to the Definitions pane.
b. Using irgb-brightness
as a helper, write a procedure,
(
, that returns true if
irgb-brighter?
color1
color2
)color1
is brighter than color2
and false otherwise.
c. Using
irgb-brighter?
as a helper, write a procedure,
(
, that returns the brighter of
irgb-brighter-2
color1
color2
)color1
and color2
.
d. What do you expect the following to accomplish?
>
(image-transform! grid irgb-brighter-2)
e. Check your answer experimentally.
f. As you may have noted, the transformation was invalid because
irgb-brighter-2
expects two colors, not one. Let's
try an appropriate call to irgb-brighter-2
. What do
you expect the following to accomplish?
>
(define irgb-brighter-of-red (lambda (color) (irgb-brighter-2 color (irgb 255 0 0))))
>
(image-transform! grid irgb-brighter-of-red)
g. Check your answer experimentally. You may want to zoom in on
grid
first, so that the changes are clearer. And remember
that you may need to use image-refresh-displays!
to
see the changes.
f. Save the definitions pane. You will want to use these procedures later.
Consider the following four colors:
(define grey0 (irgb 0 0 0)) (define grey1 (irgb 96 96 96)) (define grey2 (irgb 192 192 192)) (define grey3 (irgb 255 255 255))
a. Using if
expressions (not
cond
expressions), write a
procedure, (
, that returns
irgb-4grey
color
)
grey0
, if the brightness of color
is less than 25;
grey1
, if the brightness of color
is at least 25 but less than 50;
grey2
, if the brightness of color
is at least 50 but less than 75; or
grey3
, if the brightness of color
is at least 75.
b. Build and show a variant of grid
using
irgb-4grey
. (Testing our procedure on a small
image is a good way to make sure that there are no glaring errors.)
c. Build and show a variant of picture
using
irgb-4grey
.
d. Rewrite irgb-4grey
so that it uses
cond
rather than if
.
When you are done with this exercise, you may want to look at the notes on this exercise.
a. What do you expect the following expressions to accomplish?
>
(image-select-ellipse! canvas REPLACE -50 0 40 40)
>
(image-select-rectangle! canvas REPLACE 10 220 50 50)
b. Check your answer experimentally.
c. As you should have observed, both of those expressions generate an
error because the selection is outside the bounds of the image.
Write a new predicate, (
,
that determines whether the given selection is likely to be within
image-contains-selection?
img
left
top
width
height
)img
.
Hint: One case in which a selection is invalid is when the left edge of the selection is greater than or equal to the width of the image.
Hint: Another case in which a selection is invalid is when the right edge (given by adding the width to the left edge) is less than 0.
d. Now that you have a mechanism for determining
the likely safety of selections, write a new procedure
(
that checks whether the selection is likely to be within the
bounds of the image and, if so, does the selection using
image-safely-select-ellipse!
img
selection-type
left
top
width
height
)image-select-ellipse!
. If the
selection is unlikely to be within the bounds of the image,
image-safely-select-ellipse!
should select nothing.
Hint: You may want to use
when
to make your decision.
Here is the render!
procedure from
the reading.
;;; Procedure: ;;; render! ;;; Parameters: ;;; drawing, a drawing value ;;; image, an image id ;;; Purpose: ;;; Render drawing on the image. ;;; Produces: ;;; [Nothing; called for side effect] ;;; Preconditions: ;;; drawing can be rendered on image. ;;; drawing is a simple drawing, that is one of ;;; (drawing-rectangle? drawing) or ;; (drawing-ellipse? drawing) holds. ;;; Postconditions: ;;; drawing has been rendered on image. (define render! (lambda (drawing image) (context-set-fgcolor! (drawing-color drawing)) (cond [(equal? (drawing-type drawing) 'rectangle) (image-select-rectangle! image REPLACE (drawing-left drawing) (drawing-top drawing) (drawing-width drawing) (drawing-height drawing))] [(equal? (drawing-type drawing) 'ellipse) (image-select-ellipse! image REPLACE (drawing-left drawing) (drawing-top drawing) (drawing-width drawing) (drawing-height drawing))]) (image-fill-selection! image)))
And here are two sample drawings.
(define d1 (hshift-drawing 30 (vshift-drawing 40 (scale-drawing 50 (recolor-drawing "red" drawing-unit-square))))) (define d2 (vscale-drawing 120 (hscale-drawing 60 (recolor-drawing "green" drawing-unit-circle))))
a. Test render!
to verify that it does,
in fact, render both drawings.
b. As you may have observed, we don't immediately see the rendered
drawing. Update render!
so that, after
rendering, it updates the display and selects nothing.
c. Extend render!
so that it outlines
the drawing in black. (Recall that you can stroke
the drawing with image-stroke-selection!
to outline the
image.)
d. Extend render!
so that it outlines
the drawing in black if the drawing is a light color and outlines
the drawing in grey if the drawing is a dark color. (You can choose
your own metric for dark and light.)
Those with extra time may choose to do the extra problems, which focus on programming tasks, or the explorations, which focus on the application of procedures to image creation.
As you may recall from the reading on
Boolean values and predicate procedures, it is possible
to use and
and or
to achieve conditional behavior. The subsequent reading on
conditionals provides some additional information on the use
of and
and or
.
Using those techniques, rewrite rgb-4grey
so that
it uses neither if
nor cond
.
That is, you'll need to find ways to express the same outcome using
and
and or
.
One problem with render!
is that it
causes an error if we try to render a drawing outside of the image.
For example,
>
(define canvas (image-new 10 10))
canvas
>
(render! (hshift-drawing -2 drawing-unit-circle) canvas)
Error: image-select-ellipse!: selection is outside of the bounds of the image>
(render! (hshift-drawing 11 drawing-unit-circle) canvas)
Error: image-select-ellipse!: selection is outside of the bounds of the image
Rewrite render!
to that it only renders
drawings that fall within the bounds of the canvas.
Consider the following procedure that draws a simple logo in a color and position of the user's choice.
;;; Procedure: ;;; simple-logo! ;;; Parameters: ;;; img, an image ;;; left, a real number ;;; top, a real number ;;; color, a color ;;; Purpose: ;;; Renders a simple logo on img, with the left edge of the logo ;;; at left, the top edge at top. Uses color as the primary color ;;; of the logo ;;; Produces: ;;; img, the same image ;;; Preconditions: ;;; 0 <= left < (width img) [unverified] ;;; 0 <= top < (height img) [unverified] ;;; Postconditions: ;;; img now contains a rendering of the simple logo. (define simple-logo! (lambda (img left top color) ; Save appropriate parts of the context (let ([saved-color (context-get-fgcolor)] [saved-brush (context-get-brush)]) ; Select the logo (image-select-rectangle! img REPLACE left top 50 50) (image-select-ellipse! img SUBTRACT (+ left 15) (+ top 10) 25 25) (image-select-ellipse! img SUBTRACT (- left 5) (- top 10) 30 30) (image-select-ellipse! img INTERSECT left top 60 60) ; Fill it with the main color (context-set-fgcolor! color) (image-fill! img) ; Outline it with a fuzzy brush to get an intersting effect (context-set-brush! "Circle Fuzzy (09)") (image-stroke! img) ; Restore the context, as best as is possible (image-select-nothing! img) (context-set-brush! saved-brush) (context-set-fgcolor! saved-color) ; Return the updated image img)))
a. Make sure that you understand what the procedure does by rendering a few
copies of the logo on canvas
.
b. As you may have observed, this procedure tries to “preserve” the context by grabbing the color and brush before changing them, and restoring them to the old values after it has changed them. While this strategy works fine if we're only drawing one or two logos, it can add unnecessary work if we are drawing a lot of logos in the same color.
Rewrite simple-logo!
so that it only restores the
brush and color if they are different from the brush and color used to
create the logo. Consider using when
to make
decisions about whether or not to set and restore the brush and color.
c. If we don't care about the current brush and color (or if we are changing them a lot), we may want to give a hint to the programmer that she need not restore the brush and color. MediaScheme includes three procedures that do just that.
(context-preserve?
)
checks
whether or not procedures are encouraged to preserve the
context.(context-preserve-off!
)
makes
context-preserve?
return false
(#f
).(context-preserve-on!
)
makes
context-preserve?
return true
(#t
).
Rewrite simple-logo!
so that it only restores the
color and brush if context-preserve?
returns
true.
In exercise 3, you wrote a procedure,
image-safely-select-ellipse!
that first checked
whether the selection was in the image and, if not, simply avoided
the selection operation. However, the choice to ignore the selection
may not always be appropriate. For example, if the new selection is
to replace the prior selection, we should probably select nothing.
In contrast, if the new selection is to be subtracted from the current
selection, we should maintain the current selection.
Rewrite image-safely-select-ellipse!
to do what
you consider appropriate for each of the five cases:
image-select-ellipse!
)REPLACE
ADD
SUBTRACT
INTERSECT
Explorations are intended for students interested in further exploring the design aspects of these techniques. They also provide students who finish early with extra activities that may challenge them in different ways.
You've already noted that
render!
can do more than simply select an
area and then fill it. For example, we can stroke the drawing after
filling it. (We could even stroke it multiple times.)
Write a procedure, render-excessively!
,
that renders a drawing in a more interesting manner. You might, for
example, stroke the drawing with a large brush, a medium brush, and
a small brush, each of different colors. You might draw a contrasting
smaller shape within the object. (E.g., within a square of edge length
d, you can draw a circle of diameter d; within a circle of diameter d,
you can draw a square of edge length (/ d (sqrt 2).)
The if
-based solution will look something like the following.
(define rgb-4grey (lambda (color) (if (< (irgb-brightness color) 25) grey0 (if (< (irgb-brightness color) 50) grey1 (if (< (irgb-brightness color) 75) grey2 grey3)))))
The cond
-based solution will look something like the
following.
(define rgb-4grey (lambda (color) (cond ((< (irgb-brightness color) 25) grey0) ((< (irgb-brightness color) 50) grey1) ((< (irgb-brightness color) 75) grey2) (else grey3))))