Collections (List Methods) - rohgar/scala-principles-1 GitHub Wiki
List Methods
All the methods can be found here: http://www.scala-lang.org/api/2.12.x/scala/collection/immutable/List.html
###Sublists and element access:
xs.length
: The number of elements ofxs
.xs.last
: The lists's last element, exception ifxs
is emptyxs.init
: A list consisting of all elements ofxs
except the last one, exception ifxs
is emptyxs take n
: A list consisting of the firstn
elements ofxs
, orxs
itself if it is shorter thann
xs drop n
: The rest of the collection after takingn
elementsxs(n)
: The element ofxs
at indexn
###Creating new lists:
xs ++ ys
: the list consisting of all elements ofxs
followed by all elements ofys
xs.reverse
: the list containing the elements ofxs
in reversed orderxs.updated(n,x)
: returns a lit that contains all the elements ofxs
, except at the given index, where it containsx
. (Since lists are immutable, it returns a new list).
###Finding elements
xs indexOf x
: the index of the first element inxs
equal tox
, or -1 ifx
does not appear inxs
xs contains x
: the same asxs indexOf x >= 0
###Implementation Issues
We know that the complexity of head
is a simple field selection; it's a very small, constant time. Can last
be implemented as efficiently? tail
again is just a simple selection of a field of a list, ie constant time; can init
be implemented as efficiently?
Some implementations of List functions:
last
def last[T](xs: List[T]): T = xs match {
case List() => throw new Error("last of empty list")
case List(x) => x
case y :: ys => last(ys)
}
So last
takes steps proportional to the length of the list xs
- we need to take one recursion for each element in the list.
init
def init[T](xs: List[T]): List[T] = xs match {
case List() => throw new Error("init of empty list")
case List(x) => List()
case y :: ys => y :: init(ys)
}
concatenation
How can concatenation be implemented? So far, everything we've done has been with a pattern-match on the list in question - now there are two lists. oh man... which list should we pattern match on? In this case, the first element of the result list here clearly depends on xs
- so it makes sense to match on that.
def concat[T](xs: List[T], ys: List[T]) = xs match {
case List() => ys
case z :: zs => z :: concat(zs, ys)
}
What is the complexity of concat
? Well, it's clear that we'll need a call of concat
for each element of the left list, so complexity will correspond to the length of the list xs
.
reverse
def reverse[T](xs: List[T]): List[T] = xs match {
case List() => xs //return empty list as is
case y :: ys => reverse(ys) ++ List(y)
}
What's the complexity of reverse
? Well, we know that concatenation is linear, proportional to the size of the list on the left hand size of the operator. That list in this case is a list that grows from 1 to the length of xs
.
Furthermore, we do one step for each element in the reversed list, because we go through each element of ys
and put it at the end of the reversed list.
So, taken together, that gives us a quadratic complexity of N * N, which is a bit disappointing. We all know that with an array or a mutable linked list of pointers that we could reverse in linear time - we'll see later on how we might do better.
Sorting Lists Faster
Previously we saw Insertion Sort. Here is Merge Sort (O(nlogn)
):
Idea:
- If the list consists of zero or one elements, it is already sorted.
- Else, separate the list into two sub-lists, each containing around half the elements of the original list
- Sort the two sub-lists
- Merge the two sorted sub-lists into a single sorted list
def msort(xs List[Int]): List[Int] = {
val n = xs.length / 2
if (n == 0) xs
else {
def merge(xs: List[Int], ys: List[Int]) = ???
val (fst, snd) = xs splitAt n
merge(msort(fst), msort(snd))
}
}
// Will be improved later!!
def merge(xs: List[Int], ys: List[Int]) = xs match {
case Nil => ys
case x :: xs1 => ys match {
case Nil => xs
case y :: ys 1 => {
if (x < y) x :: merge(xs1, ys)
else y :: merge(xs, ys1)
}
}
}
This implementation of merge
has nested pattern match, and also it does not reflect the symmetry of the merge sort algorithm. A better version will be show using pattern matching over pairs
later.
splitAt function
The splitAt
function on lists returns two sublists - the elements up to the given index, and the elements from that index. The lists are returned in a pair.