 In this video, we are going to look at the most common list methods and let's do so with an example. So let's create a new file and call it list methods. And let's continue with a new example. Let's call it name. So we are going to create a list holding names. And the first name is going to be Carl with a C. And the second name is for now going to be Peter. So let's execute that and let's look at names and names is indeed a list with two names. So in our memory, what that looks like will be as follows. We create a list object like that and here I will make it a bit bigger because we will have to make some changes throughout the video. And yeah, so let's also write here list data type. And now for the first element, we simply have the name Carl and I will abbreviate that with simply a C, Carl with a C and the second name is going to be Peter with a P. So let's simply write here P. So these objects are actually string objects. I'm not going to write that here, but it's going to be string objects, Carl and Peter. And the variable name is also going to be named simply and it's going to reference this object. That is what we have created so far. And now what you're going to see is why for all the times now in this course where I drew a list in memory diagram, I always had those excess spaces, those excess fields here where I never put something in there. And I always told you that the reason why Python creates basically list objects a little bit too big is so that we have some flexibility to later on add some further objects into this list. And this is what we will do using list methods. And also not all methods, of course, do that, but list methods help us do that, of course. So let's look at the first method. The first method is very common. It is called the append method. So we say names.append. And to repeat that, a method is simply a function that is attached to an object, in this case a list object. So we are going to say the names list object, and then we're going to write the dot operator to access the attribute called append. So the append function, if you so will, that is attached to the names object. But any function that is attached to an object is what we refer to as a method. And we will see in chapter 11 when we talk about object orientation of how we can define our own methods and what methods really are. But for now, we simply call them functions attached to an object. So the append method takes one argument, and that is actually not entirely true. It visibly takes only one argument, but actually takes two. So the argument here, as an example, is going to be, let's say, the German name Eckhardt, I think, with a DT. And yeah, I think the age is wrong, so let's put it like that. So German name, some name starting with an E. And what the append method is going to do, it is going to take the object, in this case the string object Eckhardt, and it's going to append it to the list at the last position. So if I execute that, you see one minor thing, some minor detail that I want to make you aware of. Below the cell that I just executed, there is no output. So whenever a method has no output, there is a pretty high chance that something must have had a side effect. And we will see that in the memory diagram. The side effect is the following. We simply create a new string object, the name Eckhardt, put the E here, and we'll simply go ahead and put another reference into the list. So the side effect here is that the list now has one more reference than before, and it happens to reference the letter E here. And so why is that important that we don't see any output here? So there are mutable data types like lists, but there are also immutable data types. And in chapter 6, we talked extensively about the string data type to model textual data, and we said that the string data type is an immutable data type. So once you create a string object, you cannot make any changes to it. So with a list object, things are different. The list objects are mutable. And we just saw that we can put a new element at the end of a list that is called appending. And what does that have to do with method calls? Well, in the chapter 6 on strings, when we called, or when I showed the methods, most of them actually returned a string. So there was a string given, and we called some method. Let's say I called a lower method on the string, and I get back a new string object where all the characters are lower cased. However, that is something that is generic, so to say, for many immutable data types. So whenever you have an immutable data type like a string and you call a method on an object of this data type, and the method actually changes the value, then it does not change the value of the actual object in which we call the method, but it rather goes ahead and creates a new object with the changed value. That is why that is so significant here. So for data types like the list data type that are mutable, we don't get a return value, but to indicate that something has changed in place. So we have made a change to an already existing object. So that is what you can see when a method has, or what you can guess, when a method has a no return value here. It's not true for all methods, but for methods that should change, conceptually speaking, the value of an object, that is the difference between a mutable and an immutable object. So let's look at names, and indeed the name echo is at the end, just as I showed you in the memory diagram. So that's the append method. So let's take that one step further. Let's assume you don't want to simply append one object at the end of the list, but you want to append several objects. There are two ways you can do that. The first one is to write a for loop, for example, and in the body of the for loop call the append method, and then one by one append a new object at the end of the list. However, you can also use the so-called extend method, and the extend method also takes one argument, and the one argument it takes is a list object, in this case an iterable, something we can loop over, and this iterable contains the elements that are appended. So for example, I will append the name Carl with a K, and the name Oliver with an O. I activate the code cell, and we see that we don't have any return value. That is an indication that the method extend changed something in place. So let's look at names, and indeed names has been changed. So let's look at the memory diagram, and simply what the method did is it simply appended a further object right here, which is Carl with a K, and one more, which would be Oliver, and we show that with an O right here. So extending is really just appending in a loop, so to say. Repeated appending. Okay, so let's take that further. So let's assume you want to put something in the list, but you don't want to append it at the end of the list, but let's say at some other spot. So for example, let's assume I want to insert some element at the very first position. How can I do that? Well, we are going to call the insert method, and the insert method now takes two arguments, the first one being the index of the position where we want to insert. So I said at the beginning, and the beginning is index zero. And then the second argument is going to be the object that we want to insert, and let's say I want to insert Bairthold. Let's execute that. We see there is no return value under the code cell that indicates that a mutation happened. So the object, the names object was mutated in place. Let's look at names, and we will see that Bairthold is at the beginning of the list. So what has happened in memory? So in memory, what's going on is the following. Once the insert method is executed, it basically, and in this case we inserted with index zero, that basically means we have to make space here to put something in. And the way how Python does that is it simply goes ahead and it takes all the references that are already in the list and moves them one to the right. So we will get rid of that reference, just like that. This reference, this reference, and this reference here. And also last but not least, the very first reference. And now we have made space. And what are we going to do here? Well, obviously we are going to insert Bairthold, which would be the B, and we will go and put a reference right here. So now there is a consequence of that. So whenever Python has to move references, in this case it has to move all the references by one, this takes longer. A pending is very easy because there is already an empty spot at the end. We can just put in a reference, that's it. It's a so-called constant time operation. However, inserting at the beginning of the list means that we first have to move all the references by one to the right. And that is what happens in so-called linear time. Linear meaning it takes time that is proportional to the number of references we have. So if we have like here, I think it was five references, it takes five computational steps to move five things one to the right. If we had five million references here, it would take five million steps, computational steps to move everything to the right by one. That is what we refer to as linear time. Linear time is still okay, but it's lower than constant time. So inserting at the beginning of the list is a slower operation than appending at the end of the list. Something to know if you want to work with big data. So let's continue the example. And the next step is instead of inserting at the beginning of a list, what we could also have done is we could have gone away and inserted at any other position. So if I went ahead and let's say, I put let's say index one or two in here, we would insert the object at the corresponding position. And then only the reference to the right of it have to be moved, of course, but still it is still a so-called linear operation, linear time operation. So that is inserting. So now we have a list here. And obviously the names are not in alphabetical orders. So maybe let's do now, let's talk about how to sort a list. You know, list, usually list remain or list remember the order in which we appended or enter or put objects into the list. And now we want to change the order. We want to sort it. We want to sort, for example, in alphabetical order. So one way of doing that is to use the built-in function called sorted. So let's look at the documentation of the sorted function. Let's go to the library reference, the built-in functions, and where is it? The sorted function here. And now sorted takes a couple of parameters as we see here. And the first one is an interval and then the other ones all have a default value here. So we only give it one argument, which is an interval. And then it says return a new sorted list from the elements, from the items in the interval, okay? So let's go ahead and do that. So I call the sorted function and I pass names as the only argument and I get back a list object where all the names are in alphabetical orders. The reason why they are in alphabetical orders is because the sorting with a list works like that. Python simply dispatches the or delegates, that is the technical term, the sorting to the involved object. So in other words, Python will go to the Behrthold object and the Carl object and will ask them, hey, can you please sort yourself by using the lower than operator? And we saw in the chapter six when we talked about string comparison, when we compare words, how they are compared. And we remember that all the uppercase letters come before all the lowercase letters and things like that. So sorting is not as trivial as it seems, but it's not case insensitive. But here all the names come in title case, so the first character is uppercase, the rest is lowercase and also there is no first letter used twice here. So it's therefore the sorting is very much straightforward here. And we see that we get back a sorted list. However, and that is now the important thing to notice, if I look at names, it is still as we see unsorted, okay? And if we look at the documentation, it also says return a new sorted list, okay? In other words, it takes any iterable, the sorted built in function takes any iterable, for example, a list and it returns a new list. So now we have two lists here, the names list as before that we still have in memory, but also a new list. And I'm not going to draw the new list in the memory diagram to save ourselves some time and space, but this would be a new list, which referring the previous video has a shallow copy of all the elements in there, right? So maybe I showed to you in this diagram, but what this would do is it will create a new list object and all the references here, they are basically copied over in sorted order into the new list object, but the object themselves, so the string objects themselves, they are not copied. It would be a shallow copy. So in the earlier video, and there is also one of the reasons why I included the video, shallow versus steep copies, is in Python, oftentimes shallow copies are created without you explicitly knowing about it. And when you use the sorted build and you get a new list, which basically is a shallow copy of the old list and the references are in a different order, in a sorted order, of course, but at the end of the day, both the new list that comes as the return value from the sorted function and also the old list names here have references going to the same string object. So they share the same data in between them. So implicitly, we are creating shallow copies all the time when we do sorting. Okay, so let's continue with methods. So you may wonder, why am I showing you the sorted function here? Sorted here is not a method, it is a function that takes, for example, lists as an input. However, the list data type also has a sorted method. So let's see what the difference is. I say names.sort, so it's a sort method. And if I call that, I don't see a return value. So how is that? Well, the reason for that is because something happened in place. So something was mutated and of course the names object was mutated. So now names and what we see above here, the outcome or the output of the sorted function are now the same. But now we have made the changes in place. So the difference is sorted creates a new list object with a shallow copy of all the references in sorted order. The sort method, however, sorts a list in this case in place without creating a new list. So what the sort method really does, it simply goes ahead and it first removes all the references, all the orange ones here. And now it goes ahead and using red here, I think B is the first letter. So let's make B reference that. C is the second letter. So this goes here. Then comes E. So this will go to E here. This one will go, I guess, to K. Then we will go to the O. And the last reference, let's make it a bit more obvious, we'll go to the P here. So that is what the sort method does. It basically reorders the references, right? So that's basically what it does. It changes the list in place by rearranging the references. Big difference here. So now you may wonder if we can sort by, let's say, alphabetical order here. So why do we do that? Well, the reason why we are doing alphabetical sort is because this is how two names would compare to each other. So in other words, if I have, let's say, in the unsorted version, let's say I have Peter that comes before Eckhardt. Let's assume, let me simply copy-paste that. And let's copy-paste that in a cell. And let's compare that using the comparison operator. Falls here indicates that Peter should go after Eckhardt. And the true would indicate that the two objects are already in order. So this is how Python does the sorting behind the scenes. And now you may wonder how can you adapt the sorting? And you can do that using a key, so-called key function. So what is a key function? A key function is a function that takes one argument which are the objects to be sorted one by one. And it returns some other object, for example, a number that is then used to sort the actual objects, okay? So let's do an example. And that is, it sounds more complicated than it really is. So let's assume I want to sort the names here, not by alphabetical order, but let's say by the number of characters. So I want to have the short words first and the long words later. So how can we do that? So what we are going to do is, first I'm going to use the sorted function to illustrate that, I give it names. And if I call that, I get back the sorted names by alphabetical order, but what I can do is, I can pass it a second argument, the key function, and as the key function, I simply pass it a reference to the lend function, to the built in lend function. So in other words, what this does is, it will pass each of these words here one by one to the lend function, and then the lend function will return the number of characters in the word, and then this number is used to sort all the words. So let's do that. And we see that we have the short words first and the longer words last, okay? Now you may wonder what happens as a type breaker. So we see Carl with a C and Carl with a K, both have four characters. So there is an easy type breaker. The type breaker is, if two objects compare equal, then whatever object came first before the sorting also comes first after the sorting. That is called the so-called stable property of sorting algorithms. On other words, a sorting algorithm is stable if it has this property that whenever two objects compare equal, whatever came first before the sorting must come first after the sorting, okay? And now we can also do that if we look at names, we see that names are still in the alphabetical sort order. But now what we could do is, we could also go ahead and simply say, let's call the sort method now to make the change, the sorting changes on the names object. And we can also pass this method a key argument. And let's also pass it a length here. And if I now look at names, now we see that we have the same sorting order also for the named object, right? So again, sorted creates a new list with the corresponding sorting order and the sort method makes the sorting in place on the actual already existing object. One more thing, let's say you want to sort in reverse for example, alphabetical order, but you could also do is, we could go ahead and we could simply call the sort method on the names list again. And instead of providing a key argument, we can simply provide a reverse flag. So I'm simply going to say reverse equals true. That is what we call a flag. So any argument that is simply a true or false argument, a Boolean argument, is what we as programmers casually refer to as a flag. So by passing a reverse flag, a reverse true flag, what is going to happen is, we now get back a reverse alphabetical order. Why alphabetical? Well, if I don't pass a key function, then again, the objects are compared as strings and strings by default are compared using alphabetical sort. So that is why we have alphabetical sort here and the reverse equals true makes the sort algorithm go backwards basically. So now, what have we seen so far? So we have seen how we can create a list and how we can add new objects into the list by appending, extending, inserting. Then we made changes to the order of the list. And now what still remains is the following. We want to remove stuff from the list, of course. So how can we do that? So first of all, what I'm going to do is, I'm going to say names.sort again, mainly because now the names list here in the Jupyter Lab is now alphabetically sorted. And if I go into the memory diagram, we also have the same sorting, okay? So now the example in the Jupyter Lab and the memory diagram are in line, so to say. And now what we could do is we, let's talk about how to remove some objects from a list. One of the very common methods to do so is the pop method. So let's say names.pop and what the pop method does is, it simply pops off the last element by default and it returns it. So in other words, what we are going to see, let's say removed, let's assign that to a variable called removed. What we see is, removed is now Peter because Peter was the last element. And if we look now at names, names that the name Peter is gone in names or so. So pop, the pop method does really two things. The first one is it removes a reference and the second one is it returns the reference, right? We will also see another way where we can just remove the reference without returning it. So what happened in memory? So in memory, what happened is, this reference here, the last one is going to be removed from the list object and here we have P and the reference to P is given back as the return value and we called it the removed variable. So removed now points to be, okay? And so P is now, so Peter is now not in the list anymore, okay? The reference is gone. So what the pop method can also do is, and I'm not going to show that here, but what we could do is the pop method also accepts an index as an argument. So for example, index zero and then it would simply pop off the first element and then what we have to understand is, again as before, let's say if I pop off the B here, the first element, then the first slot here becomes empty and that will lead to all the references here being moved one to the left, okay? So therefore what we learn from that is, using pop with an index of negative one or maybe or simply without an index, pops off the last element, popping off the last element in the list is a very cheap operation, a very fast operation because it happens in constant time. However, if I'm going to pop off any element other than the last one, then what's going to happen is, after we pop it off, there are all the references to the right of it have to be moved one to the left. Therefore, popping off any element other than the last one is a linear time operation, right? So we have to be careful. So if we build algorithms where speed matters, using the pop method with anything other than no argument is probably not such a good idea but we are not going to do that here. So what else do we have? So what else we can do is we can use, we can remove objects by value and we do that with another method which we call simply the remove method. So the remove method takes a value, it takes some input, an object and it compares by value the value we pass in as the argument to all the values that are inside the list and the first one it finds that is equal, that compares equal is the one that's going to be removed. So let's say, if I go ahead and I want to remove, let's say, who do I want to remove? Let's say I want to remove, let's say, Carl with a K. Let's do that. I don't get a return value here that is different to the pop method. Why do I not get a return value? Well, assuming I'm already passing in an object of a certain value, I already have access to this value, so to say. So therefore it doesn't make sense to also return it. So now if I look at names, Carl with a K is now gone. What happened in memory? Well, quite simple. Carl with a K, which is this Carl here, which is this reference here, is now being removed. And because there's no further reference, the Carl is also removed. And also this red reference here is also moved one to the right. Maybe I can do that by simply drawing it like this so that you see that this O now moved one to the left, actually. Okay, so let's continue. If I call this method again a second time and the value is not in the list anymore, I get a value error, that is a nice thing because then we know, okay, we basically looked for the value in the entire list and it was not there. So I personally like those so-called loud error messages because this way you often find errors before they become, yeah, before you find errors simply before you put a program into a real life scenario, right? And so therefore that is just what the remove method does. It simply complains if the value is not in the, or if no object with the same value is inside the list object here. Okay, so what else is there? So this is removing and technically, what we could do, I showed you there or I told you that I can remove stuff from a list without getting a return value back. So we could use the del statement, for example. This is now not a method to be strict, but I could go ahead and say del names and I give it negative one as the index. And now I don't have a return value, but now what happens is, oliva, which happens to be the last element is now also gone. So now the only thing that happened here is I now remove the last reference without returning it and therefore the O is also now removed from memory. Okay, so, but again, the del method, the del statement, sorry, is not a method. So in this chapter here, this video is mainly on list method. So what are further list methods? Well, there are a couple of others that are quite useful sometimes. So for example, there's a method called index and the index method, we saw that earlier also with the string data type. It takes one argument, let's say call with a C and it gives us back the index of the first object that compares equal to this argument here, quite simple. And then a close relative to that, which you also saw in the context of strings is the, sorry, once type string here, I should type count here, it's the count method. So if I count how often do I see call in here, I also get back one. And if I use, let's say, call with a K, I would of course get back zero because we removed call with a K. Okay, so these are methods that we saw before with the string data type and you may wonder why does the list data type also have them? Well, remember that both the string data type and the list data type are both sequences in abstract terms, right? And because they're both in abstract terms similar to each other in behavior, many methods make sense for both, the string and the list data type. Okay, so let's finish that video with the methods with one more method, which is simply the copy method. So if you say names.copy, what you get back is a new list object where all the objects in it are just copied. Now you may wonder, is it a shallow copy or is it a deep copy? And the answer is, of course, it is a shallow copy. How can you find out? So you could find out by, yeah, going ahead. And first of all, maybe you want to compare the copy to names itself, you get back false. So this way this confirms to us that we get back a new list object actually. And now if you want to know, is the new list object shallow or deep? One quick check we could do is we could make a copy and we could assign it to, let's say, names copy. And let's go ahead and maybe go to names copy and simply pop off the last element, which is Eckhardt here. Let's look at names copy. Now Eckhardt is gone. But now let's also look at names, the original names. And we see Eckhardt, oh, it's still there. But the reason why, so we see that as a copy, but the important thing is, and that is something, it's actually quite good that I can show it to you like this. The Carl here and the Carl here and the Bertolt here and the Bertolt here, they're actually the same object. So now note because we only have immutable objects inside the list object and we only have strings here, strings are immutable. Working with shallow copies is actually nothing that can cause confusion because if you only work with data that cannot be changed, that is immutable, then making changes to objects that have more than one reference going to it just as we did in the nested example in the previous video, this cannot go wrong here because we cannot change the objects anyway because they are immutable. However, it's still a shallow copy. So the Bertolt and this Bertolt is the same object and this Carl and this Carl is also the same object. Okay, so that is also a list method. But now you may wonder, should you use the copy method here or should you use instead names and take a full slice? Probably this depends on your taste but these are actually exactly the same operation here. So both of them lead to the exact same result. Both of them lead to a shallow copy of the already existing list object. Usually you would say that using the copy method is more explicit but it says copy, doesn't say shallow copy. So also more explicit is maybe not really okay to say here and some beginners may be confused before what this notation here means but as we learned in a previous video, this simply means to take the full slice and the full slice is nothing but a copy, right? Okay, so this concludes this video talking about methods. So the copy method is actually not that important. The important methods for you to understand going forward are the append, extend and insert method and also the pop method and sorting is also a topic on its own so these are the things that you should take out of this video. The index and count method sometimes are usable for some algorithm you want to come up with but I personally don't use them quite often. So probably everything until the remove method here are the more important methods that you will encounter in day to day life. Okay, so I will see you in the next video when we talk about list operation. So using lists together with operators and I will see you then.