Scala has a rich set of collection library. Collections are containers of things. Those containers can be sequenced, linear sets of items like List, Tuple, Option, Map, etc. The collections may have an arbitrary number of elements or be bounded to zero or one element (e.g., Option).
Collections may be strict or lazy. Lazy collections have elements that may not consume memory until they are accessed, like Ranges. Additionally, collections may be mutable (the contents of the reference can change) or immutable (the thing that a reference refers to is never changed). Note that immutable collections may contain mutable items.
For some problems, mutable collections work better, and for others, immutable collections work better. When in doubt, it is better to start with an immutable collection and change it later if you need mutable ones.
This chapter throws light on the most commonly used collection types and most frequently used operations over those collections.
|Sr.No||Collections with Description|
|1||Scala ListsScala’s List[T] is a linked list of type T.|
|2||Scala SetsA set is a collection of pairwise different elements of the same type.|
|3||Scala MapsA Map is a collection of key/value pairs. Any value can be retrieved based on its key.|
|4||Scala TuplesUnlike an array or list, a tuple can hold objects with different types.|
|5||Scala OptionsOption[T] provides a container for zero or one element of a given type.|
|6||Scala IteratorsAn iterator is not a collection, but rather a way to access the elements of a collection one by one.|
There are two types of collections in Scala – mutable and immutable. Let’s point out some differences.
A mutable collection updates or extends in place. This means we can add, change or remove elements of a collection as a side effect. These collections have operations that change the collection in place. All mutable collection classes are present in the scala.collection.mutable package.
All immutable collections are present under scala.collection.immutable. We can perform operations such as additions, removals, or updates, but these operations always return a new collection and leave the old collection unchanged.
Scala’s collection classes begin with the Traversable and Iterable traits. These traits branch into three main categories: List, Set, and Map.
The Traversable trait allows us to traverse an entire collection. It’s a base trait for all other collections. It implements the common behavior in terms of a foreach method.
The Iterable trait is the next trait from the top of the hierarchy and a base trait for iterable collections. It defines an iterator which allows us to loop through a collection’s elements one at a time. When we use an iterator, we can traverse the collection only once. This is mainly because each element gets processed during the iteration process.
Now, let’s explore some of the most common immutable collections of the Scala library.
Scala lists internally represent an immutable linked list. It maintains the order of elements and can contain duplicates as well. This class is optimal for last-in-first-out (LIFO), stack-like access patterns.
It also implements structural sharing of the tail list. This means that many operations have either a constant memory footprint or no memory footprint at all.
A list has O(1) prepend and head/tail access. Most other operations are O(n) though; this includes length, append, reverse, and also the index-based lookup of elements. We can, for example, declare a list of integers:
val numbersList: List[Int] = List(1, 2, 3 ,4) val emptyList: List[Int] = List() // Empty ListCopy
This List class comes with two implementing case classes, scala.Nil and scala.::, that implement the abstract members isEmpty, head, and tail. A Scala list containing elements can be represented using x :: xs, where x is the head and the xs is the remaining list. Nil represents an empty list:
val numbersList: List[Int] = 1 :: 2 :: 3 :: 4 :: Nil // List of Integers val emptyList: List[Int] = Nil // Empty List val x :: xs = numbersList assert(x == 1) // true assert(xs == List(2, 3, 4)) // trueCopy
In the above example, we can represent the numbersList using the x :: xs notation. While printing the value of x and xs, we find that x is the head of the list, and xs is the remaining list.
There are three basic operations on lists:
|1||head||Returns the first element of the List|
|2||tail||Returns a List consisting of all elements except head (the first element)|
|3||isEmpty||Returns true if the List is empty|
Let’s try using each one in turn:
val numbersList: List[Int] = 1 :: 2 :: 3 :: 4 :: Nil assert(numbersList.head == 1) // true assert(numbersList.tail == List(2, 3, 4)) // true assert(numbersList.isEmpty) // falseCopy
Other common operations include concatenating two lists, creating uniform lists, and reversing a list:
List(1,2) ::: List(3,4) // List(1, 2, 3, 4) List.fill(3)(100) // List(100, 100, 100) List(1,2,3,4).reverse // List(4, 3, 2, 1)Copy
We can find the complete list of Scala List methods in the ScalaDoc.
Scala Set is a collection of unique elements. By default, Scala uses an immutable set. It doesn’t maintain any order for storing elements.
We can declare an immutable set as:
val emptySet: Set[Int] = Set() // Empty set val numbersSet: Set[Int] = Set(1, 2, 3, 4) // Set of integersCopy
If we want to use a mutable Set, we need to import it from the collection.mutable explicitly:
val mutableSet = collection.mutable.Set(1, 2, 3)Copy
The operations on a Set are similar to the ones on the List:
|1||head||Returns the first element of a Set|
|2||tail||Returns a Set consisting of all elements except head (the first element)|
|3||isEmpty||Returns true if the Set is empty|
So, let’s try them out:
Set(1, 2, 3, 4).head // 1 Set(1, 2, 3, 4).tail // Set(2, 3, 4) Set(1, 2, 3, 4).isEmpty // falseCopy
The complete list of methods of Scala Set is in the ScalaDoc.
A Map is a collection of key/value pairs where keys are always unique. Scala provides mutable and immutable versions of it. By default, an immutable version of the map is imported:
val immutableMap = Map(1 -> "a", 2 -> "b")Copy
val mutableMap = collection.mutable.Map(1 -> "a", 2 -> "b")Copy
The methods for working with maps are bit different:
|1||keys||Returns an iterable containing all keys of the Map|
|2||values||Returns an iterable containing all values of the Map|
|3||isEmpty||Returns true if the Map is empty|
Let’s see how these methods work:
Map(1 -> "a", 2 -> "b").keys // res0: Iterable[Int] = Set(1, 2) Map(1 -> "a", 2 -> "b").values // res1: Iterable[String] = Iterable(a, b) Map(1 -> "a", 2 -> "b").isEmpty // falseCopy
The get method returns an optional value. Its signature in the Map trait is as follows:
def get(key: K): Option[V]Copy
When the key exists, it returns the value in Some context, whereas if the key does not exist, it returns None:
Map(1 -> "a", 2 -> "b").get(1) // Some(a) Map(1 -> "a", 2 -> "b").get(3) // NoneCopy
We can find the complete list of methods of Scala Map in the ScalaDoc.
A Tuple is a collection that gives us a way to store different items in the same container. It combines a fixed number of items. We can pass this as a whole, and there’s no need to declare a class separately.
Scala 2.x has classes named Tuple2, Tuple3 … up to Tuple22.
If we just place some elements inside parentheses, we get a Tuple. A tuple of int and String would look like:
val t1 = (1, "A")Copy
The declaration t1 is just syntactic sugar for a Tuple:
val t1 = Tuple2(1, "A")Copy
There are two ways to access a tuple’s elements. The first way is to access them by the element number:
val tuple3 = (1, "One", "A") // tuple3: (Int, String, String) tuple3._1 // 1 tuple3._2 // One tuple3._3 // ACopy
The second way of accessing elements is by using the classical pattern matching in Scala. In this way, we can assign the Tuple elements to some appropriate variable names:
val (num, word, char) = (1, "One", 'A') num // 1 word // One char // ACopy
We can iterate over a Tuple using the productIterator method:
val tuple = (1,2,3,4) tuple.productIterator.foreach(println)Copy
which would, in this case, output:
1 2 3 4Copy
Technically, Scala 2.x tuples are not collections classes and hence they do not extend the Iterable trait.
In this tutorial, we explored the Scala’s collection library. We looked at the differences between mutable and immutable collections and explored the commonly used collections in Scala.