Sequence<T> adalah collection yang mengevaluasi elemennya secara lazy. Berbeda dengan List yang mengevaluasi semua operasi antara secara eager, Sequence hanya memproses elemen saat dibutuhkan oleh operasi terminal seperti toList() atau count(). Pada chapter ini kita akan memahami cara membuat Sequence, pipeline operasinya, dan bagaimana lazy evaluation menghindari alokasi list antara pada pipeline panjang.
Membuat Sequence #
Ada beberapa cara membuat Sequence:
| Cara | Sumber | Karakteristik |
|---|---|---|
sequenceOf(...) | Elemen literal | Seperti listOf tetapi lazy |
coll.asSequence() | Collection apa pun | Membungkus collection sebagai sequence |
generateSequence(seed) { next } | Seed + fungsi | Bisa tak terbatas |
sequence { yield(...) } | Builder dengan lambda | Paling fleksibel, bisa yieldAll |
Pipeline Lazy #
Operasi pada Sequence dibagi menjadi dua kelompok:
- Intermediate (
map,filter,onEach,sorted,distinct,take,drop): mengembalikanSequencebaru tanpa evaluasi. - Terminal (
toList,count,forEach,sum,fold,first): memicu evaluasi dan menghasilkan nilai konkret.
Saat kita menulis seq.map { ... }.filter { ... }.take(3), tidak ada satu pun elemen yang diproses. Evaluasi baru terjadi ketika operasi terminal dipanggil.
generateSequence: Deret Tak Terbatas #
generateSequence(seed) { prev -> next } menghasilkan sequence yang bisa tak terbatas. Karena lazy, kita bisa take(n) tanpa khawatir memori habis:
val duaPangkat = generateSequence(1) { it * 2 }
val ambil8 = duaPangkat.take(8).toList()
// [1, 2, 4, 8, 16, 32, 64, 128]Tanpa take, sequence akan terus menghasilkan nilai saat diiterasi — kita wajib membatasi dengan take atau operasi terminal lain yang berhenti lebih awal.
Builder sequence dengan yield #
Cara paling fleksibel adalah builder sequence { ... }. Di dalam lambda, kita memanggil yield(value) untuk menghasilkan satu elemen atau yieldAll(iterable) untuk banyak elemen. Eksekusi dijeda dan dilanjutkan saat elemen diminta.
val fibo = sequence {
var a = 0; var b = 1
while (true) {
yield(a)
val next = a + b
a = b; b = next
}
}
val fibo10 = fibo.take(10).toList()
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]Builder sequence di balik layar menggunakan coroutine. Lambda tidak berjalan sekaligus — yield menyerahkan kontrol kembali ke pemanggil dan melanjutkan saat elemen berikutnya diminta.
Contoh Lengkap #
fun main() {
val angka = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// Eager: pipeline pada List, setiap tahap membuat list baru
val eagerHasil = angka
.map { it * it }
.filter { it > 10 }
.take(3)
println("Eager: $eagerHasil")
// Lazy: pipeline pada Sequence, evaluasi sekali jalan
val lazyHasil = angka
.asSequence()
.map { it * it }
.filter { it > 10 }
.take(3)
.toList()
println("Lazy: $lazyHasil")
// sequenceOf: membuat sequence langsung
val seq = sequenceOf(1, 2, 3, 4, 5)
println("sequenceOf: ${seq.toList()}")
// generateSequence: dengan seed dan next function
val duaKali = generateSequence(1) { it * 2 }
val ambil = duaKali.take(8).toList()
println("generateSequence 2x: $ambil")
// sequence tak terbatas dengan kondisi berhenti
val fibo = sequence {
var a = 0; var b = 1
while (true) {
yield(a)
val next = a + b
a = b; b = next
}
}
val fibo10 = fibo.take(10).toList()
println("Fibonacci: $fibo10")
// Debug dengan onEach: lihat elemen yang dilewatkan tiap tahap
print("Trace lazy: ")
angka.asSequence()
.onEach { print("[$it]") }
.map { it * it }
.onEach { print("=$it ") }
.filter { it > 10 }
.take(2)
.forEach { print("-> $it; ") }
println()
// Bandingkan dengan eager yang mencetak semua elemen per tahap
print("Trace eager: ")
angka
.map { it * it }
.also { println("setelah map: $it") }
.filter { it > 10 }
.also { println("setelah filter: $it") }
.take(2)
.also { println("setelah take: $it") }
}Output yang Dihasilkan:
Eager: [16, 25, 36]
Lazy: [16, 25, 36]
sequenceOf: [1, 2, 3, 4, 5]
generateSequence 2x: [1, 2, 4, 8, 16, 32, 64, 128]
Fibonacci: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Trace lazy: [1]=1 [2]=4 [3]=9 [4]=16 -> 16; [5]=25 -> 25;
Trace eager: setelah map: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
setelah filter: [16, 25, 36, 49, 64, 81, 100]
setelah take: [16, 25]Mari kita bedah baris-baris penting di atas:
angka.map { it * it }.filter { it > 10 }.take(3)(eager): setiap tahap membuat list baru.mapmengubah 10 elemen menjadi 10 kuadrat, lalufiltermenyaring 7 elemen, lalutake(3)mengambil 3. Total tiga list dialokasikan.angka.asSequence().map { ... }.filter { ... }.take(3).toList()(lazy): pipeline dijalankan elemen demi elemen. Saattake(3)meminta elemen ke-3, sequence baru memproses elemen yang diperlukan dan berhenti. Hanya satu list hasil akhir yang dialokasikan.generateSequence(1) { it * 2 }: seed1, fungsi nextit * 2. Sequence bisa berjalan tak terbatas.take(8)membatasi menjadi 8 elemen.sequence { ... yield(a) ... }: builder dengan lambda.yield(a)menghasilkanalalu memberhentikan eksekusi. Saat elemen berikutnya diminta, eksekusi dilanjutkan setelahyield. Loopwhile (true)tidak mengakibatkan infinite loop karenayieldmemberhentikan sementara.onEach { print("[$it]") }: intermediate operation yang menjalankan lambda tiap elemen dilewatkan tanpa mengubah nilai. Berguna untuk debugging pipeline lazy.- Trace lazy: output
[1]=1 [2]=4 [3]=9 [4]=16 -> 16; [5]=25 -> 25;menunjukkan sequence memproses elemen 1, 2, 3, 4 untuk mendapatkan hasil pertama (16), lalu elemen 5 untuk hasil kedua (25). Setelahtake(2)terpenuhi, iterasi berhenti — elemen 6 sampai 10 tidak diproses sama sekali. - Trace eager: output menunjukkan semua elemen diproses pada setiap tahap.
mapmenghasilkan list 10 elemen,filtermenghasilkan list 7 elemen,take(2)menghasilkan list 2 elemen. Total 17 elemen list antara dibuat.
Kapan Memakai Sequence #
- Pipeline panjang (banyak operasi intermediate berurutan).
- Collection besar — alokasi list antara pada eager bisa boros memori.
take(n)awal — sequence berhenti begitu cukup, eager memproses semua elemen dulu.- Deret tak terbatas atau generatif (Fibonacci, prime, stream data).
Referensi Lanjutan #
- Sequences
— panduan resmi
Sequence<T>dan pipeline lazy - Sequence API reference
— API reference
kotlin.sequences
Setelah memahami dasar sequence, kita akan membandingkan secara langsung kapan eager (List) dan kapan lazy (Sequence) menjadi pilihan yang lebih tepat.