Comp 112

Lecture 10

Recursive Functions

2017.11.07

Recursive Functions

Example: Countdown

Many programming problems can be solved by using either iteration or recursion:

def countdown_iter (n) :
    while n > 0 :
        print (str (n))
        n -= 1
    print ('go!')

$n \quad ⟼ \quad \overbrace{n , \underbrace{(n−1) , (n−2) , ⋯ , 1 , \mathrm{go!}} _ {\mathrm{countdown} ~ (n−1)} } ^ {\mathrm{countdown} ~ n}$

def countdown_rec (n) :
    if n == 0 :             # how to count down from zero:
        print ('go!')
    elif n > 0 :            # how to count down from a positive number,
        print (str (n))
        countdown_rec (n - 1)   # assuming we know how to count down from its predecessor

Iteration vs Recursion

Recursion lets us “think locally and act globally”.

Example: Factorial

"""
signature: int -> int
precondition: n >= 0
returns the factorial of the argument
"""
def factorial_iter (n) :                     # from lecture 4
    acc = 1
    while n > 0 :
        acc *= n
        n -= 1
    return acc

$n \quad ⟼ \quad \overbrace{n × \underbrace{(n−1) × (n−2) × ⋯ × 1} _ {\mathrm{factorial} ~ (n−1)} } ^ {\mathrm{factorial} ~ (n)}$

def factorial_rec (n) :
    if n == 0 :                                     # compute the factorial of zero:
        return 1
    elif n > 0 :                                    # compute the factorial of a positive number, 
        factorial_of_pred = factorial_rec (n - 1)   # assuming we know the factorial of its predecessor:
        return n * factorial_of_pred

Heads and Tails

A common strategy for writing recursive functions for lists:

This strategy also works for strings.

Example: Recursive List Summing

"""
signature: list (int) -> int
sums the numbers in a list
"""
def sum_iter (ns) :
    acc = 0
    for n in ns :
        acc += n
    return acc

$[n_0 , n_1 , n_2 , ⋯ , n_k] \quad ⟼ \quad \overbrace{n_0 + \underbrace{n_1 + n_2 + ⋯ + n_k} _ {\mathrm{sum} ~ (ns [1:])} } ^ {\mathrm{sum} ~ (ns)}$

def sum_rec (ns) :
    if ns == [] :                       # sum of an empty list of numbers:
        return 0
    else :                              # sum a non-empty list of numbers,
        tail_sum = sum_rec (ns [1 : ])  # assuming we know how to sum its tail:
        return ns [0] + tail_sum

Example: Recursive List Reversal

"""
signature: any a . list (a) -> list (a)
reverses a list of any type
"""
def reverse_iter (xs) :                       # from lecture 6
    acc = []
    for x in xs :
        acc = [x] + acc
    return acc

$[x_0 , x_1 , x_2 , ⋯ , x_n] \quad ⟼ \quad [x_n, ⋯ , x_2 , x_1 , x_0] ~ = ~ \overbrace{\underbrace{[x_n, ⋯ , x_2 , x_1]} _ {\mathrm{reverse} ~ (xs [1:])} + [x_0] } ^ {\mathrm{reverse} ~ (xs)}$

def reverse_rec (xs) :
    if xs == [] :                                   # reverse an empty list:
        return []
    else :                                          # reverse a non-empty list,
        reversed_tail = reverse_rec (xs [1 : ])     # assuming we know how to reverse its tail:
        return reversed_tail + [xs [0]]

Recursive Specifications

A palindrome is a word that reads the same forward as backward.

We can characterize the property of being a palindrome with the following specification:

This specification is recursive because in the second case we refer to the same property that we are describing.

It’s easy to turn this recursive specification into a recursive predicate function.

def is_palindrome (text) :
    if text == '' :
        return True
    else :
        return text [0] == text [-1] and is_palindrome (text [1 : -1])

Self-Similarity

A self-similar shape is one whose description is recursive, i.e. it contains a reference to itself.

Very simple recursive descriptions can produce interesting self-similar shapes

def tree (size , depth) :
    # signature:  int , int -> NoneType
    # precondition:  size >= 0 and depth >= 0
    if depth == 0 :
        pass
    elif depth > 0 :
        turtle.forward (size)
        turtle.left (30.0)
        tree (4/5 * size , depth - 1)
        turtle.right (60.0)
        tree (4/5 * size , depth - 1)
        turtle.left (30.0)
        turtle.back (size)

Here’s a helper-function for speeding up deeply-nested recursive drawings:

def faster (drawer , *args) :
    delay = turtle.delay ()
    frames = turtle.tracer()
    turtle.tracer (300 , 100)
    drawer (*args)
    turtle.update ()
    turtle.tracer (frames , delay)

e.g.

faster (tree , 100 , 12)  # a faster version of: tree (100 , 12)

Fractal Triangles

Starting with an equilateral triangle

we can draw a scaled-down version of it in each of its three corners

and scaled down version of those in each of their corners

and so on and so on…

The result is called a Sierpinski triangle:

def sierpinski_triangle (size , depth) :
    # signature:  int , int -> NoneType
    # precondition:  size >= 0 and depth >= 0
    if depth == 0 :
        pass
    elif depth > 0 :
        for i in range (3) :                            # for each of the three sides of a triangle:
            sierpinski_triangle (size / 2 , depth - 1)  # draw a scaled-down fractal triangle
            turtle.forward (size)                       # draw an edge of the current triangle
            turtle.left (120.0)                         # turn to create a corner of the current triangle

Once we have figured out its recursive structure, we can optimize it by pruning the empty branch:

def sierpinski_triangle (size , depth) :
    if depth > 0 :
        for i in range (3) :
            sierpinski_triangle (size / 2 , depth - 1)
            turtle.forward (size)
            turtle.left (120.0)

To Do This Week: