 Welcome back, in the previous segment we discussed recursive objects, in particular we discussed trees and how to draw them and for this we used a recursive function. In this segment we want to say something about how to think about recursion and after that we will conclude this segment of sequence of segments. How do you think about recursion? On the face of it recursive programs, program seems very complex, where it executes, typically multiple calls are in progress and how do we reason about all of them, how do we even visualize what is going on. So the good news is that we do not need to do any such visualization, there is a much more direct simple way of thinking about recursive algorithms. So let me begin with some high level ideas and let me first consider the case when you are trying to design the recursive algorithm. Now you may think that designing recursive algorithms is tricky and indeed Euclid's algorithm is pretty tricky. However, if you are dealing with recursive objects then recursive algorithms are quite natural and you may well find yourself designing such algorithms. So how do you do this? Well you first attempt to solve the problem given to you by constructing and solving a smaller problem of the same type or what you might more accurately call a smaller instance of the same type. And I want to observe that this is exactly what we did for GCD as well as for drawing. So when we wanted to solve, when we wanted to find the GCD of m and n, we instead requested to find the GCD of m and m mod n. And why did we do this? Because these numbers are in fact smaller than the given numbers. So in some sense we are reducing or we are simplifying the problem or we are reducing its size. So when I say size, I mean it in a metaphorical sense. So we are reducing its size in some sense. Even in drawing, when we were asked to draw a tree which had L levels, we reduced it and we said let us first draw trees which have just L minus what levels. So again this is a simpler problem in the sense this is a smaller tree that we are going to draw. It has fewer number of levels. So if you can do this, so if you can say that I want to solve this big problem but I want I can break it up into problems which are of the same type but smaller in some sense, then you have already taken the first solid step towards designing the recursive algorithm. And then you should be able to solve the simplest instances directly. So that is the second step. So for example in the case of GCD, this was the simple instance. If M was divisible by M, then you can directly say that M is the GCD or in the case of drawing if you wanted to draw a tree with 0 levels, then you can directly do it. Well actually in this case there is nothing to be done but that is trivially doing it. Now I want to say something about how do you think of a recursive algorithm that is given to you by somebody? How do you understand it? So the first step is sort of the analog of this step over here. So can you sort of interpret or is the designer telling you that look, this is the problem size that is reducing in successive calls. So that is the first question and let me make this discussion more precise. So for the GCD you can argue that this second argument is always reducing as you execute the program. It does not have to be the second argument, it could be some complicated function of the arguments in general but in this case it happens to be the second argument. And for drawing it is in fact the very first argument. So here it was L then we said that this tree could be drawn by drawing several trees with L minus 1 levels. So in fact here you can think of the second argument and the first argument as the problem size. So if I give you a recursive algorithm then you should look for a problem size in it. Something that is reducing as you do the recursion. And then the question that you should ask is will we eventually get to problem sizes to problems that are solved without recursion. So now I want to look at these things in a little bit more detail and so for that I will need some terminology. So I am going to define something called the top level recursive call. So this is the call made from the main program or the first call made to the recursive function. Again taking examples for the case of GCD the top level call is the GCD of MM or this is in fact you can think of this as the call as identified by the signature of the function. And for the drawing it is this call. So this would be the call that would be in the signature of the function. Then there are the level 1 recursive calls. So these are the calls which are made directly while executing the top level call. So for GCD the next call was GCD of N and M mod N. So this is the level 1 recursive call for this top level call. And for drawing this tree the recursive call was this and also this. So these are the two level 1 recursive calls. So this is just notation. I could define level 2 recursive calls in a similar manner but I do not need to. So the whole point of this discussion is that we can just think about one level of recursion to understand what is going on. Then there is a notion of a base case. So these are simply the input values for which the top level call returns without recursing. So in the case of GCD if M and N are such that M is divided perfectly by M then that is a base case because in this case you can return N directly as your GCD. So MN such that M mod N is 0 are the base cases for the GCD function. Then for drawing similarly if L is 0 then you are not going to recurse. You are just going to return in this case without doing anything but you are going to return. So L equal to 0 is the base case. L equal to 0 and other arguments whatever they might be. So more terminology. So we have defined this earlier actually but I am just going to restate it. So for every problem or every call really there is a notion of a precondition. So this simply says what are valid values for the inputs. So in the case of GCD the two arguments have to be 0 only then this is a reasonable call. Only then is the GCD defined. And similarly for drawing the number of levels must be greater than or equal to 0 and the height and the width have to be greater than 0, greater than or equal to 0. And Rx and Ry the coordinate where the position where the root is to be placed well they could be negative. So that might just force the drawing of the screen or if you want it inside the screen then you should say I want the tree to be inside the screen. So I will require these also to be positive and in fact maybe smaller than the size that the, so this should be smaller than the width of the screen, this should be smaller than the height of the screen. But we are just ignoring those conditions for now. Then there is the problem size and we gave an example of this but let me just say a little bit more. So this is something which is indicative of the amount of work needed to find the solution. Or this is something which suggests how difficult or how complex this problem is. So we said that the number of levels is indicative of how difficult the problem is, the more the levels, the more work needed and similarly for GCD the values of these numbers, the magnitudes of these numbers are indicative of the problem size but more specifically we looked at the second argument, this m. So this we said was the problem, it could be considered the problem size and this should be chosen creatively and as I said this is how you might choose. So there is no obvious way of what it should be but as a designer you would have to say that look I want to reduce this because if I reduce this then I can recurse. So now I want to say what you need to do to verify that a given recursive function is a reasonable function. So the first thing that you have to check is whether there are any base cases. So if there are no base cases then clearly your program will not terminate and therefore base cases must be there and then for those base cases the function must produce the correct results. So that is one check that you have to make. So for the GCD the base cases were values of mn such that m mod n equal to 0. So in this case the answer was n, is that correct? Well yes, if m is divisible by m then m must be the GCD so which is being returned and so the program is working correctly for the base cases. For drawing the base case was l equal to 0 or maybe I should write l equal to 0 over here but in this case what did the program do? It did nothing. Was that correct? Well if the tree has 0 levels then it is an empty tree and so nothing should be drawn. So even here for both of these programs this is how you would check that the base cases that there are base cases and they are being correctly handled. Then we turn our attention to the level 1 recursive call. So first thing we should check is whether they are valid. So are the arguments in the level 1 calls valid or in other words do they satisfy the preconditions of the functions? So in the GCD the top level call was GCD of m and n and the level 1 call is GCD of n and m mod n. So what are the preconditions here? So we require that the argument must be non-negative integers. So these must be non-negative integers. So can we argue that these are non-negative integers? Yes, n must be bigger than 0 why because n is an argument to the top level call also and the top level call we are assuming satisfies the preconditions. So assuming the top level call satisfies the preconditions the level 1 call must satisfy the preconditions. So which is what we can argue over here and what about the second argument? Well if you go back to the code you will see that this level 1 call is made only if m mod n is bigger than 0 because if it is equal to 0 then we just return n. So if it is only if m mod n is bigger than 0 then we make this call and therefore this argument must also be positive. So in other words we have verified that the preconditions are correct. What about drawing? The level 1 calls are these. So the original call the top level call was had L levels here we have L minus 1 levels. So are these arguments satisfying the preconditions? Well level 1 calls are made only if L greater than 0. So if L is greater than 0 then L minus 1 must clearly be greater than or equal to 0 or in other words L must be bigger than or equal to 1. So this argument must also be bigger than 0. So we have checked that this argument must be bigger than or equal to 0 and you can check and I am not going to go through it right now but you can check that these other arguments will also satisfy the required preconditions. Then we come to the next check that you should make. So the next check is that does the problem size reduce and can it reduce indefinitely? If it cannot if it can reduce indefinitely there is a problem. If the problem size does not reduce there is a problem. So the answer to this first question should be yes the problem size does reduce but can it reduce indefinitely? No, it should not be possible to reduce the problem size indefinitely. So the GCD level 1 call is GCDNM or N and the top level call was GCDNM and we said that this second argument is the problem size. So second argument has reduced why? Because originally it was mn so it was n and now it has become m mod n and we said that this was the problem size and m mod n is certainly smaller than n. So the second argument has reduced. So that is a good thing. Now can it reduce indefinitely? Well the remainder has to stay above 0 so it does mean it means that it cannot reduce indefinitely. So this essentially is saying that look you cannot have unlimited indefinite recursion you cannot just have one call after another because in every call this argument is going to reduce but if it reduces then eventually it is going to go down to 0 which is not possible. So the answer to this question is indeed yes and no for GCD so that is a good sign. Trying so the level 1 calls are these and we said that the first argument was the problem size. So has that reduced over here? Well in the original or in the top level call it was L and it has become L minus 1 so it has indeed reduced. Can it reduce indefinitely? Well it cannot become negative because we are checking if L equal to 0 then we do not make a recursive call and so this cannot become negative and therefore again we have checked that this function is a good function. The problem size is reducing but it cannot reduce indefinitely. The last requirement to check is will the top level calls return the correct results if level 1 calls to. So GCD we know that the GCD of the arguments to the top level call M and N is the same as the GCD of the top level calls to the level 1, GCD of the level 1 recursive call which are N and M mod N. So the GCD of the arguments to the level 1 recursive call is the same as the GCD of the arguments to the top level call. And now what does the function do? So the function is going to return the GCD of N M mod N. If it does this correctly then the top level call will be correct. So we are not checking whether it is going to correctly return this but it is returning this. If this is correct then this will also be correct because they are exactly the same thing. So we have checked this for GCD. In the case of drawing the level 1 calls ask for a smaller trees to be, two smaller trees to be drawn. And we have to check that the two smaller trees are drawn in the right position. So what happened there? So we asked we wanted to draw a level L tree and we said that oh if we want to draw such a level L tree then we can do that by doing these small L minus 1 level trees. So if those were correct then our program, this function simply drew the branches in the right position. So what it was doing was, so we wanted to draw a tree starting from this point. So it said look you first draw two smaller trees over here. And then in the top level call you just do this, we just do these branches. So if these trees got drawn correctly then we know that oh there are just these two branches to be drawn and the top level call is drawing them correctly. So therefore the total thing must be correct. So let me summarize this part. So how do we check if a recursive function is correct? So what should we check in order to do that? We should check that there are base cases and that correct results are obtained for the base cases. Then we should check that the level 1 recursive calls satisfy the preconditions. Then we should check that the problem size reduces that there is something called a problem size and that it reduces but the code is such that it will not, it is not possible that it can reduce indefinitely. And finally we should check that if the level 1 calls work correctly then the top level call will also work correctly. That is it. In particular what is it that we do not need to argue? So we do not need to argue explicitly that the level 1 calls in fact work correctly. We do not need to do this. We do not even need to think of what the level 1 calls do. Are they going to make recursive calls? Are they just going to return the result directly? We do not have to worry about it. So basically what is going on is that these first three points are ensuring for us that this computation is going to terminate eventually. And this last point is going to say that look if the recursive calls work correctly then we will return the correct answer. And this really the whole thing together says that not only effectively the top level recursive calls call works correctly but it effectively proves that the level 1 call works correctly, level 2 call works correctly because this argument really is applicable to all the calls. And we do not really, but we do not really have to worry about that. We do not have to think about all the calls. Here is an exercise for you. So I give you this function and it has its precondition and the post condition that is what it is supposed to be returning. So I want you to tell me whether this function is correct or whether there is a problem over here. Now you should apply what we have discussed in this segment and tell me where things go wrong. This is a bit of a trick question but the questions the strategy we decided will enable you to say what is wrong, to figure out something is wrong and say what is wrong. That brings us to the end of this lecture segment as well as this sequence of segments. So I want to make some concluding remarks about recursion in general. I want to observe that recursion allows many programs to be expressed very compact. For example, GCD, the recursive GCD is certainly more compact than iterative GCD. The idea that the solution of a large problem or a problem whose problem size is large can be obtained from the solution of a similar problem, a similar smaller problem of the same type. I should really have the words smaller problem here. This idea is very powerful. Euclid probably used this idea to discover his GCD algorithm. He probably did say to himself that look I want the GCD of these large numbers. Is it sufficient if I find the GCD of some smaller numbers instead? But the moment you take this single step you can, you are implicitly developing a capability of applying it again and again and that is exactly what recursion gives you. And recursion is also very natural, in fact much easier to understand and much more natural, much less clever for objects which have a recursive definition. Say for example trees and here we saw tree drawing as an example. To understand if our high recursive function works we need to make a few simple checks outlined earlier. And I just want to observe that some programs can be written recursively or iteratively. So for example GCD or the factorial but some others are best written recursively. So for example drawing trees it will be pretty complicated to write this program using iteration or rather I should say write this program without recursion. So that concludes our discussion of recursion, it concludes this sequence of segments. Thank you.