Lewati ke konten

Iterator di Rust

Iterator (penghasil nilai berurutan) adalah apa saja yang bisa menghasilkan serangkaian nilai, satu per satu. Kamu sebenarnya sudah memakainya sejak halaman For, meski mungkin belum tahu namanya: setiap perulangan for atas sebuah koleksi dijalankan oleh iterator di balik layar.

fn main() {
let numbers = vec![1, 2, 3];
let mut iter = numbers.iter();
println!("{:?}", iter.next()); // Some(1)
println!("{:?}", iter.next()); // Some(2)
println!("{:?}", iter.next()); // Some(3)
println!("{:?}", iter.next()); // None
}

Output:

Some(1)
Some(2)
Some(3)
None
▶ Coba sendiri

.next() mengembalikan sebuah Option: Some(value) untuk setiap item, dan None setelah item-nya habis. Perulangan for sebenarnya hanya memanggil .next() berulang kali di balik layar sampai mendapat None.

.map() menerima sebuah closure dan menerapkannya pada setiap item, sehingga menghasilkan iterator baru:

fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter().map(|n| n * 2).collect();
println!("{:?}", doubled);
}

Output:

[2, 4, 6, 8, 10]
▶ Coba sendiri

.collect() adalah yang mengubah iterator kembali menjadi koleksi yang nyata (di sini, Vec<i32>). Tanpanya, kamu hanya punya iterator yang belum dijalankan sama sekali.

fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
let evens: Vec<&i32> = numbers.iter().filter(|n| **n % 2 == 0).collect();
println!("{:?}", evens);
}

Output:

[2, 4, 6]
▶ Coba sendiri

**n yang dobel itu memang terlihat aneh. .filter() memberi closure sebuah referensi ke referensi (&&i32), jadi perlu di-dereference dua kali supaya bisa dibandingkan dengan angka biasa. Ini keanehan kecil yang sering muncul; tidak perlu memikirkan penjelasan lengkapnya sekarang.

Di sinilah merangkai (chaining) method iterator mulai terasa manfaatnya: kamu bisa menggabungkan beberapa langkah menjadi satu alur yang mudah dibaca:

fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
let result: Vec<i32> = numbers
.iter()
.filter(|n| **n % 2 == 0) // simpan bilangan genap
.map(|n| n * 10) // kalikan masing-masing dengan 10
.collect();
println!("{:?}", result);
}

Output:

[20, 40, 60]
▶ Coba sendiri

Bandingkan dengan menulis logika yang sama memakai perulangan for manual dan Vec kosong yang kamu isi dengan push. Cara itu tidak salah, tapi versi berantai terbaca lebih mirip “saring, lalu kalikan, lalu kumpulkan”, persis urutan langkah yang ingin kamu jelaskan.

fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let total: i32 = numbers.iter().sum();
let count = numbers.iter().count();
let has_even = numbers.iter().any(|n| n % 2 == 0);
println!("jumlah: {total}, banyaknya: {count}, ada yang genap: {has_even}");
}

Output:

jumlah: 15, banyaknya: 5, ada yang genap: true
▶ Coba sendiri

.sum() menjumlahkan semuanya, .count() memberi tahu berapa banyak item yang ada, dan .any() menjawab “apakah ada setidaknya satu item yang cocok?”. Ini baru sebagian kecil method yang dimiliki setiap iterator. Masih banyak lagi, seperti enumerate, zip, take, dan rev. Di halaman Iterator kustom, kamu akan membuat iterator sendiri dan melihat bahwa ia mendapat semua method ini secara gratis.

Ambil angka 1 sampai 10, simpan yang ganjil saja, kuadratkan masing-masing, lalu jumlahkan semuanya. Lakukan dalam satu rangkaian iterator, tanpa variabel mut. Petunjuk: rangkai .filter(), .map(), dan .sum(), lalu beri hasilnya tipe seperti let total: i32.

Lihat solusi
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let total: i32 = numbers
.iter()
.filter(|n| **n % 2 == 1) // simpan 1, 3, 5, 7, 9
.map(|n| n * n) // 1, 9, 25, 49, 81
.sum();
println!("jumlah kuadrat bilangan ganjil: {total}");
}

Output:

jumlah kuadrat bilangan ganjil: 165
▶ Coba sendiri

Rust butuh : i32 pada total karena .sum() bisa menjumlahkan ke beberapa tipe angka, dan Rust tidak bisa menebak tipe mana yang kamu mau.

Mulai dari vec!["the", "quick", "brown", "fox", "jumps"]. Buat Vec<String> baru yang hanya berisi kata yang panjangnya lebih dari 3 huruf, dalam huruf kapital, lalu cetak dengan {:?}. Petunjuk: .filter(|w| w.len() > 3) menyimpan kata-kata yang panjang, dan .to_uppercase() mengubah &str menjadi String berhuruf kapital.

Kata-katanya sengaja dibiarkan dalam bahasa Inggris: hasilnya bergantung pada panjang setiap kata, jadi kalau diterjemahkan, output-nya akan berubah.

Lihat solusi
fn main() {
let words = vec!["the", "quick", "brown", "fox", "jumps"];
let loud: Vec<String> = words
.iter()
.filter(|w| w.len() > 3)
.map(|w| w.to_uppercase())
.collect();
println!("{:?}", loud);
}

Output:

["QUICK", "BROWN", "JUMPS"]
▶ Coba sendiri

words sendiri tidak berubah. .iter() hanya meminjamnya, jadi kamu masih bisa memakai words setelah rangkaian itu.

  • Iterator menghasilkan nilai satu per satu lewat .next(), yang mengembalikan Option.
  • Perulangan for sebenarnya adalah iterator di balik layar.
  • .map() mengubah setiap item; .filter() hanya menyimpan item yang memenuhi syarat; .collect() mengubah hasilnya kembali menjadi koleksi yang nyata.
  • Merangkai method iterator biasanya lebih jelas daripada perulangan manual dengan variabel penampung yang mutable.

Kuis singkat

1. Apa yang dikembalikan .next() pada sebuah iterator?

2. Apa fungsi .collect() di akhir rangkaian seperti .iter().map(...).collect()?

3. Pada .filter(|n| **n % 2 == 0), kenapa n di-dereference dua kali?

4. Apa yang menjalankan perulangan for saat ia melewati sebuah koleksi?

5. Apa yang disimpan .filter() di iterator hasilnya?

Skor: 0 / 5