CS200: Computer Science, Spring 2003

Problem Set 2: Function Fractals Out: 22 January 2003
Due: Monday, 3 February 2003, beginning of class
Collaboration Policy  Read Carefully
For this problem set, you are required to work with your pseudorandomly assigned partner listed below. You and your partner should turn in one assignment with both of your names on it. You should read the whole problem set yourself and think about the questions before beginning to work on them with your partner (listed below).
In addition to your partner, you may discuss this assignment with other students in the class and ask and provide help in useful ways. You may consult any outside resources you wish including books, papers, web sites and people except for materials from last year's CS200 course. If you use resources other than the class materials, indicate what you used along with your answer.
Arielle Myhre (aam3m@virginia.edu)
Nolan Madge (nrm5z@virginia.edu)Andrew Connors (apc7a@virginia.edu)
Jerome McGann (jjm2f@virginia.edu)David Madaras (dmm3j@virginia.edu)
Andrea Jacobs (amj2d@virginia.edu)Daniel Greene (dpg7g@virginia.edu)
Mai Hong Pham (mhp3b@virginia.edu)Edward Mitchell (ejm5p@virginia.edu)
Robert Schweizer (rts4j@virginia.edu)Grace Chang (gjc5h@virginia.edu)
Sarah Payne (srp2e@virginia.edu)Jessica Nute (jln2f@virginia.edu)
Margaret Olson (molson@virginia.edu)Jessica Ruge (jmr9z@virginia.edu)
Katrina Salmons (ks2wf@virginia.edu)Krystal Ball (kmb6j@virginia.edu)
Matthew Mehalso (mm4md@virginia.edu)Lauren Cryan (lac4z@virginia.edu)
Anoop Gambhir (asg4y@virginia.edu)Lindy Brown (lcb4b@virginia.edu)
Mary Eckerle (mke4b@virginia.edu)Owen Jones (ofj4f@virginia.edu)
Patrick Lane (plane@virginia.edu)Patrick Rooney (pjr2y@virginia.edu)
Sean Mays (sdm8s@virginia.edu)Steven Marchette (sam7p@virginia.edu)
Hassan Tahir (hassan@tahirs.com)Samuel Sangobowale (sos8v@virginia.edu)
Qi Wang (qw2d@virginia.edu)Sarah Bergkuist (srb5z@virginia.edu)
Salvatore Guarnieri (sg8u@virginia.edu)Timothy Shull (ts8b@virginia.edu)
James Lee (jl6eu@virginia.edu)Victoria Lynch (vnl9w@virginia.edu)
Ramsey Arnaoot (rma3n@virginia.edu)William Brand (wtb2f@virginia.edu)
Justin Alexander Pan (jap4u@virginia.edu)Zachary Hill (zfh3e@virginia.edu)
Chalermpong Worawannotai (cw7r@virginia.edu)Purpose
 Become comfortable with procedures as parameters and results.
 Get some practice defining recursive procedures.
 Play (nicely) with fractals.
Unlike in Problem Set 1, you are expected to be able to understand all the code provided for this problem set (and all future problem sets in this class unless specifically explained otherwise). This problem set requires less reading and introduces fewer new ideas than Problem Set 1, but expects you to write a lot more code. As always, we recommend you start early and take advantage of these staffed lab hours: Thursday, 23 January, 89pm (Rachel)
Sunday, 26 January, 23:30pm (Rachel)
Monday, 27 January, 78:30pm (Katie)
Thursday, 30 January, 89:30pm (Rachel)
Friday, 31 January, 34:30pm (Katie)
Sunday, 2 February, 45:30pm (Rachel)Background
The name "fractal" was invented by Benoit Mandlebrot from the Latin adtive fractus, meaning to break (as in fracture). A fractal is a geometric shape that can be recursively broken down into smaller parts. Unlike a mosaic, the smaller parts of a fractal all look like the original shape, just scaled down. There are many fractallike structures found in the real world including clouds, coastlines, trees and stock market fluctuations.
Fractals receive a lot of attention because of their interesting mathematical properties, but also because they look really cool. There are several types of fractals but the fractal we will be working with in this assignment is the Gosper Curve (you'll see a few more in PS3), pictures created by the repetition of a simple process. At every iteration of the Gosper curve, an approximation to the fractal curve is made. The next iteration consists of two scaleddown copies of the current iteration, now rotated 45 degrees and connected end to end.
Gosper Curve Level 1 Gosper Curve Level 2 Gosper Curve Level 6
Reading: Before going further, you should have finished reading all of SICP, Chapter 1 and GEB, Chapter 5.
Download: Download ps2.zip to your machine and unzip it into your home directory J:\cs200\ps2. This file contains:
Remember to click Save frequently to save your work (remember that Execute does not save your definitions) and make sure the language is set to Pretty Big (includes MrEd and Advanced from the PLT menu.Drawing Points
A curve is a (possibly infinite) set of points. We can draw a curve by mapping points on the curve to pixels on the screen. For this assignment, we will use a Cartesian coordinate system from (0, 0) to (1, 1):
(0.0, 1.0) (1.0, 1.0)
(0.0, 0.0) (1.0, 0.0) We can describe the location of a point using its horizontal and vertical location given by x and y coordinates. To represent points we would like to define functions makepoint, xofpoint and yofpoint that behave like this:
Here's one way:> (xofpoint (makepoint 0.2 0.4))
0.2
> (yofpoint (makepoint 0.2 0.4))
0.4
(define (makepoint x y) (lambda (selector) (if selector x y))) (define (xofpoint point) (point #t)) (define (yofpoint point) (point #f))The function makepoint takes two parameters and returns a function. The returned function is a function of one parameter, selector. If this parameter is false (#f), it returns the y parameter of makepoint; otherwise it returns the x parameter of makepoint.We have provided two procedures for drawing on the window in curve.ss:
Use (clearwindow) to clear the graphics window.
 (windowdrawpoint point) — Draw a black dot on the window at the point point. For example,
(windowdrawpoint (makepoint 0.5 0.5))
will place a black dot in the center of the window. (The point is only one pixel, so it is hard to see.) (windowdrawline point0 point1) — Draw a black line from point0 to point1. For example,
(windowdrawline (makepoint 0.0 0.0) (makepoint 1.0 1.0))
will draw a diagonal line from the bottom left corner to the top right corner.
Question 1: Define a procedure that uses makepoint, windowdrawpoint and windowdrawline to draw a simple picture of a boat. (You don't need to turn in your boat picture unless it is especially interesting.)
Curves
Building upon points, we can make curves and lines (straight lines are just a special kind of curve). We can think of curves as functions that take us from values to points. One way to represent a curve is as a function that evaluates to a point for every value between 0.0 and 1.0. For instance,
(define (midline t) (makepoint t 0.5))
defines a curve that is a horizontal line across the middle of the window. If we apply midline to a value x, we get the point (x, 0.5). Hence, if we apply midline to all values between 0.0 and 1.0, we get a horizontal line.Predict what each of these expressions evaluates to:
Check your predictions by evaluating them in your Interactions window.
 (xofpoint (midline 0.7))
 (yofpoint (midline 0.7))
An ideal curve would result from applying our curve procedure to every value between 0.0 and 1.0. Of course, there are infinitely many values between 0.0 and 1.0, so we can't apply it to all of them. Instead, we select enough values to show he curve well. To draw a curve, we need to apply the curve function to many values in the range from 0.0 to 1.0 and draw each point it evaluates to. Here's a function that does that:
(define (drawcurvepoints curve n) (define (worker t step) (if (<= t 1.0) (begin (windowdrawpoint (curve t)) (worker (+ t step) step)))) (worker 0.0 (/ 1 n)))The function drawcurvepoints takes a function representing a curve, and n, the number of points to draw. The inner function, worker is defined recursively: if t is less than or equal to 1.0, we draw the current point using (windowdrawpoint (curve t)) and draw the rest of the points by evaluating (worker curve (+ t step) step)). We use begin to evaluate two expressions inside the if (check the begin evaluation rule).A similar function, drawcurveconnected, draws a curve by making a line between each point on the curve and the next point. Think about how you would define drawcurveconnected. Once you have a good idea how to define it, look at the provided code in curve.ss to see if we defined it the same way.
Question 2:
 Define a function, verticalmidline that can be passed to drawcurvepoints so that
(drawcurvepoints verticalmidline 1000)
produces a vertical line in the middle of the window. Define a function, verticalline that takes one parameter and produces a function that produces a vertical line at that horizontal location. For example, (drawcurvepoints (verticalline 0.5) 1000) should produce a vertical line in the middle of the window and (drawcurvepoints (verticalline 0.2) 1000) should produce a vertical line near the left side of the window.
The good thing about defining curves as functions is it is easy to modify and combine then in interesting ways.
For example, the function rotateccw takes a curve and rotates it 90 degrees counterclockwise by swapping the x and y points:
(define (rotateccw curve) (lambda (t) (makepoint ( (yofpoint (curve t))) (xofpoint (curve t)))))Note that (rotateccw c) evaluates to a curve! The function rotateccw is a function that takes a function (a curve) and returns a function that is a curve.
Predict what (drawcurvepoints (rotateccw midline) 1000) will do. Confirm your prediction by trying it in your Interactions window.
Here's another example:
(define (shrink curve scale) (lambda (t) (makepoint (* scale (xofpoint (curve t))) (* scale (yofpoint (curve t))))))Predict what (drawcurvepoints (shrink midline .5) 1000) will do, and then try it in your Interactions window.The shrink procedure doesn't produce quite what we want because in addition to changing the size of the curve, it moves it around. Make sure you understand why this happens. Try shrinking a few different curves to make sure.
One way to fix this problem is to center our curves around (0,0) and then translate them to the middle of the screen. We can do this by adding or subtracting constants to the points they produce:
(define (translate curve x y) (lambda (t) (makepoint (+ x (xofpoint (curve t))) (+ y (yofpoint (curve t))))))Now we have translate, it makes more sense to define midline this way:(define (horizline t) (makepoint t 0)) (define midline (translate horizline 0 0.5))To check you understand everything so far, use translate, horizline and shrink to draw a line half the width of the window that is centered in the middle of the display window.In addition to altering the points a curve produces, we can alter a curve by changing the t values it will see. For example,
(define (firsthalf curve) (lambda (t) (curve (/ t 2))))is a function that takes a curve and produces a new curve that is just the first half of the passed curve.Predict what each of these expressions will do:
Try evaluating them in your Interactions window to check if you were right. (Remember to use (clearwindow) to clear the display window so you can see the new curve without the old one.)
 (drawcurvepoints (firsthalf midline) 1000)
 (drawcurvepoints (firsthalf (firsthalf midline)) 1000))
Since the curve is a parameter, we can use drawcurvepoints to draw any curve just by passing in a different function. For example, if we remember geometry well (don't worry, you don't have to remember any geometry yourself for this assignment) we can draw a circle using:
(define (unitcircle t) (makepoint (sin (* 2pi t)) (cos (* 2pi t))))This makes a circle of radius 1, centered at (0, 0). Since our window only shows the coordinate space from (0.0, 0.0) to (1.0, 1.0) most of the circle will not be visible.Try to predict what (drawcurvepoints unitcircle 1000) does, and then try it in your Interactions window. Note that drawcurvepoints prints out a warning message every time it tries to draw a point that does not fit in the display window. These warnings may be helpful when you try to draw a curve but don't see anything on the display.
To make a circle that fits on the display, we need to shrink it and translate it so the center of the circle is in the middle of the display: (0.5, 0.5). Here's how: (drawcurvepoints (translate (shrink unitcircle .25) 0.5 0.5) 1000)
Question 3: Define a function that draws a smiley face. To make the mouth, you may need to define a function similar to firsthalf, but that takes less than half of the curve. You shouldn't need to use any fancy geometry (e.g., sin or cos), but instead use functions to alter the centercircle curve we have already defined to make the mouth, eyes and nose. If you don't like procedures yet, you can draw a frowny face instead (but the eyes must be open!). If you are having a hard time drawing the mouth, read ahead a bit in the problem set (but try to do this with your own code first). Turn in the code you defined and a print out of your display window (see directions following).
To print out the graphics window:
 Drag your display window so that it's near the left edge of the screen (this will make things a bit easier)
 Press the PrtScn (or PrintScrn) key (above the Insert key on most keyboards). This will take a snapshot of your screen and save it to the Windows Clipboard.
 Open MS Paint (Start  Accessories  Paint) and press CtrlV to paste your screenshot. (Select "Yes" if it asks you whether you want to enlarge the bitmap if your screenshot is bigger than the default bitmap size.)
 Select File  Print, and print page 1 only — your display window should fit into the portion that is printed. (Ask for help if it doesn't come out right.)
Composing Functions
To draw your smiley face, you probably did something like:(drawcurvepoints (translate (rotateccw (flipvertically (firsthalf (shrink unitcircle .25)))) 0.5 0.5) 1000)This composes lots of functions together to turn a unit radius circle (the curve produced by unitcircle) into a small halfcircle oriented like a smile and centered in the display window.One of the steps was to flipvertically and rotateccw to turn the curve 270 degrees counterclockwise (or 90 degrees clockwise). We can define a function that does this by composing rotateccw and flipvertically:
(define (rotatecw curve) (rotateccw (flipvertically curve)))Composing functions is a common task. We can define a compose function that composes two functions (each of which must take a single parameter):(define (compose f g) (lambda (x) (f (g x))))Then we can define rotatecw as:(define rotatecw (compose rotateccw flipvertically))
Question 4: If you aren't quite so happy about procedures, you may want to make a smiley face with a smaller smile. You could do that by applying firsthalf twice to get a quartercurve (this produces a lopsided smile, but that's okay): (drawcurvepoints (translate (rotatecw (firsthalf (firsthalf (shrink unitcircle .25)))) 0.5 0.5) 1000)
 Define a function twice that composes a function with itself. You should be able to use twice like this:
(define quartercurve (twice firsthalf))
to get a function quartercurve that produces the first quarter of a curve. Define a function ntimes that takes a function and number n, and composes the function with itself n times. Think about a recursive definition of ntimes: to apply a function 1 time, just apply the function; to apply the function n times, compose the function with the result of applying the funtion n  1 times. (See the hint below for help.)
Hint: Your function will look like this (the parts in < ... > brackets are what you need to fill in):
(define (ntimes f n) (if (= n 1) f ; One time, is just f (compose <fill this in> (ntimes f <fill this in>))))Once you've defined ntimes, you should be able to use this code to draw a very small and lopsided smiley:(drawcurvepoints (translate (rotatecw ((ntimes firsthalf 4) (shrink unitcircle .25))) 0.5 0.5) 1000)Note that (ntimes firsthalf 4) evaluates to a function. That's why there are two (('s before the ntimes — we need to apply the function resulting from (ntimes firsthalf 4) to the function resulting from (shrink unitcircle .25).Curve Transforms
The provided code includes several other functions that transform curves including:
You should be able to understand the code in curve.ss that defines these functions.
 (scalexy curve xscale yscale) — evalutes to curve stretched along the x and y axis by using the scale factors given
 (scale curve scale) — evaluates to curve stretched along the x and y axis by using the same scale factor
 (rotatearoundorigin curve degrees) — evaluates to curve rotated counterclockwise by the given number of degrees.
It is also useful to have curve transforms where curves may be combined. An example is (connectrigidly curve1 curve2) which evaluates to a curve that consists of curve1 followed by curve2. The starting point of the new curve is the starting point of curve1 and the end point of curve2 is the ending point of the new curve. Here's how connectrigidly is defined:
(define (connectrigidly curve1 curve2) (lambda (t) (if (< t (/ 1 2)) (curve1 (* 2 t)) (curve2 ( (* 2 t) 1)))))Predict what (drawcurvepoints (connectrigidly verticalmidline midline) 1000) will do. Is there any difference between that and (drawcurvepoints (connectrigidly midline verticalmidline) 1000)? Check your predictions in the Interactions window.
Question 5:
 Define a new smiley function, smileycurve, but this time make it a single curve. The same smiley face you drew in Question 3 should now appear when you evaluate (drawcurvepoints smileycurve 1000) (except now you should definitely like procedures, so your smiley face should not be frowning!)
 Some parts of your smiley face probably appear dotty (instead of as solid lines). Explain why some parts of your smiley face are more dotty than others.
Gosper Curves
The beginning of this problem set shows three example iterations of the Gosper Curve. The curve starts off as a straight line at level 0, then transforms into what is pictured at level 1. After level 1, the curve from one iteration to the next will rotate the previous iteration and translate it 50% to the right and 50% of the viewport up. This is to ensure that the second curve's beginning will coincide with the first curve's endpoint. Putting these steps together gives us the curve transformation function gosperize:
(define (gosperize curve) (let ((scaledcurve (scalexy curve (/ (sqrt 2) 2) (/ (sqrt 2) 2)))) (connectrigidly (rotatearoundorigin scaledcurve (/ pi 4)) (translate (rotatearoundorigin scaledcurve (/ pi 4)) .5 .5))))Building upon this, we can repeatedly call this function dependent on the number of iterations, or levels, we want to see using the ntimes function you defined in Question 4:
(define (gospercurve level) ((ntimes gosperize level) unitline))Finally, to actually see all this in action we have a drawing function for gosper curves:
(define (showconnectedgosper level) (drawcurveconnected (squeezerectangularportion (gospercurve level) .5 1.5 .5 1.5) 1000))The squeezerectangluarportion translates and scales a curve to show the part in the region specified by the parameters (.5, 1.5), (.5, 1.5).Draw some gosper curves using (showconnectedgosper level). Note that if all the code you wrote earlier works correctly, there is no new code required for this.
Efficiency
Our implementation is really slow. One of the reasons for this is many of our curve transformations have to evaluate (curve t) more than once. For example, scalexy evaluates (curve t) twice:(define (scalexy curve xscale yscale) (lambda (t) (makepoint (* xscale (xofpoint (curve t))) (* yscale (yofpoint (curve t))))))We can make a more efficient version of scalexy by only evaluating (curve t) once:(define (scalexy curve xscale yscale) (lambda (t) (let ((ct (curve t))) (makepoint (* xscale (xofpoint ct)) (* yscale (yofpoint ct))))))The let construction is a short cut for:(define (scalexy curve xscale yscale) (lambda (t) ((lambda (ct) (makepoint (* xscale (xofpoint ct)) (* yscale (yofpoint ct)))) (curve t))))
Question 6:
 For the original implementation (without using let), how many times is (unitline t) evaluated for each point on the curve produced by (gospercurve 1)?
 For the original implementation (without using let), how many times is (unitline t) evaluated for each point on the curve produced by (gospercurve 5)?
 If all the curve transformation procedures are rewritten to use let and avoid evaluating (curve t) more than once in their bodies, how many times is (unitline t) evaluated for each point on the curve produced by (gospercurve 5)?
 You can use (time (showconnectedgosper 5)) to get an accurate timing of how long it takes to evaluate (showconnectedgosper 5). Do this with the original definitions for a few different gosper levels. Then, rewrite the curve transformation procedures to be more efficient. Compare the resulting times, and explain if they are consistent with your answers to the previous parts of this question.
You are encouraged to answer these questions by thinking about what the code does. You can use the trace procedure to observe applications of a procedure in an evaluation. For example, if you evaluate (trace unitline), then everytime unitline is applied, DrScheme will print out tracing information showing the application. (Don't try counting the number of applications from (gospercurve 5) by hand!)
Optional Enhancements
Everything below here is optional. You may receive a some bonus points for especially good answers and any particularly nice fractals will be posted on the course web site, but you are not required or expected to do anything more. This section provides some suggestions for interesting things you might do to make more interesting fractals, but you are encouraged to come up with your own ideas for doing interesting things with curves.Other Curves. The gosperize procedure takes a curve parameter, so we can gosperize more interesting curves than the unitline. Try gosperizing some other curves to make an interesting picture. Try gosperizing the smileycurve you produced in Question 5. The result is an approximation of what CS200 students would look like if they wait until the night before it is due to start Problem Set 3.
Other Angles. The gosper curves we've been working with have had the angle of rotation fixed at 45 degrees. To draw more varied Gosper curves, try defining a (gosperizeangle curve angle) function where the angle to turn is a parameter.
Color. Our curves are plain black lines. A more interesting fractal would use color also. Try modifying the definition of makepoint so that points have color in addition to x and y coordinates. You will need to change many of the other functions also, since makepoint will now take an extra parameter giving the color of the point. Try to define a colorful version of gosperize.
Credits: This problem set was revised for UVA CS200 Spring 2003 by Rachel Dada and David Evans. The original version for UVA CS 200 Spring 2002 was adapted from MIT 6.001 Problem Set 2 from Fall 1996 by Dante Guanlao and David Evans and tested by Stephen Liang.
University of Virginia Department of Computer Science CS 200: Computer Science 
David Evans evans@cs.virginia.edu Using these Materials 