Scala has a very rich collections library, located under the scala.collection package.
In this tutorial, we’ll do a quick overview of the common Scala collections and how to use them.
2. Types of Scala Collections
There are two types of collections in Scala – mutable and immutable. Let’s point out some differences.
2.1. Mutable Collections
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.
2.2. Immutable Collections
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.
3. Scala Collection Hierarchy Overview
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.
4. Most Commonly Used Scala Collections
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 List
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)) // true
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) // false
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)
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 integers
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)
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 // false
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")
val mutableMap = collection.mutable.Map(1 -> "a", 2 -> "b")
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 // false
The get method returns an optional value. Its signature in the Map trait is as follows:
def get(key: K): Option[V]
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) // None
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")
The declaration t1 is just syntactic sugar for a Tuple:
val t1 = Tuple2(1, "A")
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 // A
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 // A
We can iterate over a Tuple using the productIterator method:
val tuple = (1,2,3,4) tuple.productIterator.foreach(println)
which would, in this case, output:
1 2 3 4
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.
The complete code is available over on GitHub.