Fruitful Recursion and Recursion with Turtles

Acknowledgment. This notebook has been adapted from Wellesley CS111 course materials: (http://cs111.wellesley.edu/spring19).

Sum of numbers from 1 up to n

How would we write a function to return the sum of numbers from 1 up to n?

We can do this by returning a value from the recursive function instead of printing the numbers.

In [ ]:
def sumUp(n):
    """Returns sum of integers from 1 up to n"""
    if n < 1:
        return 0
    else:
        return n + sumUp(n-1)
In [ ]:
sumUp(5)
In [ ]:
sumUp(10)

It never hurts to name the subResult of the recursive call and the result of the whole call (and often helps you think about what's going on)

In [ ]:
def sumUp(n):
    """Returns sum of integers from 1 up to n"""
    if n < 1:
        return 0
    else:
        subResult = sumUp(n-1) # Result returned by recursive call
        result = n + subResult # Result for whole call 
        return result
In [ ]:
sumUp(4)

Recursive Palindromes

Write a recursive function that determines whether a given string is a palindrome or not.

In [ ]:
def palindrome(s):
    """Returns True if string s is a
    palindrome."""
    if len(s) == 0 or len(s) == 1:
        return True
    else:
        subresult = palindrome(s[1:-1])    
        return s[0].lower() == s[-1].lower() and subresult
In [ ]:
palindrome('Ana')
In [ ]:
palindrome('Hannah')
In [ ]:
palindrome('williams')

Debugging Recursive Functions

Often things will go wrong, and your recursive function is not doing what you expect it to do. Use debugging techniques to figure out what is going on. You can add print statements in the function body to do that.
Add such statements everywere there might be the opportunity to make a mistake:

  • am I passing the right parameters and changing them from call to call?
  • am I handling the base case correctly (if there is one)?
  • am I calculating the return value properly?
In [ ]:
def palindromeDebug(s):
    """Returns True if string s is a
    palindrome."""
    print("Entering palindrome('{}')".format(s))
    if len(s) == 0 or len(s) == 1:
        print('Reached base case. Returning True')
        return True
    else:
        subresult = palindromeDebug(s[1:-1])    
        result = s[0].lower() == s[-1].lower() and subresult
        print("Returning {} from palindrome('{}')".format(result, s))
        return result
In [ ]:
palindromeDebug('shannahz')

Recursive Factorial

How many ways can you arrange 3 items in a sequence? How about 4?

The general answer is given by factorial(n), often denoted in math as $n!$.

It is given by the formula: $$n! = n * (n-1) * (n-2) *(n -3) * \ldots 2 * 1$$

Your Turn: Try writing a function factorial that computes the factorial of n without any loops, using the same logic as sumUp.

In [ ]:
def factorial(n):
    """Returns n! using recursion"""
    if n == 0:
        return 1
    else:
        return n * factorial(n-1)
In [ ]:
factorial(5)
In [ ]:
factorial(7)
In [ ]:
1 * 2 * 3 * 4 * 5
In [ ]:
120 * 6 * 7

Get to know the Turtle

We'll learn about a new graphics library today, Turtle Graphics.
We will write programs that control the turtle's movements. The turtle leaves a trail behind.

In [ ]:
from turtle import *

#setup(400,400, 0, 0) # open the Turtle graphics window
fd(100) # move turtle forward 100 pixels
In [ ]:
lt(90)  # change direction by 90 degrees left
fd(100) # move forward 100 pixels
In [ ]:
pensize(5)      # change pen size
pencolor('red') # change pen color
lt(90)
fd(100)
In [ ]:
# completing the square
lt(90)
fd(100)

Clear the screen for new drawings

In [ ]:
# use clear() to erase the canvas
clear()

Notice that the turtle's properties (pen size and color haven't changed). If in addition to clearing
the drawing we want to return the turtle in its original state, use reset().

In [ ]:
reset()
In [ ]:
reset()
pensize(5)
speed(1)
fd(100)
lt(60)
pencolor('red')
fd(150)
rt(15)
pencolor('blue')
bk(100)
pd()
In [ ]:
pensize(5)
bk(250)
pensize(3)
home()
exitonclick() # click on the canvas to close the window

Oftentimes, it is helpful to control the speed with which the turtle moves.

  • We can do this with the method speed(). Above we have assigned the speed to the slowest setting (i.e., 1). The fastest setting is 10.
  • A speed of 0 is a special number. It turns off animation and will draw the graphics instantly.

Drawing polygon

Loops can be used in conjunction with turtles to make interesting designs.
Let's build together the function polygon, which will produce different shapes as shown in the slides.

In [ ]:
reset()
In [ ]:
def polygon(numSides, sideLength):
    '''Draws a polygon with the specified number 
     of sides, each with the specified length.
     '''
    for side in range(numSides):
        fd(sideLength)
        lt(360/numSides)
In [ ]:
# Draw a triangle, allow user to click on canvas to close window
# 0,0 sets the position of the canvas at the top-left corner
reset()
pensize(3)
polygon(3, 200)
In [ ]:
# Draw a square, click on canvas to close window
# setup(400, 400, 0, 0)
reset()
pensize(5)
polygon(4, 200)
In [ ]:
# Draw a pentagon, click on canvas to close window
reset()
pensize(5)
polygon(5, 75)
In [ ]:
# Draw an approximate circle, click on canvas to close window
reset()
pensize(5)
polygon(100, 7)

polyflow

In addition to the simple shapes we saw above, with loops we can create more interesting shapes too, such as geometric flowers. We will use the function polygon to write polyFlow.

In [ ]:
def polyFlow(numPetals, petalSides, petalLen):
    '''Draws "flowers" with numPetals arranged around
    a center point. Each petal is a polygon with
    petalSides sides of length petalLen.
    '''
    for petal in range(numPetals):
        polygon(petalSides, petalLen)
        rt(360.0/numPetals)
In [ ]:
# Example 1: square petals
reset()
pensize(5)
speed(5)
polyFlow(7, 4, 80)
In [ ]:
# Example 2: pentagon petals
reset()
pensize(2)
speed(5)
polyFlow(10, 5, 75)
In [ ]:
# Example 3: hexagon petals
reset()
pensize(2)
speed(5)
polyFlow(11, 6, 60)

Spiraling Turtles using Recursion

Let's try to write the recursive function spiral, which creates the images shown in the slides.

In [ ]:
from turtle import *
In [ ]:
def spiral(sideLen, angle, scaleFactor, minLength):
    '''
    Recursively creates a spiral, takes these parameters:
    1. sideLen is the length of the current side;
    2. angle is the amount the turtle turns left to draw the next side;
    3. scaleFactor is the multiplicative factor by which to scale the next side 
    (it is between 0.0 and 1.0);
    4. minLength is the smallest side length that the turtle will draw.
    '''
    if sideLen < minLength:
        pass 
    else:
        fd(sideLen)
        lt(angle)
        spiral(sideLen*scaleFactor, angle, scaleFactor, minLength)

To test the function with large values, we need to move the turtle from (0,0) in the center of the canvas to some other point. However, whenever the turtle moves, it leaves a trail behind. This is why we used the methods pu and pd (pen up and pen down).

To keep everything clean, we'll wrap it all in the function.

In [ ]:
def setupTurtle(sideLen):
    """Helper function to prepare the window for drawing and 
    then calling the spiral function.
    """
    reset()
    pensize(2)
    padding = 100
    width = sideLen + padding
    height = sideLen + padding
    pu()
    goto(-height/2+padding/2, -width/2+padding/2)
    pd()
In [ ]:
reset()
speed(8)
In [ ]:
setupTurtle(625)
spiral(625, 90, 0.7, 100)
In [ ]:
setupTurtle(700)
spiral(700, 90, 0.9, 10)
In [ ]:
setupTurtle(700)
spiral(200, 72, 0.97, 10)
In [ ]:
reset()
spiral(200, 121, 0.95, 15)

spiralBack (Invariance)

A function is invariant relative to an object’s state if the state of the object is the same before and after the function is invoked.

The following function is very similar to spiral, we just need to undo the operations we did before the recursion.

In [ ]:
def spiralBack(sideLen, angle, scaleFactor, minLength):
    '''Draws a spiral. The state of the turtle (position,
    direction) after drawing the spiral should be the 
    same as before drawing the spiral. 
    '''
    if sideLen < minLength:
        pass 
    else:
        fd(sideLen)
        lt(angle)
        spiralBack(sideLen*scaleFactor, 
                   angle, scaleFactor, minLength)
        rt(angle)
        bk(sideLen)
In [ ]:
reset()
setupTurtle(500)
pensize(2)
spiralBack(200, 95, 0.93, 10)

Fruitful Spirals: spiralLength

Run this code below to setup the screen and turtle for the next series of exercises. Run just once!

In [2]:
setup(600, 600, 0, 0)

def resetTurtle():
    reset()
    pu()
    goto(-250, -250)
    pd() 

We can apply our new knowledge of fruitful recursion to our turtle spiral example. Modify the spiralBack function to be a spiralLength function that returns the total length of the lines in the spiral in addition to drawing the spiral and bringing the turtle back to its initial location and orientation.

In [ ]:
def spiralLength(sideLen, angle, scaleFactor, minLen):
    """Draws a spiral based on the given parameters 
    and leaves the turtle's location
    and orientation invariant. Also returns 
    the total length of lines in the spiral.
    """
    if sideLen < minLen:
        return 0
    else:
        fd(sideLen); lt(angle)
        subLen = spiralLength(sideLen*scaleFactor, 
                     angle, scaleFactor, minLen)
        rt(angle); bk(sideLen)
        return sideLen + subLen
In [ ]:
resetTurtle()
totalLen = spiralLength(100, 90, 0.5, 5)
print("The total length of the spiral is", totalLen)
In [ ]:
resetTurtle()
totalLen = spiralLength(200, 95, 0.93, 10)
print("The total length of the spiral is", totalLen)

Exercise: spiralCount

Now, copy below your working version of spiralLength and modify it to be a function spiralCount that returns the number of lines in the spiral.

In [3]:
def spiralSideCount(sideLen, angle, scaleFactor, minLen):
    """Draws a spiral based on the given 
    parameters and leaves the turtle's location
    and orientation invariant. Also returns 
    the number lines in the spiral.
    """
    if sideLen < minLen:
        return 0
    else:
        fd(sideLen); lt(angle)
        subCount = spiralSideCount(sideLen*scaleFactor, 
                     angle, scaleFactor, minLen)
        rt(angle); bk(sideLen)
        return 1 + subCount # Your code here
In [4]:
resetTurtle()
totalCount = spiralSideCount(100, 90, 0.5, 5)
print("The number of lines in the spiral is", totalCount)
The number of lines in the spiral is 5

Returning Tuples: spiralTuple

Now, copy below your working version of spiralLength and modify it to be a function spiralTuple that returns a pair (i.e., a 2-tuple) of:

  1. the length of lines in the spiral and
  2. the number of lines in the spiral.
In [1]:
from turtle import *
def spiralTuple(sideLen, angle, scaleFactor, minLen):
    """Draws a spiral based on the given parameters and leaves the turtle's 
    location and orientation invariant. Also returns a pair (2-tuple) of 
    (1) the length of lines in the spiral
    (2) the number of lines in the spiral.
    """
    if sideLen < minLen:
        return (0, 0)
    else:
        fd(sideLen); lt(angle)
        subCount, subLen = spiralTuple(sideLen*scaleFactor, 
                     angle, scaleFactor, minLen)
        rt(angle); bk(sideLen)
        return 1 + subCount, sideLen + subLen # Your code here
In [5]:
resetTurtle()
spiralTuple(512, 90, 0.5, 5)
Out[5]:
(7, 1016.0)