 Hi everyone. I hope you all are doing well. In this video we're going to talk about a concept called Recursion. Recursion is very important in computer science. It's a way of solving problems that's a little bit different than the normal algorithmic approaches you have used so far. Some of you may have encountered Recursion in 131, but we'll talk about it a little more in this class because it's going to be a mechanism that we can use to solve some common problems with our data structures. As we get started, go to the module for this video and make sure that you have downloaded the Python file that's there because we're going to be typing in some code. Then also download the Recursion worksheet. I would encourage you to print out that worksheet. I think it's four pages. You can also edit it in a Word document would be fine as well. What is Recursion? Recursion is a method of solving problems that involves breaking a problem down into smaller and smaller pieces until you get to a small enough problem that you can solve easily. That's a lot of words. What does it mean? What does it look like? When you have Recursion in code, what you are talking about is writing a function or method that does something and that function calls itself inside itself. That is the essence of Recursion. We'll go in an example here in a minute. Why on earth would you want to do this? What is it used for? The main way that we have solved complex problems to date is via iteration. If you think that you have a collection of something and you want to do something to every item in that collection, you iterate over that collection and do something. Maybe you can search it, maybe you can find the max, maybe you can change the values of everything. Recursion can be used to solve similar problems. In fact, any problem that you can solve using iteration you can solve with Recursion. Think of Recursion as a fancy method that just calls itself until it solves the problem. Why Recursion? Sometimes some problems are easier to reduce into smaller ones to solve using a strategy called divide and conquer. Make the problem smaller and smaller and smaller. It's just easier to think about the solution that way. Not for everything, but for some problems. We will see some examples of those, particularly in the coming parts of the class. Some problems have a very elegant recursive solution, meaning you write methods that solve the problem by calling themselves on smaller and smaller pieces of the problem versus iterating over the whole list or what have you. It may not seem intuitive right now, but there are things that we will encounter for sorting data collections and also trees. When we get to trees at the end of the class, algorithms that operate on the tree data structure are much easier to write recursively than they are iteratively. Here's an example. We have already talked about binary search. We looked at a iterative binary search algorithm. Just to illustrate a little bit, we had this slide in a previous lecture. If you want a binary search, we've got Mario in his blocks, and whenever he jumps on a block, what he learns is the mushroom is either to the left or to the right of wherever he jumps. The iterative solution to binary search, which we went about before, is we start with the whole collection of blocks, and then in the first collection he jumps to the middle, and he learns the first iteration, he jumps to the middle, he learns the mushroom is to the right. In the second iteration, he jumps over here into the middle of this group, and here he learns it's to the left of that. Finally, in the third iteration, he jumps to this middle slot, and then he finds the mushroom. This was iterative. It's dealing with the whole list each time. If you remember your binary search algorithm, you had these two variables that were keeping track of where's the beginning of the list, the first item that we're going to look at, and where's the last item. We moved that first item and the last item as we narrowed down our search. This is a divide and conquer strategy. The recursive solution looks like this, but it's got a fundamentally different premise. We're going to start at the same place here. We tell Mario to do the same thing, jump to the middle in order to learn something about where the mushroom is. We're calling our recursive function, and here my recursive function is called search, and I search on the list, which is going to be this thing. It's my variable holding this thing. My first recursive call, I'm searching the list, which means jump here and find out is the mushroom to the right or to the left. Well, I learned that it is to the right. Now, the recursive step, the kind of big different part of this is rather than keeping the whole list, what we will do is we will call search again, but we will only search on this part of the list. So effectively what this means is that all the rest of the list is gone from the computer's perspective. The function, when it gets called, it only knows that this list from start to finish exists. So it only looks at this piece. Well, okay, so now it applies its exact same algorithm. The code is exactly the same because it's calling the same function. So the body of the function is the same. It jumps to the middle and it learns, hey, the list, the mushroom must be over here, kind of to the left of Mario. But it still hasn't found it. So it will once again call the search method, but it's only going to search this little piece. And so it does search this little piece, right? And the rest is gone from the perspective of the computer, and it finds the mushroom. At this point, we don't call search anymore because we found it, we're done. In the iterative version of binary search, this terminated our loop. But here, in a recursive call, we've reached what we call the base case. We've solved it, we've found the answer. True, yes, the mushroom is here. It is in the list of blocks, return true, okay? So we're done, we're stopped calling. And this is just an illustration of the difference between iteration and recursion, okay? So recursion and coding, and we're going to go into the code a minute, hopefully make this even more concrete, right? Recursion is when a function calls itself to solve smaller and smaller problems. We call this the recursive step. So we're calling itself on smaller and smaller pieces of this list until it reaches the base case. The base case is something that the function knows the answer to, right? If I hit the mushroom, then I found it return true, right? Then all the functions we've called ourselves, inside ourselves, the functions all return and their results are kind of combined to solve the problem. Now we'll see some examples where there may be more than one base case, but we'll get there in a few minutes, okay? So we're going to switch over to some code now. So if you haven't done it already, switch over to your PyCharm editor and open the examples for recursion example stubs, it's called. So let's look at a... We're going to look at a trivial example of a function that can be solved both... or a problem that can be solved both iteratively and recursively, and that's computing the factorial of a number, right? So how do you compute the factorial of something? So what do I mean by factorial? I mean compute and return n squared, n with the little exclamation mark, which is n factorial, right? So if you don't recall, what is 5 factorial? Well, 5 factorial is 5 times 4 times 3 times 2 times 1, right? This is the definition of factorial. So it's pretty easy to write an iterative method that solves this problem, okay? So my iterative function takes a value n, which is going to be my integer that I compute the factorial of, and then 4i in the range of 1 to n plus 1, total times equals 5, okay? So, you know, we can check this out. Let's compute the factorial of 5, and I've run my code and it's 120, right? 5 times 4 is 20 times 3. 20 times 3 is 60. 60 times 2 is 120. Times 1 is 120. So that's my answer, right? So this is a very simple iterative solution because I'm using a loop here to solving the factorial. And, you know, you can use it to solve whatever you want. 12 factorial is this largest number, right? 479 million, it looks like. Okay, so let's write a factorial function here that uses a recursive solution, okay? So a recursive solution has to do two things. One, it has to call itself on smaller parts of the problem. And two, it has to have a base case where it knows to stop, okay? So, the recursive approach to the factorial, right, is to think of, all right, well, what is 5 factorial? Well, it's 5 times, what? 4 times 3 times 2 times 1. But we could rewrite this part as 5 times 4 factorial. Well, what's 4 factorial? Well, 4 factorial is 4 times 3 factorial. And so on and so forth, right? Is 3 times 2 factorial, right? 2 factorial is 2 times 1 factorial. And 1 factorial is our base case. It's like the most trivial thing. What is 1 factorial? Well, 1 factorial is 1, right? So, this is a recursive approach to solving the factorial problem. What does it look like in code, okay? So, whenever we're going to code our recursive functions, usually what you want to do is think of them from the base case on back, okay? So, what is the base case here? When are we going to stop? Well, if our parameter is equal to 1, okay? That's our trivial base case where we know how to stop. Well, 1 factorial is 1, so let's return that, okay? But if n is not 1, greater than 1, hopefully, what do we want to return? Well, we want to return the value of n, whatever this was called with. So, say it was 5, it was called with 5, right? We want to return 5 times the factorial of n minus 1. Well, what's n minus 1? n minus 1 is 4, okay? So, what happens now is when this code is called, let's call it factorial of 5, right? The first time this code runs, the value of n is 5. If 5 equal equal 1, well, that's not true, so our else clause will trigger, let's return 5 times the factorial of n minus 1. Well, n minus 1, if n is 5, it's calling factorial of 4, okay? So, then this code executes factorial of 4, n equal 1, no. Return factorial of 4 times factorial of n minus 1, which will be 3 at this point, right? So, kind of what you wind up in is the situation where factorial keeps calling itself. This is the recursive step, right here. Factorial is, excuse me, calling itself, but every time it calls itself, it makes the parameter smaller, makes the problem smaller. But you don't get that return value, right? So, you're going to get a whole chain of these return n times factorial of n minus 1 times factorial of n minus 1. You kind of have a big, long chain of one another, them. But the actual return value, the final result, the final answer, is not computed until you hit this base case, and it returns 1, okay? But this solution should work. So, oh, I still got my guys up here, okay? Oh, I got to call it inside the block, okay? 5, factorial 5 is 120. Let's do the factorial of, what did I do before, 12? There it is. So, it's working, right? And you'll notice, right, these two are giving me the same answer. So, yeah, we've got both an iterative solution and a recursive solution to this problem, right? One is looping over a range of values and calculating a running product, and the other is recursively calling itself and also, in a way, calculating a running product, okay? So, this is kind of a, it's a strange notion, it's a strange concept. I'm going to switch over to the worksheet now and illustrate it on paper and hopefully it'll make a little more sense what's going on inside the computer. So, I'm in my worksheet now. I'm on, actually I'm on page two of the recursion worksheet. And I want to talk here about how to think about and design your recursive algorithms and also how to trace them and think about what's going on inside the machine while they are executing, okay? So, if you have to design an algorithm to solve a problem recursively, you need to remember there are two parts of a recursive algorithm. The first part is there's a base case, potentially more than one base case, where the algorithm stops because it knows the answer. And the second element is you have to have a recursive call to the function calling itself on a smaller piece of the problem, okay? So, what does that mean? Let's look at it in the context of our factorial function, right? What is the base case of the factorial function? What is the thing we know the answer to? Well, the base case of the factorial function is when we want the factorial of n when n is equal to one, right? One factorial is one. We just know that that is truth, right? It's a trivial base case, right? So, generally you want to think about your base cases first. So, our base case is going to be when n is equal to equal to one, what we want to do is return one, okay? So, I'm going to use this little arrow to indicate we should return something, right? Otherwise, else, you know, not the base case, what do we want to do? We want to return n, whatever the value of n is, right? If we call factorial of five, n is five, times, this is the recursive step. So, we've got to call ourself, call the factorial function itself, factorial of n minus one, right? And it's this n minus one step that is the recursive step that's going closer and closer, making the problem smaller and smaller. We're progressing toward the base case, right? If we keep doing n minus one, enough times, eventually, n will equal equal one, and that's our base case that we know how to solve, okay? So, how do we trace this, right? We made a huge emphasis earlier in the semester on being able to trace program execution. Well, what I call it when you trace a recursive call is what is a slice trace, right? And what we're doing in the slice trace is we're going to show the computed return value and any function parameters or local variables relevant to the computation. Okay, so let's just see an example. There's only one parameter, one variable in play here, and that is n. So, this keeps it relatively simple, right? So, we're going to execute our code, right? Print the factorial of five, right? So, the factorial of five. What is this going to print? I don't know yet. I'm calling factorial function, and I need to wait for it to return before I know what to print, okay? So, this is just going to stay blank for a second, but let's pretend that we've called this statement. All right, so the first time factorial is called. It's called where n has the value of what? Well, five, because that was the argument that I called factorial with, all right? Now, let's refer to our pseudocode. What do we do? Well, if n equal equal one, we return one, and surely is not equal to one. So, otherwise we are returning n times factorial of n minus one, all right? So, we're going to return five times what? Well, we don't know the answer yet. You know, more importantly, the computer doesn't know the answer yet. It just knows it's going to return five times something, but that something hasn't been computed yet, okay? So, what happens though? Well, the computer goes into another execution of the factorial function, right? These are nested calls. So, you can almost think of these as being written out like this. If I can get my pen to cooperate. You can almost think of these like this. Factorial of five is factorial five times factorial four, right? And factorial of four times factorial of three times. You know, you can kind of think of them like this, right? They're kind of nested within one another. It's ugly in the program execution because what you're doing is every time you call one of these methods, you're pushing it on top of the call stack. And if you remember very early in the semester, we talked briefly about the call stack, about how when you call a function, Python pushes onto the call stack where you just called it from, and then it goes and executes the function and returns back to where whatever's on top of the stack. This is happening a lot with recursive functions, right? And the number of factorial calls on the call stack. Well, that depends on how your recursive method is written, right? But anyway, let's continue tracing this, okay? So, this is calling, at this point, we were calling five times the factorial of n minus one. Well, what is the factorial of n minus one? So, we wind up calling factorial again, where n, this time, it's not five, it's four, okay? So, all right, factorial of four base case, n equal equal one. Nope, not yet. So, I'm going to return what? I'm going to return n, which is four in this case. I'm going to return four times factorial of n minus one. Well, I don't know what the answer to that is yet. I have to call my factorial function yet again, except this time, I'm calling it with n equal to three, okay? So, again, go to your pseudocode. What is factorial of three? Well, if n equal equal one, we return one, that's not the case else. You return three times factorial of n minus one, okay? Right? So, this thing's getting kind of nuts, though. All right? So, once again, we're calling factorial function. Meanwhile, all these guys, everybody up here, right, they're waiting for someone to finally hit rock bottom here, finally hit the base case and know the answer to it. They're all just kind of waiting and they're hanging in limbo, right? We're going to scroll down a little bit here since we should kind of see what's going on or know what's going on. So, factorial gets called with n equal two. Two is still not our base case, right? So, factorial of two returns two times factorial of n minus one, okay? Factorial, again, of n is one. Aha! Now, I've hit my base case. So, what do I return according to my base case? Well, when n equal equal one, I just return one, okay? So, finally, I have an answer. And this particular version of factorial returns, it exits, it's done. It kind of goes away, right? And now, this value of one goes up here, right? Aha! You have returned factorial of one. What is your result? The result is one. Aha! Now, I can return, says factorial of two, right? And so, he returns two times one. Two times one is, of course, two, right? And his job is done and he takes a nap, okay? All right, factorial of three, he says, Aha! Last you have returned to me. Aha! Your answer was two, so then my answer, my return value, is three times two, which is, of course, six, okay? And my job is done, right? What is factorial of four times six? Okay. Well, I need to return four times six, which is 24, okay? My job is done. I am done. Finally, the original factorial called factorial of five says, Ah, what do I do? I finally got an answer from factorial of four and his answer was 24. At last, I can now return five times 24. Do the arithmetic, five times 24 is 120. And finally, all the factorials are done. My print function prints 120 like we expect, okay? So you should have in your mind that when you're calling recursive functions, you are just kind of digging these holes deeper and deeper and deeper until you reach the base case at the bottom where you know the answer. And then you kind of percolate your results back up to the top and you have the answer to the question, okay? So in the next video, we'll just work through two more examples. We'll work through another trivial example of determining if a word is a palindrome. And then we will apply this to a more practical example, which is binary search, right? See you then.