Adapter Iterator

map, filter, fold, take, zip, dan chain beserta perannya.

Daftar isi

Topik ini membahas adapter iterator, yaitu metode pada trait Iterator yang membungkus iterator lama menjadi iterator baru dengan perilaku berbeda. Pembahasan mencakup peran masing-masing adapter, yaitu map untuk pengubahan, filter untuk penyaringan, take untuk pembatasan panjang, zip untuk pemadanan dua sumber, chain untuk penggabungan, dan fold sebagai penghabis yang meringkas seluruh nilai menjadi satu hasil. Setelah membaca topik ini, rangkaian metode berantai pada kode Rust dapat dibaca sebagai pipeline yang tersusun dari peran-peran kecil, dan adapter yang tepat dapat dipilih tanpa menulis perulangan indeks manual.

Adapter sebagai pembungkus iterator#

Setiap adapter dipanggil pada sebuah iterator, memakan iterator itu seluruhnya, lalu mengembalikan iterator baru yang menyimpan iterator lama di dalamnya. Tanda tangan map memperlihatkan pola tersebut secara langsung.

rust Fragmen: tanda tangan adapter map
// tidak-dijalankan
fn map<B, F: FnMut(Self::Item) -> B>(self, f: F) -> Map<Self, F>

Parameter self tanpa referensi berarti iterator lama dipindahkan ke dalam Map<Self, F>, tipe iterator baru yang hanya tampil pada tanda tangan, sehingga pemakai tidak perlu menuliskannya. Batasan FnMut pada closure f menunjukkan bahwa map hanya menuntut pemanggilan berulang atas closure dan masih mengizinkan pengubahan lingkungan, sehingga closure Fn yang lebih ringan juga diterima. Karena adapter mengembalikan iterator, pemanggilan adapter dapat dirantai dari kiri ke kanan, dan seluruh rantai tetap lazy sampai sebuah metode penghabis menyentuhnya. Metode penghabis, yang pada dokumentasi disebut consumer, menghabiskan iterator dan menghasilkan nilai akhir, misalnya fold, sum, count, dan collect.

MetodePeranHasil
map(f)mengubah setiap elemen lewat fiterator baru
filter(p)meneruskan elemen yang lolos predikat piterator baru
take(n)membatasi jumlah elemen menjadi niterator baru
zip(lain)memadankan elemen dua iterator seposisiiterator baru
chain(lain)melanjutkan dengan iterator lain setelah habisiterator baru
fold(awal, f)meringkas seluruh elemen menjadi satu nilainilai akhir

Lima metode pertama bersifat lazy, sedangkan fold langsung menghabiskan iteratornya. Diagram berikut memperlihatkan satu rangkaian lengkap dari sumber array sampai koleksi hasil, dengan isi data yang menyusut dan berubah pada setiap tahap.

sumberhasil filterhasil mapkoleksi baru12307458221230452224609044Vec<i32> = [24, 60, 90, 44]filter(|n| n >= 10)map(|n| n * 2)collect()
Pipeline filter, map, dan collect atas array enam elemen

map dan filter#

Metode map(f) memanggil closure f untuk setiap elemen dan meneruskan hasilnya sebagai elemen baru, sehingga panjang keluaran sama dengan panjang masukan. Metode filter(p) memanggil predikat p yang mengembalikan bool, meneruskan elemen saat hasilnya true, dan membuang sisanya. Keduanya sering dipakai berurutan dengan filter di depan, sehingga pekerjaan pengubahan hanya dikenakan pada elemen yang lolos.

rust pemetaan-saring.rs
fn main() {
    let nilai = [65, 82, 47, 91, 73, 58];

    let semua_dikalikan: Vec<i32> = nilai.iter().map(|&n| n * 10).collect();
    println!("map semua   : {:?}", semua_dikalikan);

    let yang_lulus: Vec<i32> = nilai.iter().copied().filter(|&n| n >= 70).collect();
    println!("filter lulus: {:?}", yang_lulus);

    let label_lulus: Vec<String> = nilai
        .iter()
        .filter(|&&n| n >= 70)
        .map(|&n| format!("lulus {}", n))
        .collect();
    println!("berantai    : {:?}", label_lulus);
}
text Keluaran
map semua   : [650, 820, 470, 910, 730, 580]
filter lulus: [82, 91, 73]
berantai    : ["lulus 82", "lulus 91", "lulus 73"]

Baris map semua memperlihatkan pengubahan tanpa penyaringan, dan keenam elemen dikalikan sepuluh. Baris filter lulus menyaring tanpa pengubahan, dan tiga nilai di bawah 70 hilang. Rantai terakhir menggabungkan keduanya dengan filter lebih dulu, sehingga closure format! hanya berjalan tiga kali, bukan enam. Pola |&n| pada closure dibutuhkan karena iter() meminjam elemen, sehingga argumen closurenya bertipe &i32 dan pola itu menyalin nilainya ke n.

zip dan chain#

Metode zip(lain) memadankan dua iterator secara berposisi dan menghasilkan iterator bertipe pasangan (A, B). Panjang hasilnya mengikuti iterator yang lebih pendek, karena pasangan tidak terbentuk bila salah satu sumber telah habis. Metode chain(lain) menjalankan iterator pertama sampai tuntas, lalu melanjutkan dengan iterator kedua, sehingga panjang hasilnya merupakan jumlah keduanya.

rust padukan-gabung.rs
fn main() {
    let nama = ["Ali", "Budi", "Citra"];
    let skor = [80, 95, 60];

    let rapor: Vec<String> = nama
        .iter()
        .zip(skor.iter())
        .map(|(n, &s)| format!("{}: {}", n, s))
        .collect();
    println!("zip   : {:?}", rapor);

    let putaran = ["putar", "istirahat"];
    let nomor = [1, 2, 3];
    let pasangan: Vec<(&str, i32)> = putaran
        .iter()
        .copied()
        .zip(nomor.iter().copied())
        .collect();
    println!("zip 2 : {:?}", pasangan);

    let pagi = ["07.00", "08.00"];
    let sore = ["16.00", "17.00"];
    let jadwal: Vec<&str> = pagi.iter().chain(sore.iter()).copied().collect();
    println!("chain : {:?}", jadwal);
}
text Keluaran
zip   : ["Ali: 80", "Budi: 95", "Citra: 60"]
zip 2 : [("putar", 1), ("istirahat", 2)]
chain : ["07.00", "08.00", "16.00", "17.00"]

Array nama dan skor sama-sama berisi tiga elemen, sehingga zip menghasilkan tiga pasangan yang langsung dibentuk menjadi teks rapor oleh map. Percobaan kedua memadankan dua elemen dengan tiga elemen, dan hasilnya hanya dua pasangan karena elemen ketiga nomor tidak memiliki pasangan. Sebaliknya, chain menempel dua jadwal menjadi satu deret empat waktu tanpa memotong apa pun, dan iterasi berlanjut ke sumber kedua hanya setelah sumber pertama mengembalikan None.

take dan fold#

Metode take(n) membatasi iterator menjadi paling banyak n elemen pertama, dan iterator hasilnya mengembalikan None begitu batas itu tercapai, sehingga iterator tak berhingga pun menjadi aman dikonsumsi. Metode fold(awal, f) adalah penghabis paling umum, yang memulai dari nilai awal, lalu memanggil f(akumulator, elemen) untuk setiap elemen dan menjadikan hasilnya akumulator berikutnya. Nilai akhir akumulator menjadi hasil seluruh proses.

rust lipat.rs
fn main() {
    let kata = ["belajar", "iterator", "efisien"];

    let kalimat: String = kata
        .iter()
        .fold(String::from("catatan:"), |tampung, kata| {
            format!("{} {}", tampung, kata)
        });
    println!("fold teks : {}", kalimat);

    let lima: Vec<i32> = (1..=100).take(5).collect();
    println!("take      : {:?}", lima);

    let faktorial: i32 = (1..=100).take(5).fold(1, |hasil, n| hasil * n);
    println!("fold kali : {}", faktorial);
}
text Keluaran
fold teks : catatan: belajar iterator efisien
take      : [1, 2, 3, 4, 5]
fold kali : 120

fold pertama menumpuk String dari nilai awal "catatan:", dan closure-nya mengembalikan String baru pada setiap langkah sehingga tipe akumulator dan hasilnya sama. Rentang 1..=100 yang panjang dipotong menjadi lima elemen oleh take, terlihat dari vektor [1, 2, 3, 4, 5] yang terkumpul. fold kedua mengalikan lima elemen itu dari nilai awal 1, menghasilkan 120, dan 95 elemen sisanya tidak pernah disentuh karena take sudah memutus aliran lebih awal. Perhatikan bahwa take dipanggil sebelum fold, sehingga pembatasan bekerja pada aliran nilai, bukan pada hasil akhir.

Kesalahan umum#

Merangkai adapter tanpa metode penghabis. Rantai filter dan map saja tidak menjalankan closure apa pun karena seluruh adapter bersifat lazy, sehingga program tanpa collect, fold, atau perulangan tampak berjalan tanpa efek. Tutup setiap rantai dengan consumer, atau jelaskan variabelnya sebagai iterator yang akan digerakkan di tempat lain.

Menyerahkan closure non-bool kepada filter. Predikat filter wajib mengembalikan bool, dan closure yang mengembalikan tipe lain ditolak sebagai ketidakcocokan tipe pada E0308. Gunakan map untuk pengubahan bentuk dan filter untuk keputusan lolos atau buang, dan jangan menggabungkan keduanya dalam satu closure.

Mengandalkan zip ketika panjang sumber berbeda. zip memotong diam-diam pada panjang sumber terpendek tanpa error maupun peringatan, sehingga data yang lebih panjang terbuang tanpa jejak. Pastikan kedua sumber memang sepanjang, atau lakukan pemeriksaan panjang secara eksplisit sebelum memadankan.

Ringkasan#

  • Adapter memakan iterator lewat self dan mengembalikan iterator baru, sehingga rantai dapat disusun dari kiri ke kanan dan semuanya tetap lazy sampai disentuh consumer.
  • map mengubah setiap elemen, filter menyaring dengan predikat bool, dan take memotong panjang, ketiganya menjaga jumlah nilai yang mengalir.
  • zip memadankan dua iterator seposisi dan memotong pada sumber terpendek, sedangkan chain menempel dua iterator berurutan tanpa memotong.
  • fold merangkum seluruh aliran menjadi satu nilai mulai dari nilai awal, dan take di depannya membatasi pekerjaan sebelum penghabis berjalan.

Referensi#