Scala’s List is an immutable collection data structure implemented using a linked list. In this tutorial, we’ll learn about different ways to create and populate Lists in Scala using the help of some simple examples.
We’ll also briefly look into the internal implementation and how it affects performance aspects during the build process.
2. List’s Performance Characteristics
Before we start looking at the APIs, it’s important to have an understanding of the performance characteristics. Appending an element to a List is a linear time complexity O(n) operation. However, prepending is a much faster operation, happening in constant time complexity O(1).
This property makes it suitable for Last-In-First-Out (LIFO) operations, as appending and removing elements from the head position is very quick. Most other operations, like random element access, follow linear time complexity O(n).
3. List Creation Methods
The Scala library provides us with plenty of options to create and populate Lists. There are methods in a functional style, object-oriented style, and there are many utility methods defined in its companion object. On top of that, there are conversion methods defined in other collection types to convert its data into a List.
We’ll go through each one of them in detail.
3.1. The cons (::) Operator
One of the easiest ways to create a List is to use the “::” (also known as cons) operator and Nil, which is an empty singleton List object.
The intended way of building a List is by prepending a new element to it. The:: operator is particularly useful in this case, as it helps to build the collection by appending elements at the head position. Let’s start by looking at an example:
val vehiclesList:List[String] = "Truck" :: "Car" :: "Bike" :: Nil
The cons operator is peculiar in its order of execution, as it’s executed from right to left instead of the general left to right. Therefore, the above expression can be re-written as:
val vehiclesList:List[String] = Nil.::("Bike").::("Car").::("Truck")
In Scala, there’s a general rule for the operators ending with a colon (:). Those expressions are evaluated from right to left. Therefore, our cons operator is also evaluated from right to left. This property is significant in the case of building Lists, as prepending an element to a List is much faster than appending at the end.
We can see that the cons operator requires two arguments, the element we are pretending and the list itself, or a Nil in case it’s the first element. This style of programming is commonly found in the Lisp programming language and also generally in functional programming.
3.2. List‘s apply Method
Another common way of creating a List is to call its apply method. This is a simple API call, however, behind the scenes, it’s a lengthy process happening in the background. The apply method creates a new Array of elements using the received varargs input.
Then the Array is converted to a Seq, and finally, the Seq is converted to List by calling the Seq.toList method on it.
Let’s look at an example:
val vehiclesList:List[String] = List.apply("Truck", "Car", "Bike")
The apply function in Scala has a syntactic shortcut, that is, we can invoke it using its object-name as a function:
List("Truck", "Car", "Bike")
This is similar to the constructor method calls in Java classes.
3.3. The List.range Method
The List’s companion object has an inbuilt range method to help us quickly create a list of continuously increasing integers. It takes a start element and an end element as parameters. Optionally, we can provide the step parameter also as a third parameter. By default, the step is taken as one.
Let’s look at an example:
val numList:List[Int] = List.range(1, 5) println(numList) // prints List(1, 2, 3, 4)
3.4. List.fill Method
Sometimes, we may want to create a list of elements with some default initial values. In such cases, we can invoke the fill method available in the List object.
Let’s create a list of elements with an initial default value of an empty string:
val filledList:List[String] = List.fill(3)("") println(filledList) // prints List("","","")
3.5. toList Method
Many collection types in Scala have a toList method to convert their data to a List. Let’s convert an Array of elements to a List using this function:
val convertedList:List[String] = Array("Truck", "Car", "Bike").toList
This method is especially useful when we have to deal with frequently changing data. In such cases, a mutable collection like ListBuffer is more suitable to build the collection, and once finished, we’d call the toList method to convert it to an immutable List.
3.6. List.tabulate Method
The List object has another useful function, tabulate. It helps us to create a list by applying a user-specific function to a series of numbers, starting at zero.
Let’s assume we need to create a list of numbers in their binary representations:
val tabulatedList:List[String] = List.tabulate(3) (n => n.toBinaryString) println(tabulatedList) // prints List("0", "1", "10")
We can see the first three integers in their binary format.
In this tutorial, we’ve learned several methods to create and populate a List in Scala. Also, we’ve seen the performance characteristics of List during an append and prepend operation. Then we learned how the performance characteristics influence the creation methods.
As usual, the full source code can be found over on GitHub.