This session introduces more utility methods on lists. You'll find out what these methods do and where they're useful, and you'll also learn how they're implemented in terms of the fundamental operations on lists and [inaudible] matching. So in this session, you're going to find out about more methods that are useful for list processing. First one is the length method, so that gives you the number of elements that are in the lists. You know already about head and tail, so if you have a list that I'm now writing as essentially a contiguous sequence of elements. Then head would be the first one here and there would be rest. The duals of head and tail are called init and last. So Last would, give you the last element in a list. And Init would give you all elements except for the last. Then there's also take and drop. So take takes an arbitrary prefix of elements in the list, whereas drop would, they could give you back all the elements in the list except for some prefix so it would drop a number of elements from the beginning and then give you back the, the remainder of the list. And finally, we can also select into a list, that's written xs of n, where n is an index, and that's expanded, as usual, to a call of the applied method of lists. So alternatively, we could also write xs apply n, that the same thing, And that would give you the element of xs at index n, Where as usual, in this is R numbered from zero. Here are some more utility methods for lists. You might be interested in new ways to create lists. The first method here is concatenation, it's written xs plus, plus ys and that gives you the list that consists of all elements of xs followed by all elements of ys. And that's reverse which, as the name says, will you give a list that contains the elements of xs in reversed order and finally that's updated. So updated is, in a sense the functional equivalent of mutable updates in an array when you change an element in an array. Of course you know that you can't do that in a list because lists are immutable. But what you can do, is you can return a list that contains all the elements of the list xs here, except at a given index n where the new list would contain the x. So. It's a new list that corresponds to the old list the x's in all elements except one. Besides creating, there are also two functions for finding elements. There is indexOf, which is very much like indexOf, on let's say a Java string. So it, finds, tries to find an element x in a list xs and would give you back its index if, of the first occurrence of x if it's found in the list and minus one if x does not appear in the list. And then there is contains. So, xs x contains x, Is asks whether the element x appears in the list of xs at all. So it's the same as xs indexOf x quite are equal to zero. Now, let's look a little bit more at the implementation issues. We know that the we've had is simple field selection, so it's a very small constant time. Can last be implemented just as efficiently? Tail, again, is just a simple selection of the, of the second field of a list, constant time. Can it be implemented at the same efficiency? Well, let's see first how we would implement last. As I said, last is a method in class list, but to study its implementation we, we might as well define it as an external function outside the list class. If we do that, then last would have the signature that you see here. So it takes a list of t for an arbitrary type t, and gives you back a t, The element type of the list. And t, Usual implementation of last would be to say well let's see what the list is. Last of an empty list should give you an error just like head of an empty list gives you an error. Last of a list that contains a single element would give you just that element. And finally, last of a list that consists of a head followed by some tail would give you the last element of the tail. So we see, by looking at this implementation, that last takes a number of steps that's proportional to the length of xs. We need to take one recursion for each element in the list here. Let's do the same as an exercise within it. So, I've given you another outline of an external function, init, analogous to last. It takes a list of t for arbitrary t returns a list of t. Its not defined of list of empty. So all you need to do is fill in the triple question marks for the two patterns. What happens if the list consist of one element and what happens in the case where list is a general first element y followed by a list ys? So lets see how we would solve this. The initial part of a list that consists of a single element would be the empty list. So, we would return list of empty. The initial part of a list that consists of a head y and some tail ys. What would that be? Well, it would certainly contain the head and then we would have a recursive call to init ys. So, one thing that I've just not yet said explicitly here but which is important, is that the patterns are actually matched in sequence. So, what this pattern would match is any list of a single element. What this pattern then would match is any list of, length two or more, Because we have already covered lists of zero and one elements. So that means that the recursive call to init on that list cannot fail, Because we know that the, the size of ys is, is at least one. So let's look at another fundamental operation on lists concatenation. How is concatenation implemented? Well, you'll remember that if we write xs and then the triple colon for concatenation ys, That really is the same as the call of the method triple colon with receiver ys and xs as the argument. So it's a prepend of xs on top of the right hand side ys. Very much like the prepend function that you've implemented last week, but now, you prepend a whole list not just the single element. To find out how the implementation of that can be done, it's actually just as good to write a stand-alone function. I've given you the signature of concat here. So, that will be a function that concatenates and ys like you see here. How could that be implemented? Well, so far, everything we did was by a pattern match on the list and question, But now there are two lists we deal with, xs and ys. So what list should we pattern match on? Well, the other observation is that, once we were done with pattern matching typically reconstructed lists from left to right. We were asking the question, what is the first element of the result list and what is the remainder. Now, does the first element of the result list, does it depend on xs or on ys. Well, clearly it depends on xs. So it makes sense to pair and match in xs. So, let's do that. We have the standard match on xs. The empty and the case where the list is nonempty with a head z and a tail zs. Ys is already taken as a name here. So for the empty list, what should concat return? Well, obviously, the empty list concatenated with sum list ys is the list ys, So that would be our result. If the list is not empty, so it contains a head z and tail zs. You could, next question is, well what is the first element of the result list? While it's the first element of xs, and that would be z and the remainder. While the remainder would simply be the result of concatenating the rest of the left list, which we call letters now here, and the right list, ys. So, that gives us the complete implementation of concat. Now, what is its complexity? Well, we see that we will need a call of concat for each element of the left list. So complexity would be correspond to the length of the list excess. Sometimes we use mathematics notations with the double bars to indicate the size of something. So now that we have seen concat, let's look at reverse. How can that be implemented? Let's try by writing a stand-alone function. So we would have a function reverse, It take xs, A list of arbitrary element type t. Gives us back a list that's a reversal of xs. Let's start with the usual pattern match on the shape of xs. So if that list is empty, then, the reversal of an empty list would give the list itself. If that list is nonempty, let's say consisting of a head element y and a tail ys. What do we do? Well, one thing we know is that the list will end with the element y. The first element becomes the last element and before that, or before that, we just have a recursive call of reverse, ys and then comes the head element, y, in the second list concatenated. So that would be the definition of reverse. So the next question is, given that definition, what is its complexity? Well, let's have a look. We know that concatenation is linear, Text time proportional to the size of the list on the left hand of the concatenation operator. So that list is a list that grows from one to the length of excess, that's for the first element that we concantenated would be the length of xs minus one. So that's linear in the size of excess. And furthermore, we do one step for each element in the reverse list because we go through each element of ys, and put it at the end of the reverse this. So that gives us a factor of n, For the concat times n for the reversal where n is the size of success. So we get quadratic complexity of reverse which is a bit disappointing because we all know that if you have, let's say an array or a link list, a mutable link list of pointers, Then we all know how to reverse in linear time. We'll see later on how we might do better and get down the complexity of reverse, But for the moment, we'll leave it like that. So, here's an exercise. The question is, how could you write a method that removes the n'th element of a list xs? If n is out of bounds in the list, then we would just return the list xs. So, as an example, if we call, removeAt at index one and the list of a, b, c, d, then we would expect the get back the list a, c, d. The element at index one would be removed. So, let's grab a worksheet to see how we could answer that question. Let's write a method signature. It takes an Int as an index and a list of Int, And we want to have the list that consists of all elements except the ones at the index n. So how would we do that? Well, remember you have taken drop already as operations. So, the easiest way to do that would be to say well, it's xs, take the first elements n, Followed by xs drop n plus one. So it means we take the first elements of the x, We skip one and then we take the rest of the x elements xs, which would be