Course Content
Android Development with Kotlin
Android Development with Kotlin
Maps and Sets
Continuing our discussion on collections in Kotlin, this chapter will cover two quite interesting types of collections: Maps and Sets.
Map
Maps are collections of "key-value" pairs. In Kotlin, they are represented by the Map
interface and have two main implementations: HashMap
and LinkedHashMap
.
This representation is very convenient when two elements are related. For example, a user ID and their name or a product type and its quantity.
However, the key's value should be unique because we use it to retrieve values when accessing elements. Otherwise, collisions may occur, which we’ll discuss later.
So, let’s see how we can create a map
and use its values with an example where the product is the key and its quantity is the value:
Main
fun main() { val product1Name: String = "Tomato" var product1Quantity: Int = 392 val product2Name: String = "Apples" var product2Quantity: Int = 161 val productsQuantity = mapOf(product1Name to product1Quantity, product2Name to product2Quantity) println(productsQuantity) }
We created a data structure called a Map
and then filled it with some test data. This data structure is stored in the productsQuantity
variable, where the key
is the product name and the value
is the quantity of that product in stock.
To retrieve data from the map
, we can use a syntax similar to what we used with arrays, but instead of accessing data by index
, we will access it by key
:
Main
fun main() { val product1Name: String = "Tomato" var product1Quantity: Int = 392 val product2Name: String = "Apples" var product2Quantity: Int = 161 val productsQuantity = mapOf(product1Name to product1Quantity, product2Name to product2Quantity) // accessing the elements by their key val TomatoQuantity = productsQuantity["Tomato"] val AppleQuantity = productsQuantity["Apples"] println("The quantity of Tomato is $TomatoQuantity") println("The quantity of Apples is $AppleQuantity") }
We can also change the value of elements by accessing them by key, just like elements in an array.
However, for this, we need to use a different way of initializing the map. The map created with the mapOf()
method is immutable, meaning its elements cannot be changed.
To create a mutable map, we need to use the mutableMapOf()
method, and the initialization will look like this:
Main
val productsQuantity = mutableMapOf(product1Name to product1Quantity, product2Name to product2Quantity)
Now let's write a method that will subtract the quantity of the purchased product from this map (simulating a user buying something from our online store):
Main
fun main() { val product1Name: String = "Tomato" var product1Quantity: Int = 392 val product2Name: String = "Apples" var product2Quantity: Int = 161 val productsQuantity = mutableMapOf(product1Name to product1Quantity, product2Name to product2Quantity) println("Initial map values: $productsQuantity") subtractProduct(productsQuantity, "Tomato", 100) println("Updated map values: $productsQuantity") } fun subtractProduct(map: MutableMap<String, Int>, productName: String, purchasedQuantity: Int) { val previousQuantity = map[productName] val newQuantity = previousQuantity!! - purchasedQuantity map[productName] = newQuantity }
You don't need to pay attention to the !!
operator right now; it ensures type safety and is used for handling null values. We will discuss this later in this section.
As you can see, the function is simple and takes three parameters:
- The map we are working with
- The name of the purchased product
- The quantity of the purchased product
You might have noticed that when specifying the map in the parameters, we used a new construction: <String, Int>
.
This is called a generic, often used with lists.
A Brief Overview of Generics
A generic is a construct used during class initialization (we will talk about this in the next section) that defines the TYPE of parameters that will be used.
In the case of a map, we define the data types that will be the key and value. For the map we created, the key's data type is String
, and the value's data type is Int
.
These data types are specified within diamond brackets (<>
).
If we want to initialize a map where the key's data type is Int
and the value's data type is Float
, we would initialize this map using the following syntax:
Main
val customMap: MutableMap<Int, Float> = mutableMapOf(some values)
This way, we tell Kotlin which data types we plan to use for a specific map. Later in this section, we will look at generics in more detail, but for now, you should remember what they are and how to use them when creating maps.
Generics can also be used with lists, which we studied in the previous chapter, if you want the list to contain a specific data type, for example like this:
Main
fun main() { val integerList: List<Int> = listOf(10, 15, 30, 22) val stringList: List<String> = listOf("Banana", "Apple", "Onion") // You can use "Any" inside generic to make a list mixed. val mixedList: List<Any> = listOf(10, 12.3, "banana", true, 29910) println("List with Integer values: $integerList," + "\n" + "List with String values: $stringList, " + "\n" + "List with any values: $mixedList") // `\n` mean moving to the next line }
MutableMap
I think there shouldn't be any questions about generics at this point, so we can move on.
With a mutable map, we can also directly add, remove, and modify values. It's straightforward; we simply assign new values to our map. For example, let's create a map that holds two values. The key will be a string, and the value will be an integer. This map will simply contain numbers:
Main
fun main() { var mutableMap: MutableMap<String, Int> = mutableMapOf("One" to 1, "Two" to 2) println("Initial map: $mutableMap") mutableMap["Three"] = 3 mutableMap["Four"] = 4 println("Map after adding 2 elements: $mutableMap") mutableMap["Two"] = 22 // Changing value of element with key "Two" println("Map after changing element: $mutableMap") mutableMap.remove("Three") println("Map after removing element: $mutableMap") }
As you can see, we access and modify elements using the syntax mapName[key] = newValue
. This allows us to add new elements by specifying a key that doesn't yet exist in the map or to update existing elements by specifying the key of the value we want to change.
Additionally, we can remove elements from a mutable map using the method mapName.remove(key)
.
We can also add or update element values using the method mapName.put(key, newValue)
.
Below is the same code as before, but this time we add and modify values in the map using the put
method instead of reassigning the values:
Main
fun main() { var mutableMap: MutableMap<String, Int> = mutableMapOf("One" to 1, "Two" to 2) println("Initial map: $mutableMap") mutableMap.put("Three", 3) mutableMap.put("Four", 4) println("Map after adding 2 elements: $mutableMap") mutableMap.put("Two", 22) // Changing value of element with key "Two" println("Map after changing element: $mutableMap") mutableMap.remove("Three") println("Map after removing element: $mutableMap") }
Which method you use is up to you; both are convenient and correct.
Checking for Element Presence
Maps also have functions that allow you to check if a specific key or value exists in the map.
For instance, you might need this when you're about to update a value associated with a specific key but are unsure if that key or value is present in the map. You can use the functions mapName.containsKey(key)
and mapName.containsValue(value)
for this. These methods return boolean values, either true
or false
.
Here's what it looks like in code:
Main
fun main() { var mutableMap: MutableMap<String, Int> = mutableMapOf("One" to 1, "Two" to 2) println("Initial map: $mutableMap") val isKeyPresent: Boolean = mutableMap.containsKey("One") val isValuePresent: Boolean = mutableMap.containsValue(3) println("Map contains key \"One\"? $isKeyPresent") println("Map contains value \"3\"? $isValuePresent") }
In this simple way, we can check for the presence of specific elements in a map.
Iterating Over a Map
You can iterate over the keys
, values
, or key-value
pairs in a map. We will use a for
loop to iterate through the map. Let's go through this step by step.
Iterating Over Key-Value Pairs:
Main
fun main() { var mutableMap: MutableMap<String, Int> = mutableMapOf("One" to 1, "Two" to 2, "Three" to 3, "Four" to 4) println("Initial map: $mutableMap") for ((key, value) in mutableMap) { println("The key is $key, the value is $value") } }
In the initializer, we specify both values. The compiler understands that we will be iterating over the map, allowing us to use both values within the loop.
Iterating Over Keys
Main
fun main() { var mutableMap: MutableMap<String, Int> = mutableMapOf("One" to 1, "Two" to 2, "Three" to 3, "Four" to 4) println("Initial map: $mutableMap") for (key in mutableMap.keys) { println("The key is $key") } }
To iterate, we use the .keys
property, which converts the map into a list of keys. We then simply iterate over this list, as demonstrated in the previous chapter.
Iterating Over Values
Main
fun main() { var mutableMap: MutableMap<String, Int> = mutableMapOf("One" to 1, "Two" to 2, "Three" to 3, "Four" to 4) println("Initial map: $mutableMap") for (value in mutableMap.values) { println("The value is $value") } }
Similarly, we use the .values
property to iterate over the list of values from the map.
Additionally, there's a useful property that allows us to find out the size (number of elements) of a map:
Main
fun main() { var mutableMap: MutableMap<String, Int> = mutableMapOf("One" to 1, "Two" to 2, "Three" to 3, "Four" to 4) println("Initial map: $mutableMap") val sizeOfMap: Int = mutableMap.size println("The size of the map is $sizeOfMap") }
This isn’t all there is to know about maps. In the rest of the course, we will explore how to use them more and demonstrate various applications. For now, it’s important to understand that a map
is a powerful tool for efficiently storing and manipulating data.
Set
In Kotlin, a Set
is a collection of unique elements. Unlike lists, sets do not allow duplicate elements.
To easily remember a set, think of it as a list with unique elements.
There are two types of sets: mutable and immutable (generally, you can infer that each data structure in Kotlin has these two versions).
Let's start with immutable sets, which can be created as follows:
Main
fun main() { val immutableSet: Set<Any> = setOf("Potato", "Tomato", 30, 30) println(immutableSet) }
We see that a Set
also uses generics, and we can fill it with values at the time of declaration using the setOf(values)
function.
Note that we tried to insert two identical values into the set, but when outputted, the duplicate was removed, leaving only one value, '30'.
Now, let's move on to mutable Sets:
Main
fun main() { var mutableSet: MutableSet<String> = mutableSetOf("Potato", "Tomato") println("Initial Set: $mutableSet") mutableSet.add("Cucumber") // Adding element mutableSet.remove("Potato") // Removing element println("Set after changes: $mutableSet") }
As you can see, a MutableSet<>
is declared using the .mutableSetOf()
function, and like a mutable map, it has methods for adding and removing elements.
Note
To modify an element in a Set, you should remove the element you want to change and then add the new element.
Similarly to a map, we can check for the presence of elements in a Set
using the .contains()
function. Here's how it looks in the code:
Main
fun main() { val vegetables: MutableSet<String> = mutableSetOf("Potato", "Tomato", "Cucumber") println("Initial Set: $vegetables") val isTomatoPresent: Boolean = vegetables.contains("Tomato") val isCabbagePresent: Boolean = vegetables.contains("Cabbage") println("Is Tomato in the list? $isTomatoPresent " + "\n" + "Is Cabbage in the list? $isCabbagePresent") }
We can also use the size
method, which will return the size (number of elements) of the set, similar to how it works with lists or maps.
Note
All methods that work with sets also work with lists because they are very similar data structures, with the only difference being the uniqueness of elements.
Iterating Over Set Elements
Iteration over set
elements is similar to iterating over list
elements.
However, let's look at an example code just in case:
Main
fun main() { val vegetables: MutableSet<String> = mutableSetOf("Potato", "Tomato", "Cucumber") println("Initial Set: $vegetables") for (vegetable in vegetables) { println(vegetable) } }
This way, we can use a Set
when we need a collection of unique elements!
Sets and Maps are powerful tools and useful data structures! Keep them in mind, and they will definitely come in handy!
Thanks for your feedback!