Lecture 16: Recursion on Non-numerics
Key Takeaways
The video lecture covers recursion on non-numerics, including the Fibonacci sequence, list operations, and memoization, using Python as the programming language.
Full Transcript
let's get started everybody so last lecture we began talking about this topic of recursion and um it hopefully solidified a few sort of really fundamental ideas about recursion that we're going to use in today's lecture um today's lecture the first uh half of it is we're going to talk about um recursion just kind of to review on some actual numerical examples but then the second half just the main event for today is going to be recursion on non-n numerics so specifically recursion on lists but the techniques we'll see on lists can be applied to other things that are non-numeric as well like tupal or strings or things like that so so let's do let's start the review of uh or a little bit of a review of what we talked about last time and some of the Big Ideas by looking at this example so we're going to uh write a recursive function for the Fibonacci sequence and the Fibonacci sequence exists in nature in a lot of places um one specific place is you can model uh mating of rabbits using Fibonacci Sequence um but we won't be studying that in depth today we're just going to be looking at the sequence itself so just to remind you the idea behind Fibonacci is we start out with two sort of basic values Fibonacci of one is one and Fibonacci of two is one so in my table here I've got these two starting values and we can fill in the remainder of the table by basically saying Fibonacci of n is Fibonacci of n minus 1 plus Fibonacci of n minus 2 so Fibonacci of 3 will be 1 + 1 Fibonacci of four will be 2 + 1 Fibonacci of 5 is 3 + 2 Fibonacci of 6 is 5 + 3 and Fibonacci of 7 is 8 + 5 right that's the sequence we all know and love okay so our two base cases if we're going to put this in sort of mathematical terms are Fibonacci of one is one Fibonacci of two is one and our recursive step right in terms of the math and SL programming uh uh lingo is going to be the Fibonacci of n is equal to Fibonacci of nus1 plus Fibonacci of n minus 2 so we put that in our function so we slap a definition around that that code and turn it into a nice function that we can run if x is one or X is two those are our two base cases we just return one right off the bat right nothing to call no functions to call there are base cases but otherwise we have uh we're going to return a value and the thing we're going to return is a call to Fibonacci of n minus one plus Fibonacci of n minus 2 just like the mathema mathematical definition said to do okay so this is different than what we saw last lecture last lecture in our recursive step we had basically just one function called to ourselves right so whatever function we had defined up here we only had return you know some variation of that function down here with some you know something else tacked onto it like an addition of some value or something else in this case we actually have the function being called twice okay so we're going to see what implications this has as we Trace through the code and so as I trace through the code I'll remind you of some of the big ideas that we learned last last lecture so let's say that we wanted to calculate Fibonacci of six and so I'm going to illustrate a function called just by you know this the name of the function with the parameter that I'm calling so one of the big ideas from last lecture was that when you make a function call that to a function that's recursive you're going to you know Trace through that function call and the environment for that function just as you normally would but as soon as you see another function call so in this case Fibonacci of six doesn't enter the base cases it goes up into the recursive step and it says I'm going to calculate Fibonacci of xus one plus Fibonacci of xus two so for this uh Fibonacci of six function call let's follow along and say well Fibonacci of 6 will say I want to calculate Fibonacci of five is it this is my question to you is it going to now calculate Fibonacci of four no very good because Fibonacci of five is a function call right we need to explore what this function will return before Fibonacci of six can add the result of this the return of this to Fibonacci of four right so that means then this new FIB five is an entirely new environment calling Fibonacci with n is equal to 5 completely separate than our original Fibonacci of six call so let's explore what Fibonacci 5 is going to do well in its function call it's going to again go in the recursive step it's going to figure out Fibonacci 4 and then it's going to pause there right because it needs to figure out what Fibonacci of four is before it finishes its other half right to do Fibonacci of three so Fibonacci 4 will now create a new environment and now it has to explore its results a return so it figures out Fibonacci of 4 is again going into the recursive step to calculate Fibonacci of 3 plus something but we don't know what that something is yet because we have to explore what Fibonacci of three is right so already where four function calls deep and we haven't really done any work any any work that we can see the result of right there's no values being passed back all we're doing is exploring this path down until we get to some sort of base case that will kick off our um uh our uh our sort of conquer step where we pass values back up the chain so Fibonacci of three again is going to look at Fibonacci of two and finally we've reached a base case so Fibonacci of two will immediately return right it doesn't make another function call so Fibonacci of two will return a value and then Fibonacci of three in its function call has the result for Fibonacci of two and then it's going to do plus that Value Plus Fibonacci of one right 3 minus two so that's this one here it can easily grab the the the do that addition and return the value back up uh to Fibonacci of three so now Fibonacci of three has its first half ready right so Fibonacci of four sorry so Fibonacci of four has its first half ready Fibonacci of three so Fibonacci of four was trying to figure out what FIB three was and it did right it was FIB two plus FIB 1 two so now it has a value for its first half here and it needs to add that value to Fibonacci of two four minus 2 so it will explore that path that's a base case so all it does is return the value immediately and now Fibonacci 4 has its value whatever FIB three was that we figured out plus FIB 2 okay now FIB four we have a value for it when we called FIB five so FIB five is now halfway happy because it knows what FIB four is but it needs to add that to FIB three so FIB five is still halted right it can't return anything but because it now it needs to explore what FIB three is well FIB three is going to be have uh do another function call right so it's going to call FIB 2 and FIB one which are two base cases which easily return the value back up to FIB three and now FIB five is happy because it knows this value and now it knows this value it can add them together and FIB five now has a value that it it can keep track of and now finally FIB six we're not even close to being done you guys FIB six has a fib five value so it has half of the things it needs to figure out what FIB 6 is because now it has to figure out what FIB four is and already you can tell what we're going to do next we're going to start exploring the exact same way like we did before FIB four needs to calculate FIB three it can't do FIB two yet because FIB 3 needs to calculate FIB two and FIB one right pass back up the value FIB four can now finish its job by calculating FIB three and FIB two pass up the value and now finally FIB six has its two halves here FIB five and FIB four and it can add them together and return the value okay so um a super inefficient algorithm because there's a lot of sort of stuff going on but not much work being done until the end right we've got a bunch of Base cases we get to and then we can start building back up our result and the reason why I say it's inefficient is because well we're exploring these paths and as we go along the way right we figure out what FIB 3 is and what FIB four is right but then when we explore this the right half of FIB six over here we're actually recalculating these values all over again that's why I said we're not even halfway done because when we got FIB five we had to explore FIB four and FIB four this this Branch down here is basically a copy of this one down here okay so there's a lot of work being done here where you just do the same thing over and over again and so that leads me to say well what if we didn't have to do all this work all over again right if only there was some sort of data structure that we could use to keep track of things as we calculate them right right to basically map one thing to another so if we already calculated FIB 4 to be some value why don't we just look it up so anytime we use things like you know keeping track of and looking things up that should you know ring ring a little bell that says dictionaries can help us do that and so what we can do is actually write a more efficient Fibonacci recursive Fibonacci function that uses um it's it's still recursive but it uses Diaries to keep track of values as we calculate them okay and so uh this is this is the Fibonacci efficient function so my name is fib efficient notice we're still calculating Fibonacci of some n but we're going to pass in another parameter a dictionary and this dictionary will keep track of the Fibonacci values as we calculate calculate them so the key will be the n and the value will be FIB of that n okay and so down here you can see we're going to initialize a dictionary that has FIB of one maps to one and FIB of two maps to one right those are our B our base cases so let's take a look at our uh Fibonacci uh recursive function now that uses dictionaries no longer do we need to think about the base cases as you know Fibonacci of one is this and Fibonacci of two is this now all we need to do is say well let's look up the value in our dictionary that's that's what our base case will be and we don't need to make a call to ourselves if the item is already in the dictionary right so we can just return the value associated with n in Dictionary d if that n is already in the dictionary right so our two base cases down here will initially be in our dictionary and as we we figure out the values of Fibonacci we'll add them to our dictionary and that's exactly what the recursive step will do so else the values not in our dictionary so unfortunately we have to calculate it right which is fine we'll basically do that the first time through that sort of exploring the left half of our uh of our path but that's pretty much the only times that we're going to calculate it all the other times we'll just look it up so this is going to be a little different than what we've seen before because I'm not um right off the bat returning uh FIB n minus1 plus FIB n minus 2 I'm actually still running the same you know the same recursive step FIB n minus1 plus FIB n minus 2 but I'm saving it in a variable and that's totally fine to do okay and then before I actually return this value let me add it to my dictionary so this is simply just you know saying this dictionary at this particular n for this particular function is equal to this thing that I just calculated just a straight up you know dictionary addition adding this item to the dictionary and then after I've added it to my dictionary I can return the answer or return that that value so still passing it back up the chain of function calls but we'll save it first everyone okay with this code okay so then um this is the dictionary I mentioned uh uh where we initialize our two base cases and then we can you know print the function so let's Trace through the code um to see what exactly happens with these function calls now so we're initializing our dictionary where we have N1 Fibonacci of one is one and N2 Fibonacci of two is one right our base cases Fibonacci of six again we're doing the same function calls right so that means there's nothing uh stored for FIB five so we still have to explore what it what value it it will be nothing stored for FIB four we're still exploring nothing stored for FIB 3 we're still exploring we've reached a base case so so now we you know the first thing we do is check if it's in the dictionary it is so we just return the one directly check if the other half is in the dictionary return the one directly and now we've got a value for FIB three before returning it let's store it in our dictionary so I just calculated what FIB 3 was let's put it in the key is three and FIB three is two okay so far so good it's pretty similar to what we've done before except that we're you know storing this value in the dictionary so now we explore the right half of this FIB 4 right FIB 3 plus FIB FIB two it it's already in the dictionary so it immediately returns this addition now we know what FIB four is so we add it to our dictionary FIB four is three explore the right part of FIB five right so FIB four plus FIB three do we go further now right in the previous case we explored two and one in this case do we keep exploring no exactly because our base case says if it if three is already in the dictionary simply return the value associated with it so yep there it is right there we added a while ago we just return the two immediately no need to go down this path okay so now FIB five is done pretty quickly so the right half uh so that means we have the value for FIB five and we add it to a dictionary we explore the right half of FIB six remember beforehand I said we were not done we don't need to we don't need to explore this FIB four anymore because we added it to our dictionary long ago so now all we need to do is look up the value associated with four from our dictionary okay so boom there it is and then we can just add FIB five and FIB four together and get the value for FIB six stored in the dictionary and you know this case it's it's the end we don't need to do anything else with this value passing it back or anything like that okay so we're not recalculating any anything else right we're just checking the dictionary and if need be we calculated so it's an improvement but how much of an improvement is it actually so if we run this function and it's in the python code you can play around with it yourself if you run the function that we originally wrote FIB the one where we don't store anything in dictionary if we try to calculate Fibonacci of 34 it results in 11 and a half million function calls that's a lot of function calls because right even FIB six had FIB three being called twice right FIB four being called or FIB three being called three times FIB four being called twice things like that so can you imagine how many times you know FIB three will be called when we are trying to calculate FIB 34 probably thousands if not more right so overall the number of function calls we're making is 11 and a half million with our original code but the efficient version only makes 6 five it's not like we went from 11 and a half million to like two million right we went from the order of millions to tens which is really really impressive in terms of speed so if you try to run this program it'll you know take a couple seconds for F34 but the efficient one will be instant and all of these uh function calls have some overhead right you need to create an environment in Python it need to pass these parameters so all of these function calls take a lot of time whereas a Dictionary lookup is basically instantaneous right so in this particular case we've given up some of our memory to store values right the dictionary is storing 34 entries which is not much but there are applications where you can't spare 34 entries right in your memory um in which case you might you know spare some time to to to continue calculating without taking up some memory so there's a little bit of trade-off between these two programs right one of them doesn't store anything but is slow the other one stores things but is fast okay let's look at one more example where we do um Fibonacci on numerics and this uh I don't know when you'd use Fibonacci in your real world you know real life but knowing all the possible ways you can make a score of X in basketball is a little bit more useful so let's think about this problem recursively certainly we could do it iteratively and Brute Force our way through all the possible combinations of scores right so in basketball you can make a basket that's worth one point two points or three points so you can think about all the possible combinations you can make um to give you some score of X um we're going to think about this problem recursively right so let's start with our base cases okay base cases we've got three of them so if we think about a score of one so if x is equal to one so that means if we have a score of one in basketball what are all the possible ways we could have made a one well you could just score one point and then that's it right I just did 1 plus Z just emphasize that we're just scoring one and nothing if we make a basket that's worth two point or if we have two points in basketball what are all the possible ways we could have made two well we could have scored a one and a one or we could have just scored two right off the bat so that's two possible ways to make a score of two right and similarly to make a score of three what are all the possible ways well we could have scored a one then a one then a one we could have scored a two and a one or we could have scored a three right off the bat so that's three different ways you can make a score of three in basketball okay all right everyone with me so far these are our base cases okay because the recursive step will be very will blow your minds it's so simple okay so the recursive step looks like this okay now somebody giv give me um what's a reasonable basketball score like for a team 87 87 okay it's been probably 25 years since I've played pro basketball in grade five you guys so I forgot what's a reasonable score all right so 87 so let's say now we're not dealing with our base case we're dealing with some number that's bigger than one of these based cases how do we think about this problem recursively well there's three possibilities right if I have a final score of 87 let's say that um I think about the score of 86 right if I know all the possible ways I can make a score of 86 all I need to do is add one to that score right it'll give me 87 right so that's one possibility here but that's not the only possibility right because I could have a score of 85 and if I add two to that 85 not two counts right just the score if I have an original score of 85 if I just add two to that score it gives me my desired score of 87 so if I know the possible combinations to make 85 then I know that all I need to do is you know attitude to my score and that'll give me 87 and then the last possibility is is to score is to know all the possible ways to make 84 a score of 84 because then I would just add a score I would take that score and add a three to it to give me 87 right so I'm sort of using my base cases to guide uh my recursive step so the number of ways I can make a score of 87 is the sum of all the possible ways I can make 86 or 85 or 84 right because if I've made 886 I would just add one to it if I made 85 I add two to it and if I made 84 i' add three to the score so this so that's essentially what this recursive step is doing right I've got these are all the possible ways I can make a score of 80 you know x minus one right so 87 86 and that's just me calling my function right so score count x - one score count X plus all the possible ways to make a score of x - 2 plus all the possible ways to make a score of xus 3 so if I add all these three ways together I would get all the possible ways I can make a score of X does that make sense okay so that's it right it's pretty clean code it looks really nice um if we were to write this iteratively it would be a mess because we'd probably have a whole bunch of nested Loops to try to Brute Force off the possible combinations of scores that we can make um and it wouldn't look very very nice very pythonic okay um so let's do a trace of this code just to you know to to to bring it all together the trace will be very similar to the Fibonacci Trace except that now we have three paths to explore before having a return value right so for a score of six I would explore how can I make a score of five and of course I will explore how can I make a score of four and three but I'm not there yet right first I need to explore how to make a score of five which is a function call this one we'll explore how to make a score of four and of course a three and a two but not just yet a score of four is our will will lead us to our base cases it's just how to make a score of three and a two and a one these are base cases they immediately return and we know how to make a score of four a score of three is also a base case and a score of two is also a base case so these ones will immediately return to give us the score of five so now we know how to make a score of five we need to follow uh through how to make a score of four which is just three and two and one oops I should changed that to be a one um and then how how to make a score of three and that's just a base case so very similar Trace as the Fibonacci code okay all right questions about those examples are they okay do they make sense Okay cool so uh there is one exercise in the python uh file it's it's for the for at home um I I would like you to try to memorize this code so memorize means basically try to use a memo a a dictionary to store values as you calculate them because you see that it's going to be just as inefficient as the Fibonacci code right so here we're calculating score of four again where we had calculated it way back here right and so try your hand at at um adding a dictionary to this code to try to um try to speed it up okay so the next the second half of this lecture we're now going to talk move away from recursion on numbers and sort of you know having these nice mathematical operations that we can just translate to code um easily and start looking at recursion on non-numerical things and we're just going to look at lists but again as I said you can apply these very similar codes to um uh any sequences of values tupal or strings or things like that so the reason why we're looking at lists is because lists are naturally recursive so one of the motivations uh I gave at the end of last lecture is that we have um lists that can have elements that are other lists that can have elements that are other lists that can have elements as other lists so without knowing sort of how deep these lists within lists within lists go so um it's going to be really hard to write iterative code it's possible but it's going to be really hard and instead we're going to see that the recursive version of this code is going to be a lot more intuitive in the long run maybe not in you know maybe not right off the bat but definitely it's a lot easier to write uh to write and to read so let's think about lists in a recursive way okay so if we were doing iteratively what we'd say is we're going to Loop through each element and do something the problem we're going to solve is figuring out the sum of all the elements in a list to begin with so iteratively we just said right we Loop over each element in the list and keep it uh in our result so I've got these State variables I talked about last time right result and E that keep track of which element we're at and what the value is recursively remember we're going to make all these function calls into until we get to a base case at which point we're going to start to build up our result so how can we think about this uh this list recursively well let's say that we have a list and we want to find the sum of all its elements that's our original problem okay now let's say that we take the first element and we just extract it out right we know we have this list with a bunch of elements let's take the first one we know it's a 10 and then let's consider the remaining elements so the 20 onward if I take my 10 and I know the answer to the sum of all the elements in 20 onward right then all I need to do to figure out the sum of my original list right this one here is to say it's the 10 plus the sum of whatever the the sum of the 20 onward is okay now the sum for elements 20 onward is the same problem again right it's the problem of finding the sum of all the elements in a list it just so happens that our list is now our original list without that first element in it does everyone understand that right we've got our original problem and we've just made the same problem again just a slightly different version of it all the list except for that first element so now we do the same thing right let's say this is our new list we extract the first element from it and we consider the elements except for that first one as a new list and again if I knew what the sum of 30 all the way on to 60 was all I need to do is add it to the 20 that I extracted and I would know the sum of this list so we keep doing that right we take our list extract the 30 and consider the remaining elements as a as a list same deal if I knew what 40 plus 50 plus 60 was right the sum of all the elements in this list I'd just add it to the 30 and I have the answer to that problem and we keep doing this extracting an element and considering the remaining lists all the way down to when we have a list with just one element in it well this is a pretty simple problem to solve if I have a list with one element in it the sum of the elements within that list is just the value of that element right it's just 60 so very simple problem no need to keep sort of going further dividing this problem into smaller pieces I already know the answer to this one it's very simple so this is our base case and we know the sum of the elements of in a list with length one is that element so once we reached the base case we build back up our result right we take the 60 and we had extracted the 50 originally so we're going to pass the sum back up to whoever called it which was the function that extracted the 50 so now the 50 plus the 60 is 110 now this 110 gets passed back up the chain when we extracted the 40 we said well I'm going to add the 40 to the sum of the 50 and the 60 110 which is 150 pass that answer back up the chain when I extracted the 30 I said I was just going to add the 30 with the sum of the remaining things which I figured out is 150 the 20 right I had extracted it becomes 20 plus the sum of everybody else which is 180 right so the sum is 200 and then finally my original question was to take extract the 10 add it to everything else which is the 200 that we figured out so the full sum is 210 uh does that make sense this this animation Okay so we've got the division all the way down to the base case and building back up the result so let's try to write it so we're going to write it in in pieces so the function is called total recur it takes in a list L we're going to recursively figure out the sum of all the elements in this list so we can have a base case when the list is empty we can return zero up to you um another base case which is the one that I Illustrated on the previous slide is when the length of the list is one okay so when the length of the list is one what's the sum going to be no need for recursion it's just that element and so in these slides what I've also included right in addition to the code is a little example so it helps you think about what the function returns right so in this base case when the length of the list is one the list would look something like this and all I'd need to do is return L at index zero so the 50 and that's my sum okay and that's what I'm doing here returning L at index zero cool now the recursive step remember in the recursive step I extracted the first element and I said let me save this first element so here it is being saved as L at index0 and I'm going to add it to something right so in this example here I've got this list that's longer than one I'm extracting the 30 Ln index zero and I'm going to add it to something well that something based on the uh slides uh the previous uh uh slide right where I did the animation is going to be us putting our trust in the fact that we write this function correctly right that something is going to be us figuring out what the sum is of 40 and 50 right it's the same problem we're trying to solve right now the sum of 30 40 50 except that now I'm just going to take the sum of just the 40 and the 50 okay so that's something becomes the same function we're writing right now total recur except that I'm not calling it on L not the whole thing all over again that would be bad but I'm going to call it on L from index one onward so essentially removing that first element is everyone okay with that okay so that's it that's the function nothing else to write right no Loop we've basically written a function assuming that we wrote the function correctly right which is a very strange way to think about recursion but that's essentially what it is you're trusting yourself to write this function correctly such that your recursive step leads you to the base case so that you can build back up the result correctly so there's a lot of trust involved in writing these functions recursively okay um okay so I'm not going to go through the python tutor but you should definitely go through it on your own um you know as a practice for the quiz things like that um let's have you write this then okay so it's going to be a slight modification to the code we just wrote so it's going to take in a list as its parameter and instead of summing the elements in the list right like we did you know 10 plus 20 plus 30 whatever I would like you to sum the lengths of of the elements in the list right so if I pass it in this function it's going to sum the length of this two plus the length of this one plus the length of this five 2 plus 1 plus 5 so it'll be a very slight modification to the code that we just looked at and here it is online 70ish um so think about the base case right if you have a list with one element in it what do you return and if you have a list with many elements how can you put your trust in something that you just wrote to help you get uh uh get to the answer all right what do you guys have for me so let's start with the base case and if you're having trouble I encourage you to just in a little comment just write down sort of what that base case looks like right like I did in the slides it looks like this right so what would I return if I have a list with one element in it yeah yep exactly so we would return the length of that element right so the length of whatever this is AB whatever awesome how do we do the recursive step yeah yes exactly total Len recur with what list yep so we're going to extract that first one so this will give us the the sum of the links of everybody else exactly so we also need to add it to yeah L at zero right so it's it's it's fine to do it even before or after because we're just summing these two values so it doesn't matter if you're you know the order that you're summing them um so that that that's perfect any questions about this code yes sorry than doing the what so in terms of efficiency um recurs this Rec this function will be slightly less efficient I would say yeah because it there's a little overhead in actually making a function call um whereas if you use a built-in operator it's been optimized to work pretty fast yeah not doing in the backg no when it's doing plus equals it's definitely not doing this in the background exactly y but this is just I mean we we're I I'm trying to uh show you recursion on something that you know you wouldn't typically use recursion on just to help illustrate the idea of recursion um certainly you can use an iterative algorithm obviously to calculate the sum of these these elements and it's more intuitive more in line with what we've been learning so far right yeah okay um excellent so now let's look at a slightly different problem so instead of finding the sum of all the elements in a list let's tackle the problem of looking for an element in a list right completely different but we're still doing some sort of list operations um we're going to start with an implementation that's not quite right and you'll see why in a little bit so let's follow the same sort of pattern that we've seen in the previous one so let's consider a list of length one in this particular case if I have a list with only one element in it how do I know if that element is the one I'm looking for well I'm just going to return this Boolean right whether L at index zero that element is the one one I'm looking for the E so notice this en list is passing in a list uh the list itself and the element I'm looking for um I think okay so then let's look at the recursive step the recursive step in this particular case let's say it says well else right we might think to say well if it's not the one I'm looking for then let's look in the remainder of the list so like we did in the previous case let's apply the same function we're writing right now to all the elements except for the first one right and we're still looking for Element e in that remaining uh those remaining elements okay so we can test it out and if we actually run it again please I encourage you to do python tutor on your own but we can test it out and say uh if in this particular case two 2581 if I actually run this code it will give me true so it found the one inside the list 2581 which is good right it's exactly what we wanted but if I change my input list slightly right and I've got 2158 the element I'm looking for is here the code will actually give me false the one that I just wrote which is not okay right I see the one is right over there and so what exactly is going on so we can run uh the code um here so this is this code here if you see that it gives you the incorrect value one thing you could do when you're doing recursion is to put a print statement within um within the function itself right so we can print maybe the list we're currently at and the element we're looking for and see exactly what's going on so if I run it it will say well first time through the uh through the function call I'm looking for the number one in this list the next time I'm looking for the one in this list the next time I'm looking for the one in this list and the last time uh for my function call I'm looking for the one in this list right and already we see something went wrong because as I was looking through these lists right I'm basically skipping over important elements right what this code is actually doing is only checking if the last element is the one you're looking for right because it basically ignores that first element in the code right the code here yes it extracts that first element but it doesn't do anything with it so that's our problem okay what we want to do is still look at further elements in the list so that part of the code is correct but we we only want to do it in a certain situation and that situation is when the element that we just extracted L at index zero is not the one we're looking for right that little else case right so we still want to extract the first element if we have a list with more than one element in it but as we've extracted it check if it's the one we're looking for if it is returned true no need to keep searching the rest of the elements in the list if it's not the one we're looking for this else here then we can look at the remaining elements in the list and run the exact same function we're writing right to check if the element is in the remaining list does this code make sense is it all right okay so the way I wrote this code is sort of how I uh I I personally think about the problem um and right and if we run the code again it'll give me the correct answers each time but I wanted to mention that we can actually clean up the code a little bit and write it a little bit more uh pythonic um so it's you know it's a little bit nicer to read it's it's more cleaned up but one of the things that was um confusing for me when I first started learning recursion is that I would always see these beautiful cleaned up versions of code that do the recursion and that's not sort of how we approach thinking about the problem right I I can't can't come up with this nice form right off the bat and this is one example but there are certainly other examples of more complicated code where you see it and it's just it looks beautiful and yes if I look at it I can figure it out and I say okay yeah that makes sense but I personally could never come up with it on my own um so I was I was writing these lectures I thought well how do I actually think about the problem so I just went back one slide and the way I think about the problem is to kind of separate it into these smaller the a bunch of different base case right or a bunch of different cases and so that's what I've been trying to do in this particular lecture to help you guys understand recursion it's you know think about the case when we have a list with one element in it right how would you solve that problem and then think about the case when you have a list with many elements in it how would you solve that problem yes it's true there are some pieces here that are that are repeating right so we've got L at Z equal e is in a couple places but you can do that clean up later right so here I've got two test cases that return uh two cases that return L at zero so we can pop them into the same test case here and then we can check if the length of the list is zero we can add that test case and else we check the remainder of the list right that's totally fine and if it helps you think about the problem this way that's okay too but personally for me it was a lot easier to think about the problem in terms of a list with one element in it and then a list with many elements in it and you know that it's it's totally fine to have to write you know a little bit qu unquote inefficient looking code um to begin with certainly don't hard code all the base cases right if length is zero do this if length is one do this if length is two do this right but some reasonable base cases are okay to do so this is just uh showing the simplified code um one thing that I wanted to mention and hopefully you've uh noticed this already is the function that you're writing all of the return returns from this function need to have the same type right when we wrote I'll go back a couple slides when we wrote the function that calculated the sum of all the elements in the list so that's this one here what were we returning here we were returning an actual number and then here we were assuming that this function returned an actual number that we can add to this actual number right so every single return statement needs to return the same type of object because if you don't if you're assuming that the base case returns a list but then at some point in the code you're going to be uh you know working with a with a number or a Boolean then python as soon as it gets that base case is going to say hey you're trying to add a Boolean to a list what's up right and so in the summing of the list elements all the test cases returned a a a number and in this uh case where we are trying to return the whether the element is in the list or not notice every single one of my returns is going to turn a Boolean so here Boolean here a Boolean and here in the recursive step I'm assuming that I'm just passing this Boolean back up the chain of command Okay so very very important thing again something I that was not made clear to me when I first started recursion but you know once I knew this it was it just made so much more sense and it helped me write my code um you know better more perfectly right off the bat let's look at a slightly a different example now so we've looked at taking the sum of all all the elements in a list we've looked at figuring out whether an element is in a list let's do something completely different still working with lists let's say that we now have an input list that looks like this so we've got a list we've this is my list um beginning and end and this list only has list elements within it so no integers but its elements are lists so here's one list element here's another list element and here's another list element right so in this example I've got a list with three list elements what I'd like to do is to flatten this list which means that I want to remove any semblance of subl lists and take just all the elements of these Su lists and put them top level does this task make sense okay so I'm not assuming I got lists within lists within lists I'm just assuming I've got lists with list elements that have integers or whatever in them okay so again let's think about the base case let's think about the case when we have a list with one element in it and then we can figure out the recursive step so if I have a list with one element in it again I've got an example here on the right hand side it's a list right with one list element in it that's why I've got the double square brackets if I wanted to flatten this what could I do I could just grab the element at index zero right because the element at index zero is this inner list and it is a flattened version of my list okay else what am I going to do well let's do the same pattern it seems to have worked so far for us let's do the pattern of extracting that first element so grab element at index zero so here we would grab something like square brackets 1 comma 2 and concatenate it with something okay remember when we concatenate a list with another list it gives us a big list with all the elements in it exactly what we're looking for right when we want to flatten a list so the something we're going to add this uh L at index zero with is is just us flattening the remainder of our list again same pattern we've been seeing already right so if I extract in this example here the one comma 2 as a list I'm going to concatenate it with the assumption that the function I'm writing will work correctly to flatten 3 comma 4 and 9 comma 8 comma 7 right so if I flatten that um this will give me just a list with 3 4 987 alt in it okay and if I canate one comma 2 with three 3 4987 that just gives me 34987 everyone with me is that all right okay good I see some nods so that's actually pretty good sign okay okay you are with me right because now it is your turn so we're g to write a variation of whether an element is in a list so I'm going to give you a very similar scenario to this flatten one so I'm going to give you a list that contains list elements so here's my list that contains list elements in it and what I'd like you to do is write a recursive function that checks whether this element whatever the second parameter of the function call in my function call is in these list elements so not at the top level like we wrote last the last code to check if an element is in the list right but in these subl lists right so just to show you kind of the difference if I check whether three is in 1 comma 2 comma 3 that will be true but if I check whether three is in you know the list containing the list one comma 2 comma 3 that's false because it's checking whether the three is equal to this list right it's just doing a top level equality here so uh let's have you write this code down online 166 um you may use the in operator right to check if an element is in a list itself but obviously you won't be able to use the in operator nor should you because then we're not writing a recursive function to check if the element is within a list uh list element right so um have you work on it for a couple minutes and and then we can write it together all right does anyone have a start so so let's look at the case where we have one element in it how do you um check whether that element is within within the the list inside so if right this is our our case with one element in it the length of L um equ equals 1 yeah yeah exactly e and e and l is is is the correct um thing to do L at index zero right so if if this is our L that's why I added this little example here so it can help us so L at index zero is this guy here and all I need to do is check if e is in L at index zero right and I can just return that right off the bat right I could do if e in l0 return true else return false but e in l0 is already a Boolean so I can just return that directly okay else we have a list uh with more than one element in it right so what do we do here remember extract the first element and then you know do the rest so let's say this let's say the first element is L at index zero right that'll help us think about it a little bit so before looking at the remainder of the list right and calling our recursive function what did we do when we checked if an element was in a list when we just had a plain list we just said if you know um e is in first return true else return false right but we don't want to do else return false because that's not quite true else we want to look at the remainder of the list right we want to see if the ele obviously if the element is not in the first thing that I just extracted right this list here then I would like to say is it in the rest of this list right which is US calling the function that we're just writing all over again so we can return uh the name of this function in lists of what did I call it lists of lists and then L from one onward with the same element we're trying trying to find and of course we can simplify this just like we could simplify the previous one but it helps to think about it in these two cases a list with one element and a list with many elements okay any questions about this yes uh this one this one we're considering a list with one list inside it um yeah we could include another base case I suppose if the length of L is zero return false that would also work because obviously if the list is empty then there's it's not in there okay so when do we use recursion obviously a lot of the examples we've seen here we they're very intuitive to write iteratively right but I mentioned a couple examples last time where uh it's more uh intuitive to use recursion and specifically um I wanted to draw a little bit of a parallel to this thing when we learned about while Loops right we said well what if we tried to code a little game that just used if and El's um I said that we would have a bunch of nested if else statements right without a while loop because we don't know how deep to make these if L if else you know if statements and so a very similar idea exists with um recursion and when to use recursion so if I had a list with a whole bunch of lists in it and those lists could have lists within it and so on and so on I don't know how long I need to how deep I need to make my code go right so an example using a for Loop would be to say for each element in L right I'm going to say I'm going to look at each element I'm going to say well if you're not a list then I can deal with you directly but if you are a list then I need to iterate over you and so I've got this other iteration here for each J and I right for one of those lists again I would say are you a list if not I'll deal with you directly else you are a list so I do need to iterate over you okay and you can see this nested idea now comes into play here and of course we could try to use a while loop to to optimize the code a little bit say you know while this element type is not a list do this you know things like that but it leads to some really verbose and uh uh verbose code okay and so recursion is a way for us to deal with these lists within lists within lists and of course when you have data structures that you don't know how long or how H how deep they go so I mentioned file systems and a set of operations last lecture has really nice places to use recursion Scooby-Doo gang looking for um you know their their culprit uh you know rooms that have doors that lead to other rooms that have doors lead to other rooms they don't know how many doors they need to go through to get to a room without doors obviously recursion they should use um and then a bunch of other fun examples of places to use recursion so the last bit of class I would like to um work through this example where we're going to see the code to solve lists within lists within lists within lists okay but before we do that we're going to talk about so we're going to do that example in the context of reversing a list but before we look at a list that has all these different Su lists within it let's look at a list that has just integers how would we think about this problem recursively to reverse all the elements in this list okay so again we're going to use the very same pattern we've been using all throughout today when we've been dealing with lists we're going to take out the first element extract it and we're going to deal with the remainder of the list basically by writing the uh running the same function we're writing on the remainder of the list so let's say I have my original list and I look at my first element just like before I'm going to extract it out if I take this first element and I pop it at the end and then I consider the remainder list right everything except for that first element that I put at the end I can just call the same function I'm writing right now to reverse the remaining list okay which means that I'm going to take this remaining list grab the first element pop it at the end a
Original Description
MIT 6.100L Introduction to CS and Programming using Python, Fall 2022
Instructor: Ana Bell
View the complete course: https://ocw.mit.edu/courses/6-100l-introduction-to-cs-and-programming-using-python-fall-2022/
YouTube Playlist: https://www.youtube.com/playlist?list=PLUl4u3cNGP62A-ynp6v6-LGBCzeH3VAQB
The recursion procedure from this lecture can be applied to any indexable ordered sequence. The same idea will work on problems involving strings or tuples.
License: Creative Commons BY-NC-SA
More information at https://ocw.mit.edu/terms
More courses at https://ocw.mit.edu
Support OCW at http://ow.ly/a1If50zVRlQ
We encourage constructive comments and discussion on OCW’s YouTube and other social media channels. Personal attacks, hate speech, trolling, and inappropriate comments are not allowed and may be removed. More details at https://ocw.mit.edu/comments.
Watch on YouTube ↗
(saves to browser)
Sign in to unlock AI tutor explanation · ⚡30
Playlist
Uploads from MIT OpenCourseWare · MIT OpenCourseWare · 0 of 60
← Previous
Next →
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
21. Post Trade Clearing, Settlement & Processing
MIT OpenCourseWare
10. Financial System Challenges & Opportunities
MIT OpenCourseWare
7. Technical Challenges
MIT OpenCourseWare
3. Blockchain Basics & Cryptography
MIT OpenCourseWare
19. Primary Markets, ICOs & Venture Capital, Part 1
MIT OpenCourseWare
1. Introduction for 15.S12 Blockchain and Money, Fall 2018
MIT OpenCourseWare
Chalk Radio, A Podcast about Inspired Teaching at MIT (Teaser)
MIT OpenCourseWare
Nuclear Gets Personal with Prof. Michael Short (S1:E1)
MIT OpenCourseWare
How Africa Has Been Made to Mean with Prof. Amah Edoh (S1:E2)
MIT OpenCourseWare
Making Deep Learning Human with Prof. Gilbert Strang (S1:E3)
MIT OpenCourseWare
Social Impact at Scale, One Project at a Time with Dr. Anjali Sastry (S1:E4)
MIT OpenCourseWare
Film is for Everyone with Prof. David Thorburn (S1:E5)
MIT OpenCourseWare
Lecture 12: Aircraft Performance
MIT OpenCourseWare
Lecture 3: Learning to Fly
MIT OpenCourseWare
Lecture 13: Interpreting Weather Data
MIT OpenCourseWare
Lecture 21: Weather Minimums and Final Tips
MIT OpenCourseWare
Hand-on, Minds On with Dr. Christopher Terman (S1:E6)
MIT OpenCourseWare
Part 4: Eigenvalues and Eigenvectors
MIT OpenCourseWare
Part 5: Singular Values and Singular Vectors
MIT OpenCourseWare
Part 3: Orthogonal Vectors
MIT OpenCourseWare
Part 2: The Big Picture of Linear Algebra
MIT OpenCourseWare
Part 1: The Column Space of a Matrix
MIT OpenCourseWare
Intro: A New Way to Start Linear Algebra
MIT OpenCourseWare
9. Chromatin Remodeling and Splicing
MIT OpenCourseWare
28. Visualizing Life - Fluorescent Proteins
MIT OpenCourseWare
20. Roth's theorem III: polynomial method and arithmetic regularity
MIT OpenCourseWare
8. Szemerédi's graph regularity lemma III: further applications
MIT OpenCourseWare
19. Roth's theorem II: Fourier analytic proof in the integers
MIT OpenCourseWare
12. Pseudorandom graphs II: second eigenvalue
MIT OpenCourseWare
1. A bridge between graph theory and additive combinatorics
MIT OpenCourseWare
Special Episode: Teaching Remotely During Covid-19 with Prof. Justin Reich
MIT OpenCourseWare
Spring 2020 Update from Dean Rajagopal
MIT OpenCourseWare
S1E7: Unpacking Misconceptions about Language & Identities with Prof. Michel DeGraff
MIT OpenCourseWare
Climate 101 Live
MIT OpenCourseWare
Welcome for Volunteers (for EarthDNA's Climate 101)
MIT OpenCourseWare
Learning to Fly with Drs. Philip Greenspun & Tina Srivastava (S1:E8)
MIT OpenCourseWare
Thinking Like an Economist with Prof. Jonathan Gruber (S1:E9)
MIT OpenCourseWare
2. Cyber Network Data Processing; AI Data Architecture
MIT OpenCourseWare
1. Artificial Intelligence and Machine Learning
MIT OpenCourseWare
2: Resistor Capacitor Circuit and Nernst Potential - Intro to Neural Computation
MIT OpenCourseWare
14: Rate Models and Perceptrons - Intro to Neural Computation
MIT OpenCourseWare
4: Hodgkin-Huxley Model Part 1 - Intro to Neural Computation
MIT OpenCourseWare
18: Recurrent Networks - Intro to Neural Computation
MIT OpenCourseWare
3: Resistor Capacitor Neuron Model - Intro to Neural Computation
MIT OpenCourseWare
15: Matrix Operations - Intro to Neural Computation
MIT OpenCourseWare
13: Spectral Analysis Part 3 - Intro to Neural Computation
MIT OpenCourseWare
16: Basis Sets - Intro to Neural Computation
MIT OpenCourseWare
20: Hopfield Networks - Intro to Neural Computation
MIT OpenCourseWare
8: Spike Trains - Intro to Neural Computation
MIT OpenCourseWare
7: Synapses - Intro to Neural Computation
MIT OpenCourseWare
19: Neural Integrators - Intro to Neural Computation
MIT OpenCourseWare
5: Hodgkin-Huxley Model Part 2 - Intro to Neural Computation
MIT OpenCourseWare
6: Dendrites - Intro to Neural Computation
MIT OpenCourseWare
17: Principal Components Analysis_ - Intro to Neural Computation
MIT OpenCourseWare
12: Spectral Analysis Part 2 - Intro to Neural Computation
MIT OpenCourseWare
11: Spectral Analysis Part 1 - Intro to Neural Computation
MIT OpenCourseWare
9: Receptive Fields - Intro to Neural Computation
MIT OpenCourseWare
10: Time Series - Intro to Neural Computation
MIT OpenCourseWare
1: Course Overview and Ionic Currents - Intro to Neural Computation
MIT OpenCourseWare
The Power of OER with Profs. Mary Rowe and Elizabeth Siler (S1:E10)
MIT OpenCourseWare
More on: RAG Basics
View skill →Related Reads
🎓
Tutor Explanation
DeepCamp AI