Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download

Jupyter notebook Lesson1.ipynb

297 views
Kernel: Python 3

Lesson 1: Recursively defined structures

Overview

Summary: This lesson introduces a unifying theme in the course: the concept of recursion. Recursion refers to the process of computing something in terms of smaller or prior versions of itself. It is a computational technique, and a way of defining certain kinds of structures, that is common and powerful in computer science -- but also mysterious and not always well understood. In this lesson we will encounter recursion by looking at mathematical and computer science objects that are defined using recursive definitions.

This lesson addresses the following learning target(s):

  • RI.1: I can construct several instances of a recursively defined structure, such as integer sequences, graphs, and trees.


Background

The factorial function is very commonly used in discrete mathematics. We can define the factorial function directly as follows:

Definition: Given a positive integer nn, the factorial of nn, denoted n!n!, is defined to be the number n×(n1)×(n2)××3×2×1n \times (n-1) \times (n-2) \times \cdots \times 3 \times 2 \times 1. Additionally, we define 0!0! to be equal to 11.

So for instance, 4!4! is 4×3×2×14 \times 3 \times 2 \times 1, which equals 2424. We use the factorial a lot in counting arguments, because n!n! counts the number of ways to take a collection of nn different objects and put them in order. For instance, look at the letters A, B, C, and D and try to arrange these in different order, like ABCD, ABDC, ACBD, and so on -- eventually you should come up with 24 different arrangements.

Did you notice that the factorial function has an interesting structure to it? In particular, the factorial function is computed in terms of smaller versions of itself. Here is an alternative definition of the factorial function that uses this structure:

Definition: Define 0!0! to be equal to 11. Then, for any positive integer nn, the factorial of nn, denoted n!n!, is given by n!=n×(n1)!n! = n \times (n-1)!.

This seems strange -- it seems like there is really no definition happening here because we are defining n!n! in terms of another factorial, namely (n1)!(n-1)!. Does this definition really work? Let's try to compute 4!4! again using this new definition:

  • Step 1: 4!4! is defined to be 4×3!4 \times 3!. So... what is 3!3! equal to?

  • Step 2: 3!3! is defined to be 3×2!3 \times 2!. So... what is 2!2! equal to?

  • Step 3: 2!2! is defined to be 2×1!2 \times 1!. So... what is 1!1! equal to?

  • Step 4: 1!1! is defined to be 1×0!1 \times 0!. By definition, we know that 0!=10! = 1. Therefore 1!=1×0!=1×1=11! = 1 \times 0! = 1 \times 1 = 1.

  • Step 5: Now we have that 1!=11! = 1. Therefore 2!=2×1!=2×12! = 2 \times 1! = 2 \times 1. Therefore 3!=3×2!=3×(2×1)3! = 3 \times 2! = 3 \times (2 \times 1). Therefore 4!=4×3!=4×(3×(2×1))4! = 4 \times 3! = 4 \times (3 \times (2 \times 1)) which equals 2424.

What just happened? We computed 4!4! by calling 3!3!, that is by using a smaller, simpler factorial. Then we had to find 3!3! by calling 2!2!, again a smaller and simpler factorial. Then we found 2!2! by calling 1!1! and 1!1! by calling 0!0!. Then, finally, we have a definite numerical value for 0!0! -- namely, it's equal to 11 -- and so we were able to back-substitute everything and eventually calculate all of the factorials we used.

This second definition of factorial that computes a factorial in terms of smaller factorials is called a recursive definition, and the computation that we did uses recursion.

A recursively defined structure -- like the factorial of an integer -- is a lot like a set of Russian dolls, like the ones shown in the video:

from IPython.display import YouTubeVideo YouTubeVideo("aNQV45Wichw")

(Direct link: https://www.youtube.com/watch?v=aNQV45Wichw)

It looks like one doll at first, but then you open it up to find a smaller doll inside, which has another smaller doll inside, which has another smaller doll inside, and so on -- until you end up with one, tiny doll that cannot be reduced down any further. Then, at that point you have the whole family of dolls.

Can we compute the factorial of a negative integer, like (3)!(-3)!? Let's try using the recursive definition and see what happens:

  • The definition would say that (3)!(-3)! is equal to (3)×(4)!(-3) \times (-4)! because the new factorial is one less than the previous one.

  • Then we would recursively call the definition on (4)!(-4)!, which would give us (3)!=(3)×((4)×(5)!(-3)! = (-3) \times ((-4) \times (-5)!.

  • Then we would recursively call the definition on (5)!(-5)!, which would give us (3)!=(3)×((4)×((5)×(6)!))(-3)! = (-3) \times \left((-4) \times ((-5) \times (-6)!)\right).

We'll stop at this point, because do you see what's happening? We're stuck in an infinite process. When we were computing 4!4!, the process ended because we subtracted 1 repeatedly and eventually hit 0, whose factorial value we had defined to equal 11. But here, we can keep on subtracting 1 but we will never "hit bottom". The process just continues forever.

This means that there are two essential ingredients to a correctly-formed recursive definition of a structure:

  1. There must be a rule for how to construct the structure from previous, simpler versions of the structure. And,

  2. There must be a base case that allows us to make a computation without recursion.

In our definition of the factorial, the rule was that n!=n×(n1)!n! = n \times (n-1)! when nn is a positive integer; and the base case is the declaration that 0!=10! = 1.

Another kind of structure that can be recursively defined are integer sequences. Here is a famous example:

Definition: The Fibonacci sequence is the sequence of integers given by 1,1,2,3,5,8,13,21,34,55,89,144,1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, \dots The first number in the sequence is denoted F1F_1, the second F2F_2, and so on. The Fibonacci sequence is defined recursively as follows:

  • (Base case) We define F1=1F_1 = 1 and F2=1F_2 = 1.

  • (Recursive rule) For all n>2n > 2, define Fn=Fn1+Fn2F_n = F_{n-1} + F_{n-2}. (That is, each Fibonacci number starting with the third one is the sum of the previous two Fibonacci numbers.)

So for example, what is F6F_6? We can see from the sequence that it should be 88, but if that list of integers weren't there and all we had was the definition, how would we know? According to the definition, F6F_6 equals F5+F4F_5 + F_4. Now we'll "unpack" each of F5F_5 and F4F_4 using the definition. Every set of nested parentheses below is an instance of this "unpacking". Remember we don't have to "unpack" F2F_2 or F1F_1 because those are defined to be equal to 11.

F6=F5+F4=(F4+F3)+(F3+F2)=((F3+F2)+(F2+F1))+((F2+F1)+F2)=(((F2+F1)+F2)+(F2+F1))+((F2+F1)+F2)\begin{align*} F_6 &= F_5 + F_4 \\ &= (F_4 + F_3) + (F_3 + F_2) \\ &= ((F_3 + F_2) + (F_2 + F_1)) + ((F_2 + F_1) + F_2) \\ &= (((F_2 + F_1 )+ F_2) + (F_2 + F_1)) + ((F_2 + F_1) + F_2) \end{align*}

Since each of these numbers on the right now equals 1 by definition, we can plug in 11 for each of them and see that F6=8F_6 = 8.

Here's one more example to illustrate the idea that recursively defined structures are not always numbers or sequences of numbers. This has to do with strings. A palindrome is a word that is spelled the same forwards as it is backwards, like RACECAR or NOON or KAYAK. We can extend this idea to say that any string of characters, so for example the string XYQQZQQYX is a palindrome. What's a way to formally define a palindrome? Here is a recursive definition:

Definition: Let λ\lambda be a string of characters from the English alphabet. The empty string is defined to be a palindrome. A single character is also defined to be a palindrome, and a two-letter string formed from the same letter (for example RR) is a palindrome. Finally, if α\alpha is any single character and λ\lambda is a palindrome, then the string α+λ+α\alpha + \lambda + \alpha is also a palindrome where + stands for concatenation of strings (as in Python).

This is a recursive definition because it gives appropriate base cases (the empty strings and single letters are defined to be palindromes, which makes sense) and then a way of generating a new palindrome out of smaller ones.

Let's play with that definition to get a few examples.

  • According to the base case, we can pick any letter we want and it's a palindrome, so K is a palindrome.

  • Since K is a palindrome, according to the recursive rule I can choose any other character I wish and sandwich K in between them, and I will have another palindrome, so for example EKE is a palindrome.

  • Since EKE is a palindrome, so is REKER for example. And so would be PREKER, ZPREKERZ, and so on.

  • Another basic example would be NN. So another palindrome could be ANNA, and therefore BANNAB, and so on.

This recursive definition allows you to construct something, namely a palindromic string. This might be useful if, for example, we wanted to write a computer program that accepts a string as input and determines whether it is a palindrome.


Other resources for learning

NOTE: The resources below are supplemental, not required. They are here for you to get alternate presentations of concepts and additional examples. Use as many or as few of these as you wish.

from IPython.display import YouTubeVideo YouTubeVideo("txdmCgThR6o")

(Direct link: https://www.youtube.com/watch?v=txdmCgThR6o)

Preview Activities

NOTE: Preview Activities are to be done in your own notes, for your reference later. However you will be asked to submit your work using a Google Form using the link given below in the "Submission Instructions" area. It is highly recommended that you do your work on paper, then type out your answers into a text editor or note-keeping software (like Evernote, OneNote, etc.), then copy/paste the answers into the Google Form. You will not have access to your answers once you submit them (hence the idea of keeping them in a text editor or note-keeping tool). We will discuss the answers briefly in class (see dates below).

Also, recall that Preview Activities are graded either Pass or No Pass. The criteria for earning a Pass grade are:

  • All questions must have a complete answer. Submissions in which parts of questions are unaddressed or left blank will receive No Pass.

  • All answers must represent a good faith effort to be right. This means that you have done your best to give a correct response. Responses that consist of questions or expressions of confusion (such as "I didn't understand what the question was asking") will receive No Pass. If you have a question, go to the discussion board and ask your question or come to office hours.

  • Answers must be submitted prior to one hour before class time (see below); late submissions will receive No Pass.

  • Please note that mathematical correctness is not a grading criterion on Preview Activities.

Activities

  1. The Gibonacci sequence is an integer sequence defined recursively by: G(0)=5G(0) = 5, G(1)=6G(1) = 6, G(2)=7G(2) = 7, and G(k)=G(k1)G(k2)+G(k3)G(k) = G(k-1) - G(k-2) + G(k-3) for k3k \geq 3. Write out the first 15 members of the Gibonacci sequence. (You are already given the first three.)

  2. Here is a set that is recursively defined. Call the set SS. The set SS consists of strings, as follows: We will define the letter a to belong to the set SS and the letter b to belong to SS. Additionally, for any element xx of SS, the string axax and the string bxbx also belong to SS. At the response form, there is a multiple-selection question that lists several strings; check all the strings that belong to SS. (Note that xx is a string that is an element of SS; it does not mean literally the letter x.)

  3. The final "activity" for you to do is to write out any specific mathematical questions you have about the material in this lesson. There's a large text box for you to do so.

Submission instructions

Please submit your work using the Google Form located here: http://bit.ly/1MJqPNB

Again, you are strongly encouraged to do your work on paper, type up your answers and save them in a text editor or note-keeping tool, and then copy/paste the answers into the Google Form because you will not be able to access the answers once they are submitted.

You are to submit your answers no later than one hour before your section's class meeting on Friday, January 15. Late submissions are automatically marked No Pass. We will take the first few minutes of class on that date to debrief the answers and address any major, widespread misconceptions.


Daily Homework

About Daily Homework: Daily Homework consists of problems to work that will have you dig deeper into the concepts of this lesson and serve as raw material for class discussion. You are expected to write out complete solutions on paper (electronic notes are OK too) and have these ready to be checked during class. During class, you will work with other students to work through and correct your solutions and also to present your work at the board. We will also use class time to build on your Daily Homework to explore more advanced topics, so giving a good-faith effort to complete all your Daily Homework is essential to understand the rest of the class meeting.

Daily Homework is graded Pass or No Pass on the basis of whether the solution that you bring to class is complete and shows a good-faith effort to be right on each problem. Correctness is not a criterion; you'll work to correct your solutions in small groups.

Here are the problems for Daily Homework for Lesson 1, which will be discussed in class on Wednesday January 20:

  1. Generate the first 10 terms of each of the following sequences.

    • an=an12a_n = a^2_{n-1}, where a1=2a_1 = 2

    • an=nan1+n2an2a_n = n a_{n-1} + n^2 a_{n-2}, where a0=1a_0 = 1 and a1=1a_1 = 1

  2. The binomial coefficient C(n,k)C(n,k) (where n,kNn,k \in \mathbb{N}) is an expression that counts the number of ways to select kk objects from a group of nn objects without considering the order in which they are chosen. It can be defined in two ways. A direct formula for the binomial coefficient is C(n,k)=n!k!(nk)!C(n,k) = \frac{n!}{k! (n-k)!} For example, C(5,3)=5!3!(53)!=5!3!2!=10C(5,3) = \frac{5!}{3! (5-3)!} = \frac{5!}{3! 2!}= 10 and counts the number of ways to select a group of three people out of a group of five without considering order. However, we can also define the binomal coefficient recursively as follows. First of all, for all define for all nNn \in \mathbb{N}: C(n,0)=1andC(n,n)=1C(n,0) = 1 \qquad \text{and} \qquad C(n,n) = 1 And for any kNk \in \mathbb{N}, define: C(n,k)=C(n1,k1)+C(n1,k)C(n,k) = C(n-1, k-1) + C(n-1,k) Trace through the steps of this recursive algorithm to show that C(5,3)=10C(5,3) = 10 as explained above. In the class meeting, your group may be called upon to apply this recursive definition to a different pair of numbers so we can have two examples on the board.

  3. Define the function maxList to accept a nonempty list of integers as its input, and the output is the largest element in the list. For example, maxList([-3,2,0,5]) = 5. Below is a possible recursive definition for this function. Before we give that definition, there are three helper functions we will define:

    • max accepts two integers (not in a list) and returns the larger of the two. For example, max(-5, 2) = 2.

    • first accepts a list and returns the first item in the list. For example, first([a,b,c,d]) = a.

    • tail accepts a list and returns the list without its first element. For example, tail([a,b,c,d]) = [b,c,d].

    Now here is the recursive definition:

    • Define maxList([x]) = x whenever the input is a single-element list.

    • Otherwise define maxList(L) = max(first(L), maxList(tail(L))).

    Walk through the steps of the recursive definition to compute maxList([-4,5,7,1,-9,2, 3, 0]). (The answer is obviously 7, so the steps are what matter.) In the class meeting, your group may be called upon to apply this recursive definition to a different list so we can have two examples on the board.