Kotlin Recursion

Recursion is a powerful programming technique where a function calls itself to solve a problem. Recursive solutions are often elegant and concise, especially for problems that can be divided into similar subproblems.

This guide covers the following aspects of recursion in Kotlin:

  1. Basics of Recursion
  2. Tail Recursion Optimization
  3. Major Recursive Algorithms
  4. Real-world Examples

1. Basics of Recursion

In a recursive function, the function calls itself to solve smaller instances of the same problem. To prevent infinite recursion, there must be a base case that stops the recursion.

Here’s a simple example of a recursive function to calculate the factorial of a number:

Kotlin
fun factorial(n: Int): Int {
    return if (n == 0) 1 else n * factorial(n - 1)
}

fun main() {
    println(factorial(5))  // Output: 120
}

2. Tail Recursion Optimization

Kotlin supports tail recursion optimization, which allows the compiler to optimize recursive calls and prevent stack overflow errors by reusing the current function’s stack frame.

A function is tail-recursive if the last operation of the function is the recursive call. To make a function tail-recursive, use the tailrec modifier:

Kotlin
tailrec fun factorial(n: Int, accumulator: Int = 1): Int {
    return if (n == 0) accumulator else factorial(n - 1, n * accumulator)
}

fun main() {
    println(factorial(5))  // Output: 120
}

3. Major Recursive Algorithms

Here are some common recursive algorithms implemented in Kotlin:

3.1. Fibonacci Sequence

The Fibonacci sequence is a series of numbers where each number is the sum of the two preceding ones.

Kotlin
fun fibonacci(n: Int): Int {
    return if (n <= 1) n else fibonacci(n - 1) + fibonacci(n - 2)
}

fun main() {
    println(fibonacci(10))  // Output: 55
}

3.2. Binary Search

Binary search is an efficient algorithm for finding an item from a sorted list.

Kotlin
fun binarySearch(arr: IntArray, key: Int, low: Int = 0, high: Int = arr.size - 1): Int {
    if (low > high) return -1
    val mid = (low + high) / 2
    return when {
        arr[mid] == key -> mid
        arr[mid] > key -> binarySearch(arr, key, low, mid - 1)
        else -> binarySearch(arr, key, mid + 1, high)
    }
}

fun main() {
    val arr = intArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
    println(binarySearch(arr, 5))  // Output: 4
}

3.3. Merge Sort

Merge sort is a divide-and-conquer algorithm for sorting an array.

Kotlin
fun mergeSort(arr: IntArray): IntArray {
    if (arr.size <= 1) return arr
    val middle = arr.size / 2
    val left = mergeSort(arr.sliceArray(0 until middle))
    val right = mergeSort(arr.sliceArray(middle until arr.size))
    return merge(left, right)
}

fun merge(left: IntArray, right: IntArray): IntArray {
    var i = 0
    var j = 0
    val result = mutableListOf<Int>()
    while (i < left.size && j < right.size) {
        if (left[i] <= right[j]) {
            result.add(left[i])
            i++
        } else {
            result.add(right[j])
            j++
        }
    }
    result.addAll(left.slice(i until left.size))
    result.addAll(right.slice(j until right.size))
    return result.toIntArray()
}

fun main() {
    val arr = intArrayOf(5, 3, 8, 4, 2, 7, 1, 10)
    println(mergeSort(arr).joinToString(", "))  // Output: 1, 2, 3, 4, 5, 7, 8, 10
}

4. Real-world Examples

4.1. Directory Traversal

Recursively traverse a directory to list all files.

Kotlin
import java.io.File

fun listFiles(directory: File): List<File> {
    val files = mutableListOf<File>()
    directory.listFiles()?.forEach {
        if (it.isDirectory) {
            files.addAll(listFiles(it))
        } else {
            files.add(it)
        }
    }
    return files
}

fun main() {
    val directory = File("path/to/directory")
    val files = listFiles(directory)
    files.forEach { println(it) }
}

4.2. HTML Parsing

Recursively parse an HTML document to extract all links.

Kotlin
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import org.jsoup.select.Elements

fun extractLinks(element: Element, links: MutableList<String>) {
    if (element.tagName() == "a") {
        element.attr("href")?.let { links.add(it) }
    }
    val children: Elements = element.children()
    for (child in children) {
        extractLinks(child, links)
    }
}

fun main() {
    val html = """
        <html>
            <body>
                <a href="https://example.com">Example</a>
                <div>
                    <a href="https://kotlinlang.org">Kotlin</a>
                </div>
            </body>
        </html>
    """.trimIndent()

    val document = Jsoup.parse(html)
    val links = mutableListOf<String>()
    extractLinks(document, links)
    links.forEach { println(it) }  // Output: https://example.com, https://kotlinlang.org
}
Recursion is a fundamental concept that can simplify solving complex problems by breaking them down into smaller, more manageable subproblems. Kotlin provides robust support for recursion, including tail recursion optimization. This guide has covered basic recursion, tail recursion, major recursive algorithms, and real-world examples, illustrating the power and versatility of recursion in Kotlin.