0:00

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.

4:52

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,

10:32

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,