Data structures play a crucial role in software development by enabling efficient storage and manipulation of data. Kotlin, a modern and expressive programming language, offers a variety of data structures to cater to different needs.

In this tutorial, we'll delve into the common data structures available in Kotlin, along with their use cases and implementation examples.

## Major Data Structures in Kotlin are

Arrays

Lists

Immutable List

MutableList

ArrayList

LinkedList

Sets

Maps

Stacks and Queues

Trees (Binary Trees)

Graphs

## Arrays

Arrays in Kotlin are collections of elements with a fixed size. While most languages allow only the same type of data to be stored in an array, Kotlin arrays can hold elements of any **type**.

```
// Creating an array of integers
val numbers = arrayOf(1, 2, 3, 4, 5)
// Accessing elements
val firstElement = numbers[0]
// Modifying elements
numbers[2] = 10
// Iterating through an array
for (number in numbers) {
println(number)
}
```

Arrays are suitable for scenarios requiring a fixed-size collection of elements accessed by index.

## Lists

Lists in Kotlin are ordered collections that can grow or shrink in size dynamically.

Kotlin provides several types of lists, including **List**, **MutableList**, and specialized implementations like **ArrayList** and **LinkedList**.

### Immutable List (List):

An immutable list cannot be modified after it is created. You can use the **listOf()** function to create an immutable list.

```
// Creating an immutable list of strings
val names: List<String> = listOf("Alice", "Bob", "Charlie")
// Accessing elements
println(names[0]) // Output: Alice
// Iterating through the list
for (name in names) {
println(name)
}
```

### Mutable List

A mutable list allows you to add, remove, and modify elements after it is created. You can use the **mutableListOf()** function to create a mutable list.

```
// Creating a mutable list of integers
val numbers: MutableList<Int> = mutableListOf(1, 2, 3, 4, 5)
// Adding elements
numbers.add(6)
// Removing elements
numbers.removeAt(2)
// Modifying elements
numbers[0] = 10
// Iterating through the list
for (number in numbers) {
println(number)
}
```

### ArrayList

An ArrayList is a resizable array implementation of the MutableList interface. It provides dynamic resizing and **efficient random access**. ArrayList in Kotlin is same as Java.

```
public actual typealias ArrayList<E> = java.util.ArrayList<E>
```

```
// Creating an ArrayList of doubles
val prices: ArrayList<Double> = arrayListOf(10.5, 20.3, 15.8)
// Adding elements
prices.add(25.0)
// Removing elements
prices.removeAt(1)
// Iterating through the ArrayList
for (price in prices) {
println(price)
}
```

### LinkedList

A LinkedList is a doubly linked list implementation of the MutableList interface. It provides **efficient insertions **and** deletions** at both ends of the list.

```
// Creating a LinkedList of characters
val letters: LinkedList<Char> = LinkedList()
letters.add('A')
letters.add('B')
letters.add('C')
// Adding elements at the beginning and end
letters.addFirst('Z')
letters.addLast('D')
// Removing elements
letters.removeFirst()
letters.removeLast()
// Iterating through the LinkedList
for (letter in letters) {
println(letter)
}
```

These examples demonstrate different types of **lists** available in Kotlin and how to use them for various purposes. Depending on your requirements, you can choose the appropriate list implementation that suits your needs for immutability, mutability, random access, or efficient insertions and deletions.

## Sets

Sets in Kotlin are collections of unique elements with no duplicates.

```
// Creating a set of integers
val uniqueNumbers = setOf(1, 2, 3, 3, 4, 5)
// Adding and removing elements
val mutableSet = mutableSetOf<Int>()
mutableSet.add(6)
mutableSet.remove(3)
// Checking membership
val containsFive = 5 in mutableSet
```

Sets are useful when you need to **ensure uniqueness** or perform set operations like union, intersection, and difference.

## Maps

Maps in Kotlin are collections of key-value pairs where each key is unique.

```
// Creating a map of student grades
val studentGrades = mapOf("Alice" to 90, "Bob" to 85, "Charlie" to 92)
// Accessing values
val aliceGrade = studentGrades["Alice"]
// Modifying values (Not allowed for immutable maps)
val mutableGrades = studentGrades.toMutableMap()
mutableGrades["David"] = 88
// Iterating through a map
for ((name, grade) in studentGrades) {
println("$name: $grade")
}
```

Maps are ideal for situations requiring key-value associations and efficient lookups based on keys.

## Stack and Queues

Stacks follow the **Last-In-First-Out** (LIFO) principle and support push and pop operations where as Queues adhere to the **First-In-First-Out** (FIFO) principle and support enqueue and dequeue operations.

ArrayDeque in Kotlin, introduced in *version 1.4*, is a mutable collection that functions as both a queue and a stack. It is provides efficient operations for adding and removing elements from both ends of the deque.

```
import kotlin.collections.ArrayDeque
val deque = ArrayDeque<Int>()
// Adding elements to the deque
deque.addFirst(1)
deque.addLast(2)
// Removing elements from the deque
val firstElement = deque.removeFirst()
val lastElement = deque.removeLast()
println("First Element: $firstElement") // Output: First Element: 1
println("Last Element: $lastElement") // Output: Last Element: 2
```

## Trees (Binary Trees)

Binary trees consist of nodes where each node has at most two child nodes.

```
// Node structure for a binary tree
class BinaryTreeNode<T>(val value: T) {
var left: BinaryTreeNode<T>? = null
var right: BinaryTreeNode<T>? = null
}
// Creating a binary tree
val root = BinaryTreeNode(1)
root.left = BinaryTreeNode(2)
root.right = BinaryTreeNode(3)
//Reading the tree nodes.
fun <T> inOrderTraversal(node: BinaryTreeNode<T>?) {
if (node == null) return
inOrderTraversal(node.left)
println(node.value)
inOrderTraversal(node.right)
}
println("In-order traversal:")
inOrderTraversal(root)
Output:
In-order traversal:
2
1
3
```

Here we excute an In-order traversal. In-order traversal visits the left subtree, then the root, and finally the right subtree. Binary trees are useful for hierarchical data representation, searching, and sorting algorithms like binary search.

## Graphs

Graphs are complex structures consisting of nodes and edges.

Graphs are essential for modelling relationships and solving problems like *route finding* and *network analysis*.

```
// Implementing an adjacency list for a graph
class Graph {
val adjacencyList = mutableMapOf<Int, MutableList<Int>>()
fun addEdge(from: Int, to: Int) {
adjacencyList.getOrPut(from) { mutableListOf() }.add(to)
}
}
// Creating a graph
val graph = Graph()
graph.addEdge(1, 2)
graph.addEdge(1, 3)
graph.addEdge(2, 4)
// Accessing adjacency list
val adjacencyList = graph.adjacencyList
// Reading all vertices and their adjacent vertices
for ((vertex, adjacentVertices) in adjacencyList) {
println("Vertex $vertex is connected to: $adjacentVertices")
}
// Reading adjacent vertices of a specific vertex
val vertexAdjacent = adjacencyList[1]
println("Adjacent vertices of vertex 1: $vertexAdjacent")
Output
Vertex 1 is connected to: [2, 3]
Vertex 2 is connected to: [4]
Adjacent vertices of vertex 1: [2, 3]
```

The **Graph** class represents a graph data structure using an adjacency list. In this implementation, the **adjacency list** is stored as a mutable map where the keys represent the **vertices** (nodes) of the graph, and the corresponding values are lists containing the adjacent vertices.

**adjacencyList:**

It's a mutable map where the keys are integers representing the vertices, and the values are mutable lists containing adjacent vertices.

**addEdge:**

The addEdge function adds an edge between two vertices in the graph. It takes two parameters: from (the source vertex) and to (the destination vertex).

If the *from* vertex already exists in the adjacency list, **getOrPut** retrieves its associated mutable list of adjacent vertices. If it doesn't exist, a new entry is created and *to* vertex is then added to the list of adjacent vertices for the from vertex.

In the above code snippet, the for-in loop prints each vertex along with its adjacent vertices.

The last println statement prints the adjacent vertices of vertex 1, which are [2, 3].

## Conclusion

This blog post aims to provides a comprehensive overview of various data structures available in Kotlin, along with their use cases and implementation examples. Experiment with these data structures to enhance your understanding and proficiency in Kotlin programming.

## Comments