Setelah memahami seleksi elemen, kita sering perlu mengubah struktur collection: memetakan nilai, meratakan list bersarang, memasangkan dua list, atau membentuk map dari list. Pada chapter ini kita akan mempelajari transformasi struktural paling penting: map, flatMap, zip, unzip, associate, groupBy, chunked, dan windowed. Setiap fungsi memiliki pola penggunaan khas yang akan kita lihat dalam contoh konkret.

map dan Variannya #

map adalah transformasi satu-ke-satu: tiap elemen input menjadi satu elemen output. Variannya menangani kasus khusus:

FungsiMengembalikanCatatan
map { it -> ... }List<R>Transformasi satu-ke-satu
mapIndexed { i, x -> ... }List<R>Lambda menerima indeks
mapNotNull { ... }List<R>Membuang hasil null
mapIndexedNotNull { i, x -> ... }List<R>Gabungan dua di atas
kotlin Contoh: map dan mapNotNull
val kuadrat = listOf(1, 2, 3).map { it * it }            // [1, 4, 9]
val parsed = listOf("1", "abc", "3").mapNotNull { it.toIntOrNull() }  // [1, 3]

flatMap dan flatten #

flatMap melakukan dua langkah: pertama, lambda mengubah tiap elemen menjadi collection; kedua, semua collection tersebut diratakan menjadi satu list. flatten adalah versi sederhana yang hanya meratakan list bersarang tanpa transformasi.

kotlin Contoh: flatMap
val kalimat = listOf("Halo Dunia", "Belajar Kotlin")
val kata = kalimat.flatMap { it.split(" ") }
// [Halo, Dunia, Belajar, Kotlin]

val grup = listOf(listOf(1, 2), listOf(3, 4))
val rata = grup.flatten()  // [1, 2, 3, 4]

zip dan unzip #

zip memasangkan dua collection berdasarkan indeks, menghasilkan List<Pair<A, B>>. Jika panjang berbeda, hasil mengikuti yang lebih pendek. unzip adalah kebalikannya: List<Pair<K, V>> menjadi Pair<List<K>, List<V>>.

kotlin Contoh: zip
val nama = listOf("Budi", "Siti", "Dewi")
val umur = listOf(25, 30, 22)
val pasangan = nama.zip(umur)        // [(Budi,25), (Siti,30), (Dewi,22)]
val gabungan = nama.zip(umur) { n, u -> "$n=$u" }  // custom transform

associate, associateWith, associateBy #

Tiga fungsi ini mengubah list menjadi Map:

FungsiMengembalikanLogika
associate { x -> Pair(k, v) }Map<K, V>Lambda mengembalikan pair
associateWith { x -> v }Map<T, V>Key = elemen, value = lambda
associateBy { x -> k }Map<K, T>Key = lambda, value = elemen
kotlin Contoh: associate
val nama = listOf("Budi", "Siti", "Dewi")
val petaPanjang = nama.associateWith { it.length }   // {Budi=4, Siti=4, Dewi=4}
val petaByKey = nama.associateBy { it.first() }      // {B=Budi, S=Siti, D=Dewi}

groupBy #

groupBy { key } mengelompokkan elemen berdasarkan kunci yang dihasilkan lambda, menghasilkan Map<K, List<T>>. Operasi ini sangat berguna untuk kategorisasi.

kotlin Contoh: groupBy
val buah = listOf("Apel", "Anggur", "Mangga", "Alpukat")
val perHuruf = buah.groupBy { it.first() }
// {A=[Apel, Anggur, Alpukat], M=[Mangga]}

chunked dan windowed #

Kedua fungsi ini memecah list menjadi potongan-potongan:

FungsiPerilakuHasil dari (1..5).chunked(2)
chunked(n)Potongan non-overlap ukuran n[[1,2], [3,4], [5]]
windowed(n)Jendela geser ukuran n, overlap 1[[1,2,3], [2,3,4], [3,4,5]]
windowed(n, step=m)Langkah mBebas

Contoh Lengkap #

kotlin
fun main() {
    val angka = listOf(1, 2, 3, 4, 5)

    // map: transformasi satu-ke-satu
    val kuadrat = angka.map { it * it }
    println("map kuadrat: $kuadrat")

    // mapIndexed: akses indeks + nilai
    val denganPosisi = angka.mapIndexed { i, v -> "[$i]=$v" }
    println("mapIndexed: $denganPosisi")

    // mapNotNull: buang hasil null
    val aman = listOf("1", "2", "abc", "4")
    val parsed = aman.mapNotNull { it.toIntOrNull() }
    println("mapNotNull: $parsed")

    // flatMap: tiap elemen jadi collection, lalu diratakan
    val kalimat = listOf("Halo Dunia", "Belajar Kotlin")
    val kata = kalimat.flatMap { it.split(" ") }
    println("flatMap kata: $kata")

    // Contoh lain: deret ke sub-list
    val grup = listOf(listOf(1, 2), listOf(3), listOf(4, 5, 6))
    val rata = grup.flatten()
    println("flatten: $rata")

    // zip: pasangkan dua list berdasarkan indeks
    val nama = listOf("Budi", "Siti", "Dewi")
    val umur = listOf(25, 30, 22)
    val pasangan = nama.zip(umur)
    println("zip: $pasangan")
    pasangan.forEach { (n, u) -> println("  $n umur $u") }

    // zip dengan transformasi
    val gabungan = nama.zip(umur) { n, u -> "$n=$u" }
    println("zip transform: $gabungan")

    // unzip: pecah list pair jadi dua list
    val (nama2, umur2) = pasangan.unzip()
    println("unzip nama: $nama2")
    println("unzip umur: $umur2")

    // associate: ubah ke map
    val petaUmur = nama.zip(umur).toMap()
    println("toMap: $petaUmur")

    val petaPanjang = nama.associateWith { it.length }
    println("associateWith: $petaPanjang")

    val petaByKey = nama.associateBy { it.take(1) }
    println("associateBy: $petaByKey")

    // groupBy: kumpulkan per kunci
    val mahasiswa = listOf("Budi", "Siti", "Bunga", "Budi", "Andi")
    val perHuruf = mahasiswa.groupBy { it.first() }
    println("groupBy huruf awal: $perHuruf")

    // chunked: potong ukuran tetap
    val angkaPanjang = (1..10).toList()
    val potongan = angkaPanjang.chunked(3)
    println("chunked(3): $potongan")

    // windowed: jendela geser
    val jendela = angkaPanjang.windowed(3)
    println("windowed(3): $jendela")

    val jendelaLangkah = angkaPanjang.windowed(3, step = 2)
    println("windowed(3, step=2): $jendelaLangkah")
}

Output yang Dihasilkan:

text
map kuadrat: [1, 4, 9, 16, 25]
mapIndexed: [[0]=1, [1]=2, [2]=3, [3]=4, [4]=5]
mapNotNull: [1, 2, 4]
flatMap kata: [Halo, Dunia, Belajar, Kotlin]
flatten: [1, 2, 3, 4, 5, 6]
zip: [(Budi, 25), (Siti, 30), (Dewi, 22)]
  Budi umur 25
  Siti umur 30
  Dewi umur 22
zip transform: [Budi=25, Siti=30, Dewi=22]
unzip nama: [Budi, Siti, Dewi]
unzip umur: [25, 30, 22]
toMap: {Budi=25, Siti=30, Dewi=22}
associateWith: {Budi=4, Siti=4, Dewi=4}
associateBy: {B=Budi, S=Siti, D=Dewi}
groupBy huruf awal: {B=[Budi, Bunga, Budi], S=[Siti], A=[Andi]}
chunked(3): [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
windowed(3): [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], [5, 6, 7], [6, 7, 8], [7, 8, 9], [8, 9, 10]]
windowed(3, step=2): [[1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8, 9]]

Mari kita bedah baris-baris penting di atas:

  1. angka.map { it * it }: tiap elemen Int diubah menjadi Int lain (kuadratnya). Lambda mengembalikan nilai baru; list asli tidak berubah.
  2. aman.mapNotNull { it.toIntOrNull() }: lambda mengembalikan Int? — null untuk string non-numerik. mapNotNull membuang null dan menyempitkan tipe menjadi List<Int>.
  3. kalimat.flatMap { it.split(" ") }: split(" ") menghasilkan List<String> per kalimat. flatMap meratakan kedua list menjadi satu [Halo, Dunia, Belajar, Kotlin].
  4. nama.zip(umur): pasangan (Budi, 25), (Siti, 30), (Dewi, 22). Bila umur lebih pendek, elemen nama yang berlebih akan diabaikan.
  5. pasangan.unzip(): pecah List<Pair<String, Int>> menjadi Pair<List<String>, List<Int>>. Destructuring val (nama2, umur2) = ... mengambil kedua list.
  6. nama.associateWith { it.length }: elemen list menjadi key, panjangnya menjadi value. Hasil {Budi=4, Siti=4, Dewi=4}.
  7. nama.associateBy { it.first() }: karakter pertama menjadi key. Bila ada duplikat (misal Budi dan Bunga), nilai terakhir menimpa yang sebelumnya.
  8. mahasiswa.groupBy { it.first() }: menghasilkan Map<Char, List<String>>. Semua nama berawalan B terkumpul dalam satu list value.
  9. angkaPanjang.chunked(3): potongan non-overlap. Potongan terakhir [10] hanya berisi satu elemen karena tidak cukup.
  10. angkaPanjang.windowed(3): jendela geser dengan overlap. Setiap jendela berisi 3 elemen berturut-turut, total 8 jendela dari list 10 elemen.

Referensi Lanjutan #

Setelah transformasi satu collection, kita akan melihat agregasi berbasis kunci yang lebih kaya: groupBy, groupingBy, fold, dan reduce akan dibahas pada chapter grouping.