Kotlin Collections

Hanife K.
12 min readAug 24, 2023

--

Hello, today’s post will be about Kotlin Collections. I was having a problem for 2 days in my application that I developed, and the problem I actually had was due to my lack of knowledge of data structures. For this reason, I wanted to refresh my knowledge by doing research and create an article. So, let’s get started. :)

Introduction to Data Structures

Data structures are one of the most important elements in programming. Determining the optimal data structure to use while writing program codes ensures more efficient use of memory and working style. Data can be any value that can be expressed in numerical, alphanumeric or logical formats in a computer environment. (for example: integers like 5, -9,0, characters like ‘D’, ‘H’, strings like “Hello” and “World” and logical values like 0 and 1)

Information, on the other hand, is the proccessed and meaningful form of data. (for example: weight like 5 kg, temperature like -9 degress, the string “Hello” as a greeting word, logical values 0 for empty and 1 for full)

When we run computer programs, a complex collaboration actually takes place. The Central Processing Unit (CPU) and Random Access Memory (RAM) are at the core of this collaboration. The process works as follows:

When we run a computer program, a complex collaboration takes place between the Central Processing Unit (CPU) and Random Access Memory (RAM). The process is as follows:

  • When the program starts, the CPU recognizes the beginning of the code and starts processing the first command. These commands are designed for data processing, calculations, or performing certain tasks.
  • The data to be used during the running of the program are loaded into RAM. RAM is used as temporary memory and provides fast access. This data is retrived as needed by the CPU during operations.
  • While the process commands are executed sequentially, the CPU accesses relevant data through the RAM. This is a necessary step for perform operations and obtaining results. Thanks to the fast access of RAM, the CPU can access data quickly.
  • New data generated as a result of the process can be written back to the RAM. This represents a stage where temporary results are stored and can be used when needed.
  • During this process, continuous data exchange occurs between the RAM and the CPU. The fast access of RAM allows the CPU to access data easily. This collaboration ensures the smooth operation of the program, data processing, and obtaining results.
  • As a result, this collaboration between the CPU and RAM establishes the fundamental functionality of computer programs. Effective storage, processing, and accessing of data form the foundation of data structures and programming principles. Thanks to this concerted work, we can fully exploit the potential of modern computing systems.

Understanding how to store data effectively and efficiently is fundamental to understanding and using data structures. By choosing the right data structure for a given problem, developers can create more performant, organized, and sustainable code that takes full advantage of the capabilities of modern computing systems.

Now that we’ve refreshed our basics of data structures, let’s take a closer look at a particular type of data structure, namely “collections” that play an important role in modern programming.

Collections

In programming, collections serve as versatile tools for storing, managing, and processing data. Kotlin, as a modern programming language, provides a range of collection types that allow you to look at collections in a more meaningful way. These types simplify many tasks and allow developers the opportunity to write more concise and efficient code.

Now let’s examine frequently used collections in Kotlin language:

  • Lists: Represents a collection of ordered and mutable elements. The order of the elements is important and can contain the same element more than once. There are multiple types of lists in Kotlin like List, MutableList, ArrayList, LinkedList.
  • Sets: It is a collection of unique items that do not contain repetitive items. It is unordered and can contain an item only once. There are multiple types of sets in Kotlin such as Set, MutableSet, HashSet, LinkedHashSet, TreeSet.
  • Map: Represents a collection of key-value pairs. Each key can only be found once and is assigned a value. There are multiple types of maps in Kotlin such as Map, MutableMap, HashMap, LinkedHashMap, TreeMap are available.

Let’s do some examples about the collections I mentioned.

Lists

In Kotlin, there is an interface called List that forms the foundation of collections. The List interface is used to maintain and access data in a sequential order within collections. This interface is designed to be used for reading data, meaning it cannot be used to add or remove data from a collection. The List interface defines some basic methods such as:

  1. size: Returns the number of elements in the list.
  2. get(index: Int): Returns the element at a given index.
  3. contains(element: T): Checks if a given item is in the list.
  4. indexOf(element: T): Returns the index where a given item was first found.
  5. isEmpty(): Checks if the list is empty.

Thanks to these basic methods, the List interface is used to easily access and control ordered data. The List interface can be used by implementing different collection types such as ArrayList, LinkedList.

  • List:
val myList: List<Int> = listOf(1, 2, 3, 4, 5)

This example creates a List containing the sequential numbers 1, 2, 3, 4, and 5. The elements of the list are immutable, meaning that you cannot add, remove, or modify elements once the list is created.

  • MutableList:

Kotlin collections have MutableList which is an interface that allows to modify (add, remove, update) data. This interface extends the List interface, adding the ability to add, remove, and replace data to collections.

The MutableList interface includes basic methods such as:

  1. add(element: E): Adds a specific element to the collection.
  2. remove(element: E): Removes a specific element from the collection.
  3. set(index: Int, element: E): Modifies an element at a given index.
  4. addAll(elements: Collection<E>): Adds items from another collection to the current collection.
  5. removeAll(elements: Collection<E>): Removes items from another collection from the current collection.

Thanks to these methods, the MutableList interface provides the ability to dynamically change the contents of the collection. In other words, operations such as adding, removing, and updating data can be performed with MutableList. This property is often used when it is necessary to change the contents of collections.

val numbers: MutableList<Int> = mutableListOf(1, 2, 3, 4, 5)
println("Initial list: $numbers")

// Adding elements to the list
numbers.add(6)
numbers.add(7)
println("After adding elements: $numbers")

// Removing an element
numbers.remove(3)
println("After removing element 3: $numbers")

// Updating an element at a specific index
numbers[1] = 8
println("After updating element at index 1: $numbers")

// Adding elements from another collection
val newNumbers = listOf(9, 10)
numbers.addAll(newNumbers)
println("After adding elements from newNumbers: $numbers")

// Removing elements based on another collection
val elementsToRemove = listOf(4, 6)
numbers.removeAll(elementsToRemove)
println("After removing elements based on elementsToRemove: $numbers")

In this example, we first create a mutable list of integers named numbers and initialize it with some values. Next, we show adding elements, removing elements, updating elements, adding elements from another collection and removing elements based on another collection on the MutableList. Each step is supplemented with a print statement to show changes in the contents of the list.

Initial list: [1, 2, 3, 4, 5]
After adding elements: [1, 2, 3, 4, 5, 6, 7]
After removing element 3: [1, 2, 4, 5, 6, 7]
After updating element at index 1: [1, 8, 4, 5, 6, 7]
After adding elements from newNumbers: [1, 8, 4, 5, 6, 7, 9, 10]
After removing elements based on elementsToRemove: [1, 8, 5, 7, 9, 10]
  • ArrayList:

In Kotlin, ArrayList is a dynamically sized collection class. This class implements the MutableList interface and is a collection type that can grow data dynamically. The ArrayList collection is used to store a set of data and provide quick access to that data.

val arrayList: ArrayList<Double> = arrayListOf(3.14, 2.91, 5.61)
arrayList.add(0.99)
arrayList.remove(2.91)

An ArrayList resembles a dynamically sized array. You can use the add and remove functions to add and remove elements.

  • LinkedList:

In Kotlin, LinkedList is a collection type where data is stored in a doubly linked list structure. This data structure is based on nodes where elements are interconnected. Each node contains a data item and references to both the previous and the next nodes.

val linkedList: LinkedList<String> = LinkedList()
linkedList.add("apple")
linkedList.add("banana")
linkedList.add("cherry")
println("Initial LinkedList: $linkedList")
linkedList.add(1, "grape")
println("After adding 'grape': $linkedList")
linkedList.remove("banana")
println("After removing 'banana': $linkedList")
linkedList.removeAt(0)
println("After removing the first element: $linkedList")

This code initially creates an empty LinkedList. Then it adds elements, adds an element at a specific position, removes an element, and removes an element at a specific position. The results of the operations are printed to the console.

Output:

Initial LinkedList: [apple, banana, cherry]
After adding 'grape': [apple, grape, banana, cherry]
After removing 'banana': [apple, grape, cherry]
After removing the first element: [grape, cherry]

Sets

The Setinterface in Kotlin is a base interface for storing and managing unique items in collections. The Setis designed to contain each item only once, so if you add an item more than once, it will still be stored only once.

The Set interface does not guarantee the preserve of the order of the items in the collection. Therefore, instead of accessing elements through indexing, it focuses on the existence and uniqueness of elements.

Some basic operations you can perform using the Set interface include:

  1. size: Returns the number of elements in the set.
  2. contains(element: T): Checks whether a specified element is present in the set.
  3. isEmpty(): Checks if the set is empty.

The Set interface is particularly useful when you want to ensure that the items inside the collection are unique. It allows you to create various types of sets by being implemented by different classes (HashSet, LinkedHashSet, SortedSet, etc.).

  • Set:
 val uniqueNumbers: Set<Int> = setOf(1, 2, 3, 2, 4, 5, 4)
println("Initial set: $uniqueNumbers")
println("Size: ${uniqueNumbers.size}")
println("Contains 3: ${uniqueNumbers.contains(3)}")
println("Contains 6: ${uniqueNumbers.contains(6)}")
println("Is empty: ${uniqueNumbers.isEmpty()}")

In this example, we create a set of unique numbers using the setOf function. The output will show that duplicate numbers are automatically removed, and you can check for the presence of elements using contains and determine the size with size.

Output:

Initial set: [1, 2, 3, 4, 5]
Size: 5
Contains 3: true
Contains 6: false
Is empty: false
  • MutableSet:

The MutableSet interface, in addition to the Set interface, provides methods to add, remove, and update elements within the collection. These methods allow you to modify the collection in a mutable way. Here are the methods:

  1. add(element: E): Adds the specified element to the set. If the element is already in the set, it is not added again and it returns false.
  2. addAll(elements: Collection<E>): Adds all elements from the specified collection to the set. If at least one element is already exitsts in the set, it is not added and it returns false.
  3. remove(element: E): Removes the specified element from the set. If the element is not in the set, it is not removed, and it returns false.
  4. removeAll(elements: Collection<E>): Removes all elements from the set that are present in the specified collection. Returns true if the set is changed, false otherwise.
  5. retainAll(elements: Collection<E>): Removes all elements from the set that are not in the specified collection. Returns true if the set is changed, false otherwise.
  6. clear(): Removes all elements of the set, emptying the set.

Thanks to these methods, you can update, add, and remove elements in your MutableSet collection. This feature is especially useful when managing dynamic collections.

val mutableColors: MutableSet<String> = mutableSetOf("Red", "Green", "Blue")
println("Initial set: $mutableColors")

mutableColors.add("Yellow")
println("After adding an element: $mutableColors")

mutableColors.remove("Green")
println("After removing an element: $mutableColors")

Output:

Initial set: [Red, Green, Blue]
After adding an element: [Red, Green, Blue, Yellow]
After removing an element: [Red, Blue, Yellow]
  • HashSet:

In Kotlin, a HashSet is a dynamically sized collection type where elements are unique and provide quick access to elements. This collection type works based on the hash table data structure. The hash table stores the elements as key-value pairs and provides quick access to the items.

Here’s an example of using a HashSet:

val hashSetOfColors = hashSetOf("Red", "Green", "Blue")

println("Initial HashSet: $hashSetOfColors")

// Adding an element
hashSetOfColors.add("Yellow")
println("After adding an element: $hashSetOfColors")

// Removing an element
hashSetOfColors.remove("Green")
println("After removing an element: $hashSetOfColors")

In this example, we create a HashSet with the hashSetOf function. Then we add the "Yellow" element with the add function and the remove the "Green" element with the remove function.

Output:

Initial HashSet: [Red, Green, Blue]
After adding an element: [Red, Green, Blue, Yellow]
After removing an element: [Red, Blue, Yellow]

It’s important to note that a HashSet does not store elements in the order they were added and does not guarantee a specific order of elements. But it provides quick access to elements and is especially useful for quickly checking for unique elements.

  • LinkedHashSet:

LinkedHashSet is a collection type in Kotlin that maintains unique elements and preserves the order in which elements were added. Combines the properties of a HashSet and a LinkedList. Therefore, the elements are both unique and accessible in the order they were added.

Here’s an example of using a LinkedHashSet:

val linkedHashSet = linkedSetOf("Apple", "Banana", "Orange", "Apple")
println("Initial LinkedHashSet: $linkedHashSet")

linkedHashSet.add("Grapes")
println("After adding an element: $linkedHashSet")

linkedHashSet.remove("Banana")
println("After removing an element: $linkedHashSet")

In this example, we create a LinkedHashSet using the linkedSetOf function. First, adding the items “Apple”, “Banana”, “Orange” and again “Apple”. But LinkedHashSet ensures that only unique elements are stored, so only one "Apple" element is retained. Then, we add the "Grapes" element and remove the "Banana" element.

Output:

Initial LinkedHashSet: [Apple, Banana, Orange]
After adding an element: [Apple, Banana, Orange, Grapes]
After removing an element: [Apple, Orange, Grapes]

Maps

The Map interface in Kotlin is a basic interface for storing and managing key-value pairs within collections. A Map is designed to store relationships between keys and values, where each key is unique and maps to a single value. Unlike Lists or Sets, which deal with individual elements, a Map operates on pairs of elements: keys and their associated values. This allows you to effectively retrieve values based on the relevant keys.

The Map interface doesn’t guarantee a specific order of its key-value pairs, so you don’t access elements with an index, but by their keys. Some key operations you can perform using the Map interface might be:

  1. size: Returns the number of key-value pairs in the map.
  2. containsKey(key: K): Checks if a given key is found in the map.
  3. containsValue(value: V): Checks whether a given value is associated with at least one key.
  4. isEmpty(): Checks if the map is empty.
  • Map:
val capitals = mapOf(
"USA" to "Washington, D.C.",
"Canada" to "Ottawa",
"United Kingdom" to "London",
"France" to "Paris"
)

// Using size method to get the number of key-value pairs
println("Number of countries in the map: ${capitals.size}")

// Using containsKey method to check if a specific key exists
val hasCanada = capitals.containsKey("Canada")
val hasGermany = capitals.containsKey("Germany")
println("Does the map contain Canada? $hasCanada")
println("Does the map contain Germany? $hasGermany")

// Using isEmpty method to check if the map is empty
val isEmpty = capitals.isEmpty()
println("Is the map empty? $isEmpty")

In this example, we use the size method to determine the number of key-value pairs in the map. We also use the containsKey method to check if specific keys ("Canada" and "Germany") exist in the map. Finally, we use the isEmpty method to check if the map is empty.

Output:

Number of countries in the map: 4
Does the map contain Canada? true
Does the map contain Germany? false
Is the map empty? false
  • MutableMap:

The MutableMap interface in Kotlin is a mutable collection interface for storing and managing key-value pairs. This interface extends the Map interface, allowing you to make changes such as updating existing values, adding new values, and removing values.

val myMap: MutableMap<String, Int> = mutableMapOf()

// Adding key-value pairs
myMap["one"] = 1
myMap["two"] = 2
myMap["three"] = 3

// Updating a value
myMap["two"] = 22

// Removing a key-value pair
myMap.remove("one")

println(myMap)

In this example, using the basic functions of the MutableMap interface, a mutable map is created, key-value pairs are added, a value is updated, and a key-value pair is removed. Finally, the contents of the map are printed on the screen.

Output:

{two=22, three=3}
  • LinkedHashMap:

The LinkedHashMap class is a data structure in Kotlin collections and is an implementation of the MutableMap interface. This data structure preserves the insertion order while keeping key-value pairs. That is, elements of this map type are sorted in the order in which they were added, and this order is preserved.

val linkedMap = linkedMapOf(
"one" to 1,
"two" to 2,
"three" to 3
)

// Adding a new entry
linkedMap["four"] = 4

// Displaying the elements in the order they were added
for ((key, value) in linkedMap) {
println("Key: $key, Value: $value")
}

In this example, a LinkedHashMap was created using the linkedMapOf function and used to loop through this map by adding key-value pairs. It can be seen in the output that the key-value pairs are sorted in order of insertion.

Output:

Key: one, Value: 1
Key: four, Value: 4
Key: two, Value: 2
Key: three, Value: 3

If you want to learn in more detail, I’m leaving an amazing link for everyone. Happy coding :)

--

--