 Hey folks, so we've switched over to our PyCharm editor, and now we're gonna work on coding a stack. We'll get you started anyway. So if you go and download the next assignment, I have already done so, and I've unzipped it, and you'll see that there's a bit more files than you are perhaps used to for this assignment. But the key one that we're gonna work in right now is stack.py. So we are, yes, we are in fact going to go ahead and start implementing a stack. Now when you open this code, you're gonna see some different stuff. Let me get rid of my head for just a minute. You're gonna see some different stuff here. So I've already started to define the class for you, and then here's what you need to do. I'm asking you to implement the following class methods. The constructor, pushing an item, popping, peaking, is the thing empty, and what's its length. Now you notice that length here is one of those weird Dunder methods, okay? And this is also a typo, so I'll fix that right now. All right, so there's some other stuff down here at the bottom, and we will talk about this here in a little while. But I wanna point out to you now, you can run this code, okay? And if you run this code, it will actually check for you. And it'll give you some output that says, oh, it failed, it passed the test, something like that. So this assignment is nice because it kind of self tests. And if you have passed all the tests, you're in good shape. Now this is no excuse for not reading all the instructions because the instructions may say that you have to do something in a certain way to get full credit, okay? But you can use this to help you test your code, all right? Just by running this file, so right clicking it and running it. All right, but everything is gonna be failing now because I haven't written my stack class yet. So let's implement the stack class. All right, I guess I'll bring my head back. Okay, there we go. So follow along here, all right? The first thing you're gonna need to do is implement the class constructor. Now we are taking the abstract data type of a class and the things it does, pushing, popping, peaking. And we have to concretely implement it in Python to create a stack data type. That's what our goal is. And then later in the assignment, you need to use the stack you made to solve a problem. You're gonna do a version of the undoing what you last typed problem, okay? So let's start by defining our constructor. We have an abstract data type. We're working on defining our class. Now we're gonna have to think about our data structure. Internally, how are we gonna organize the stacks data? Let me get started by defining my constructor. Constructor is named init. And in fact, the only parameter I need is this wacky self parameter, right? Don't need anything else because I'm not putting any data into the stack with its constructor just yet. But I do need to initialize the internal data structure. Okay, so the internal data structure I'm going to use is Python's array-based list, okay? So I'm gonna make an internal instance variable, an instance variable, and I think I'll call it data. You could call it, or how about this? How about items? We'll call it items. Those are the things that are gonna live inside the stack. And I'm gonna use a Python list to store it, okay? And so right now you're like, why are you using a list inside a class? Why not just use the class, the list directly? The reason is because we're trying to learn about stacks, but you're also trying to simplify what your data type does. The user of your data type. Let's imagine that you're gonna write this stack code and send it out to the whole planet of Python users. They just want to stack. They just want to push, peek, pop. They don't care about all the other wonderful magical things a Python list does, okay? They don't care. They just need a stack, right? So we're wrapping, we're hiding the full implementation from them. We're hiding the fact that we're using an array-based list internally as our data structure, okay? Just like you probably had absolutely no clue how an array, how a Python list works. Python hides those details from you, because you don't care. You don't care about the details of how a string class works. You just want it to work, right? You don't care how data is arranged in memory. Same here. You're creating a data type, a stack, and your users, which are you and me, maybe some other people, don't care how it works. They just want it to behave with these behaviors, okay? It's called encapsulation, keeping stuff behind a wall, okay? The other reason we hide the data structure is we can change what we do here. We don't have to use a Python list. I'm going to, but you don't have to, right? So we're keeping that hidden, how we store this in our data. The other thing I'm gonna do, and I'm kind of cheating here, I'm kind of peeking ahead, I know that I need to support calls of type length, like you would do on a list, length of list. So I'm gonna cheat and I'm gonna keep a counter and I'm gonna initialize that counter to zero because the stack is initially empty, okay? So now I have all that I need to make a stack, okay? And I'm gonna do that down here, right? So I'm down here, I'm outside of my class stack, and I can make a new one of these guys now, okay? S gets stack, right? So my new stack is stored in this variable and my tests are still running. Why don't I, you know what I'm gonna do? I'm going to comment these out for now. So here's my new stack, and that's it. Don't do anything, but I didn't get an error. So that's a good sign. Let's start adding stuff to our stack, okay? Let's implement push. Def, all right, so push takes one parameter, which is the item, okay, that we wanna add to the stack. So we def push, okay, the first parameter to every method you write inside a class is always gonna be self. You don't pick this, you don't choose this, it's always self, okay? But I need to be able to add something. I'll add it and I'll call this parameter item, okay? Now, how do I add to the list? All right, well, when I push to the list, where am I gonna put it? I've got this thing called self.items, and that's where I'm going to keep the data, right, inside. I use this list to hold the data items. Now let's think, where do we want? A stack, though, behaves a little differently than a list. We only operate at one end of the stack. So you have to think conceptually, where do I want the top, where do I want the top of the stack to be in my internal list, okay? You really only have two choices, the beginning or the end, okay? The beginning of the list or the end of the list, right? So if it's the beginning of the list, we're gonna put anything we push onto the stack at the beginning of self.items, and any time we pop from our stack, we remove from the beginning of self.items. The beginning is index zero, index zero, or the end, which is index negative one, okay? If we have it at the end, we can append items using list.append to our items list, or we can call list.pop to pop an item off the end of the list. Popping removes and returns, okay? What do we do? Well, think about algorithmic efficiency. A Python list operating at the end of the list, appending and popping, those are big O of one operations, operating at the beginning of the list by inserting or popping zero, that's a big O of N operation. So which do you pick? You pick the faster one, okay? You always pick the faster one. So we will choose the end of our internal list to be the conceptual top of the stack, okay? So when I wanna add an item to the top of the stack, the top of the stack is the end of the list. How do I add something to the end of the list? I append to it. What am I gonna append? Item. And that's it, okay? That's it, we can push to a stack. Push does not return anything, okay? We just add it in there. Don't do anything, don't return anything, just add it onto our stack, okay? Now, let's work on pop. How do we pop from the list, okay? Well, pop does not take a parameter, right? No parameters here. Removes and returns the top of the stack. Returns none, there's our none guy showing up if the stack is empty, okay? So I need to think here. Well, how do I return the last, how do I remove and return the last item in the list? Well, if you know your list operations, there is a shortcut. Self.items, which is our data, .pop will remove and give me back the last item on the end of the list, okay? Let's try this now. Let's work with our stack, okay? So let me put a few things in my stack. I'm going to push onto my stack, one, two, and then three, right? So I should have behind me in my stack, my stack should look like, whoops, that did not work out, okay? My stack should have one, two, three, with three at the top of the stack, right? At this point, okay? So when I call s.pop, what I expect to get back is three, okay? What I expect to get back is three, and let me print. Let me do something with this value. I'm going to print it out. Let me run. I got none, all right? So why did I get none? This looks right. Well, the problem is, I did not return this value. Self.items.pop goes in here and it gets the last item and pops it out, but I have to return it out of here, too, right? Right now, my method is just gobbling it up. I'm not doing anything with this value, I get out of this call. So I got to return this, okay? Now, let me run it, three. Three was on top of the stack. Let me pop the stack again. What should I get? Second time, two, okay? So notice, I'm popping. These things are coming back out and they're coming out in reverse order. Last in is the first one out from how they were added. Okay, pop one more time. And I'm just duplicating these lines. If you want to duplicate a line, put your cursor on the line and hit control D or command D on Mac, okay? Three, two, one. All right. So, what is the status of my stack after this last pop runs? Stack is empty, isn't it, okay? What is gonna happen here if I try and pop one more time? Do you know? Well, let's find out. Error, pop from an empty list. Yuck, that's no fun. And that's not what I wanted you to do either, right? So if you look at the call to pop, it says returns none if the stack is empty, okay? So, right, how do I know if the stack is empty? But there's a couple ways to do this. The first thing I would have to do, I could do, is every time I push onto my stack, I increment my counter, right? Every time I add an item, I kind of keep track. This kind of mirrors what Python's list does, right? Every time I add an item, I add, increment my counter. Problem. And then every time I remove an item, I decrement my counter, okay? What I'm telling you to do here is, if the stack is empty, return none. Don't do anything, don't edit it. So, before I go ahead and start popping things, let me just check to see if self.count equal equals zero, whoops. If so, or excuse me, if it's not equal, if it is equal to zero, let me return none, else pop the stuff off the stack, okay? So, take a minute, pause this, appreciate what this code is doing. The count is equal to zero, return none. That's my instruction to you. If it's empty, return none. If the count is, if there is something on the stack, count is greater than zero, decrement the count and pop it, cool, okay? This is just one way to go about it, okay? How about peaking? Now let's try this, okay? So now if I run this, I get none back when I try and pop the empty stack. That is in fact what I want to happen, okay? All right, so now let's just finish this, why not? Peek returns the top item on the stack but does not remove it and returns none if the stack is empty. Okay, so let's work on it. Peek, a peek on the stack, okay? What happens if the list is empty? Well, we can actually do, you can do this a couple of different ways, frankly. And honestly, I don't care which way you do it, okay? At this point, I want the thing, I want the stack to behave correctly, okay? So self.count equal equal zero, return none. Otherwise, give me the last item on the stack but do not remove it, okay? I can't use pop here. If I try and use self.items.pop, that'll destroy the item that's on the top of my list. I don't want that. Instead, just give it to me, self.items negative one, right? What's negative one? It's the last item in the list, okay? Let's try this. I'm gonna get rid of a few of these pops but let me print s.peak. So I'm peeking here, right? It gives me back two. If I peek again, I should still see two because I haven't removed the item off the top of the stack, okay? That's the difference between peeking and pie. Cool, simple. All right, there are two more methods, is empty and lend. Well, these are pretty trivial because I've got my counter variable. So let's do isEmpty first, okay? How do I know if the list, if the stack is empty? Well, if the count is equal to zero. If the count is equal to zero, then this will evaluate to true. Yeah, it's empty. Otherwise, this will evaluate to false. Easy. Now the last one is lend. The last thing we have to implement is lend and you will see it is a Dunder method. It is one of those magic methods. You'd never ever call Dunder methods by invoking like .under, underscore, underscore, lend, underscore, underscore. There's a different way of calling them, okay? But length should return the number of items in the list or excuse me, in the stack. That's easy. I'm keeping track of what that is, right? As long as I keep updating this, I'm in good shape. Okay, so if I print, now how do I call this Dunder method? I can call this, now, lend of S, right? You call lend of strings, you call lend of lists, you can call lend of tuples, you can call lend of a couple of different things. Well, now I can call the length of the stack. When I call lend like this, this gets invoked magically. This Dunder method is magically called. And what does it do? It returns the count, okay? So if I print this, here we go. All right. So I'm kind of, let me just print length of S after I push three things and voila, three, okay? So looking good, maybe let me pop somebody and then check the length again. Should go from three down to two, okay? Cool. Now I will say, I will admit or I will tell you, you do not need this, you do not need the counter. Think about if you, following our programming mantra, we've got something that works. And now in fact, let me go down to this code. Do you remember all this test code down here that I commented out? Let me uncomment it, okay? And now let me run this. Now here's a beautiful thing. Now when I run this code, I see that all this stuff passed. And if you see a lot of check marks here, you should feel pretty good. And if you see a big X that says failed, you shouldn't kind of have a clue as to what you're failing on, right? It'll hopefully the error message will be useful there. So that's it, it's kind of cool. Now my tests are all passing, that's a good sign. It may not be enough to earn you full credit. Always go and check and read through the instruction specifically, the instructions may say you have to do something a certain way. You have to read the assignment. Don't just rely on these tests passing. This is a good sign. Don't rely on it exclusively, okay? All right, so that said, we have now arrived at the part where my code works in my programming mantra, it works. You can make this more concise and the way you will do it, I'm not gonna show you how to do it. The way you do it is you don't even need this. Use the LEN method on this guy, okay? How many items are in the list? I don't need a counter. I could just do return LEN of items, okay? That'll work. There are a couple other optimizations you can do in here. You can get all of these except I wanna say popping and peaking, maybe, or maybe just popping. You can get all of these down to one-liners, okay? See if you can do that. Good exercise. All right, so we have created, you have created a stack data type. Congratulations. Now let's use your stack more like you would use other data types in Python, not from within this method, but from another program, okay? So part of what you will have to do is you're gonna have to use your stack to process some of these undo files. And there are descriptions of how to do that in the assignment. But what I wanna talk about quickly is how to use your stack from a different file. So you will need to create a new Python file called undoer, right? And again, this is all part of the assignment instructions for this. And now I wanna use this stack that I defined here in this other file, okay? Okay, so time for a bit of terminology. Technically speaking, every file in Python that ends in .py, that is technically called a module, okay? Module is group of related code, right? That's all. But technically these are called modules. Now, if you want to use in here, right? Let's say I need a new stack. I'm gonna use a stack to solve a problem. I want to use my stack class to solve a problem, okay? We're gonna think of the stack class as something that someone else wrote. It's this other tool that we can use, kind of like the Python list. It's this tool that we can use, but we don't know the implementation. We're gonna use our stack class. So if I need a stack here, I'm running into trouble. PyCharm is already telling me, hey, I don't know what that is, right? Name stack is not defined. Let me say, well, wait a minute, I defined it over here. That's because this module right here, undoer, does not know where to look for it, so there are a couple of ways to resolve this. I'm gonna show you one. And you have seen this before, right? You have seen calls like this, import sys, we've done that before, or import math, right? You've used these things before. We're gonna do a variation on that. We're gonna say from stack, okay? So stack in this case is in reference to this stack.py file. So the name is really important here. It's lowercase stack. Python and PyCharm are gonna look in your directory for in the same directory as undoer here for stack.py, because you said from stack. Not from stack.py, don't do that. Just from stack, import the stack class, okay? So this name here is gonna be the name of this class here. All right, and it has to match. PyCharm and Python, they're doing a match on text here. If I change the name of this class, it gets mad. It says, do you mean this thing? Okay, so the spelling, the capitalization, all of that is super important here. All right, but now though, now I can make a stack and I can use it to do something. Stack.push, Achilles, print s.peak, print length of the stack. Right, and I can do it. But now if you look down here, right? So I have imported this stack.py module. This is importing. And this is what happens whenever you import sys, whenever you import math, whenever you import random, whatever you've imported before in Python. There's actually a file named those things. It's inside of Python's directory of stuff and it loads it in, right? That's exactly what's happening. You're just doing it here in your own code, okay? So I've imported this module of stack. And now you may notice though, there's some extra stuff down here, right? And in fact, if I comment all this code out down here and I only leave the import, let me run this. I still get some output. Where does that come from? Well, it's coming from the stack module. Remember my stack module, here's my class. And then after my class, I have all of this stuff. This code runs, okay? Everything, when you import a module, everything inside that module, everything in that Python file runs, it executes. So if you leave a bunch of stuff hanging out in here when you import the module, it gets executed. And that's exactly what's happening here, right? I've pushed one, two, three, and then I've printed the length of the stack and then I popped and then I printed the length of the stack. That's getting executed. And you probably don't want that, right? You probably don't want that. You may have noticed, if you're not exhausted by now, that when I ran this code here, when all it was doing was importing, it didn't run the test code from this file, right? I'm importing this file, but all this test code didn't run. But now it does, right? Now it does when I run right here. The reason for that is down at the bottom. See here, I've got this if underscore underscore name equals main. This thing says, this code, this is kind of like a very special Python convention thing. It says, it's basically an if statement that says, okay, if this is the main program you are running, in other words, you're running this file directly, like by right clicking and running it. I want you to do this stuff, but only if you are being run directly. If this code is running as a result of being imported, don't run it, okay? So that's what this if name underscore underscore main stuff does. And I will tell you, I'll just say now, it's probably best practice in general to have this at the top of your main method of your program, okay? So be aware of what this does. I'm not gonna like quiz you on it or test you on it per se, but try and understand what it does, all right? And so all the stuff we talked about with the modules, there's more information in the slides on it, and then also that if name equals main operation. Okay, so we're gonna leave off with the stack by talking about the big O of these stack operations, okay? Here again, when we implemented the stack, we used an array-based list as our data structure of how we arrange the contents of the stack. Where we chose the top of the stack to be makes a big difference, okay? When the top of the stack is at the end of the list, pushing and popping from the stack is big O of one. That's because appending, our operations for appending to the end of the list and popping from the end of the list are big O of one. However, if we treat the top of the stack as index zero of the list, big O of n. That's because insert zero of value on a Python list is big O of n. Similarly, pop zero, which pops by index in a Python list is also big O of n, right? Because you have to shuffle everything down a slot or you have to shuffle everything up a slot, okay? So here our choice of data structure was really key in how we use it, all right? Now a stack is pretty simplistic. Your choice of the top matters though. So make sure you understand this. And if trying, you know, maybe experiment, flip around the stack operations instead of using list.append and list.pop, use list.insert and list.remove. For big, if you put a lot of stuff in that stack, it's really gonna slow down. Choosing the right data structure to use, using it the right way will determine the efficiency of your data type. And that's important, right? This is in computer science, you're in programming, you are always gonna be making trade-offs. You are always gonna be in search of the most efficient way to do something. So you have to be aware of the cost of the decisions you make. And so this is just the first example of that. Now a stack though, we can make it really efficient than we have, but stacks are very limited in what they can do, right? It's just adding and removing from one end of the stack. And sometimes that's all you need. It's a perfectly adequate solution. Next video, we're gonna talk about a queue and we'll see that it's also pretty simple in what it does, but it also has a pretty severe limitation it using the array-based list as its data structure. Talk about that next time.