4.4 - Recursive functions.mp4
4.4 - Recursive functions.mp4
necessarily only in Python. So recursion basically means calling a function within a function.
So in a nutshell, that's what it is, but let's understand why it's needed. So for those of you
who don't know what a factorial is, factorial of a number. So let's say, if I say factorial of five,
what it means is you multiply five with four, with three, with two, and with one. Okay?
Whatever value you get here is called the factorial of five. Factorial of five is 120, okay? But
one fun thing here is factorial of any number n. So, for example, factorial of five can be
written as five multiplied by factorial of four. But because what is factorial of four? Factorial
of four is multiplication of four with three, two, one, and what is factorial of five? Multiply
the factorial of four with five and you get factorial of five. Right? So in a nutshell, factorial of
any number n of any positive number n is nothing but n multiplied by factorial of n minus
one. Here, the fun part is the factorial of one is one itself, right? When, when you're writing
something like this here, you have to remember that factorial of one is one itself. So when
you have these two conditions, now what is factorial of two? Two into factorial of 10 is
neither positive nor negative. So we'll not talk about factorial of zero. Actually, factorial of
zero is one, but let's not talk about it right now. It's not useful for our case. It's not useful.
Okay, so when you have these two, so this is called a recursive statements, or this is called
recursion, where I'm computing the value of a function using some operation, and the value
of the same function on a slightly different number. See here, factorial is computed using the
factorial, and this is called the limiting case or the boundary case. This is called the
boundary case, where I'm saying the factorial of one is one itself, right? So when you have
functions like this, we'll see one more function called fibonacci a little later. It's also
pronounced as fibonacci. Okay, so we'll come to the function fibonacci a little later, but let's
look at factorial. This is simple, right? Now, how do you implement functions like this? Such
functions occur a lot in mathematics, and they occur a lot in, if you have studied data
structures, they occur a lot in data structures. And algorithms, if you can represent a
function as itself with the slightly changed parameters or arguments, it's extremely useful.
We'll see an example. Okay, so imagine now let's implement factorial. It's not very hard.
Right. Here is my function definition. It's very, very elegant. See, what I'm saying here is
factorial of number return one if num equals to one. Okay, what does this mean? This
means, I am saying that factorial of one equals to one. Else, return num multiplied by
factorial into num minus one. So what am I saying here? Factorial of n is nothing but n
multiplied by factorial of n minus one. This is a definition of factorial. Right? I'm just using
the definition of factorial itself. And what I've done is within, this is literally one line of code.
This whole thing is one line of code. Of course, I could have computed factorial using a for
loop, but recursion makes it much more elegant because this is how I think of factorials.
This is the recursive definition of a factorial. There is a non recursive definition of a factorial
which is factorial of n equals to n into n minus one into n minus two into so on and so forth
up to one. This is a non recursive definition. This is a non recursive definition. I'm using the
recursive definition to be able to compute factorials very elegantly. Now, the way I call it is
very simple. I just say num five, right? And I just say factorial of num. Now let's understand
what's happening internally, okay? This is very, very important. What happens when you
call this function the flow of the function, the flow of the program is very, very important.
Okay, let's understand. So when I call factorial of five. So here I'm calling factorial of five. So
I'm calling factorial of five. Okay, when I call this, what happens? It goes here, it says return
one if num equals to one. No. So it says return num multiplied by factorial of num minus
one. To produce this product, to compute this multiplication, it now has to compute factorial
of num minus one. So now it calls again the function factorial with a value of num minus
one, which is four. Right? Now when it calls factorial of four, now it goes back again here.
Now it says, oh, return one if num equals to one. It's not right. So what is it doing here to
compute factorial of five? It called factorial of four, but here again, it will try to multiply four
with factorial of three. It'll keep calling this. It now call factorial of three, which will call
factorial of two, which will call factorial of one. So it will keep calling this recursively. This is
the recursion part. So when it calls factorial of one, now it goes in here and it says return
one if num equals to one. So it will return one when this returns one here at factorial of two.
I was supposed to do two into factorial of one. So what it does is it does now two into one,
which becomes two. So this function now returns two here. Now this two will be multiplied
by three, because see, look at this. Here I have num multiplied by factorial of num minus
one. So two into three becomes six. So this function now returns six here. Now this six will
be multiplied by four and I get 24 here. And this 24 will be multiplied by five, and I get 120
here. This is how the function flow happens, okay? It's basically like I'm going into the lower
values of factorial and I'm coming back. So this is called the function call stack. You can think
of it like a heap, like a stack of things, right? Steep has a different heap, has a different
meaning in data structure. So I'm not using that. This is a stack of function calls, right?
Because if you look at it, I'm starting with factorial of five, and I'm going down the pile. As I
go down the pile here, right? As I go down the pile here or the stack. So I go down the stack
and then I come up the stack. Okay, this is how internally recursion works, okay? Now the
good part is, what are the advantages and disadvantages of recursion? The biggest
advantage is it makes the code look clean and elegant. Instead of writing for loops, if you
have a complex task that can be broken down, if there is a recursive definition, if there is a
recursive definition for any operation you're doing, for example, for factorial, there is both a
recursive and a non recursive definition. If a recursive definition exists, it makes code much
more elegant, simple to implement. The biggest disadvantage is it is hard to follow because
it's not like a simple for loop or if fills condition. Sometimes it becomes inefficient because
of the stack that we saw. Because you're calling a function within a function, within a
function. And when you call a function, there is some overhead, there is some memory and
time that are used more than normal cases. Okay? So it is slightly inefficient. The worst part
about recursion is if you have a bug in your code, if you have done some mistake, it's
extremely hard to debug because unlike regular code, which you flow like step by step by
step or iteratively in a for loop, recursion is calling the function within the function. It's
slightly more tricky for us to typically think in recursive fashion. And hence, if you have
introduced a bug, if you have written some mistake in your code, debugging, it can become
fairly hard. Okay, so since we have understood, we have to be careful when we implement
recursion. Just slightly careful. Okay, now let's look at a very interesting code. So for
something called as a Fibonacci sequence, and what is a Fibonacci sequence? Let me explain
that. The Fibonacci of, okay, let me show you. Fibonacci of zero is zero. Fibonacci of one is
one. Two is basically sum of these two numbers. Okay, now for three, for 3012-345-6789.
Okay, I can call this the zero 8th number or the first number. It really doesn't matter. Okay?
But the essence is this. Let's say you might call this the first number, the second number, the
third number in the Fibonacci sequence. Fifth number, 6th number, 7th, 8th, 9th and 10th.
So any number in the Fibonacci sequence is the sum of the previous two numbers. Pick any
number. Pick this number. This is the sum of the previous two numbers. So if you sum 13
with 21, you get 34. So fibonacci of any number. N is fibonacci of n minus one plus fibonacci
of n minus two. Okay, this is the recursion, it's called the recursive equation because you are
defining a function with using itself with slightly different arguments. Now, for every
recursive equation, you also need to have the boundary case. Okay? So let's look at the
boundary case of recursion. Boundary case has been called multiple different terms by
other people here. Let's just call it boundary term. So I am saying fibonacci of two equals to
one and fibonacci of one equals to zero. Because if I don't have these first two values, I can't
compute the third value, right? So my boundary case basically has two statements, that
fibonacci of two equals to one and fibonacci of one equals to zero. Now we can implement
this using recursion very easily. Look at what I'm saying. Return num if num is less than
equal to one. Okay, so here in this code, what we're doing here is, let me just call it num less
than equal to two. Okay, so Fibonacci of two, fibonacci of one and two is this right? So now
let's see what happens when I execute this code. Okay, there's a small error here. This is a
problem with Python two and Python three. Those of us who are used to python two do not
put parentheses after for print. Right, so now let's rerun this. Now you're getting the same
value, right? So what I'm saying here is that if you are given fibonacci of one, I'm saying
fibonacci of one equals to one itself. Fibonacci of two equals to. Oh, there is a small bug.
Sorry. Okay, let me just say num less than equal to one. So this is the right implementation.
Now let me explain you why. Okay, so what it's saying here is return num if num is less than
equal to one. Okay, so let's see. So what I'm saying here is fibonacci of zero equals to zero,
fibonacci of one equals to one. These are my boundary cases. And my recursive case is
fibonacci of n equals to fibonacci of n. Sorry, fibonacci of. Let me just erase this small error
here, but we'll correct it. Is Fibonacci of n minus one plus fibonacci of n minus two. Okay? So
imagine if these were my conditions. That's how the code is written. Okay? If num is less
than equal to one, if num equals to one, return one. If num equals to zero, return zero. That's
what I'm saying here. I have my boundary cases here. I have my boundary cases there,
right? And this is my recursive case. This recursive case is here. Okay? So if you forget your
boundary case, your program will continue to run forever. So these are your boundary case
and this is your recursive function. That's all. That's all there is. You don't have to worry
about anything else. So for Fibonacci sequence here, right, all I have to say is print, print
whatever terms I want. I can print all the Fibonacci terms here. Now it has been much
easier. See, I've written the whole Fibonacci function. To get any fibonacci number, I've
written it in literally one line of code. Imagine, of course, I could have implemented this in a
for loop or a while loop. It would have taken much more code. This is much more elegant
because this is how I think of feminiki sequences, that any number is a sum of the previous
two numbers, right? That's how I imagine feminine when I learn about it. Right? But
remember, please don't forget your boundary case. If you forget your boundary case, the
function will keep running. So the most important things to remember about recursion is
always write your boundary case properly, and don't skip it. And write your recursive
equation. And always remember. Recursion is hard to debug. If you do mistakes, you will get
caught up in it. It's very, very hard to debug, relatively speaking, as compared to simple for
loops or while loops or if else conditions. Okay, if you have these two, you can write your
recursion. And recursion is extremely useful. If you have studied data structures and
algorithms, you would have seen recursion everywhere. It's one of the most powerful ideas
in programming across languages. And by the way, recursion, as I told you earlier, is not
unique to python. It's there in every major programming language we can imagine.