CS 111 f21 — Recursion

1 Practice

For a string s, use a dictionary to count how many of each letter appear in the string

  • buggy version:
s = "how now brown cow"
char_counts = {}
for c in s:
    char_counts[c] += 1
print(char_counts)
  • fixed version:
s = "how now brown cow"
char_counts = {}
for c in s:
    if c in char_counts:
        char_counts[c] += 1
    else:
        char_counts[c] = 1
print(char_counts)

Reading in a file and counting word frequency:

fp = open("dickens.txt")
text = fp.read()
for c in "!\"#$%&()*+,-./:;<=>?@[\\]^_'{|}~":
    text = text.replace(c, " ")
words = text.split()
word_count = {}
for word in words:
    if word in word_count:
        word_count[word] += 1
    else:
        word_count[word] = 1
for word, count in word_count.items():
    print(word, count)

2 Recursion

If you are standing in line, and you want to know how many people are ahead of you, you have two options:

  1. Get out of line and walk along counting the people ahead of you (or have a friend do it). In Python we might represent this approach using a loop—it is an iterative solution.
  2. Ask the person ahead of you how many people are in front of them, and then add 1 to that number. But how would that person know? There are three possibilities:
    • They are at the front of the line, so there are 0 people ahead of them
    • They use approach (1)
    • They use approach (2). In this case they are using the same procedure as we are: do a little bit of work (add 1) and ask someone else to do the rest. In Python, we could represent this with a function that calls itself—a technique called recursion (or a recursive solution).

What's a Python-like task similar to finding the number of people in line? Finding the length of a list! Pretend the len function does not exist; how could we write a function list_length that takes a list and returns its length?

def list_length(v):
  # make a little progress by counting the first element in the list
  length = 1
  # ask someone else to count the rest---in this case, another call to list_length
  # the slice v[1:] goes from the second element to the end
  length = length + list_length(v[1:])
  return length

The above solution has a problem: it never stops making recursive calls. Eventually Python gets sick of this and stops the program with

RecursionError: maximum recursion depth exceeded

Going back to our counting people in line example, we observed that if the person we ask is at the front of the line, they just know there are 0 people ahead of them. In recursive functions this is called a base case. It is the point where we know the result and don't need to make a recursive call. In this case of list_length, this is when the list is empty—we know the length of an empty list is 0.

def list_length(v):
  # base case: the length of an empty list is 0
  if v == []:
    return 0

  # recursive case:
  # make a little progress by counting the first element in the list
  length = 1
  # ask someone else to count the rest---in this case, another call to list_length
  # the slice v[1:] goes from the second element to the end
  # if v has only one element, v[1:] will return an empty list
  length = length + list_length(v[1:])
  return length
def list_length(v):
    print("called list_length(" + str(v) + ")")
    if v == []:  # base case
        print("returning 0 from base case")
        return 0
    length = 1 + list_length(v[1:])
    print("returning", length, "from list_length(" + str(v) + ")")
    return length


x = [2, 4, 6]
list_length(x)

2.1 Recursively Summing a List

def rec_sum(nums):
    if len(nums) == 0:
        return 0
    sum_of_the_rest = rec_sum(nums[1:])
    return nums[0] + sum_of_the_rest

2.2 Practice: Recursively Reverse a String

  • rev_str("cat") should return "tac"
  • identify a base case (empty string) and how you will divide the work between the current call and the recursive call
def rev_str(s):
    if len(s) == 0:
        return ""
    rest_reversed = rev_str(s[:-1])
    return s[-1] + rest_reversed

2.3 Recursive class

  • a recursive data structure because each instance contains a reference to another instance
class PersonInLine:
    def __init__(self, name):
        self.name = name
        self.next_person = None
    def __repr__(self):
        if self.next_node != None:
            return self.name + " -> " + repr(self.next_person)
        return self.name

def make_line(names):
    start = PersonInLine(names[0])
    current = start
    for name in names[1:]:
        current.next_person = PersonInLine(name)
        current = current.next_person
    return start

h = make_linked_list(["Listo", "Mr. Dictionary", "Queen Tuple", "The String"])
print(h)