 Hello, everyone. Hope you all are doing well. This lecture, we will talk about Python's list, which you are all very familiar with by now, and our very first data structure. Yes, we're four weeks into the semester, and now we're going to finally look at a data structure, and this particular data structure is called an ArrayList. So we'll talk about what it means to be a data structure and how they, this particular one, the ArrayBasedList, operates. So we've had a lot of build-up to this point, right? We talked about kind of how memory works a little bit in Python, and we're going to see why that's so important today. And last time, last week, we talked about the concept of Big O, and this week we're going to put it all together along with classes and objects. We're going to put it all together, and we're going to see why all that stuff, memory, Big O, objects, classes, why it was all so important that we kind of lay the groundwork for that, because now we're going to get into really talking about the substance of the course, which are data structures, okay? So let's get into it. So, so far we have been using the term data type quite a lot, and you're familiar with data types. You know integers, you know strings, and now you know how to create your own custom data types by defining Python classes. Now there's a difference between a data type and a data structure, and you need to know this difference. So the data type, recall, is the data and the operations you can perform on that data. So in Python, you have a list class, for example. That is a data type. It's got methods, it's got information that is stored within it, right? And in our discussion of classes, we had a student class, and it had data about names, and it had an operation called say name, okay, right? So that's a data type. A data structure has more to do with how the data items inside the data type are stored. How are they arranged? Where are they put? And when we say, you know, how are they stored? What are they, how are they arranged? What we're talking about is how are they arranged and stored in memory, okay? And what we'll see in this lecture, in the coming lectures, is that how these things are arranged in memory has an enormous impact on their efficiency. And how do we measure efficiency? We measure efficiency using big O, okay? So data type, data and operations. It's a concept, right, and an implementation. Data structure is more of like the strategy. How does the data type deal with the data it's storing? And you haven't had a lot of control over that so far. But in future parts of this course, we are going to be implementing our own data types, and we are going to have very precise control over the data structure. But let's start by talking about a data structure that you are already familiar with, and that is the array list, okay? So you need to know this, the array list. Python's list class, which you're all intimately familiar with, organizes its data internally in what we call an array list data structure. Now what is an array? If you're coming from other programming language background, you've heard of this word array. If Python's your only experience with programming, you haven't probably. But what is an array? An array is a contiguous block of memory. Okay, so we've got these main memory sticks out there living. And then inside Python, we have heap memory, which lives somewhere physically on these RAM sticks. A contiguous block just means that we've got a whole bunch of things right in a row. Okay? Remember memory is byte addressable. We can go to any single byte of memory. A contiguous block just means they're all packed one next to another, right? It's a chunk of this memory that is kind of set aside. It's not just a single place, but it's a group of these things, right? That is an array. Contiguous means there's no gaps in here, okay? All right. So arrays are really powerful, and they've been around since the earliest days of computing. The main benefit of using an array-based list, or an array list data structure, is that we can access any element of the list in constant time in big O of one time. Okay? And that's really useful. That is why Python's list, and when we talk about list indexing, we're talking about, you know, when you call list sub zero or list sub one. Turns out looking those things up is really, really fast, and it doesn't matter if you've got a billion items in your list. If you call list sub 999 million, it takes the same time to go and find that 999 millionth item in the array-based list, as it does to find the very first item or the very last item, okay? So we're going to talk about why that is. All right, let me, let me get rid of myself on the screen here, so we've got more room. Okay, see you later. I'm still here. All right, so you are creating a list in Python. You have made a new empty list, all right? Let's talk about very specifically what happens, um, and so that we can understand why this array list data structure is so fast in certain cases, okay? So here's our view of memory, all right? I've just kind of picked a block out of memory. The address values here really aren't important. Just know that I've picked a chunk of memory here, okay? And this is out there, it's living on the stick. So when you write your code that says names gets empty list, okay? Python goes to its heap memory and says, hey, I need a chunk of real physical memory for my list that I'm going to make. And as we demonstrated in one of your homeworks, if you go and look, this thing, despite the fact that it's an empty list, it actually occupies some space in memory, right? Like, uh, I don't have, let me see if I can pull up a command prompt real quick, right? Hopefully you can see this and hopefully I can get it to work, right? So I've got Python here and when I create a new empty list, I call it names, right? And I do sys.getSize of names, 28 bytes, right? So yours may be different on your computer if you do this thing, the number of bytes returned, that's okay. But the point is this empty list occupies some space, all right? Well, what space does it occupy? It occupies this stuff, okay? Python jumps out there and it puts a few pieces of information in there for you. The first is it creates an object header, okay? And let's say that this is 24 bytes. You don't need to worry about what this object header does. You don't need to worry about how big it is. Just know that it's there. It's something that Python uses internally for some stuff. The other part of what it creates, and this is interesting and you do need to know this, is called the length, right? The length of a list is stored as an integer, okay? So what does Python do? Python is keeping track of the size of a list as you add things to it or even removes things to it and it keeps track of it in a little bit of space here dedicated to kind of an integer variable. This is kind of interesting and it's going to be important for us later. So again, when you make an empty list, Python creates this block of space and it puts some data at the beginning called the object header. We're not going to worry about that. And then immediately after the object header goes the length of the list. Well, what is the length of an empty list? The length is zero, okay? So the value here is going to be zero, all right? That's cool. Fine, no worries. It also kind of reserves, and this is important. Oh, before I get there, the exact number of bytes that each one of these things takes varies. It's going to depend on your computer, right? Are you on Mac? Are you on Windows? And are you running 64-bit Python, 32-bit Python? That's not important. What is really important here is that the number, the amount of space that these things occupies is constant for your computer, okay? We're going to see why that is so important in a little while, okay? But anytime you make an empty list on your computer, it's going to take some fixed amount of space in memory just to say, hey, I'm a list and I live here, and also here's my length. This happens every time you create a Python list. The other thing that happens, so I guess I should say, when you call length, length of names, length of a list, all Python does is it returns this integer, right? So it's kind of neat. It doesn't actually run through and compute how many items are in the list. It's just keeping track of how many items are in the list as you add and as you remove, and then whenever you need to know how big or how many items are in the length, it just returns this integer. It's not being computed every time. That's a neat little efficiency, right? Calling length of list is big O of 1, and that's why all it's doing is returning an integer. As we talked about in our discussion about analysis algorithms, we treat returning values, looking up variable values as big O of 1, okay? The other thing that happens when you create a list, Python goes out to memory, it puts this information in there, it pre-allocates, it reserves a larger block of memory in anticipation of you putting stuff in this list, okay? Now arrays need to be contiguous. We'll see what happens later. But now we've got some room here, this green area, that if we want to append stuff to a list, it goes in here. Down here, we've got some free memory. This is not part of the list. This is like empty space. If you define, say, you create a new variable, it may go in here. This is free memory. It's not part of the list. It's there. It's up for grabs, okay? All right. So what happens when we do like one of the most basic operations, which is list.append, right? So let's append names, let's append Alice to names. Okay, what happens? Appending an item to the list, the first thing that happens, actually, is the string Alice is created somewhere in memory. We don't know where it is. Python goes to its heap and says, hey, the user wants a string called Alice. I need to put that somewhere. And so it goes and it puts it somewhere. Who knows where, right? Python knows. You don't know. But that's the first thing that happens. The string is created somewhere in memory. Then when you append Alice into the list, you don't append the actual value of Alice, okay? Instead, what you are appending is the reference to Alice that lives in memory. Okay, so Alice lives at memory address, this thing, the string of Alice lives at this memory address. When you are appending, you are appending a reference that kind of says, hey, the first item in this list, I don't know what it is. You got to go over here and look for it. Okay, so that's really interesting because this means, if you recall, you can append different things into memory. Names can have strings, which makes the most sense, but you could also append an int in here. You could append a float in here. Regardless of what you append, all what you are really appending is a reference, okay? Out to this thing that lives in memory. Each object reference is going to be 8 bytes. Now, the exact number of bytes depends on your computer, but it's constant, and that's the important thing, okay? Let's go over here. We'll go to the next slide, and then we'll go take a quick look at the Python tutor, right? So let's append Bob. Bob is going to get created somewhere here in memory, right? We don't know where. Python knows, it's at this address, and what happens inside the list data structure is you get a little reference to Alice or to Bob. You're not storing Alice directly in this space in memory. You're not storing Bob directly in this space in memory. You're storing a reference to them. It's really important, okay? So, Python's list, what happens is this, it has a data structure called an array, an array-based list, and every time you append, you are adding a reference to that thing you appended inside the list, okay? The data structure here, this is the data structure for a list. The data structure is an array-based list, okay? So let's take a look at this in our Python tutor. Let me pull it up real quick. Here we go. Let's see this, PythonTutor.com, okay? Start visualizing our code, and I need to make sure that let me render all objects on the heap down here, okay? And let me go into live programming mode, okay? Render all objects on the heap, all right? Let's do names gets this, cool. I've got a names object and it's pointing to an empty list. Now let's append Alice to this list, all right? So see here what's happened? I've got this string Alice that's living somewhere in memory, and list, my names is a list of type, and at index zero, it's pointing to this thing. The actual value is not there. It's pointer to that value, or a reference. Pointers in reference, that means the same thing. Bob, right? The list are full of references to these things, and I can append an integer, I can append a float, doesn't matter. They're all references to these things that live elsewhere in memory, right? So it may be convenient for you to think of lists like this, and that's okay if it's convenient for you to think of lists in this way. But be aware, you need to know, as good computer scientists, that what the list actually looks like is this. And you have to understand that, and this is why we talked about memory so much a couple weeks ago. You have to understand that because this representation of a list explains why certain Python list operations are big O of 1, and certain Python list operations are big O of n, okay? All right, so again, you know, this is how you can think of lists, and this is totally fine. Just be aware that lists in Python are not storing the actual values in this block of memory. Instead, they are storing references or pointers to where these values actually live in memory. Hugely important point, okay? So, we have a lot, I'll bring myself back here for a minute. There are a lot of list operations in Python, and they have different big O's depending on which one we are using, okay? We're going to go, in a minute, we're going to switch over to our worksheet, and we're going to explain why these list operations are the big O that they say they are, okay? You are responsible, just telling you now, you are responsible for knowing what each one of these list operations does, and you are, so, here's the code, here's the operation, you need to know them, and you need to know their big O, okay? Why do you need to know? Because we are going to use this list to solve some common problems in computing. We're going to do that in a video or two, okay? So, you got to know the big O's here. Now, what we're going to do is we're going to switch over to our worksheet, and we're going to talk through why these things are what they are, okay? So, we'll be back in a minute in a different video where we're going to work on our worksheet. See you then.