Hello and welcome back to the fourth lecture of the Python programming course at WHU. Today I'm here on my own without any audience due to the coronavirus and that's because all the lecture halls have been closed down for all the students so I am recording this lecture today without an audience but I hope that it's still worthwhile even though some of the interaction may be missing. So today's lecture is about recursion and about looping. These are two concepts but they are two sides of the same coin and usually recursion is only taught towards the end of a programming course if at all. So why did I choose to teach recursion as a very early concept in the course? Well I have learned over the years that the less a student knows about anything in regards to programming the less confused he or she may be when being confronted with topic on recursion. So just lean back and I promise you that recursion will be rather easy it will come to you almost naturally because you actually know it already you just have not yet heard of the name recursion before and then once we understand recursion we will basically review the same topic just from another angle and I refer to it as looping in this lecture and yeah we will do both of this in today's lecture. So what is recursion? Well a good way to introduce something is always with a trivial example so we will have a trivial example here that mimics what all of you know what usually happens on New Year's Eve. So what happens let's say 10 seconds before midnight before the next year starts well usually someone starts to count down a countdown usually around 10 so people start to count down 10 9 and so on and then when midnight comes everyone is screaming happy new year and congratulating each other. So how could we build such a you know program that mimics this type of output in Python? So what you see here is a function called countdown it takes one argument which is called n and n is supposed to be an integer and it's the seconds before the party begins so it's the seconds until midnight in our example and then we have an if else branch and then let's look at the if first the if statement the if headline checks if n is equal to zero and if so there is no more number to be counted down so we just wish each other a happy new year. In the else clause on the contrary we are not yet at midnight so what we do is we just print the number that was passed to the countdown function and then something interesting happens the countdown function in its last step calls itself after decrementing n by one so let's say if countdown is started with let's say n equals three then countdown would be called again here with n equal to two so that is a good example for recursion it's an almost minimal example of a recursion so what is recursion? Well you should think of recursion as a function an ordinary function in Python that somehow calls itself but also a function that has an if else logic so that not in all cases it calls itself because a function that will always call itself under all circumstances we will soon see is not a good function so the first the first branch here the if branch is what we call a base case and the base case is any is any branch any logical step in a function that does not lead to a call of itself so that's the base case and then the other case that we only have one other case is the recursive case the recursive step sometimes also called the recursion so I define the function and of course nothing happens we don't have an output here because def is a statement so it has a side effect and the side effect is it does create a variable called countdown and it does not yet execute the function so in order to execute it we just call the function with the parentheses the call operator and pass it for example the number three so we count down from three to zero to midnight what's the output well the output is what we expect it would be it's just saying three two one and happy new year so that's an easy example the output makes sense so is there something more to be learned here well the answer is yes and as you know by now in this course I value a lot of I value that you learned a lot about what's actually going on in the computer's memory and we will see that something interesting is happening here so I'm here a Python tutor and I copy pasted the code of the example here I just left out the doc string to make it a bit shorter and then after the definition of the function we call it with the same argument so let's go through the steps the first step is now executed and all it did is it created a variable called countdown that references a box in memory that holds the ones and zeroes the code that is in the function's body and then in our second step we now call this function with an argument of n equals set to three so what happens well as we've seen before a new box is now created and this box will hold what is called the local scope of a function call and in this local scope of the function call n is set to three unsurprisingly so what happens when the function executes well the if branch is skipped because n is not equal to zero so we go to the s branch right away and then all that happens is we print n and we will see the printed output up here and then the function calls itself after n is decremented by one and then what happens now comes the surprise well we have a second box now which is there because of another function call that is going on a new function call and within this new function calls local scope n is now two but the important thing is the first box did not go away yet so the n set to three is still there it's just not active so we see the active scope is the one that is in blue here in python twitter but we see the old function call is still active it's still somewhere in memory and then what happens when the function is called with n set to two well nothing surprising happens so all we do is we just print out two again and then we call the function again now with n set to one and see now we have a third function call that happens simultaneously in memory so the first two function calls have not yet returned now the function is executed for n set to one nothing surprising happens one is printed out and then the function is called one more time now with n set to zero and now we hit the base case we hit the case where n is set is equal to zero in which case we print out happy new year now we see it here and now something happens that we have not seen before in this example so now for the first time after the print happy new year has been executed there is no more code to be executed therefore the last function call where n was set to zero now returns and we see this instant where a function call returns in python twitter when we have for just one step where we see the return value here the return value is set to none and that is that is so because we didn't say return something explicitly in the code so whenever we don't say return something what happens is there is an implicit return none and we have seen none before it's a special object which basically means the absence of a value but it's a value on its own and this value is returned and whereas it returned to well it's returned to the other function call where n was set to one so now the the last function call where n is zero disappears it goes away the function has returned it's over and now we go back to the function call where n was set to one and now there's only one more step to be done which is now and now after countdown n minus one has returned there is also not more code so this the next function call will also just disappear and so on and then what we left with at the end in the last step we left with just the global name countdown pointing to the function definition and then we see the output three two one happy new year but we we observed that there were actually four function calls happening simultaneously in memory so there have been four m four n's in memory simultaneously they appeared one by one and they went away one by one so the memory consumption was rather high here right so we had to we had to basically keep track of all the individual function calls so let's go back to the presentation so now we've understood a simple example of a recursion and now you may wonder well that's nice but you know what can i need it for what can i do with it what what can i do with recursion that i couldn't do you know otherwise or what is a an example that is realistic and not so trivial well as i said you know recursion already you know it actually for quite some time usually around tenth grade maybe eleventh grade in math in high school you learn something that is called the factorial of a number and you usually do so in in a discipline called combinatorics that has to do with probability theory and so on you may remember you may not like it but you've definitely heard of it before and so the factorial is one very popular example and i say here it's an easy example easy because i think it's rather easy to understand because all you need to understand is the following this is the definition so the definition says that n factorial is defined as n times and then n minus one factorial again and in the case where n equals zero then the zero factorial is defined to be one so let's assume you want to calculate the factorial of the number three so how do you do that well the factorial of the number three would be three times two times one and that is six so now i did something that is usually done in math i counted backwards so when you calculate factorial of let's say five you would you would go ahead and and say five times four times three times two times one and that's it now the thing is multiplication is symmetric so we could also have said one times two times three is also equal to the factorial of three or one times two times three times four times five is also equal to the factorial of five it doesn't matter in which order we go and this observation will become important later on but here on the slide we see the generic case and the generic case may not be so readable for anyone right away but i think it's also rather easy and so but the thing is how do we what can you now do with that how can we translate this into python into code and the thing is this it's rather easy because it basically is already screaming at us so so let's go ahead and define a function that i call factorial it takes one n as the argument which is the factorial of the number we want to the always the number the factorial of which we want to calculate it it should be an integer it's technically possible to to calculate factorials with non-integer numbers however that would require some higher math and we don't want to do that here and then we have a very easy if else logic again which mimics exactly what the definition of the factorial says well first we say here the factorial of zero is defined to be one well that's not a natural law that's something mathematicians made up it makes sense philosophically speaking but it's definition it's nothing that you know you know has to be discovered or something it's just something that mathematicians assume to be true and how can we put this into code well we just say well if we call the factorial function with n equal to zero then we just return the number one because that's the the definition you know that's the vector of zero is just one and now here that's also a difference to the aforementioned attribute example of the countdown now here we see an explicit return statement and what that means is we don't return none once the function call ends but we return the number one in this case and because in this if branch the function vectorial does not call itself it's the base case and then what do we do if we are not in the base case let's say if we want to calculate factorial three what would happen well what we would do is we would do what is called the recursive step so what we do is we call factorial so factorial calls itself with n decremented by one so if let's say in the example i call factorial of three then here we would call it factorial with n set to two and whatever we get back from here we store in the variable called recurs and then in the next step we say the actual result that we care about is not just recurs it's not just the the thing that is here in the parentheses it's n times this recurs value so in other words if we look up here to the mathematical definition we also have a base case and then we say n the factorial is defined as n times n minus one vector so in other words the factorial is defined in terms of a factorial and usually in in school in high school we are taught in usually in language classes that it's not a good thing to define something in its own words to define something with itself basically by referring it to itself so self-referential definitions they are basically pointless right however we are not in traditional language studies here we are programmers and programmers as you may have noticed already are able to think outside the box and formulate things outside the box a little bit so we are as programmers actually able to reference to have a self-referencing referencing function definition so that's it and so we see the recursion actually also in the mathematical definition and up to now i suppose you have not yet heard the term factorial at the term recursion but basically you have known it already and now let's define the function factorial and now let's check if it works so let's call a factorial with the argument of three and we get back six why well because the vectorial three is just three times two times one which is six it works so now let's make the function a little bit nicer what do you mean with that well let's go back one more time to the actual vectorial function we have an explicit if else logic here so what i what do i mean with explicit well we both we say we write out if something and else now and also what we see here is in both cases we return right so if we hit the if clause we have we hit the return as well if we hit the else clause we hit the return as well and you know you remember from the chapter on functions that after we hit a return statement there is nothing that will be executed after it anymore so even if even if i have let's say another line of quote here let's say one plus one this would not this would never be executed right after return statement there is nothing that will be executed in other words if we go into the if branch and hit the return there is no way anything that is below here including the else can be hit in other words since we have a binary choice between if and else between just two branches what we just do in the revised version here is we just check for the base case for n equals to zero in which case we just return one but if we don't hit this case we don't need an else here we can just say return something because we know if we had hit the if branch then we wouldn't be down here to begin with so we don't have to care about the else here anymore and then what we do is in the old version i called i made the recursive function call to factorial of n minus one and i stored the result in a variable called recurs and then in the second step i calculated the actual result that we care about and then in the third step i returned it so let's go ahead and put all those three steps into one how do we do that well i just say return and then we can after the return statement of the return keyword we can put basically any expression any legal expression syntactically so for example n times the return value of factorial n minus one would be an expression so we don't need to have any variable that we assign to in this case here we can just return the expression right away and it will be evaluated at runtime when the function is called and then whatever is calculated here will be returned right away we don't need any more variables so so there are actually two improvements right the first one is we can skip the else and the second one is we can also give all the temporary variables and whenever we see this this pattern this has the name it's the so-called early exit pattern why early exit well because if we hit the if we exit from the function called early and then the any line it comes after return cannot be hit it cannot be executed therefore we exit from the function called early and this is something not every language supports but in python it's easy to implement this and i think this makes reading the function even nicer right it we are now as close as it can get to the mathematical definition where we said factorial of zero is one and if it's not zero it's just n minus the factorial of n minus one we couldn't get any closer to the function to the function definition and then of course it's always a good idea if you if you refactor a function to just check if it still works and factorial of three is still six so it seems that we have not made any mistake here that's the factorial so now we have seen a realistic example and now i want to show you and this is just mainly for illustration an example of a recursion that is not so easy to see and it's an old algorithm it's called Euclid's algorithm and i call it an involved example because it's really not so easy to see why the following works so what Euclid discovered was if you want to calculate the greatest common divisor of two numbers let's call them a and b you can do that using a recursion so we see the recursion because there is an if branch here and we return from it from it and then there is an implicit else clause here so we have an if else logic and in the else clause here the function gcd for greatest common divisor calls itself and it does not call just itself with a and b it calls itself with b in the place of a and in the place of the former b we call we have a modulo divided by b and this is not something that you know we would suspect right i mean this is not something that we can intuitively say that has to be this has to work like this and however Euclid discovered that very long time ago and then the modulo division that we've also seen before we have seen it with three different use cases actually um what we what what the modulo division does is let's say a and b are very big numbers the modulo division makes the result very small and in other words and in other words it's a very fast operation and what we will see here that Euclid's algorithm to calculate the greatest common divisor of two whole numbers is not just possible to formulate it in a recursive way but it's also an algorithm that is very very fast so for example the greatest common divisor of 12 and 4 would be 4 because 12 is divisible by 4 it would also be divisible by 2 and 6 and 12 of course but 4 is only divisible by 1 2 and 4 so 4 is the greatest common divisor and then that's what i just meant but with the speed that comes from the modulo division so here i have two very large numbers and i want to find the greatest common divisor a whole number and i execute it and it's nine so i don't know if it if nine is correct it's hard to to validate this especially with what we know what we remember from elementary school because i think in elementary school all of us had to calculate greatest common divisors of two numbers and here for for such big numbers we didn't do that by hand back in the days but let's assume this is correct and you can believe me it is correct the thing is it's a very fast calculation and then of course if you if we now now we caught the function with two numbers in two examples where we know there is a greatest common divisor and we we calculated it and let's put a third example here where we also know what the example what the result is supposed to be i used numbers two and seven thousand nine hundred and nineteen so you might wonder what is so special about the number seven thousand nine hundred and nineteen well it's the one thousandth prime number and what we know from back in high school and maybe also elementary school prime numbers have the property that any two prime numbers they don't really have a greatest common divisor right the greatest common divisor among prime numbers is one that's what the function output is so this is also a way to check a corner case when we when we create a new function we should always check corner cases and a corner case for the greatest common divisor function would always be to check it with two prime numbers and see if the the returns value is one so that was just to show you that recursion can do magic things and it's not so easy to you know think it up basically it's not so easy as maybe in the factorial case so let's find let's look at another case there is not so well known outside mathematics and computer science but it's an example that some of you may know from hollywood movies where usually when in hollywood movies the roman catholic church is involved in some movie and there is some quiz to be solved usually it is about fibonacci numbers so many many people use fibonacci numbers if they need some quiz to be to be solved and it shouldn't be a quiz that yeah that is very common and also the fibonacci numbers they also are used in in art for example or in geometry so when artists paint paintings and they ask the question you know in what ratio should certain elements be then usually they use what is called the golden ratio and the golden ratio in art also comes from the fibonacci numbers so fibonacci numbers even though as business people maybe you won't find them quite useful it's a very useful example to know so what are fibonacci numbers well here's an example of a fibonacci series it's the first 13 fibonacci numbers and fibonacci numbers are defined as follows the first two fibonacci numbers they are just defined as zero and one so those first two numbers they are just given it's just like the factorial of zero is defined to be one that's just a given we have to accept it and then the rule is this the next fibonacci number is always the sum of the previous two numbers so in this case we have zero plus one coming from the left side zero plus one is equal to one and then we have one plus one is equal to two and then we have one plus two is equal to three and so on and towards the end we have 55 plus 89 and this will be equal to 144 so now I did I did something that you know seemed intuitive I went from the left to the right however we are here in a chapter on recursion and recursion usually means in a naive way to solve a problem from the end and not from the from the beginning so how can we view this problem as a problem coming from the right direction well we can say if we wanted to calculate this fibonacci number and we didn't know it was 144 yet what we do know is it's the sum of the previous two so the problem of finding the 13th fibonacci number can be reduced to the problem of finding the 11th fibonacci number and the 12th fibonacci number and then adding them right and now let's see at some code let's see a python function that does exactly what I just said previously what this function does it takes one argument called i and by i usually I mean an index so previously I used n for a number and now or a and b for number and now I use i for index so index is a little bit something else and then what we do is the function it returns the i of fibonacci number so if I want to calculate the 13th fibonacci number I would be 12 right because we start counting at zero so let's see what the code does well now first of all see we have three return statements three times the word return so that is something new we haven't not seen this before before that we only had two different returns now in the first case I say if I is equal to zero just return zero what that means is if I am asked to calculate the first fibonacci number with which is index zero I just return zero why because it's a by definition you know so there's nothing to be calculated here if my problem is to calculate the fibonacci number with the index i equals one the second fibonacci number then the number will be one by definition so notice that is the same idea of a base case just as we had in the factorial where I said well the factorial of zero is defined to be one there's nothing to calculate and because there is nothing to calculate I can just return the value as is and that is a base case because I don't call fibonacci itself fibonacci does not call itself in the first and the second branch here and so this is also a different scenario now before that we only had examples where there was exactly one base case now we have we see an example where there are two different base cases and then in the last step which is basically like an else so this could also we could have written here you could or could also write just else and then return like this but then using the early exit pattern that I mentioned before we just leave away the else and put the return on the same intonation as the if and the elif and then what we do is this we say the highest fibonacci number so let's say the 13th fibonacci number is just the sum of the i minus one plus the fibonacci number i minus two so in other words I just specify a rule as to how to calculate a fibonacci number given its two predecessors so the 144 is the sum of the 55 plus the 89 so we are calculating the fibonacci number from right to left here and and now you may wonder is it always possible to to calculate the solution to a problem both in a forward and b in a in a in a backward direction usually this is the case and usually one of the two cases easier than the other but usually you have to it's not as easy that to say that both cases both directions are equally good so we will see that calculating fibonacci numbers from backwards from coming from the right side is actually not a good thing to do but that's exactly why I put it here to show it to you to show you why your recursion even though it may seem intuitive in the in the in the factorial case may be not so easy to understand in the fibonacci case but I think the logic is clear we go from right to left and we reduce a big problem the defining the is the bunaji number into two smaller problems finding the fibonacci numbers for index i minus one and i minus two so let's define the function and go ahead and then let's calculate the 13th fibonacci number with index 12 and we get the number 144 so I just said that calculating the fibonacci numbers from right to left is not the best thing to do why is that well there is a topic that we have not yet talked about in this course which is a topic on efficiency of algorithms so what I mean by efficiency of algorithms is we want to you know all code that we write will run very fast for small inputs but the question we usually are interested in is what happens if my problem becomes big so instead of finding the 13th fibonacci number maybe I want to find the 1000th fibonacci number so instead of a factor or you know an order of magnitude of 10 or in the tens I now want to find now my problem scales up to the order of magnitude of thousands or maybe 10 thousands and so on and the question I ask is what happens to the speed so if let's say here's an example let's say I want to go ahead and calculate the 13th fibonacci number with index 12 what I can do is I can use the time magic here and time the function so what this code here does the double percent sign time it and the dash n 100 means this cell is now run 100 times and then the the run times averaged and the standard deviation is taken so on average running the cell running fibonacci of 12 takes 43 microseconds so that's micro and for those of you who don't know what micro means it means times 10 to the power of negative 6 so we shift the the period point by six digits here and then we ask the question what happens if I want to calculate the fibonacci number the 25th fibonacci number so fibonacci of 24 index 24 so what we see here is first the problem size doubles so the input before was 12 now the input is 24 so in that regard in regard to the index our input now doubles now what happens to runtime it would be natural to assume that if you solve a problem that is twice the size of another problem we would need twice the time right so let's see what happens to runtime I execute the cell and it still calculates as we see it takes very long time and now it's done and we see here on average we have 100 calls here 100 times we ran the cell on average one run takes 13 milliseconds and so now you may wonder what is milli well milli is times 10 to the negative 3 so on average if we disregard the 40 and the 10 let's say if we just look at the micro and the milliseconds that means the second cell on average is slower by a factor of 1000 so by just doubling the input the runtime of the algorithm goes up by a factor of 1000 so that's a very weird and also bad behavior and now let's do this one more time let's do this for the 37th fibonacci number which is fibonacci with an i of 36 and now see here I only run this cell once because running it 100 times then we would you know sit here for an hour just watching the cell run so now I run the cell just once and let's see how long the cell takes so it's 4.2 seconds so disregarding the exact numbers in the front we just look at the the order of magnitude here so we look at the micro the milli and then here is no prefix here so we are talking seconds here so again from going from fibonacci 24 to fibonacci 36 there is another slowdown of a factor of around about 1000 so in other words calculating fibonacci of 12 and calculating fibonacci of 36 has you know a difference in runtime if we scale it up by one million so to calculate fibonacci of 36 is like one million times more expensive or one million times slower than calculating fibonacci of 12 and yeah so you may wonder what's going on here and then of course how can we how can we analyze this well what we do here we go into a python tutor and we just copy paste it to code here into python tutor and I calculated for fibonacci of five but that's enough it's a small problem size here but it's enough to see what the problem actually is so let's see what happens in memory well first we create a new function called fibonacci and now we start calling it for i equals five and now what happens for i equals five well the if and the elif branches they escaped because the conditions are not true so we go to return fibonacci i minus one plus fibonacci of i minus two so what happens is before this function can actually return it will call fibonacci again now with i equal to four and now i equal to four runs but remember the i equal to five function is still running it's still running and let's go on and now what happens when i is equal to four well um this function goes until the return statement here and then calls return fibonacci of four minus one which is three so now we call fibonacci of three again and now we go down and now we call fibonacci with i equals two and now for the first time with our fifth overall function call we call fibonacci where i is equal to one and now that i is equal to one there is no more function call because that's the base case so we hit i is equal to one and we return one right away we see this here the return value of one goes back to the previous function call now what does the previous function call do well the the previous function call if i go back one here where we have i is set to two this now has evaluated the left hand side of the plus here and now what we what happens is now this part has returned now the right part has also to be executed and now for the right part fibonacci is two minus two which is zero so this is why we see fibonacci of i equals zero here now this is executed good for us that fibonacci of i equals zero is also a base case so we also return zero here right return zero and now finally after having executed fibonacci of i minus one and also executed fibonacci of i minus two we have both return values and now we can actually return this function call so now the function call where i was set to two can finally return after having made two more function calls and now this goes back but now what's the problem the problem is now we have to evaluate the right hand side of the last line for the previous function call where i was set to three and now the right hand side so let's go back one more time the case where fibonacci was called with i equals to three it called fibonacci where i is equal to two now now that this returned the right hand side executes and fibonacci is called again with i set to one but wait a minute i set to one have we caught this before let's go back in there let's go back some steps and we'll see that yes we have actually called fibonacci with i set to one before and now if you go back into the future so to say some steps forward we will call fibonacci with i set to one again and let's let's play this game further let's just go ahead and now we go back to fibonacci where i is set to three and this now calls i two but and this also calls i with one so again that's now the third time that we call fibonacci where with i set to one the third time already so see the see the the problem here whenever for every function call we make no matter where we are in the chain two more function calls will be made unless we are in the base case of course but as long as we are not in the base case every function call we make will result in two more function calls that's an example of exponential growth because for every call we make two more will be made and for two more that will be made four more will be made two for each so in other words there is literally a trillion of function calls being made in the background and just to calculate the fifth or people not the fourth fibonacci note no the sixth fibonacci number where i is set to five it requires already 70 steps and it's as we see if i click through it here we see that function calls and new function calls are made they return and then a new one is made and it goes the the recursion tree so to say goes up and down that's what we say so it takes a long time to calculate fibonacci with i set to five and that's the problem here and the problem is so we can also visualize it here maybe if i go back in the slides when we see the numbers so in order to calculate the 144 i have to calculate the 55 and the 89 however in order to calculate the 89 i have to calculate the 34 and the 55 so for every function call i i make going from right to left i have to make two more that's a big problem now writing the numbers down like here also suggests to us what is the solution to the problem the solution is simply to just calculate the fibonacci numbers from left to right that's way easier it's way way faster because we can just always take two numbers add them together get the next one add them those two together and get the next one and so on we will see an example of going from left to right at the end of this chapter today but for now we view the problem from you know right to left in the recursion way but we see that sometime so what what can we say as an intermediate result well or as an intermediate result we can say we like recursion already because in some cases like the factorial case recursion is very natural to formulate it's basically like writing math and it's also fast however in some cases the recursive way really breaks down because we calculate the same you know the same function called over and over again but we will find ways to deal with that in a future chapter and for now we just take this as given and look at some more cases some more things to know about recursion so what we have seen right now was a case where we get two more function calls for every function call we make so that's exponential growth it's bad another thing that's bad is when the number of function calls doesn't even stop it just goes on forever we call this an infinite recursion so what's an infinite recursion i also give you a very trivial example here an example that is so trivial that you should never do this in practice i define a function called run forever and all it does the only line of code this function executes ever is run forever so this function calls itself and that's pointless right but it's just a trivial example to show you what can happen so i define this function and now let's call it let's call run forever what do you think happens well in the worst case scenario my computer will now die and the presentation is over the recording is over but i will do it anyways in the hope that my computer won't die and it won't because python is luckily smart enough to see that i messed up the code i have a function that calls itself over and over and over again it never stops and python being smart enough figures that out and then basically interrupts this function this infinite number of function calls and it gives me a recursion error so recursion error isn't it's a dedicated ever type that basically says well whatever you programmed it kept on calling itself and calling itself and never stopped and um yeah that's it so it's like a fail-safe system here so that our computer does not die now that was a trivial example an example where you met where there was a mistake and i'm sure that in a real world scenario you wouldn't make this type of mistake but how can we run into an infinite recursion without making this mistake on purpose well it's very easy to do actually let's just call the introductory countdown example with 3.1 the float 3.1 instead of the integer 3 what happens well let's see what happens the function still executes and at some point it stops and we can already see with the output what went wrong here so our countdown function starts counting down at the number 3.1 and the next number would be 2.1 and so on and then it misses the zero right it goes from 0.1 to minus 0.9 it misses the real zero the 0.0 and then the counter function just keeps on running calling itself decrementing the number by one and it never hits the new year case and then if i scroll down at some point in my version of jubilant book the the output is limited so here it stops at negative 179 you can believe me that this would have run until around about 3000 so around about negative 3000 that's the limit and then python luckily throws a recursion error because it it sees okay this function the countdown function is now calling itself over and over again and it will never stop and then python steps in and stops it of course even without output without printing output we can also run into the exact same problem by calling factorial with 3.1 the float 3.1 so if i execute this i won't see any output for a while and then at the end i will still see the recursion error so just because i don't print out any output this doesn't mean that the recursion error does not happen so the question is now what is actually the problem here the problem here is that we made some assumptions about what the user would enter into our function call in particular we wrote both countdown and also factorial to take integer as arguments and not floats it would still work with floats if the float were to say 3.0 then the function would still work fine but if we have any float number that does not end with a dot o then we will run into an infinite recursion so the problem here is not that we the problem is really not that we messed up the code the problem is really that the user of our function messed up the function call and what do you do in such a situation if you make the assumption that a user of your function maybe yourself enters an integer and the user may not do that how can you force your user to enter an integer if you need it well you can check the input you can do what is called type checking so in the case of floats and ins we check for example is the argument passed in an integer type and then also a related topic very similar topic is called input validation so assuming that the type of the of the argument passed into a function is correct it could still be wrong so for example what would happen if i asked you to calculate the factorial of let's say negative 10 you know you could it's a it's a whole number so the type would be correct but calculating the factorial of a negative number we don't know what to do here in this introduction course so how can we implement type checking and input validation in our examples so let's see the factorial example again now it's a bit longer but it's usually it's still very easy so the thing that is new in the doc string is just this section where it says raises and then it mentions two different type of errors a type error and a value error and then a short description of when we will see this error so the type error we will see if n is not an integer and the value error we will see if n is negative because as i said a negative factorial yeah it we would have to ask a real mathematician of what should happen then and then maybe we can find a way to make it work but for our simple example we will not accept a negative number and then you you may wonder you know how do you know what type of error you should you know raise raise is a technical term it means throw an error that's another word that basically means just give the user an error and then so for a beginner it's not so important that you write that you write down the right error here and whenever you are unsure of which error you should use you can always write simply the most generic one which is runtime error runtime error as we've learned in the in the very first chapter is an error where syntactically the code is correct but something other than this goes wrong some you know value is wrong or and something goes wrong some user import is invalid whatever so runtime error is the most generic type of error in this regard and type error and value error are more specific and as a professional software developer you should have good error messages so you should have correct error messages specific error messages but as a beginner if you write your own first couple of problems and you don't know what type of error to raise you can always raise a runtime error and let's see now in the code how we detect those cases well at first we have the first if statement the first the first branch it says if not is instance n comma int then raise type error and then we have an error message here which is a string so what is is is instance is instance is a function that gives you back either true or false and what it does is it takes two arguments the first one is any object you want so maybe let's see what the documentation says the Python documentation on on is instance if we go here it says is instance it takes any object so object basically means anything and class info is an object that we refer to in an earlier chapter as a constructor so we have the int constructor the float constructor the str the string constructor and so on and that's what class info is what the term class means we will learn later in the course but for now just use what we understand as a constructor and then it returns either true or false true in the case that the object is of the type that is passed in second and false if the object is not of the type of the second argument so let's go back into the slides so this reads as if int is not an integer then do this then do this branch and then what do we do in this branch well we raise raise is a keyword it's a the raise statement and the raise statement basically raises an error that's where the term comes from and then we just raise the type error we have seen type errors before and now we raise it on our own and then we put a custom message here where we say well user the factorial is only defined for integers and then the second branch the lf n smaller than zero it just checks if the user passed or a factorial was called with an n smaller than smaller than zero because we know that down here in the in the section that I just marked we know for sure that n must be an integer otherwise we would be in the race in the race um race type error branch here so we know that n is definitely an integer down here because of that I can check I can compare it to zero and if n is smaller than zero strictly smaller than zero I just say well raise a value error because the factorial is not defined for negative integers in our example and then in the next branch the lf branch this is the actual um the actual base case so the first two branches here they are really uh type checking and input validation and then the third branch is the actual base case the actual first branch you could also write you know sometimes you could just write if and maybe put a space an empty space an empty line in there to to show that up here we do the input and type checking and down here we have the actual code but syntactically the concept the most concise way to write this program is to just use three branches and then use the early exit pattern again at the end so if we hit the base the base case where n is zero then we just return one because the factorial of zero is one and in the case where we call factorial with an n that is an integer and also strictly greater than zero then we are down here then what we do is we return n times the factorial of n minus one so the logic remains the same all we do is we add the um type checking and input validation up here let's define the function and now what we can do is we can call factorial for example with zero it will give us one so it works we can call factorial with three which we did before we get back six because the factorial of three is just three times two times one which is six and then we can do the following we can call factorial for example now with a float 3.1 and what we get is a type error why do we get a type error well we specified it to be a type error and we also see our custom error message here factorial is only defined for integers technically speaking i could also call factorial now with some text and i will also get this message so any type any data type that is not in will lead to the type error here similarly if i call factorial now with an integer that is negative i will also get an error but now i get our value error that we specified because we said factorial is not defined for negative numbers for negative integers and this way we what we what we achieve is twofold well we make sure that a user does not use our function in the way we want him or her to they get a specific a custom error message however to be yeah however we should also mention that we are not really responsible for for for checking the input because we actually tell the user already hey give me an integer that is positive but if a user does not read the doc string then we are kind enough to not have our code run into an infinite recursion and we by raising an error so that's the second advantage of our of our new code that we prevent an infinite recursion the infinite recursion would be bad because if we ran this code on our on a production server then you know we we don't want this we don't run we don't want to risk that our production server would run into an infinite recursion it's just not a good thing so it's better to put in the input validation here even though it's four lines of code and even though it's documented that the user should not pass in a negative value but we just made we it's like a fail-save system here okay that now concludes the first part of today's lecture and as I said today's lecture can really be seen as two sides of the same coin and now the coin that is called recursion is now over and now we look at the other side of the coin and I call this looping sometimes the more technical term would be iteration but I prefer looping because iteration in a sense in my opinion is also recursion you know recursion is just looping from back from the back and looping is looping from front from the beginning so in both directions we iterate we loop so it depends on how we use the words and I personally prefer to use the word recursion if I mean I go backwards in a backwards direction over some problem and I prefer the term looping if I to mean to go and view a problem from the beginning to the end so what is looping first and foremost you have already seen a for loop you have seen many examples of a for loop and the for statement basically exists in almost any language now the for loop in python is what is called syntactic sugar so we don't really need it it's there to make some situations some common situations nicer for us the more generic way of looping in python and also in other languages would be the while statement so let's look at a while statement so what I do here in the second part of this lecture is I look at all the examples from the first part and I provide you with a formulation with a loop it could be a while loop or for loop so what we do here is look at the countdown example again from the beginning and we rewrite it using a while loop so now here's our revised version of countdown first what changed well we also do some input validation here so I included the races here in docstring I included the type checking here I included the input validation so in order for the countdown to make sense it must be strictly positive here and then we go to the actual code and the actual code is down here so anything that is above you can for now ignore if you want to understand the while statement only the thing down here matters so what happens if the function is called with valid input let's say n set to three if we count down from three to happy new year then what happens is this we hit the while loop the while statement and the while statement is very similar to the if statement in that it has a condition here so a condition means it's a boolean expression meaning it's evaluated and at the end of the day it returns either true or false and we've also seen before that in the case where a true or false is not returned then we python does what is called it what is called evaluating a non boolean expression in the boolean context and this is when we used the terms truthy and falsey in the last lecture so but here we get actually back a true or false so what is this this says while n not equal to zero and let's say I assumed that I called the function with n is set to three so that means n is now not equal to zero so the the result of the condition would be true what happens well just as we saw with the if statement the the while statement here has now an intended block so what happens if the condition is true is this code that is intended here is executed so what we do here is we just print out n so let's say n was three so we just print out three and then what we do here is we change the n we decrement it by one and that's important if we did not change the n inside the loop then this condition would never change it would be either only false or only true only one of the two cases it would never change and that would mean we would either never run this loop or we would always run this loop that would be kind of of an infinite loop example so it's important that we make sure that within the body of the while loop we do change some variables that are also referenced in the condition up here so that this condition potentially changes and by starting with a positive integer and I mean down here after the input validation we know n is a positive integer we know for sure that by decrementing any positive integer no matter how big it is by one in steps of one we will at some point hit the zero we will at some point hit exactly zero because we we know for sure it's not a flow we know it's an int it's bigger than zero it's strictly positive so at some point by executing those three lines of code the while statement and its body the n will hit zero eventually and what will happen then well first we started with n is set to three so after decrementing n the first time n is two so then we compare here two it's not equal to zero and two is not equal to zero is true so we will go through the loop again print out two decrement n again then n becomes one then we are up here then we compare one to zero and it's not equal to so the condition is still true we print one and then we decrement n one more time and then now it's zero and then the first line will say zero not equal to zero and zero not equal to zero will be false therefore the the condition is false and what happens then is once the condition is false that the false statement is over so the first time this condition is false we will basically jump to the next line of code after the while statement so also note that if this condition were false the first time around we would never ever even execute this body here and but however this cannot happen here because the case where n is equal to zero is already excluded up here so we know for sure that n is strictly positive that means we know that the while statement is executed at least once and then what happens is after the while statement all we have to do is print happy new year and conceptually what we see here is that the base case and the base cases in the recursion examples were usually in the beginning of the if else logic we would be we could also reformulate it so that the base cases go always last this is always possible you can always exchange the order of an if and else statement by just negating the condition but usually in recursions the base cases go somewhere somewhere in the front and in the looping version the base case usually goes somewhere to the end that's a difference but other than that that's it that's the while that's the while loop that's all you need to know so the only thing you need to know is we write while a condition so along as the condition is true we execute the body once the condition is false we don't execute it that's it and now and then of course if I call the new countdown function again I get the exact same output as before let's see one difference in memory so here in python tutor I have here the looping version the while loop version of countdown and let's go in and execute it well first we create the new revised version of countdown in memory and then we execute it and then what happens is the function is called and one box is created where n is set to three and now we hit the while loop so it says while n not equal to zero print n so n is not equal to zero so therefore we print n we print three now we decrement n by one and becomes two we see this down here the while the y line starts running again the condition is still true therefore we print n another time we decrement n another time and is now one and we do this one more time print the one and now n becomes zero and now we go back to the while to the header line of the while statement one more time however now the condition is false and because of that we jump right to the next line of code to the print happy new year line now we print happy new year and then the function returns and it's gone one important difference to the recursive recursive version as I go back to the recursive version and we do this again the recursive version as we saw now here is a box for the first function code where n is set to three we'll now call the function another time and now we have a second function code where n is equal to two so remember in the beginning of today's chapter I told you that the recursive version of an algorithm will usually lead to several simultaneous function calls in memory so we know that python here has to really keep them separate so python has a lot of work to do actually so it has three function calls happening simultaneously in the recursive version but if I go back to the to the iterative version to the looping version and I run through this one more time I don't see this I only have one box here so there is a big difference in memory and this is usually a rule that generalizes usually a recursive version of any algorithm requires a lot more memory handling by the programming language there are languages that optimize this but python doesn't and so here the why loop has a big advantage in the memory we only need one function box only one local scope and this is not a big problem as long as we don't have lots of data but if we have if we work with very big problem instances then maybe we have to maybe then then maybe the while version would be better in memory because it only needs one local scope but these are subtleties for now since you are learning recursion today and also looping today it's not so important to focus on the differences in in memory it's important to understand two ideas and again the ideas the big ideas are you can calculate most problems once from the beginning and once from the back and usually you should end up with the same solution remember Euclid's algorithm the one that you know I formulated with the modular division and that was very hard to understand so I couldn't see myself when I saw at the first time why this works actually and Euclid back back in the day he knew that it works now let's look at Euclid's algorithm and formulate it with a while statement so up here the doc spring is only amended with the races part here we have the input validation and the type checking and then we have Euclid's algorithm here and it looks totally different I'm not going back now but if you went back to the recursive version it's way longer here the recursive version is a lot shorter and then also it's not so easy to read so it says well I try to find the greatest common divisor of two numbers a and b and as long as a and b are two different numbers what I do is I basically subtract the smaller one from the bigger number and yeah that's it and I keep on doing this until a and b are the same so I'm slowly decrementing the two numbers alternatively until they both are the same and this number that is then the same when a and b are the same this number is then the greatest common divisor I also have troubles to see why this works at least in the short run if you think about this I think it will make sense but this problem even though it works and is correct it has one other problem let's see what it is let's see let's define the new greatest common divisor function let's call it for 12 and 4 we know that the answer should be 4 because that's the greatest common divisor and now also just to send eject the function let's call it with two prime numbers if you call it with two prime numbers the output would be one another example for prime number would for for example be seven so the output would still be one so whenever we pass in two prime numbers we get back one as the greatest common divisor that that is to be expected and then let's look at how efficient this algorithm this while this algorithm with a while loop in it works as compared to the recursive version remember that in the recursive version I calculated greatest common divisor for those two numbers and it was very very fast and the fact that I time it here with only one iteration of the cell with only one execution of the cell should also already make you think that this will take some time and let's see and it does take some time and hopefully it will give me back the same result well we don't see the result it's suppressed because of the timing but the function works it's correct but what we see here is that now the while loop version the iterative the looping version is now very inefficient and the recursive version is super efficient so when I did this lecture the first time a student came after class and said well is it correct that recursive version even though it may be easier to write it down because some problems are just easier to formulate in a recursive way is it generically so that recursive implementations may be slower than iterative versions so in other words the students asked me is it always better to use while even if it seems more complicated and the answer as we can see here is no it's not so easy to say sometimes the recursive algorithm is faster sometimes the while version is faster so it depends and it really depends on the concrete case and in both cases however there are additional tools that we could use to make to improve the algorithms to make them to make them faster but those tools require us to know some stuff that we don't know yet so I cannot talk about this here but for now what we summarize is this when we have a problem we can often look at it from the beginning and from the end and it's not so clear which way helps us to calculate something in a faster way there's always a theoretically a correct answer to this but we look at it from a pragmatic point of view from a programming point of view and with the tools we have learned in python so far we cannot always tell in advance which is the best version now another topic infinite loops so we have seen infinite recursions before and now there's also something that is very similar to that namely infinite loops and whenever you hear the term infinite it's usually not good it usually means that you have miscalculated or misprogrammed something but you but let's look at an example where we don't know if we did a mistake or not now you may wonder how can I not know if the code that I write is correct or not well let's look at an example it's called colas conjecture it's a mathematical problem that has been known for centuries now or for at least quite some time and if you can answer what I'm about to tell you you actually will win some prize money and I think it's a lot of money actually that you can win so even very smart mathematicians over a very long time horizon have not been able to solve the problem that I'm about to tell you here so let's play a game the game is this you think of any positive integer call it n and then there are two rules well the first one is if n is even then you take your n and you divide it by two and then you have the new n if your n is odd you multiply the old n by three and add one to it and if an n is odd and you multiply three it will be still be an odd number but if you then add one it will be an even number and if you divide an even number by yeah if you divide an even number by two or by yeah by yeah by two then sometimes you will get an even number sometimes you will not get an even number so what we see here is independent of the number n that you think that you think of to start the problem with we will get a series of n's that go from even number to odd number to even number and so on so it will always iterate back and forth and then the question the mathematicians ask is if you play this game will you always reach one and what we also see here is if n is even then the next even is defined to be half the old even to have the old n and that means the n is getting smaller but on the other hand if n is odd and then we multiply by three and add one to it then that makes n bigger right so one step makes n smaller one step makes n bigger so the question is will this you know these steps always lead to the n equal to one and if you can prove that you will make some money because as I said if you can prove that mathematically there is a price on it so let's look at this game in Python code I define a function called collabs it takes one n the one n it takes the argument is of course an integer it must be positive and it's the number that you think of to start the game with then we do some input checking so we check if if n is indeed an integer and strictly positive and then we write code that goes like this while n is not equal to one because that's what the assumption we want to check then we do this if n is even that's if n modally divided by two is equal to zero or in other words if n divided by two has no rest then what we do is we divide n by two and I use the double slashes here to preserve the int type you remember if you divide an int by let's say two you will get back a float but if you divide an integer with the double slash operator then whatever remains will be an integer it may not be the exact mathematical solution so if you write seven double slash two you will get three so it will be rounded down so the decimals will be cut off but if you know ahead of time that a number is perfectly divisible by two that's what we check here for so it's if we know a number is even then we can use the double slash operator without error so we will get back the correct mathematical result and the type will remain an integer if n is odd we multiply by three and add one to it that's it and then in in each iteration of the loop we print n and yeah since it will be lots of n's I print them on one line and then if the loop ever ends and we don't know this then we also print the last number so print n here is really print one because down here we only reach this case the base case if n is actually one so let's let's define the function and let's call it let's say starting from 100 so 100 is divisible by two so the next number will be 50 50 is an even number it would be divisible by two it would be 25 25 is not even so we multiply by it by three to get 75 and add one to get 76 so so let's see if that's the output we see so as I just said we started 100 divided by two divided by two and then have we have 25 this is times three plus one 60 76 divided by two 38 and so on and then if we continue with these rules we will end up with the one that's what the people want to show but that's what nobody has proven so far do we always reach one so now what happens if I if I call call us with let's say 1000 as a start value well the series will become a lot longer but as we see at the end we will also reach the number one now the question is this what happens if I call call us with 10 000 now intuitively the series should get longer right so if 100 leads in these many numbers and 1000 leads to these many numbers then 10 000 should lead to a lot more numbers right unfortunately this doesn't work the series will become shorter however it will still end with a one so the fact that different starting numbers will result or have no no real implication on how long the series is the call it series this is this makes it very hard for mathematicians to prove that it always reaches one and so far nobody has proven it so that's the problem I go back in the code one more time that's the problem where we don't know if this function will end in also never so what you could do is you could write a big big for loop and you could loop over let's say I don't know 10 billion numbers and you could call call us with all the numbers in this for loop and see if it always terminates and what you will see it will terminate all the time it will just take very very long to do so and also we don't know because if we if we test this number for big big ends we don't know what happens for n plus one right so that's what mathematical that's what mathematicians do they want to prove it with analytical tools without using concrete examples and so far nobody has done that so this here is more of a theoretical question however the question is important because there are some loops some why loops that we don't know ahead of time if they terminate or not and we cannot even prove it okay so finally we introduced the fourth statement and now I already told you the term syntactic sugar so what is syntactic sugar syntactic sugar means that python provides us some syntax that we some some some something that we can use in the language that is not really needed and what do I mean by that so we have seen two ways of looping we have seen recursion and we have seen the wild statement the wild statement allows us to build any loop we want and the fourth statement is really just a special case of a wild statement and the special case is only there to make it shorter for us or more convenient for us as the programmers to write certain loops certain loops that appear frequently in programs but we really don't need a fourth statement so that's why if the fourth statement is syntactic sugar so let's look at an example I define elements to be a list of the numbers zero one two three and four and now I want to loop over them and we have seen how to loop how we loop over numbers in the list before with four but now let's see how we can actually loop over those numbers with a wild statement so what do we have to do well at first we have to initialize a variable called index at zero and then because why zero because we start counting at zero and in order to get to the first number we would have to write elements and then index operator zero so maybe maybe here I can show it to you I put in one more line of code let's say I defined elements in the way we did above and now I can use the index operator let's say with index zero now the autocomplete got in the way elements with an index of zero gives them back zero elements with an index of let's say three gives me back three so it's just by accident that the index will result in the number of the same in the same number so if the first number were 99 then of course index zero would return 99 as well right so just to reiterate how indexing works now we have to do this indexing in every iteration of the loop how do we do that we need an index number that's why we set index to zero and then we say as long as the index is strictly smaller than the number of elements in in the list so the number of elements is the which we just use we just use the land built in function which gives us back the number of elements in the list so five so as long as the index is smaller than the number of elements in the list what we do is this we get the next element in line by indexing into the elements list so elements plural index we index into it and we get out the next element and store it in a variable element singular right and then i print it i print the element because i want to see it and at the end in the last line of the while loop i increment the index by one and that makes it makes the index one in the next iteration and two next iteration and so on and so once the index once we hit index equal to four which is the last index in the in the list this is the last time when the condition is true so this would be the last time this body would be executed and then at the end when index is increment one more time index becomes five now so index is now equal to the length of the elements and because it's equal to the length of the elements this condition would be false in which case we jump to the end so that's basically a for loop in a more complicated way and then of course the index variable we didn't really need right it's a variable that we don't want there actually it's only we we needed there to index into the list but we don't really want this variable to exist so what we do we just delete it we de-reference it you know let's execute this and what what gives this back is basically it prints out the individual elements in the list so now how are we used to do to to do exactly this with less lines of code so we are used to loop over lists with using the for statement by saying for element in elements and note that this the variable in the big in the in the frontier can be named anything and then we write the body of the for loop and notice how this the body of the for loop here the print element is exactly the same as this so in other words in order to mimic this one line of code we need one two three four five lines with a while loop so in other words the for loop saves us four lines of code and that's all the for loop does that's all the follow pass the for loop has no other no other thing it can do for us and just to illustrate a point this variable I called it element singular but now let's call it x it also works and of course it makes sense to always use a name that makes sense here and when we loop over element plural it makes sense to call this variable element and the variable in the for loop in the beginning here we call this the target variable so in a formal language the what we are looping over here is yeah it's the object is the is a so-called iterable we will come to this later today this is what we loop over and the element that we we get in the in every iteration of the loop that this is what we call the target variable and that's how looping works and this is how the for loop is only a special case of the while loop in other words the for loop is syntactic sugar for the while loop in other words we don't need the for loop and whenever you write the for loop in python what you really do is you write this it's as if python wrote this for you that's it okay so that's the for loop and of course we will use the for loop heavily because most a most iteration logic we will implement in scores is rather simple and the for loop works and so we always want to make the code look nice and be readable and which is why we prefer to use the for loop over the while loop but again remember that it doesn't add any functionality that's the that's the definition of syntactic sugar something that makes a code to be used nicer it makes it more readable but it does not provide any new functionality so another side here that is important if we look at if we look at the previous example i defined a list in memory and then i loop over this list so what the first line up here does it creates a list element on the right hand side and in memory and then it takes and then it creates five integer objects and then inside the list it will put five references to those five integer objects so in total this this expression here will evaluate into six objects in memory one list object referencing five integer objects and then we get then then we create an additional reference from the elements variable to the list object so we have six references six errors in the um in the diagrams i used to draw so and the important thing is they exist simultaneously so the six objects here they exist simultaneously now whenever we want to iterate over a simplistic sequence of integers the range built in can help us so what is the range built in so the range built in if we go back here to the documentation we have we see that the documentation exists twice so there's um there are two different ways of using it the first usage for ranges we just pass one number one argument called stop and the other one is we use either two or three variables called start stop and step and um this is um and what what range does it helps us to loop over the the sequence of numbers without and that's important without creating all the numbers simultaneously so while as i said this list here creates six objects one list object pointing to five different integer objects hold that meaning there are six objects in memory at the same time what this here does the range five only creates one object in memory that knows how to produce the next one in line so when i loop over the range object i have at all times i only have two objects in memory namely the generic range object which is kind of like a rule that knows how to create the next object without creating it and then the for loop which basically pulls out the next object in line one by one so here uh the range the range five object gets created before the loop is executed and then in every iteration of the loop a new number is created so we have a lot of less memory usage in this in this formulation and then of course if i execute this i also loop over the number zero one two three four just as if i looped over them in the list but again the second version is a lot more memory efficient and in a way it's also syntactic sugar but it's not really syntactic sugar because the range type it really gives us a new functionality which means to loop over something that doesn't exist in memory yet so what does the five mean well five means give me back five integers and as we start counting at zero in python the first number we get out of it is zero and the last number that's also common in python is not included so that is why range five gives gives us five numbers starting at zero and then excluding the five of course so what is what is range five well it's of type range object again um as i said it's an object in memory that um all it does it it knows how to calculate new integer objects without calculating that and the for loop basically forces the range object to spit them out one by one that's what how it works so now in some special scenarios we may want to loop over numbers in a different pattern let's say for example i want to look over the numbers one to ten and jumping over every second number and that's why the ten is left out here so um how do i do this well i could write out the list i could write one three five seven and nine i could loop over this and it works so i loop over them i print them out how could i do could i do this with uh with range well i provided a start value which is one now if i don't provide a start value as we've seen the start value is set to zero but here i want to start um i want to start looping over the number one and i want to um loop until ten but excluding ten so the the lower limit is always included the upper limit is always excluded and then the third argument is called the step size so i want to get every other element so this range object gives me back the same sequence of numbers but again the upper version generates a list object with references to five different interobjects so there are six different objects in memory at the same time down here as we only have at most two objects in memory at the same time one range object and then only in every iteration of the for loop we get a new integer object that is forgotten immediately after the loop is over so the second version again is a lot more memory efficient so the thing here maybe i go back here the thing that you should ask is well wait a minute if you have any experience with some other programming languages you may know that you know it's not so common that in other programming languages you can loop over different types of things so in particular here i loop over a list object and here i loop over what we what we saw to be a range object so it's two objects of different types and whenever i loop over something of a different type the question you should ask is why does this work so obviously the for loop does not require the the object to be looped over to be a list in particular it does not require the object to loop to loop over to be anything any specific object the only thing it requires is that the object is being able to be looped over and that is the definition of what in python is called an iterable that's the technical word iterable means any object that has a type or is of a type that can be looped over is by definition and iterable and a similar concept is the concept of a container and what a container means is basically any object that holds references to other objects so to say it contains other objects and these are two different attributes that any type may have or may not have and oftentimes they coincide as we will see but they are different concepts and let's see an example and what i'm about to tell you is similar to the discussion of callables in the functions chapter and remember that back in the chapter on functions i defined a callable to be any type that we can call and then we saw three different types that can be called namely a built-in function we saw the constructor and then we saw the user defined function so three different concrete examples of what is callable and callable again is the abstract concept the thing that in abstract sense the three different types share and just like here just like the difference between the abstract concept and the concrete type back then we also do this the same or similar type of analysis here so the example goes like this we create a list the list now it's called first names and it holds five german names and now i can ask the question hey is the the name ahim in the list of first names and note we have not seen this before i left out the four here so it does not say for ahim in but it just says ahim in first names so what is in here well in is an operator so it's not the four statement here so the in in this in this example has a difference in tactic meaning it means it's an operator it's basically just like the plus sign in a way the plus sign the minus sign and the model division all these are the the parentheses when we call when we call something these are all operators and just like they are all operators the in we should also view it as an operator and what does the in operator do well it checks if the left hand object is contained in the right hand object and in other words it's a boolean expression it gives us back a true or false if the left hand side is contained in the right hand side and in this case this is true and then of course if i check another name for example alexander this is not included in first names and the in operator that is that is shown here is the characteristic operator of what is of everything that is considered a container in python so whenever we can ask the question does something contain something else whenever we ask this question the thing that contains other things is called a container and that's an abstract thing and the list a python list object is a container it's one example of a particular container we will also see other examples and i can already give you away one more example so for example the text string achim as we saw here here's just achim this itself is also a container because we can ask the question is the letter let's say lowercase a in achim and the answer is of course false but if i ask the question is the uppercase letter a in achim then the answer is true so not only is a list so the brackets not only are lists containers but strings itself they are also containers and we'll see many many more examples and but the important idea is that the fact that the the fact that the in operator works only relies on the type only on the property of being a container it does not rely that the right hand side is a list or a string or something it only relies that the right hand side supports the container property so it's an abstract concept here and then similar to the container property there's the so-called iterable property and iterable all it means is it asks the question can we loop over it can we loop over something and whenever we can loop over something for example with a for loop but also with a while loop whenever we can loop over something then we say it's an iterable so here i can of course loop over first names now this seems trivial but i'm not emphasizing the for loop here and i'm not emphasizing the fact that this is a list here i'm emphasizing the the the property of being iterable and we will also see other things that are iterable maybe i give you another example already just to make the point so we've seen the name ahim the question is can i iterate over the name ahim the answer is for character in ahim and then i write print character and let's end it with an empty string and it also works so i can also iterate over a text string and that's important to know so i can not only iterate over lists i can iterate over many many different things among which are also strings and when i iterate when i loop over strings i loop over the individual characters so we have seen both lists but also text strings they are both containers and they are both iterable and now you may wonder well doesn't this go together and the answer is yes most of the time those two properties they go together but then the question to ask here for example is another question to ask is when i loop over something for example i loop over the character do i loop in order in order so does the a come before the c and so on and the answer is for strings yes there is an order and for a list there is an order too so ahim in the list comes before batholz so being having an order is also a property and we will see things that we can loop over so iterables that are not ordered and so the important idea here is i want you in the in the past couple of weeks i told you the story like this i said something is anything is an object in python and every object has a type and the type tells us what behavior the object supports so in other words we used types to classify objects right to ask the question what can they do what can they not do now we look at types and we try to classify the types and when we classify the types then what do we classify them into well this is what i mean in my lecture here by abstract concepts so just as we classify objects into types we classify types into into abstract concepts and two concepts that we learned in this chapter today are the concepts of being a container and the concept of being an iterable but there are many many more concepts to come and we've also seen one other concept before which is the concept of callable so why why do i teach this well i told you that one of the goals that i have by teaching you abstract concepts is to answer the question does this help you does knowing the concept help you to read the documentation in a better way so for example let's go to the enumerate built-in enumerate takes as we see a so-called iterable and what does it do it returns an enumerate object we don't know what this is but iterable must be a sequence sequence is another such a word an iterator or some object which supports iteration so it's very abstract here and i want you to at the end of the course be able to read this documentation and understand it and so as you can see the word iterable here is important so we want to call enumerate with an iterable and we have learned that now a list like first names is an interval so we can pass enumerate an iterable like first names and then what enumerate does it instead of looping over only the elements of the list so only the names in this example we get a second variable which is an index so whenever i use i the gen is very high that the variable is supposedly an index variable and if i execute the next code cell what i do is for every iteration of the loop i get now two variables namely first and index i and second the name so the index zero points to akim because it's the first element in the list one points to betwood because it's the second element and so on and we can also use the start um the start keyword here by and specify let's say start equals to one and then we can start counting with the index variables at one so enumerate um it only it helps us to get us an index variable in other words going back to the original while example that we had a little bit earlier today i told you that the for loop is nothing but syntactic sugar for a while loop and in the while loop we used an index variable and usually the rule is this whenever you want to use an index variable in python most likely you're doing something wrong because python makes it very easy to loop over things without an index variable however in the rare cases where you still need an index variable you can easily get one you can always get one by just using the enumer rate built in because the enumer rate building can just be put around everything about about anything that we can loop over and it will give us back whatever we are looping over anyways the name but also an index variable so it's a very cheap of a thing to do to always get an index variable but usually you don't need the index variable so in the rare cases where you need it um always use the enumer rate and i don't want you to see i don't want to see you to manage your own index variable to do something just as we did with the while statement whenever you do this genesis are pretty high that you will mess up the code you will you know mess up the index variable you will have a one by an off by one error or something so it's always easier to loop without index variables by using a four and in the rare case where you do need an index variable for example for printing an out or something um then just use enumer rate so let's go on now that we know what the fourth statement is and how the fourth statement works let's look at some other examples and rewrite them from the recursive version into a looping version an iterative version so let's look at Fibonacci numbers again so remember the big problem we had with Fibonacci was that we tried to calculate the Fibonacci numbers from right to left and this resulted in an exponential growth in function calls so that was bad so the easier way would of course be to calculate Fibonacci numbers from left to right so let's do this how do we do this well we just write a new Fibonacci function we also put in now some input validation because we now learned how to do this and then down here we put in the entire logic so how do we calculate the highest Fibonacci number let's say for example the 13th Fibonacci number how can we do this well we do it like this we have two variables a and b we set them to 0 and 1 why why do we set them to 0 and 1 well because 0 and 1 are by definition the first two Fibonacci numbers they are given there is nothing to be calculated here and then we um we loop from left to right and how often do we have to loop if we want to calculate the 13th Fibonacci number then i would be what i would be 12 right because the 13th Fibonacci number we start counting at 0 so i would be 12 so how often do we have to loop well in this example we would have to loop 11 times why because we already have the first two Fibonacci numbers so in other words in order to calculate the 13th Fibonacci number if we are already given the first two by definition we only have to calculate 11 more Fibonacci numbers this is how this is what explains the range and the i minus 1 here and then what we also see here in the for loop is an underscore and that's the convention that means whatever we are looping over so the numbers 0 1 2 and 3 because that's what the range spits out we don't need and whenever we loop whenever we get back some variable and we don't really need it well then it's a good idea to just so to say throw it away and the convention to do that in python is to use an underscore so don't be confused by the underscore the underscore just means the variable here is not really useful so we don't need it in the body here and then also note that the print here the two prints uh print function calls here i only put them in so that we can see in the immediate results so we can actually pretend that the print and this print here that they are not there this is just to print intermediate results and then what do we do in the for loop well we do basically what we just did in the beginning when we went uh the when we went from left to right and calculated the Fibonacci numbers in our heads so with a being zero and b being one how do we calculate the next Fibonacci number well the next Fibonacci number would be just a plus b right because every Fibonacci number is just the sum of its two predecessors so the next number would be a plus b and here what i do is i store the next number the next Fibonacci number as temp and what do i do then well then i set a to b so i set my my my second predecessor to b so the first one and then i set b to temp so what do i do here really well what the for loop really does is it takes the two last Fibonacci numbers starting with zero and one in the first in the first iteration of the loop it calculates the next one and then it resets a and b to to be the current one and then one before so this way we always we go ahead from left to right and we always um calculate the next sum in line the next sum and again if if for example we want to calculate the 13th Fibonacci number given the first two numbers by assumption we only have to iterate 11 more times and then the b at the end that remains after the for loop must be the Fibonacci number we look for so let's see if the function works i define it and now i call Fibonacci of 12 again which is the 13th Fibonacci number and i get back 144 as before and now here we see the result of all the prints that i put into the function here the print here and the print here i see all the Fibonacci numbers from left to right in increasing order and um yeah that's um so in other words if i calculate Fibonacci from left to right i don't have the problem that i run into exponential growth of function calls in fact i don't even have many function calls i only have one function call because i don't see any call to itself here right this is not a recursive version that's an iterative version so there are no more there's only one function call and um that's it and now let's um so let's uh go ahead here and look at um what happens in the Fibonacci call in the recursive in the iterative version so let's go back to Python tutor and here i copy paste the function that you just saw including the prints and uh let's run it so what happens here is i create the function first i create the Fibonacci name pointing to the function object and then i call it i call the function with i set to five and what happens inside the function corner well a and b are set to zero and one right and then the loop starts and then also the zero and one are printed out here but that's only for you know intermediate output and then uh the for loop starts to run and the underscore i told you we just ignore it right so whatever is here in the underscore let's just ignore it we don't need this and then we calculate temp what is temp temp is the next Fibonacci number in line so zero a zero plus b is one is temp is equal to one right and then i go ahead and i change a to one so the a comes from the b so the previous b now becomes a and temp then becomes b and because b and temp were both one we don't see it then i go and then i print it out to print one and then i go in the next iteration of the loop the underscore was changed either i ignored i don't care and now temp will be two why because one plus one is two the next Fibonacci number and now i set a to the previous value of b which i don't see because one and one are the same and then i take the two here and put it in place of b so here and that's it and then i print it of course but then i go in the next for loop in the next iteration and then temp will be three y three well one plus two is three and then the two from the b will move to the a position and then the three from the temp will move to the b position and then in the next iteration of the loop i can just add two and three to get five again so this is how Fibonacci works and this is how we see that there's only one function called in memory and we only keep track of the last two Fibonacci numbers and by only keeping track of the last two Fibonacci numbers we are always able to just calculate the next one and then we we we reset the two numbers that we keep track of and then we can calculate the next one and so on so this is a very almost trivial way to calculate Fibonacci numbers and then once the function is over it returns five that's the result and we see up here the print output is just debug information so the actual result will be five okay let's go back to the script and that's the iterative version of Fibonacci way more efficient and now of course we can contrast this with the recursive version so the 100th Fibonacci number back with the recursive version that we saw before we could not calculate this but now if i calculate Fibonacci of 99 so the 100th Fibonacci number in the iterative version it works it's a very big number we see that Fibonacci grows very rapidly but at the end of the day it took no time to calculate it okay one more time let's look at the factorial example and let's also calculate it from left to right so how do we do how do we do that for the factorial well first we update the doc string we put in the input validation down here we can assume that n is definitely non-negative so n is yeah n is basically allowed to be zero here but this doesn't have any negative effect down here and then we do this we set product equal to one and why do we do this well because zero faculty factorial factorial of zero is defined to be one that's why we set the product of one and then we loop over we write a for loop and then all we do is we update the product we loop over a variable called i and i starts to be at one and then we loop up until n plus one and why do we write n plus one here well we know that in the range built in the left hand side the start value is included and the right hand side the n plus one is not included so if i want to calculate let's say n factorial so let's say three factorial i want to go and calculate one times two times three so i want to include three so therefore i just write n plus one i make it one bigger so that the n in my example three would also be included so if n were three i would run through the for loop exactly three times for one for is one for is two and for is three and then three times i would just update the product by just taking product times the current product times the next number to multiply by so what i do here is effectively in the recursive version of factorial i calculated the factorial from right to left so three factorial would be calculated as three times two times one which is six and now i do it in a forward way so three factorial here would now be one times two times three so i just change the order i calculate from left to right but nothing else changes and the nice thing here is if i do it in the forward way i can also put in a print a call to the print function here and so i can also track the intermediate output the intermediate product conceptually what we do here is in the very first example in the first chapter we calculate a so-called running total right remember what i do here is i calculate a running product so to say so it's kind of kind of very similar here let's define the function and let's see if it works factorial of let's say let's use something smaller let's use the factorial of three it would be one times two times three and then here we see the intermediate result so factorial of one factorial of two factorial of three so if i calculate factorial of 10 just as it said before um the factorial also grows very fast and the tens number here the tens iteration of the loop is the return value as well so um yeah that's it that's calculating the factorial from left to right okay so what have we done so far today we've learned the recursion we have learned the while statement and then the fourth statement and i told you that the fourth statement we really didn't need to learn right the fourth statement is syntactic sugar for the while statement and just as the fall loop is syntactic sugar for the while statement i will show you for the remainder of this lecture today some more syntactic sugar that makes working with the fall loop even nicer for example the break statement so let's look at the following example i will give you shortly um a list of numbers that you already know the numbers from one to twelve unordered and i asked the question can you tell me if at least one number in this list of number has a square that is above 100 so that's the example so let's look at the code here is the numbers list that you know from the very first chapter on and the question again is is any number in there whose square is greater than 100 how could i write code that checks for this here's a first example using only constructs in python that you know about so what does the code do first i i set a um variable threshold to 100 that's the value i want to check for so i want to check is any square of the number of any number in here greater than 100 then i uh initialize so-called indicator variable called is above to false so i so my my basic assumption is no number is greater than 100 or no numbers of square is greater than 100 that's my assumption here and then i loop over all the numbers and i print them the printing is only there so that we can follow the code as it runs and then i do this i take the square of the number and i check is it bigger than threshold if so i set the indicator variable to true so um yeah so whenever i find a number whose square is greater than 100 i set the is above variable to true and at the end there is a third section so to say in this code cell which only checks is above true or is it false and depending on if above is true or false the output will be either at least one number square is above 100 or no number square is above 100 let's execute this code and of course the answer is at least one number square is above 100 and it's the number 11 for example but also 12 and 100 would not be the case because it's exactly 100 and that's not strictly greater than 100 so what is the problem with this code there are two problems the first one is a real problem the second one is a problem that has to do with code style and code beauty and readability so the first the real problem is this even though the 11 the second element in the list is greater i have a square that is greater than 100 i run to the end of the list so independent on us knowing the result of the calculation already after the second of 12 elements we have to do the calculation for all the 12 elements on the list that's inefficient so our calculation has takes 10 steps that are totally not necessary that's a that's a big problem imagine this list where a billion a number is big right so this would take a very long time and then the second problem that i have with this code is the code conveys one idea it should convey one idea because it's like the question is you know what's the task the task here is to check if at least one number is greater than 100 and what i do here is i have to write three sections three conceptually different sections in the code so at first i have to initialize some variables i need them without them i couldn't run this then i run the actual follow up and the actual logic that does the checking and then i have third section that only checks what was the result and has a different output message so the code is separated into three different sections even though i only do one with thing really i'm only checking if one number is has a square that is greater than 100 so i'm checking only one thing but i have i have three conceptual steps here and i don't like that so the message of what the the program does is actually is not so easy to see so how can we do it how can we improve on those two things that we don't like the first one is very simple so the first time i see a number whose square is greater than 100 i already know i already know that i don't have to go to the end so what i can do here is i i set is above the true and then i write the word break break is a statement and what it does it breaks out of the fall so whenever python hits the break it checks inside which loop it is the closest loop inside in the most enclosing loop inside it is and it breaks out of it in other words if i run the cell we see after we hit the 11 in numbers we stop looping so that's an improvement so our runtime is a little bit better at least um uh at least on average now if i have 1000 if i check if the number if the square of a number of at least one number is above 1000 i still go to the end so but again if i have if i check for a square of a number who is who fulfills the condition then i might exit the loop um earlier so i have improved one aspect of the code and now let's improve another one and i do this by introducing another concept in python that does not exist in many other languages it's called the four else clause and we know else clauses already um else clauses go together usually with an if statement but if we look at this example here what we see here is i still have my initializing up here so i still set threshold to 100 but i don't set is above here so i i skip this this was there before so if i go back there is a is above here this is now gone so i have uh eliminated 50 percent of my initializing code and um then i loop over numbers and then i check again and i i set the is above variable to true if i find at least one number whose square is above 100 and i break out of it and then i say i see the word else and usually else goes with an if but note the intendation is correct here so four and else else they go together it's on the same level of indentation it's not this year we don't have this year on purpose we have this and what the else clause does it is only executed if i run to the end of the loop in other words whenever i go to the loop to through the entire for loop and i don't go and i don't hit the if statement if i don't hit the break statement here if i don't enter the if condition only then do i go into else so that means conceptually speaking the else here still goes together with the if here syntactically speaking the else goes together with the for conceptually speaking the else here goes together with the if here so either i am in this clause or i'm in this clause i cannot be in both clauses when in at the same time it doesn't work in the same run of the cell so and that is why i can initialize or i don't have to initialize the is above variable i can put the is above equals true and the is above equals false right either here or here if i run the cell i get the same output as before after i check the 11 i know that a square is above 400 if i put 1000 here i run to the end and it says no number square is above 400 so the code is still correct however it's a little bit nicer now in that i don't have to initialize this indicator variable before and now the question is how can i make this code even nicer well i don't like this ending part here i would preferably like this thing to be in the for statement as well how do i do that that it's very easy i just copy paste the print um the cost to the print function inside the if and the else clauses and i break out here so in this case what i do is i eliminated the need for the is above indicator variable i don't need this indicator variable and technically speaking i also don't need the threshold 100 here i could hard code it either here if i you know have if i could afford hard coding it or i could parameter parameterize the fact this into this logic into a function for example but this isn't really uh initializing code here this is just um what i'm looking for so now why is this code nicer well first it's nicer because it still has to break so it's still efficient it still does not go to the end once it hits uh an element that fulfills the condition and second um i convey now the idea that this statement the four l statement it goes together so now i convey the idea to the reader of the code that this is really just one thing i'm doing it's one task i'm looking for one number in one list it's not three steps i'm only doing i'm doing one operation conceptually speaking so this code in the long run will be a lot more readable and again the else clause doesn't exist for four statements in many many languages but in python it does and i think it's a very nice thing to know how to use it especially if you also know how to use the break statement and this makes some writing for loops that search something and searching something in a sequence of something is a common theme that you will do when programming and this makes searching something very easy okay let's do um let's review another statement that is related kind of to the break statement but it's basically a different statement it's kind of like the opposite almost so to do that i introduce another example it's an example that we have seen similarly before in the first chapter so let's do this let's look at the numbers from uh one to 12 in an unordered fashion so it's the same list of numbers that you're used to and the task is this i should calculate the sum of all even numbers so that that's something we've heard before after squaring them and adding one to the squares so again it's not just taking the uh calculating the average of all even numbers in the list it's um calculating something out of the numbers after changing them after adapting them after transforming them so let's take this sentence apart the task so whenever you read the word all there's a good chance that whatever you need to do in code is you have to loop so in this case we look at all the numbers in the list so we have to loop over this that's what the translation means even it says calculate the sum of all even numbers even basically means we have to filter something out in this case we have to filter out the odd numbers we have to do something only for numbers that remain after filtering and then what what it says here it says after squaring them and adding one to the squares so that basically means i shall not only just sum up the numbers that remain after filtering but i have to transform the numbers first and then sum them up and this is a technical term whenever you transform all the elements in a list before you do something you call this a map and a map in is just a fancy programming word for a function so what we do here is square and at one basically means take an x square it at one and that's your y in other words it's a parabola right it's the the function y is f of x is x square plus one and so it's nothing that you shouldn't know from math you know how to do transformations or maps from early in your math education in high school so that's what a map is and then at the end after we filtered out the numbers and after we transformed or mapped some of the elements the remaining elements what we do is in this case in this example we sum up all remaining and map numbers into one number and whenever you take many many elements and you basically summarize them into one number or into one element then what you effectively do is what is called reducing so these are the three technical terms you filter map and reduce and most of the things you do in programming when you work with data most of the operations you do in programming goes back into one of those three bigger ideas it's either a filtering of something let's say let's say you have some let's say sales data from a company and you want to filter out days that had some extraordinary event let's say you want to filter out maybe christmas eve because on christmas eve your your company didn't sell anything because it was closed so you want to filter out the outlier right so filtering out outliers is a very common theme transforming something or mapping something is also in the business context very often done so for example imagine you have a company a big company that operates in many many countries and the sales data from one country is reported in let's say jack krona and in another country the or in your main currency is europe is is the euro so maybe you want to transform all your jack krona sales data into euro okay that's mapping and then at the end you want to reduce something let's say you have individual sales all the sales of all the individual orders of a day and let's say you want to aggregate your data into sales by day or sales by week or sales by months or sales by quarter or something like this these are typical reduction steps so filtering mapping and reducing these are three steps that um are usually very helpful to think of and um there is a nice side benefit whenever you are able to um um to break down whatever your programming task is into these three different steps then it's very easy to build software with scales to um really big data that doesn't fit into a single machine because the filter map produce paradigm is really a paradigm as to how to organize big amounts of data when you do calculations in an efficient way so let's see the example in code so we see numbers again the numbers that we know and what do we do here what I do here is I initialize again a variable called total we've done this before then I loop over all the numbers and then I say if number uh modulo divided by two equals zero so if the number is even that's basic about the if statement here says then what I do is I take the number I square it I add one to it and then I add the square to the total to the running total and I have a print um a call to the print function inside here so we see intermediate results so let's execute this and what I see here is um the seven is an it's an odd number so we filter it away we don't keep it this the 11 is an odd number we filter it away the eight is the first even number we see and the eight is then squared and then we add one so we get 65 so it's mapped so the eight is mapped 65 we do this for all the numbers in here and then once we have the the numbers like filtered and mapped we just reduce them by in this case by just summing them up and we get 370 so that's a typical you know um use case of the map filter reduce paradigm so now what happens if you have several filters that's a typical use case so let's look at the example and adapt it a little bit and it says calculate the sum of every third and even number after squaring them and adding one to the square so the only thing that changes now in this example is that is that we have two filters right we have we have to filter for every third element only and we also only we only filter for the even number so we filter out the odd numbers here and the rest remains the same so how do we do this in code very easy well let's first take the numbers and then write a for loop and let's use the enumerate building that we just learned of as today and enumerate gives us an index variable so in this way I can loop over numbers on a one-by-one basis but I don't only loop over the number itself but I also get the index variable and now the task says filter away all the numbers or only keep the numbers that is every third number and only keep the even numbers in other words the seven and the eleven we don't even look at the eight we will look at because it's the third number the sixth number we look at the ninth number we look at the twelfth number we look at because we only look at every third number right and how do we check if the number is the third number well if the index variable divided by three has no rest so in other words we enumerate all the numbers we start the enumeration by one or at one and then whenever I modally divided by three is equal to zero we know we have reached a third number we have reached number three what number six number nine number 12 and so on then we have the second filter the second filter means we are indented we have one level of indentation more and what we do here is I check if number divided by two has no rest that means we only keep the even numbers right and then we go ahead and do what we did before we square the number we add one through it and we sum up all the squares so if I execute this line of code the first number that is kept is the eight it's mapped to 65 the next number that is mapped 12 why because it's the sixth number and it's even and it's mapped to 145 and the next number that is kept is the four it's the last element so the nine number nine here is not kept it's it may be a third number it's the ninth number here but it's not even so it does not fulfill both filters right so that's why it's filtered out but the learning is we can add many many more filters we can add as many filters as you want every filter we want to add is just another if statement right there's one problem with this approach though for every if statement we put in there our code the the code block here moves further to the right and you see this right dotted line here in my jubilee notebook that is the line that says here it's 80 characters and you remember the rule that we don't want to write more than 80 characters on the line and we do this not because you know we don't have any space on on our big screens these days but we do this to keep the code readable that's a measure to make sure that the code does not grow into too complex code that we cannot read anymore and also people especially programmers tend to like code or to read code preferably from top to bottom and not from left to right so we only want to keep our code as narrow as possible or as far to the left hand side as possible so how do we do this if we let's say if we want to add a third on a fourth filter what would happen is in this scenario the code will just move further to the right so what could we do instead instead what we could do is this we can write the same for loop and then instead of checking if a condition is fulfilled so here we check if the if the condition is fulfilled instead we check for the opposite we check if the condition that we want to filter for is not fulfilled and then what and then what we do is we write the word continue so basically how this reads is if the index variable i is not divisible by three then continue and what does the continue do well if we're inside one iteration of the follow the continue just jumps into the next iteration of the follow and then the same is true for the for the elif case here so i say if the number is not even then continue that continue with the next number so basically what what we do here is we we filter out all the cases all the numbers that we do not want to add in other words down here where is the body of the entire transformation this body is only executed for numbers that survives the filtering so for numbers that we want to keep in other words now adding new filters becomes very easy because all we do is for every filter we want to add we only add one more elif clause and the continue and then we can add as many filters as we want and our code does not grow to the right hand side if i execute this uh code cell i get of course back the exact same result as before with just the side benefit that the code looks probably a lot better at least in my eyes i think from a beginner's point of view you might ask yourself the question you know why can i not leave code like this well of course in the beginning you can you absolutely can if you're still learning but in the long run you should aim for code that looks a lot more nicer and is a lot more readable even for yourself and one of the rules that we learn to keep code readable is to just not cross the red dots here and to keep the code as far to the left as possible and the continuous statement may be helpful to do also when you read the code not only does it not go to the right it's also easier to read conceptually here because up here i have all the filters and i know um if any of the filter applies then the number will be skipped and then i know down here for all the numbers that are not skipped we do the following so it's a lot easier to read also from top to bottom here okay but again both the break and also the continuous statement they have no new functionality so they are also only syntactic sugar and now let's come to the last subsection of today's lecture and it's a lecture that i titled indefinite loops so we've seen infinite loops before that was the example of collabs where we didn't know or we couldn't prove if a function would actually return ever and now we basically look at a similar type of looping but it's a loop where we don't know when it will finish so it will finish we we know that for sure but we don't know when that's indefinite and when would you use an indefinite loop well for example when you deal with user input let's say let's play the following game we will model a coin toss so heads or tails from some some fair coin which has a 50 50 chance to reach either side and then we have user input and the user tries to guess what the computer what the computer simulation through or tossed and so the following game is random and it's random for two sources with the first source of course the coin toss is random the second source of randomness is the user because we don't know what the user of our program will guess and depending on the user guessing the coin toss correctly the program will finish and if the coin toss is not guessed correctly then the user will just get another chance to to to guess the coin toss and of course another chance meaning there is a new coin toss and it starts all over again so that's why this game is kind of like a loop that will end we know for sure it will end but we don't know when we don't know when the user is able to guess the coin toss correctly so let's see how can we build such a game first of all we will import the random module from the standard library and then just for good practice we will set a random seed remember that the random seed is basically kind of like the start value of our random sequence of numbers so that whenever we execute the following lines of code the random numbers that are drawn will be the exact same numbers this is important for being able to reproduce some simulation or some analysis okay let's go on and let's see how does an indefinite loop look like so first of all we will use a while loop because for a for loop we will always know how long it will run but for a while loop we can actually write down a condition and the condition may be true it may not be true but what do we do here well here I write while true colon so when is while true true well the answer is it is always true so while true is a pattern that you will see whenever we write a loop that we want to break out later on but we don't know exactly when we want to break out so in other words this block of code better had some break statement somewhere and it does without the break statements here this loop would indeed run forever and we don't want that we don't want an infinite loop we want an indefinite loop so what do we do in the first iteration of the loop we use the built-in input function which we as we will see in a moment we'll just ask a user to type in something and that is the user's guess so we will ask the user here hey hey user please guess if the coin come up came up heads or tails and then we will store whatever the user wrote whatever the user entered in the variable code guess and we can already see that input will basically return a string right you can also see this in the documentation of course but you can also trust me the input function will return a string and then what do we do then I write if random dot random and call it is smaller than point five so what this is the random dot random function as you remember it will draw a random variable between zero and one and it's a float so any number any any decimal number between zero and one is possible and all of the numbers between zero and one are uniformly possible so they are possible with the same probability so if I know that random dot random will give me back a number between zero and one uniformly distributed I know that if the number that I get back is smaller than let's say point five this happens with 50 chance so if I want to model a fair coin toss I just compare random dot random to zero point five I can add I can say smaller than or greater than zero point five it doesn't really matter so I chose the smaller case smaller than case and this basically this expression that is now highlighted this models a coin toss a random coin toss so in the example here in the first branch in the if branch I modeled a case where the computer um um tosses a coin and gets back heads the else branch does the opposite this is what is reached when the coin toss will give back us tails so again above the if branch is the is what will be executed if we if the computer through heads and down there is what is what what is executed when the computer through tails and then what do we do inside those branches well I check if whatever the user entered guess is equal to heads and if it is then the user guessed the guessed correctly and I will print out yes it was heads and then I will break out of the loop in the case where the user x um where the user guessed incorrectly I will just say oops it was heads so I will let the user know what I threw what the computer threw but I will also tell him hey um you guessed wrong please guess again and I do the same kind of logic down here with tails so if the user guessed tails we give him a success message saying yes it was tails and we pray out of the loop and if the user did not correctly guess the result um we say oops it was tails and because it does not have a break here what will happen is the loop will start running again the user will have to enter input uh the guess again and then the entire game starts all over again so let's let's execute this and now down here we see this text um message which basically says guess if the coin comes up heads or tails and I will now enter heads and uh let's write it like this heads I'm a user I'm a stupid user so I just write heads in a capital H I enter and then the computer says oops it was tails so I say well okay I had a 50 50 chance of winning the game so let's guess again let's write heads again and now I enter and now something interesting happens the computer says oops it was heads lowercase so we see our program here it actually has a bug uh it requires our user to enter heads or tails in a lowercase and uh if the user does not enter this in a lowercase then even if the guess is correct the computer does not recognize this so now uh knowing this we write heads in a lowercase enter it and then all of a sudden it says oh yes it was heads so that's how the game works we could play it again uh but I won't uh now because this you know code needs to be improved first and foremost there we saw we have a bug in it so we we need to handle a bug so um it should not be important how the user enters the the solution and then secondly there's something else that I don't like about this code and what I don't like about this code is that this big if else logic it's actually it's three if if statements so it's one outer if statement that models the coin toss and then we have two inner if statements that model the guess by the user so two different steps that are independent so causing the tossing of the coin and also the guessing of the of the coin toss they are modeled in the same kind of code fragment right and I don't like this and why don't I like this well if there are two things in the reality that happen totally independent of each other so throwing a coin is totally different step than asking the user what to guess then I shouldn't mix this code up in the code because then whenever I do this what happens is the the code will at the end of the day be more complex than reality and I don't like this whenever I write code I want to simplify reality right so I want to have a model that is just good enough just complex enough to model the reality and be helpful and the code shouldn't be more complex than reality so what can we do to improve this code well at first we do what is called modularization so I put in the individual steps into functions on their own for example I will first write a function that is called get guess and get guess already tells us what it will do first it does not take any input but it returns a guess which is either a string or a none type so none so and it goes further further it says either heads or tails if the input can be passed and none otherwise so as a user of this function I already