This lab has two parts, “Recursion on lists and strings”, and “Koch Curve”, both of which should be implemented in the same file. The sections are described separately below.
Having now discovered recursion, you’ve decided to become a recursion purist and not write any functions that use loops.
To get started, let’s re-implement some existing list and string functions recursively. What we’re trying to do here is get used to designing recursive solutions – we’re asking you to ignore the built-in already-defined functions and methods you’ve been using, and design your own recursive versions. (Just for practice with recursion, not because these are better!) So don’t use built-in functions or any other extraneous functions that get around the heart of the problem.
In your Python file implement the following functions using recursion. Your functions should return values and should not modify their parameters.
Here are some example problems and their solutions
length
that takes a list as a parameter and returns the
length of the list. Your function may not use the len
function (Hint: to
see if a list has a length of 0, check if it equals []
)
For example:
>>> length([20, 10, 35, 50, 17])
5
>>> length([])
0
rec_max
that takes a list as a
parameter and returns the largest value in the list. You may assume
that the list has at least one entry in it and all types in the
list are comparable without conversion, i.e., the list won’t be a
mix of integers and strings (and, of course, you may not use the
max
function). You can use the len
function.
For example:
>>> rec_max([20, 10,35, 50, 17])
50
>>> rec_max([25])
25
replace
that takes as parameters three strings,
and replaces in the first string, all instances of the second string with the third string.
That is, replace(text, old_substr, new_substr)
should replace all instances
of old_substr
in text
with new_substr
.
For example:
>>> replace("abracadabra", "a", "z")
'zbrzczdzbrz'
>>> replace("one fish, two fish, red fish, blue fish", "fish", "bird")
'one bird, two bird, red bird, blue bird'
>>> replace("one fish, two fish, red fish, blue fish", " fish", "")
'one, two, red, blue'
avoids
that takes as parameters two strings,
and checks whether the first string avoids using all characters in the second string.
That is, avoids(word, letters)
should return True
if word
contains
none of the characters in letters
and should return False
if there is any overlap.
>>> avoids('geronimo', 'abc')
True
>>> avoids('banana', 'abc')
False
>>> avoids('banana', '')
True
>>> avoids('', 'zebra')
False
Write a recursive function draw_koch(length,
generation)
that draws a shape called the Koch
Curve. The Koch curve is a fractal – a shape that is
self-similar, that is, it can be decomposed into smaller
versions of the same shape.
Many of the techniques that we use to generate fractals computationally rely on the repetition of a set of steps, so we tend to talk about generations, which are the number of iterations that we used to produce the drawing. The Koch curve is a fairly simple one, and we can see its structure most clearly, by looking at how it changes from one generation to the next.
The starting place (generation 0) is a straight line.
lab8_koch0.png
For the first generation, we cut the line into thirds and replace the middle segment with two segments of the same length, forming two sides of an equilateral triangle.
lab8_koch1.png
The second generation is formed by applying this same process to every line in generation 1.
lab8_koch2.png
Further generations are formed by repeating this process, taking each line, breaking it into thirds and replacing the middle segment with a bump. Conceptually, if you take this out to infinity, you will have a very curious mathematical phenomenon – an infinitely long line between two fixed points a finite distance apart that also occupies a finite area. In practical terms, however, when the length of the individual lines becomes 1 pixel long, that is pretty much as small as we can draw.
lab8_koch3.png
To actually draw the shape, of course, we won't draw a line and then go back and remove a piece. Instead, we start conceptually with the highest generation of the fractal first. If you think about a generation 2 curve, it is really made up of four generation 1 curves that are each 1/3 the length of the generation 2 curve. But each of those generation 1 curves are made up of generation 0 curves 1/3 of that length. What that means is that we don’t actually draw any of the higher generation curves, the only thing we draw is a whole bunch of generation 0 curves (straight lines!) in different locations.
Step one: Start with this code at the bottom of your script. This
is just for convenience to set up your drawing, and so you can run
draw_koch
with different parameters. It also gives you a couple
options to speed up the drawing.
if __name__ == "__main__":
if len(sys.argv) == 3:
# t.tracer(False) # to show drawing all at once
# t.speed(0) # to increase speed of the drawing a bit
length = int(sys.argv[1])
generation = int(sys.argv[2])
t.penup(); t.backward(length//2); t.pendown()
draw_koch(length, generation)
t.update()
t.done()
Step two: Start writing the draw_koch()
function. Define the
function and make sure it takes parameters length
and generation
. We will be drawing this
shape using a recursive function.
Step three: Write the base case. When generation
is 0, draw a straight line
of length length
.
Step four: Test this by running the program with the
command %Run lab8_recursion.py 200 1
It should draw a line that is 200 pixels long.
Step five: We are
going to start work on the recursive case, but it won’t be recursive
yet. When generation
is greater
than 0, draw a first generation curve. We want the curve to fit within
length
pixels. So, move forward
length/3
pixels. Turn left by 60
degrees. Move forward length/3
pixels (putting you at the apex of the triangle). Turn right and draw
the second leg of the triangle. Turn left again and draw the final
base line. You should now have a generation 1 curve that fits on a
length length
line
Step six: Test. At this point, if you change the number of
generations in the call to draw_koch
you will
still get a generation one curve.
Step seven: Time to
go recursive. Instead of drawing straight lines in the recursive case,
each call to forward
should be replaced with a call to draw_koch
with parameters one third of length
), and one generation less.
Step eight: Test this by calling draw_koch
with different values for generation
. We recommend starting
small with something like a length of 200 and 1 generation just to
make sure your code is working. Then you can work up to bigger curves
and higher generations. IMPORTANT Keep the number of
generations low or you will be waiting for it to finish forever. if
you want to try higher generations, you should turn off the tracer
(the tracer is what animates the drawing process. With it off, you
just get the final picture). You can do that by uncommenting the line turtle.tracer(False)
.
A second problem you
may encounter is if the length gets to be less than 1 before you hit
the base case, the line segments drawn by the base case will all have
a length of 0, and the curve will not draw at all.
When you’re done, create a screen capture of your program output
for draw_koch(400, 4)
along with any additional drawings you
did for creativity. (See the When you’re done
section below for a reminder of how to capture a screenshot of a window.)
You may earn up to 2 creativity points on this assignment. Below are some ideas, but you may incorporate your own if you’d like. Make sure to document your extra point additions in comments at the top of the file.
rec_count
that takes a list and
another object as parameters. The function returns how many times the object
occurs in the list (you may not use the count
, in
, or index
functions/methods).Make sure that your program is properly documented:
In addition, make sure that you’ve used good coding style (including meaningful
variable names, constants where relevant, vertical white space, etc.).
Invoke your program code from within the __main__
conditional, so if your code were imported as a module nothing would happen.
For this lab, when run your program
should only draw the Koch curve (but your file should also contain the “Warming
up” functions and any additional ones you write for creativity).
Submit your program and screenshot via Gradescope. Your program file must be named lab8_recursion.py and your screenshot lab8_screenshot.png.
To take a screenshot of the drawing window:
Mac: Press command+shift+4. Then hit the spacebar (on a mac), doing so will change the crosshairs into a camera to screenshot a specific window. If you then click on the window where your picture is drawn, an image file will be saved on your desktop entitled “Screen shot…”. You can double-click on this file to make sure it worked.
PC: You can use the “Snipping Tool”. Save your image in png format on the desktop.
Make sure you submit both your code and your image at the same time. You can submit multiple times, with only the most recent submission graded. Note that the tests performed by Gradescope are limited. Passing all of the visible tests does not guarantee that your submission correctly satisfies all of the requirements of the assignment.
Feature | Points |
---|---|
Warmup | |
length |
3 |
rec_max |
4 |
replace |
4 |
avoids |
3 |
Koch curve | |
draw_koch |
4 |
Generates Koch curve on run | 2 |
Comments, style | 3 |
Creativity points | 2 |
Total | 25 |
Here are the most important commands (more can be
found in the online
documentation.) Recall that all of these must be prefaced with
turtle.
forward(distance)
distance
in the direction it is facing.backward(distance)
distance
in the opposite direction to where it is facing.right(angle)
angle
. Note that this can be any angle, not the four cardinal directions we had with LightBot.left(angle)
angle
.goto(x, y)
down()
up()
pencolor(colorstring)
fillcolor(colorstring)
begin_fill()
end_fill()
tracer(state)
True
or False
to turn the tracing on or off.update()
window_width()
window_height()