Kotlin menyediakan banyak fitur bahasa yang membuat implementasi algoritma menjadi lebih ringkas, ekspresif, dan aman secara tipe dibanding Java tradisional. Pada chapter ini kita akan memadukan fitur-fitur tersebut—extension function, sealed class, sequence, scope function, dan tail recursion—dalam satu berkas yang dapat dijalankan. Pembaca akan melihat bagaimana algoritma Kadane untuk maximum subarray menjadi method pada IntArray, bagaimana sealed class HasilPencarian memodelkan hasil pencarian dengan aman secara tipe, dan bagaimana asSequence() menghemat memori pada pipeline besar. Chapter ini menutup section Algorithms dan Data Structures dengan ringkasan praktik terbaik Kotlin.

Extension Function untuk Algoritma #

Extension function memungkinkan kita menambahkan method ke class tanpa mewarisinya. Algoritma yang beroperasi pada koleksi tertentu dapat diorganisir sebagai method yang terasa natural saat dipanggil.

kotlin Extension function pada IntArray
fun IntArray.maxSubarraySum(): Int {
    var maxSoFar = Int.MIN_VALUE
    var maxEndingHere = 0
    for (x in this) {
        maxEndingHere += x
        if (maxSoFar < maxEndingHere) maxSoFar = maxEndingHere
        if (maxEndingHere < 0) maxEndingHere = 0
    }
    return maxSoFar
}
// Pemanggilan: arr.maxSubarraySum()

Algoritma Kadane di atas kini menjadi method pada IntArray sehingga pemanggilan terasa seperti API bawaan Kotlin.

Sealed Class untuk State Algoritma #

Sealed class membatasi subkelas hanya pada yang dideklarasikan di dalam file yang sama. Kombinasi dengan when ekspresif memungkinkan compiler memverifikasi bahwa semua cabang tertangani.

kotlin Sealed class untuk hasil pencarian
sealed class HasilPencarian<out T> {
    data class Ditemukan<T>(val index: Int, val value: T) : HasilPencarian<T>()
    data class TidakDitemukan(val jumlahDiperiksa: Int) : HasilPencarian<Nothing>()
}

Hasil pencarian kini berbentuk nilai Ditemukan atau TidakDitemukan—tidak ada kemungkinan null yang harus dihandle secara terpisah.

Sequence untuk Lazy Evaluation #

Sequence mengevaluasi elemen secara lazy (saat dibutuhkan) dan merangkai operasi tanpa membuat koleksi antara. Hal ini menghemat memori pada pipeline yang besar.

kotlin Pipeline dengan Sequence
(2..1_000_000).asSequence()
    .filter { isPrime(it) }
    .map { it * it }
    .take(10)
    .toList()

Tanpa asSequence(), setiap filter dan map membuat List baru—memboros memori untuk jutaan elemen.

Implementasi Lengkap #

kotlin
// Demo Implementasi Algoritma dengan Kotlin idiomatic.

// === Extension function: algoritma sebagai method ===
fun IntArray.sumInRange(low: Int, high: Int): Int {
    var sum = 0
    for (i in low..high) {
        if (i < size) sum += this[i]
    }
    return sum
}

fun IntArray.maxSubarraySum(): Int {
    var maxSoFar = Int.MIN_VALUE
    var maxEndingHere = 0
    for (x in this) {
        maxEndingHere += x
        if (maxSoFar < maxEndingHere) maxSoFar = maxEndingHere
        if (maxEndingHere < 0) maxEndingHere = 0
    }
    return maxSoFar
}

// === Sealed class untuk state algoritma ===
sealed class HasilPencarian<out T> {
    data class Ditemukan<T>(val index: Int, val value: T) : HasilPencarian<T>()
    data class TidakDitemukan(val jumlahDiperiksa: Int) : HasilPencarian<Nothing>()
}

fun <T : Comparable<T>> linearSearchSealed(arr: List<T>, target: T): HasilPencarian<T> {
    var diperiksa = 0
    for ((i, x) in arr.withIndex()) {
        diperiksa++
        if (x == target) return HasilPencarian.Ditemukan(i, x)
    }
    return HasilPencarian.TidakDitemukan(diperiksa)
}

// === Sequence untuk lazy evaluation ===
fun isPrime(n: Int): Boolean {
    if (n < 2) return false
    if (n == 2) return true
    if (n % 2 == 0) return false
    var i = 3
    while (i.toLong() * i <= n) {
        if (n % i == 0) return false
        i += 2
    }
    return true
}

fun primesUpTo(n: Int): List<Int> {
    return (2..n).asSequence().filter { isPrime(it) }.toList()
}

fun sumOfPrimesUpTo(n: Int): Long {
    return (2..n).asSequence().filter { isPrime(it) }.sumOf { it.toLong() }
}

// === Algoritma fungsi: pipeline dengan sequence ===
fun pipelineFungsional(data: List<Int>): List<Int> {
    return data.asSequence()
        .filter { it > 0 }              // hanya positif
        .map { it * it }                // kuadratkan
        .distinct()                     // hapus duplikat
        .sorted()                       // urutkan
        .toList()
}

// === Scope function untuk algoritma ===
data class Statistik(val mean: Double, val median: Double, val modus: Int?)

fun hitungStatistik(data: List<Int>): Statistik = data.run {
    val sorted = sorted()
    val mean = average()
    val median = if (size % 2 == 0) {
        (sorted[size / 2 - 1] + sorted[size / 2]) / 2.0
    } else {
        sorted[size / 2].toDouble()
    }
    val modus = groupingBy { it }.eachCount().maxByOrNull { it.value }?.key
    Statistik(mean, median, modus)
}

// === Tail recursion ===
tailrec fun factorialTail(n: Int, acc: Long = 1): Long {
    if (n <= 1) return acc
    return factorialTail(n - 1, acc * n)
}

tailrec fun gcd(a: Int, b: Int): Int {
    if (b == 0) return a
    return gcd(b, a % b)
}

fun main() {
    println("=== Demo Implementasi Algoritma dengan Kotlin ===")

    println("--- Extension function ---")
    val arr = intArrayOf(3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5)
    println("Array: ${arr.toList()}")
    println("Sum range [2..5]: ${arr.sumInRange(2, 5)}")
    val withNeg = intArrayOf(-2, 1, -3, 4, -1, 2, 1, -5, 4)
    println("Array (dengan negatif): ${withNeg.toList()}")
    println("Max subarray sum (Kadane): ${withNeg.maxSubarraySum()}")

    println()
    println("--- Sealed class untuk state pencarian ---")
    val data = listOf(5, 3, 8, 1, 9, 2, 7)
    listOf(9, 100, 5).forEach { target ->
        val hasil = linearSearchSealed(data, target)
        val pesan = when (hasil) {
            is HasilPencarian.Ditemukan -> "ditemukan di index ${hasil.index} (value=${hasil.value})"
            is HasilPencarian.TidakDitemukan -> "tidak ada setelah ${hasil.jumlahDiperiksa} pemeriksaan"
        }
        println("Cari $target: $pesan")
    }

    println()
    println("--- Sequence untuk lazy evaluation ---")
    println("Prima sampai 50: ${primesUpTo(50)}")
    println("Jumlah prima sampai 100: ${sumOfPrimesUpTo(100)}")

    println()
    println("--- Pipeline fungsional ---")
    val input = listOf(1, -2, 3, 3, -4, 5, 5, 6, 0, 7)
    println("Input: $input")
    println("Pipeline (filter>0, kuadrat, distinct, sorted): ${pipelineFungsional(input)}")

    println()
    println("--- Scope function untuk statistik ---")
    val statistik = hitungStatistik(listOf(85, 90, 78, 92, 88, 76, 95, 88, 80, 88))
    println("Data: ${listOf(85, 90, 78, 92, 88, 76, 95, 88, 80, 88)}")
    println("Statistik: mean=${"%.2f".format(statistik.mean)}, median=${statistik.median}, modus=${statistik.modus}")

    println()
    println("--- Tail recursion ---")
    println("factorial(20) = ${factorialTail(20)}")
    println("gcd(48, 36) = ${gcd(48, 36)}")
    println("gcd(17, 5) = ${gcd(17, 5)}")

    println()
    println("--- Idiomatic Kotlin: compareBy dengan chained comparator ---")
    data class Produk(val nama: String, val kategori: String, val harga: Int)
    val produk = listOf(
        Produk("Buku", "ATK", 15000),
        Produk("Pulpen", "ATK", 5000),
        Produk("Baju", "Fashion", 75000),
        Produk("Celana", "Fashion", 75000),
        Produk("Penggaris", "ATK", 5000)
    )
    println("Sorted by kategori then harga then nama:")
    produk.sortedWith(compareBy({ it.kategori }, { it.harga }, { it.nama })).forEach {
        println("  ${it.kategori} | Rp${it.harga} | ${it.nama}")
    }
}

Output yang Dihasilkan:

text
=== Demo Implementasi Algoritma dengan Kotlin ===
--- Extension function ---
Array: [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
Sum range [2..5]: 19
Array (dengan negatif): [-2, 1, -3, 4, -1, 2, 1, -5, 4]
Max subarray sum (Kadane): 6

--- Sealed class untuk state pencarian ---
Cari 9: ditemukan di index 4 (value=9)
Cari 100: tidak ada setelah 7 pemeriksaan
Cari 5: ditemukan di index 0 (value=5)

--- Sequence untuk lazy evaluation ---
Prima sampai 50: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
Jumlah prima sampai 100: 1060

--- Pipeline fungsional ---
Input: [1, -2, 3, 3, -4, 5, 5, 6, 0, 7]
Pipeline (filter>0, kuadrat, distinct, sorted): [1, 9, 25, 36, 49]

--- Scope function untuk statistik ---
Data: [85, 90, 78, 92, 88, 76, 95, 88, 80, 88]
Statistik: mean=86.00, median=88.0, modus=88

--- Tail recursion ---
factorial(20) = 2432902008176640000
gcd(48, 36) = 12
gcd(17, 5) = 1

--- Idiomatic Kotlin: compareBy dengan chained comparator ---
Sorted by kategori then harga then nama:
  ATK | Rp5000 | Penggaris
  ATK | Rp5000 | Pulpen
  ATK | Rp15000 | Buku
  Fashion | Rp75000 | Baju
  Fashion | Rp75000 | Celana

Bedah Kode Baris-per-Baris #

Mari kita amati bagian kunci:

  1. IntArray.sumInRange (baris 4-10): Extension function menerima low dan high sebagai parameter. Di dalam function, this merujuk ke IntArray penerima. Pemanggilan arr.sumInRange(2, 5) terasa seperti method bawaan Kotlin—API jadi terasa alami.
  2. IntArray.maxSubarraySum (baris 12-21): Algoritma Kadane melacak maxSoFar (subarray maksimum global) dan maxEndingHere (subarray maksimum berakhir di indeks saat ini). Bila maxEndingHere jatuh di bawah nol, reset ke nol karena subarray negatif tidak akan meningkatkan jumlah di iterasi berikutnya.
  3. HasilPencarian (baris 24-36): Sealed class generik dengan dua subkelas. Ditemukan<T> membawa indeks dan nilai; TidakDitemukan membawa jumlah elemen yang diperiksa. Tipe Nothing pada TidakDitemukan membuatnya cocok untuk semua HasilPencarian<T> berkat deklarasi out T (covariance). Konstruksi when pada baris 111-114 mendapat keuntungan dari smart cast—hasil.index tersedia tanpa cast eksplisit.
  4. primesUpTo (baris 51-53): Memakai asSequence() untuk membuat pipeline lazy. Method filter tidak membuat list antara; setiap elemen mengalir melalui filter dan langsung masuk ke toList() di akhir.
  5. pipelineFungsional (baris 60-67): Pipeline lima tahap—filter positif, kuadratkan, hapus duplikat, urutkan, kembalikan list. Berkat sequence, setiap tahap diproses secara streaming tanpa koleksi antara. Untuk input [1, -2, 3, 3, -4, 5, 5, 6, 0, 7], hasilnya [1, 9, 25, 36, 49]—kuadrat dari nilai positif yang unik dan terurut.
  6. hitungStatistik (baris 72-82): Memakai data.run { ... } sehingga this di dalam block adalah List<Int>. Method sorted(), average(), groupingBy { } dapat dipanggil langsung tanpa prefix data.. Hasilnya data class Statistik yang merangkum tiga nilai statistik.
  7. factorialTail dan gcd (baris 85-93): Modifier tailrec menginstruksikan compiler mengubah recursion menjadi loop iteratif bila recursive call adalah operasi terakhir. factorialTail(20) mengembalikan 2432902008176640000 tanpa risiko stack overflow.

Praktik Terbaik Ringkas #

Implementasi algoritma idiomatic Kotlin menggabungkan beberapa prinsip: extension function untuk API yang natural, sealed class untuk state yang aman secara tipe, sequence untuk pipeline hemat memori, scope function untuk reduksi boilerplate, dan tail recursion untuk rekursi yang aman. Kombinasi ini menghasilkan kode yang ringkas, ekspresif, dan setara dalam kinerja dengan implementasi imperatif tradisional.

Penutup #

Section Algorithms dan Data Structures telah menelusuri perjalanan lengkap—dari kompleksitas waktu, struktur data dasar, algoritma searching/sorting, recursion/backtracking, greedy, dynamic programming, graph algorithms, hingga penulisan idiomatic Kotlin. Pemahaman ini menjadi bekal untuk menyelesaikan masalah nyata di produksi dengan kode yang efisien dan mudah dibaca.

Referensi #

Referensi Lanjutan #