1. Introduction

In this tutorial, we’ll go over the ListMap data type in Scala. We’ll also discuss how to add and remove elements from a ListMap along with how to perform some other basic operations.

2. What Is ListMap?

A ListMap is a list-based data structure that can be used to store entries as key-value pairs in the order the entries were inserted.

Unlike ListSet, ListMap previously had both immutable and mutable types. However, the mutable type has been deprecated since Scala version 2.13.0. So, in this tutorial, we’ll be referring to an immutable ListMap unless specifically mentioned.

Similar to a ListSet, a ListMap also maintains a reversed insertion order. Thus, it stores the last added entry at the head.

We can create an empty ListMap in two ways – by using a constructor or by using the function ListMap.empty():

val listMap1 = new ListMap();
val listMap2 = ListMap.empty

assert(listMap1.size == 0);
assert(listMap2.size == 0);

3. ListMap Operations

In this section, we’ll see some of the common operations that we can perform on a ListMap.

3.1. Declaring a ListMap With Values

We can initialize a ListMap with elements using a constructor:

val countryCapitals =
  ListMap(
    "Canada" -> "Ottawa",
    "India" -> "Delhi"
  );

assert(countryCapitals.size == 2);

3.2. Adding New Entries to ListMap

Subsequently, to add new elements to countryCapitals, we can use the + operator:

val updatedCountryCapitals = countryCapitals + ("Russia" -> "Moscow")
assert(updatedCountryCapitals.size == 3);

After doing the above operation, countryCapitals has not changed, and the new element is in the newly declared updatedCountryCapitals.

Similar to ListSet, the addition of an entry to a ListMap takes O(n) time and hence, ListMap is suitable for a small number of elements.

3.2. Updating the Value of an Existing Key

We can also use the + operator to update the value of an existing key:

var countryCapitals =
  ListMap(
    "Canada" -> "Ottawa",
    "India" -> "Delhi"
  );

assert(countryCapitals("India") == "Delhi")
countryCapitals = countryCapitals + ("India" -> "New Delhi")
assert(countryCapitals("India") == "New Delhi")

Here, the entry “India” -> “Delhi” already exists in countryCapitals. Therefore, when we add an entry with duplicate key “India” to countryCapitals, it replaces its value “Delhi” with “New Delhi”.

However, if the key doesn’t exist in the ListMap, it simply adds the entry as we saw in the previous subsection.

3.3. Iterating Over a ListMap

A ListMap internally stores entries in the reversed insertion order. However, the iterators access the entries in the insertion order. That means the key-value pair that was inserted first would be visited first by the iterator:

val countryCapitals =
  ListMap(
    "Canada" -> "Ottawa",
    "India" -> "Delhi"
  );

countryCapitals.foreach { entry =>
  print("country: " + entry._1)
  println(", capital: " + entry._2)
};

Here’s the output of the above print statements:

country: Canada, capital: Ottawa
country: India, capital: Delhi

We can see that the key-value pairs are displayed in the same order they were inserted.

3.4. Getting a Specific Value

We can use the contains() method to check if a specific key is present in the ListMap before actually getting the value associated with it:

val countryCapitals =
  ListMap(
    "Canada" -> "Ottawa",
    "India" -> "Delhi"
  );

if (countryCapitals.contains("Canada")) {
  val capital = countryCapitals("Canada");
  println("Capital of Canada is: " + capital);
}

We can also use the get() method to get the value associated with a specific key of a ListMap. The get() method returns an Optional type. Therefore, we can first use the Option.isDefined() method to be sure that the key is present and then the Option.get() method to finally fetch the specific value:

val capital = countryCapitals.get("Canada");
if (capital.isDefined) {
  println("Capital of Canada is: " + capital.get);
}

Here, we should note that if the key “Canada” is not present in countryCapitals, then capital.isDefined would, of course, return false.

Both of the above print statements would print:

Capital of Canada is: Ottawa

3.5. Removing Single Entry

We can use the operator to remove a particular entry from a ListMap. For this, we need to provide the specific key of the key-value pair:

var countryCapitals =
  ListMap(
    "Canada" -> "Ottawa",
    "India" -> "Delhi"
  );

assert(countryCapitals.size == 2);

updatedCountryCapitals = countryCapitals - "India";
assert(updatedCountryCapitals.size == 1);

updatedCountryCapitals.foreach { entry =>
  print("country: " + entry._1)
  println(", capital: " + entry._2)
};

Here’s the output of the above print statements:

country: Canada, capital: Ottawa

Here, we should note that the initial ListMap remains unchanged:

countryCapitals.foreach { entry =>
  print("country: " + entry._1)
  println(", capital: " + entry._2)
};

// country: Canada, capital: Ottawa
// country: India, capital: Delhi

Thus, we get a new ListMap that doesn’t contain the entry for the key we just removed. However, if the given key doesn’t exist in our ListMap, then it returns the initial ListMap.

3.6. Removing Multiple Entries

We can also remove multiple keys from a ListMap using the same method as above:

val countryCapitals =
  ListMap(
    "Canada" -> "Ottawa",
    "India" -> "Delhi",
    "Australia" -> "Canberra"
  );

assert(countryCapitals.size == 3);
updatedCountryCapitals = countryCapitals - ("India", "Australia", "Japan");
assert(updatedCountryCapitals.size == 1);

countryCapitals.foreach { entry =>
  print("country: " + entry._1)
  println(", capital: " + entry._2)
};

// country: Canada, capital: Ottawa

3.7. Combining Two ListMaps

First, we’ll define two ListMaps containing different sets of capitals:

val countryCapitals1: ListMap[String, String] = ListMap(
    "Canada" -> "Ottawa",
    "India" -> "Delhi"
  );

val countryCapitals2: ListMap[String, String] = ListMap(
  "Cuba" -> "Havana",
  "India" -> "New Delhi"
);

Next, we will use the ++ operator to merge the two ListMaps together:

val combinedCountryCapitals = countryCapitals1 ++ countryCapitals2

println("combinedCountryCapitals:")
combinedCountryCapitals.foreach { entry =>
  print("country: " + entry._1)
  println(", capital: " + entry._2)
};

The output of the above print statements is:

combinedCountryCapitals:
country: Canada, capital: Ottawa
country: India, capital: New Delhi
country: Cuba, capital: Havana

As we can see, for the duplicate key “India”, the ListMap on the right-hand side of ++ overrides the value of the left-hand side ListMap.

3.8. The head and tail Operations

The head() method returns the first key-value pair of a ListMap. The tail() method returns all the elements of a ListMap except the first element. This means that the return type of tail() is again a ListMap:

val countryCapitals =
  ListMap(
    "Canada" -> "Ottawa",
    "India" -> "Delhi",
    "Australia" -> "Canberra"
  );

assert(countryCapitals.head == ("Canada" -> "Ottawa"));

assert(
  countryCapitals.tail == ListMap(
    "India" -> "Delhi",
    "Australia" -> "Canberra"
  )
);

Since a ListMap stores the elements in the reversed insertion order, the complexity of both head and tail operations is O(n).

4. Conclusion

In this article, we explored ListMap in Scala along with its syntax, and some of the basic operations with examples.

As always, the code for these examples is available over on GitHub.