Fruitful Graphical Recursion and Introduction to Classes

Today we will look at a few more examples of graphical recursion. Recall that fruitful functions contain explicit return values. In fruitful graphical recursion, we recursively calculate values as we recursively draw our pictures.

After wrapping up recursion, we will move on to discussing how to define our own classes and objects in Python.

from turtle import *

Fruitful Nested Circles

Last time we drew a pretty picture involving nested circles. Suppose we wanted to calculate the number of circles drawn of each color. We can accomplish this by making our nestedCircles function fruitful.

def drawDisc(radius, color):
    # put the pen down
    down()
    
    # set the color
    fillcolor(color)
    
    # draw the circle
    begin_fill()
    circle(radius)
    end_fill()
    
    # put the pen up
    up()


def nestedCircles(radius, minRadius, colorOut, colorAlt):
    if radius < minRadius:
        # since we do not draw any circles, we return (0,0)
        return (0,0)
    else:
        # contribute to the solution 
        # note that we are drawing one circle with color colorOut
        drawDisc(radius, colorOut) 
        
        # save half of radius
        halfRadius = radius/2  
        
        # position the turtle at the right place
        lt(90); fd(halfRadius); rt(90); fd(halfRadius)

        # draw right subcircle(s) recursively
        # we need to save the resulting tuple
        # order of resulting tuple values corresponds to order of arguments
        rightColorAlt, rightColorOut = nestedCircles(halfRadius, minRadius, colorAlt, colorOut)

        # position turtle for left subcircle
        bk(radius)

        # draw left subcircle recursively
        leftColorAlt, leftColorOut = nestedCircles(halfRadius, minRadius, colorAlt, colorOut)

        # bring turtle back to start position
        fd(halfRadius);  lt(90);  bk(halfRadius);  rt(90)
        
        # finally, let's return the total sum of the circles
        # values should be ordered colorOut, colorAlt 
        return (rightColorOut + leftColorOut + 1, rightColorAlt + leftColorAlt)

setup(1200, 1200)
reset()
speed(0)
up()
goto(0, -300)
down()
print(nestedCircles(300, 30, "gold", "purple"))
hideturtle()
(5, 10)

Recursive Trees

Before moving on, let’s look at one more recursive example that doesn’t involve circles. Suppose we want to draw recursive tres.

# trunkLen is the trunk length of the main (vertical) trunk
# angle is the branching angle, or the angle between a trunk and its right/left branch
# shrinkFactor specifies how much smaller each subsequent branch is in length
# minLength is the minimum branch length in our tree

def tree(trunkLen, angle, shrinkFactor, minLength):
    if trunkLen < minLength:
        pass # do nothing
    
    else:
        # Draw the trunk
        fd(trunkLen)
        
        # Turn and draw the right subtree.                                                                                                                                               
        rt(angle)
        newTrunkLen = trunkLen*shrinkFactor
        tree(newTrunkLen, angle, shrinkFactor, minLength)

        # Turn and draw the left subtree.                                                                                                                                                
        lt(angle * 2)
        tree(newTrunkLen, angle, shrinkFactor, minLength)
        
        # Return to starting position                                                                                                                                 
        up(); rt(angle); bk(trunkLen); down()

        
# test the tree function
setup(1200, 1200)
reset()
speed(0)
up()
goto(0, -300)
down()
pensize(4)
# start turtle facing north
lt(90)
tree(100, 45, 0.5, 5)
hideturtle()

Exercise: Can you make this function fruitful and return the sum of the lengths of all of the branches drawn?

Classes and Methods

Python supports many different kinds of data:

1234 (int), 3.14 (float), “Hello” (str) [1, 5, 7, 11, 13] (list), {“CA”: “California”, “MA”: “Massachusetts”} (dict)

Each of these is an object, and every object has:

  • a type

  • an internal data representation (primitive or composite)

  • a set of methods for interaction with the object

Classes allow us to define our own objects and data types in Python. After defining a class, we can create an instance of a class and interact with that instance using methods.

All methods belong to a specific class, and are defined within that class. A method’s purpose is to provide a way to access/manipulate objects (or specific instances of the class).

The first parameter in the method definition is the reference to the calling instance (self). When invoking methods, this reference is provided implicitly.

Simple Class and Method

Let’s define a simple class with a single method that prints “Hello” and see how we can call the method on an instance of the class.

class A:
    """Class to test the use of methods"""
    def greeting(self):
        print("Hello")
a = A()
a.greeting()
Hello

Digging Deeper: Self

Lets try to understand what the purpose of the parameter self by using the python function id().

Recall that id(obj) displays the unique identifier to the object. You can think of this number as the address in memory this object is stored.

Let us rewrite class A to print the id of self.

class A:
    """Class to test the use of methods"""
    def greeting(self):
        print("Hello, object with ID", id(self))
obj = A()
obj.greeting()
Hello, object with ID 5119842528
id(obj)
5119842528