List operations 101

Such Lists, many wow! Much immutability!

This is not going to be a comprehensive description of aaalll the operations available in Scala Lists, but blah blah blah… show me the code.

val anEmptyList = Nil
val emptyListOfInts : List[Int] = Nil // type annotation is not mandatory
val anotherEmptyList = List()
val stillAnEmptyList = List.empty

Wonderful we are now able to create empty lists. We are going to conquer the world or finding a job as Senior scala developer at £ 100.000 a year!

Surprise surprise lists can contain elements, and they are a fundamental data structure in Scala and other functional programming languages.

val nonEmptyList = 1 :: 2 :: Nil      // :: (prepend) with O(1) complexity
val anotherOne = List(1) ++ List(2)   // ++ (append)  O(n) with n = size of the left hand side
val ages = List( 31, 22, 12, 44, 71)
val names = List("John", "Mario", "Carlos", "Bill")
val maybeCities = List( None, Some("Rome"), Some("NY"), Some("LA"))

// Note that a list is built as a composition of Cons (::) cells
// so the resulting structure is an unbalanced tree. E.g. List(31, 22, 12) is
//   ::
//  /  \
// 31  ::
//    /  \
//   22  ::
//      /  \
//     12  Nil

Remember that we are using immutable lists, meaning that you cannot change their values. The most similar operation you can do is to get a copy of the existing list with a modified value. Beware that the operation’s complexity depends on the index of the item you want to change.

val updatedNames = names.updated(2, "Rudolph") //  --> List("John", "Mario", "Rudolph", "Bill")

We can extract and manipulate data easily and in a powerful way

val firstName = names.head                   // better use headOption, if the list can be empty
val restOfTheList = names.tail               // there is no tailOption
val supposedYearOfBirth = age => - age)
// map applies the passed function to each element. so --> List(1985, 1994, 2004, 1972, 1945)

def isGrownUp(age:Int) = age > 40            // we will use this as predicate
val grownUps = ages.filter(isGrownUp)        // List(44, 71)
val grownUps = ages.filterNot(isGrownUp)     // List(31,22,12)
val youngAndOld = ages.partition(isGrownUp)  // (List(44,71), List(31,22,12))

Another cool thing you can do is to compose together different lists, usually containing related data. This is helpful to work with different (but related) lists together, thing that in a non-functional programming language we would usually do inside a for loop using the same index for both lists.

val namesAndCities = names zip maybeCities // we can pair people with their city
    // --> List((John,None), (Mario,Some("Rome")), (Carlos,Some("NY")), (Bill,Some("LA")))
val namesAndAges = 
    .unzip      // from a list of pairs to a pair of lists (List[String], List[Option[String]])
    ._1         // take the first item of the pair (i.e. names)
    .zip(ages)  // as before. Since ages.size > names.size, the exceeding items will be ignored

// once zipped we can use the related data together, e.g. printing
namesAndAges.foreach{ (name, maybeCity) => // use curly braces if you go multiline
  println( s"$name lives in ${maybeCity.getOrElse("an unknown city")}" )

That’s enough for now, we will delve further into lists’ magic powers in the following posts, there’s a lot of interesting stuff to learn and experiment. Stay tuned!