CS200: Computer Science, Spring 2002

Problem Set 2: Function Fractals Out: 25 January 2002
Due: 4 February 2002, before class
Turnin Checklist: On February 4, bring to class a stapled turn in containing:
 All the code you wrote for Questions 2 – 7. Make sure that the answers to the different questions are clearly marked.
 Print outs of the graphics window for Question 3 and Question 6.
 Your written answers (can be done as comments in your code) to Question 7.
 (Optional) Any printouts and modified code you did for the optional enhancements.
Collaboration Policy  Read Carefully
For this problem set, you may either work alone and turn in a problem set with just your name on it, or work with one other student in the class of your choice. If you work with a partner, you and your partner should turn in one assignment with both of your names on it.PurposeRegardless of whether you work alone or with a partner, you are encouraged to 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. If you use resources other than the class materials, indicate what you used along with your answer.
 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 more code. As always, we recommend you start early and take advantage of staffed lab hours. 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 mainly because they look really cool. There are several types of fractals but the fractal we’ll 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, 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.
Downloads: You will need to download this file to your machine: curve.ss Create a new definitions file by selecting File  New. Put, (load "curve.ss") at the beginning of that file. This loads all the definitions from the curve.ss file. Edit your definitions after that.
Click Save frequently to save your work (remember that Execute does not save your definitions). The first time you Save, DrScheme will ask you for a filename.
Remember to make sure the Language is set to Full Scheme by selecting Language  Choose Language from the DrScheme menu. A dialog box will appear. Select Full Scheme in the Language choice menu and Graphical (MrEd) in the right radio box.
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 coordinate system from (0, 0) to (1, 1):
(0.0, 1.0) (1.0, 1.0)
(0.0, 0.0) (1.0, 0.0) Points have x and y coordinates. To represent points we would like to define functions makepoint, xofpoint and yofpoint such that:
> (xofpoint (makepoint 0.2 0.4)) 0.2 > (yofpoint (makepoint 0.2 0.4)) 0.4Here's one way:(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 the parameter is false (#f), it returns the y parameter; otherwise it returns the x parameter.We have provided two procedures for drawing on the window in curve.ss:
 (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.
 (graphicsclear) — Clear the graphics window.
Question 1: Define a function that uses makepoint, windowdrawpoint and windowdrawline to draw a simple picture of a boat. The primary point of this not particularly pointed problem, is to point out how to practice producing pictures by putting point procedures in proximity pointedly. (That is, there is nothing to turn in for this, but attempt the next question until you can do this.)
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 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 (xofpoint (midline 0.7)) and (yofpoint (midline 0.7)) evaluate to. Try them in your Interactions window.
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 the 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)). The code uses the special form begin. The evaluation rule for begin is:Evaluation Rule 4begin. To evaluate (begin Expression_{1} Expression_{2} ... Expression_{k}), evaluate each subexpression in order from left to right. The value of the begin expression is the value of Expression_{k}.We stop once t is greater than 1.0, since we defined the curve over the interval [0.0, 1.0].A similar function, drawcurveconnected draw a curve by making a line between each point on the curve and the next point. Think about how you would define drawcurveconnected. (You can see how we defined it in the provided code.)
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 using 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. It just swaps the x and y points:
(define (rotateccw curve) (lambda (t) (makepoint ( (yofpoint (curve t))) (xofpoint (curve t)))))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 before 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))))))Try to predict what (drawcurvepoints (shrink midline .5) 1000) will do before trying it in your Interactions window.The shrink 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.
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 centered in the middle of the display window.In addition to alterning 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 (drawcurvepoints (firsthalf midline) 1000) will do. Then try it in your Interactions window to check you were right. Predict what (drawcurvepoints (firsthalf (firsthalf midline)) 1000)) will do. Then try it in your Interactions window to check you were right. (Remember to use (graphicsclear) to clear the screen so you can see the new curve without the old one.)
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 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. You will probably 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 smile, eyes and nose. If you don't like procedures yet, you can draw a frowny face instead. 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). Turn in the code you defined and a print out of your display window. 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 radius on 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:
(define (rotatecw curve) (rotateccw (flipvertically curve)))Composing functions is a common task. We can define a compose function that composes two functions:(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.
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:
 scalexy curve xscale yscale  stretches a curve along the x and y axis by using the scale factors given
 scale curve scale  stretches a curve along the x and y axis by using the same scale factor
 rotatearoundorigin curve degrees  will take a curve and rotate it counterclockwise by a given number of degrees.
It is also useful to have curve transforms where curves may be combined. We call a transformation where two curves are used to produce a new curve a binary transformation.
connectrigidly is a simple type of binary transform. The function will return a curve which 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.
(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 drawing function, smileycurve but this time make it a single curve. The same smiley face you drew in Question 3 should now appear when you do,
(drawcurvepoints smileycurve 1000)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.Optional (bonus points  don't try this until you have finished the rest of the problem set). Define a better function for turning many curves into a single curve that does not have this problem.
Gosper Curves
If you look back at the beginning of the problem set, you can see 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 this set of steps together brings us to 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).
Question 6: Draw some gosper curves using (showconnectedgosper level). Print out your graphics window. 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 7:
 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 consist`ent with your answers to the previous parts of this question.
Enhancements
Everything below here is optional. You may receive a some bonus points for especially good answers and may get a poster of any particularly nice fractals, 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 adapted for UVA CS 200 Spring 2002 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@virginia.edu Using these Materials 