 Okay, so that is reading by index. Writing by index is exactly the same thing, so the example that I give on the paper is list sub 2 gets Eugene. The principle here is exactly the same. In fact, I'm not going to change anything on the whiteboard because I'm lazy like that, right? I know that I need to put at list sub 2 the variable Eugene. Well, the math is the same. How do I know where it goes? I start out by getting the ID of the list. I add 24 for the header. I add 8 for the integer, and that puts me right here at index 0. Now, if I want to go to index 2, I multiply 8, which is the size of a reference, by the index 1, 2. Index 2 is right here. Okay, and variable assignment just like reading variable is very fast inside a computer. Big O of one time. The only thing I'm kind of doing different here is I'm creating the string variable, but creating data in memory is very, very fast too. So this is all big O of one time. No big deal, okay? But it's the math here. It's the key of the math that makes this work. And it's just simple arithmetic, but it's because object header is going to be a fixed size, integer is going to be a fixed size, and each reference inside the list will be a fixed size. So you have to know, you really have to understand that the Python list using an array-based list data structure is not storing values at these points in memory. It's storing references to values in memory. Okay, that's why this works. The other reason why it works is that this block is contiguous. They are all packed in right after one another. If they're not packed in, the math isn't going to work out. Okay, so let's talk about append. Append is our next. So I'm going to do two things. Let me get rid, first of all, let me get rid of this little pop up here. Okay, it should be gone. Now, we want to append. All right. How does append work? So what do I append? In my example, I am a pending art. Okay, so the question is, what is the big O of this? Generally, all right, why do we care about big O? We like efficient algorithms. And especially for something like a list, a list that gets used all the time, it should be as efficient as humanly possible. So what is the efficiency of appending? Well, when you append to the list, what happens? It gets added on down at the very end of the list in the last position, right? Okay. Well, it's going to turn out that list.append is also big O of one. Well, why is it big O of one? How do we know in memory where the last item in the list is? Bring up my little document again real quick. Well, we have this handy dandy thing, or Python has this handy dandy thing inside its list that tells you the length of the list, right? So initially, this list had how many items? Initially, it had six items, zero, one, two, three, four, five, six, and then I added a seventh, right? So my length has gone up. But look at it from this perspective. Okay, let's say I only have one item in the list, like I do on your show. Well, if I, if the length is one, and I need to append, okay, meaning I don't want, I want to put the thing after the last element. The way I get to the item after the last element is I start at the beginning, my memory address of my list. I'm going to add 24 bytes to it for the object header. I'm then going to add eight bytes for the size of the list, the length of the list right here. And then I want to go to the index after the last item. Well, each reference in memory is eight bytes times the length. That is going to take me to the address. So this little equation is the address immediately, hopefully this is going to be somewhat applicable, immediately after the last byte. So I've gotten here using once again, basic math and basic assignment where the item needs to go. I don't need to go down through this list. I don't need to walk the list or loop over the list. I can just use math to figure out where you go part. Right. And again, it doesn't matter if this list has a billion items, a million items, a quadrillion items doesn't matter. It's just math. I don't have to go through the list. Remember, we were talking about our big O's of ends. And it's the size of the input. What I'm saying here is that the size of the list doesn't matter. So this is also appending is also big O of one. All right, let's talk about popping. All right. So popping is maybe one that you're not quite familiar with. Reset my list. So imagine what you have on your sheet. List.pop. Now we're going to use this method a lot in this class and it'll be useful for you to know what it does. List.pop removes and returns the last object in the list. Okay, so if you called, let's say x gets list.pop. Okay, what will happen is x will have the value five or no excuse me x will have the value 10 because that's the last item in the list down here written my pop up there. 10 is the last item in the list and then we will shrink the length of our list here now has five items and this guy comes out. Okay, so it removes and returns the last item. Okay, you got to remember that it removes and returns the last item in the list. Okay, not necessarily the last item you added to the list, definitely the last item you appended to the list. There are other ways of getting items into list, but it's the one of the highest index is what happens. It removes it and it returns. Now, you don't have to do anything when you with the value, you can just pop it and get rid of it. Sometimes that's useful as well. Okay, so the question is how does it know which one to pop? All right, well, math, right? It's got math. It knows what its length is, right? So we can kind of take the same formula that we had for appending and subtract length minus one. So it'll be the ID of the list. That's our starting point. And our question is where is the last item in the list? It was this. Where is the last item in the list? Okay, well, ID of list plus our object header plus our eight bytes for keeping the length plus eight times, right? The eight is the size of each reference. So each one of these is eight bytes. Eight times what? I want to get this guy right here. Well, to get right here, I need to do length minus one. Okay, that'll get me right there. Now, once again, this is just arithmetic. So very quickly, Python can figure out where the end of the list is, thanks to the fact that it's keeping this length value. And it's got to keep it up to date. Then there's a little bit of efficiency overhead with managing this length value. And that's okay here. When you're adding something in, like just gets plus one, when you remove something or pop something out, it gets minus one. That's all. Okay, so math, math, math, math. And computers are good at math. They're fast at reading and writing to memory. This is also big O of one. Okay, that's popping. All right, now we're on to page number two. All right, page number two is going to get a little bit more interesting. All right, so clean off my board here a little bit. You can draw some happy little trees, all of us. Let's talk about insert. Insert is kind of interesting. Right, so we're talking about list.insert, zero, let's see example, like zero comma pairs. Okay, so what's the efficiency of doing this? Well, now things are a little less crisp and clear. Okay, so insert takes two parameters on a list. The first is the index where you want to put the thing. The second thing is the value you want to have referred to at that location at that index. So insert does not replace this. That's different, right? To replace this, you would do list sub zero gets pairs. Insert actually, pairs goes in there and everybody kind of moves down a slot. And in fact, that is exactly what happens. Okay, so Python can figure out and leave establishness through the last couple of examples. Python can figure out where in memory index zero is in big O of one time. Or if it's index five or 10 million, doesn't matter. You can figure out where this index is in memory in big O of one time. But it needs to keep the list contiguous. And we've seen through the previous examples that the index matters in figuring out where the item is. So when we push index, pairs into index zero, what happens to Alice? What happens to Bob? What happens to Fran? What happens to John? What happens to these people? Well, the answer is they all shift down a slot. Okay, so Alice will go here. Bob will go here. Fran will go here. John will go here. One, two, three, four, five will go here. And then 10 needs to go down a slot. So the list actually has to grow. So what's going to happen is, scrub this out. Basically, it works like this. It kind of works in reverse. 10 goes here. One, two, three, four, five goes here. John, Fran, Bob, Alice. I inserted Paris at index zero. So Paris is the new value that goes here. Okay, so what happened? I had to shift everybody because I inserted right here at the beginning. I had to shift everybody who came after this down a slot. Everybody. Right? What is this cost of doing that? Well, an individual move, you know, the copying of the reading of this value that 10 was here, copying it, writing it back down here. That's big O of 1. But how many times did I have to do it? Well, that depended on the length of the list. Right? So the length of the list was 6. It's actually now 7, but it was 6. Right? How many moves did I have to do? One, two, three, four, five, six. Six moves. Bego of the length of the list is 6. Bego of N. So inserting like this is Bego of N. Well, now it does matter how big that list is because if I've got a list of 10 million items and I've got to shift them all down a slot, you'll feel that pain. You'll see that and you'll wait for it to finish. Okay? So inserting is not cheap. Appending to the end of the list, very cheap. But if I have to move everything down, not so good. Now obviously, quick thought exercise. What if I want to insert, say, an index 6? Maybe I want to put something here. Well, what if I put something here? Well, all I have to do in that case would be shift 10 down and put my whatever in 6. So I would only have to shift one thing. Right? Here's a cake. Well, so is that Bego of 1? Well, no. Okay? When we're talking about algorithmic efficiency, one of the things we consider, we mentioned this in those slides, is you consider the worst case. The worst case here is when we insert at the beginning. Okay? So we insert at the beginning, we get a Bego of n operation. So this is where we are. Let's talk about the next operation, list.pop at an index. Now, I'm not going to reset my board exactly. Popping an index. Well, just like regular old pop, regular old pop removed the last item in the list. Okay? Popping by an index is going to go to the index and pull that item out of there and return it. So it's going to remove and return just like regular old popped in. What's the Bego of this? Okay? Well, it's kind of the opposite of insert. Right? If I pop Paris out of here, if I call pop zero, what happens? Well, this, you know, Paris will get returned here. Paris will get returned. But now the array list has to maintain that property that it is a contiguous block. Okay? So it's not acceptable for me to leave this empty because what would happen? Right? If I tried to call list sub zero and there was nothing here, who knows what's going to happen? Bad day is what's going to happen. Security errors happen this way. Okay? So I can't have this whole here. It's not going to work. It's going to break my array based list. Okay? My list just won't work anymore. So what am I going to do? Probably what you think they're going to do. I also need to maintain the order. Right? If I was using in a list, the order I put things in matters, I would be very upset if I suddenly found out that 10 got moved up here. It's a quick move. I don't have to copy it from here to here, but that's not the order of things. That's not how they were put in there. An order is probably really important. It often is. Okay? So what do I do? What does Python do? It shifts everybody up a slide. Right? So Alice moves up a row. Bob is going to move up. Back to where they were. Fran is going to move up. John is going to move up. 1, 2, 3, 4, 5 is going to move back up. And 10 is going to move back up. The other thing that's going to happen is the length that's going to be reduced up here. Okay? So now I've got six items back in my list again. That means that this guy is kind of gone. Right? There's nobody in there. That space is probably still reserved for this listing memory, but it's not being used. Okay? So this is good. The length has to be reduced, though. That's what's going to make my append and my pop out an index work really fast. Okay? So once again, what's the worst case? The worst case is I remove the item at index zero. Okay? And that means everybody who comes after has to shuffle up a spot. Okay? So this also is the go of an operation in the worst case. Okay? So I'm going to do just a couple more. One is list. What do we got next? List.remove. Okay? So list.remove. Okay? So my first example is list.remove. All right. So list.remove. Removing from a list is a little more sophisticated. Remove is saying I know that the value is in the list somewhere, but I don't know where the index is. So I have to search for it. So removing is two things. It's search plus removing the leading. This method does not return anything. It doesn't give you anything back. It is a so-called in-place method. All of these are in-place methods. In-place just means it actually modifies the thing in memory. Okay? So when Alice will come out and the list will be changed, but it doesn't return anything. It just does it makes the change. Okay? So we need to remove Alice. All right? Well, we don't have to look far. So it actually does a search of this list. So what's the simplest way you can possibly imagine to do a search for a value in a list? Well, the easiest way is just to kind of go from top to bottom and say, are you it? Are you it? Are you it? Are you it? Are you it? Are you it? Are you it? And that's exactly what Python does. It's just going to take a for loop, go through the list and say, are you it? Are you it? Are you it? If so, I have found you. All right? So in this case, Alice is right at the beginning. For loop starts right here. Are you it? Yeah. Okay. I'm done. I'm done looping Alice as well. So I'm going to take Alice out. What happens when we remove an item out of this list? These bad boys, they all got to shift up a spot. Bob moves up. Fran moves up. John moves up. 12345 moves up. 10.0 moves up. And I reduced the count. Okay. I'm not going to clean this out. It's a lot of work right now. So how many moves did I do? Well, I had six items, one, two, three, four, five, six. I used to have six items. And I did 12345 moves. That's on the order of the length of the list. It is n minus one. So the big O here is big O of n, right? Because I don't care, you know, I don't care about n minus one. I don't care about the minus one. It's that n that is the dominant factor. I take the highest order term. So in fact, this is big O of n right here. Okay. Because once I remove the thing, I got to shift all the others up. But there's another way that this can be big O of n. And that's the next example. So let me put, let me put Alice back in this list for a minute. Alice here, length six. So the next thing that I do is I'm going to do list dot remove Horatio. Okay. Now you know, just by glancing at this, that Horatio is not in this list. The computer does not know, Python does not know that Horatio is not in that list. It just doesn't keep track of that information. We will look at a data structure later in the semester where it effectively does know that it's in there in real fast. But Python doesn't know. So it's got to look, right? So list dot remove is searching for the value and then removing it if it finds it. Okay. So what Python will do is it starts at the beginning in a list and says, are you it? Nope. Are you with it? No, I am not it. Alice is not it. How about you, Bob? Are you it? Nope. How about you, Fran? Are you it? Nope. John? No. One, two, three, four, five, no. Ten, no. I guess Horatio is not in here, right? It only knows that Horatio is not in there once it has inspected every single element of the list. Now this is the worst case once again, right? Horatio isn't in there. It has to go all the way through the list to find, figure that out. There's no shortcuts to that. This list isn't sorted or anything like that. There's no way of shortcutting this problem. Okay. How many times did it go through and ask the question, are you Horatio? One, two, three, four, five, six, length of six. So in the worst case, removing is still they go then, right? Because I'm trying to remove something that isn't there. If you do this at Python, go try it. You will get an exception. Believe it will say value error. Value is not in the list, something like that, right? So pretty mad. And it doesn't matter, right? This combination, this combo here of searching and removing. Say you want to remove Fran out of this list, right? You've got a search. One, are you it? Are you it? Are you it? Yes. Three searching. And then I've got to move one, two, three up, right? So it's still big O event. Even if you pluck something out of the middle, the combination of search number, search steps and removing steps are going to come out to big O event. Okay. So removing, popping, inserting, they can be expensive. They, it depends on the size of the list. But if we're talking about lists with millions of items, which is not uncommon in computing, this could be a real problem if you need to do this kind of stuff. All right. I want to look back at one more example here. Last thing, scroll down to the very end of the page here, right? So suppose that you have this scenario that I've got down here. Let me go a little bit. Okay. Suppose you've got this scenario. You've got a nice full list. You've got three items in it. Let me drag it over top. And I want you to pay attention to the, to the board behind me anyway. Let me wipe this out. Got a nice big list. Okay. And it's full, right? We said that Python preallocates a contiguous block of memory. Okay. What happens then if the memory after your list is also full? What's it going to do? Let me drag this down. So you're not looking at, I guess I can't get rid of that wonderful tool, but white. Thank you so much. Acrobat. Okay. Sorry about that. So again, suppose that you've got a full list and you need to grow. I want to append Bart onto this list. That's a problem. Why? What has happened? The operating system or Python or someone else in the physical memory here, and we're talking, let's try and do this. We're talking, wow, this is really challenging to do. We're talking right there-ish. Wow. Reverse. Don't try and do things in reverse, folks. I have new appreciation for dental hygienists. Right here. There's something else here. It's not my list data. Could be some other object in Python. Could be something from another program that you're running on your machine. Could be the YouTube video that you're watching right now. We're listening to, or the game you're playing instead of watching me. In any case, there's something else right here. Now, Python or your operating system, one or the other, they're not going to let Bart overwrite this data. That would be bad. If it's the Wild West in there and data is just overwriting data without any kind of control, that's a problem. That's a security issue. There are security flaws that function that way. You can't touch this hammer. What's going to happen? I still need to append the bar, right? You will run into this situation. You have run into this situation. You didn't know it. What do you think Python does? It needs to grow. It needs to grow this list, but it's got no room right there. What does it do? Well, what it does is actually fairly simple. It's going to take this list. Let's try this. Oh my gosh, what's he doing? It's going to take this list and it's going to find a brand new space. A brand new space where everybody can live. It can accommodate the new size of the list. We'll still have the object header here. We'll still have the length here, but now we will have room for everybody else. Who do I have on my list? Alex, Fran, and now Bart. Bart has room. There's even more room to grow down here. All of this stuff is going to be allocated to the list and then we run out of room. It copies the list to a different physical place in memory where it has enough room to accommodate the list. You may have run into out of memory errors in Python. You get out of memory errors when there's no more room for Python to play completely. But as long as it can, it'll just take this old object and move it somewhere with more space. That's fine. And when this happens, you actually see the ID of the list change. You will see its memory errors change. But what this means, though, is that every once in a while, in terms of efficiency, we said that a pending was big O of 1. Every once in a while, and this can also happen when you insert into the list, whenever you're adding to the list in any way. Every once in a while, this gets expensive because it has to copy the whole list to a new location. But what does copying entail? It involves reading an old value and writing to a new location. But how many times do you do that? You would have to do that for every element in the list, which is length n big O of n. You don't have to worry about that implementation detail Python does for you. It's seamless. It's transparent. But just know, just be aware that if Python's list runs out of room to grow, it has to copy the whole thing to a new location in memory. It might be slow for just a little bit. That's our deep dive into the array list. I'm going to switch over to the computer and demonstrate what big O of 1 and what big O of n looks like so you can actually see the difference.