Portal/Notes πŸ“
Rust

Iterators

Versi Bahasa Indonesia. Baca dalam Bahasa Inggris

Pola iterator memungkinkan Anda melakukan suatu tugas pada urutan item secara bergantian. Iterator menyediakan cara untuk melintasi, mentransformasi, atau mengonsumsi elemen secara efisien tanpa perlu mengelola index atau loop secara manual.

Iterator Rust bersifat lazy secara default, artinya mereka tidak melakukan operasi sampai secara eksplisit digunakan (consumed).

Closures dan iterators adalah fitur Rust yang terinspirasi dari ide-ide bahasa pemrograman fungsional. Mereka berkontribusi pada kemampuan Rust untuk mengekspresikan ide tingkat tinggi dengan performa tingkat rendah. Implementasi closures dan iterators sedemikian rupa sehingga performa runtime tidak terpengaruh. Ini adalah bagian dari tujuan Rust untuk menyediakan zero-cost abstractions. - Comparing Performance: Loops vs. Iterators

Iterator Trait

Iterator adalah tipe apa pun yang mengimplementasikan Iterator trait. Iterator trait memiliki satu method yang diperlukan:

fn next(&mut self) -> Option<Self::Item>;

next mengembalikan item berikutnya dalam urutan yang dibungkus dalam Some atau None jika iterator sudah habis.

Memajukan iterator dan mengembalikan nilai berikutnya.

Mengembalikan None ketika iterasi selesai. Implementasi iterator individual dapat memilih untuk melanjutkan iterasi, dan memanggil next() lagi mungkin atau mungkin tidak mulai mengembalikan Some(Item) lagi di beberapa titik.

Contoh:

fn main() {
    let numbers = vec![1, 2, 3];
    let mut iter = numbers.iter();

    println!("{:?}", iter.next());
    println!("{:?}", iter.next());
    println!("{:?}", iter.next());
    println!("{:?}", iter.next());
}

Output:

Some(1)
Some(2)
Some(3)
None

Consumer Adapters

Iterator trait memiliki sejumlah method berbeda dengan implementasi default yang disediakan oleh standard library. Method yang memanggil next disebut consuming adapters, karena memanggilnya akan menghabiskan iterator.

Beberapa method consumer adapters yang umum digunakan: count, sum, fold, product, collect, dll. Anda dapat menemukan lebih banyak method yang disediakan di sini: https://doc.rust-lang.org/std/iter/trait.Iterator.html#provided-methods.

Contoh

Sum

fn sum<S>(self) -> S
where
    Self: Sized,
    S: Sum<Self::Item>,
  • Menjumlahkan elemen-elemen dari sebuah iterator.

  • Mengambil setiap elemen, menjumlahkannya, dan mengembalikan hasilnya.

  • Iterator kosong mengembalikan nilai nol dari tipe tersebut.

  • Contoh:

    fn main() {
        let numbers = vec![1, 2, 3];
        let iter = numbers.iter();
        let x: i32 = iter.sum();
    
        println!("{:?}", x); // 6
    }

Fold

fn fold<B, F>(self, init: B, f: F) -> B
where
    Self: Sized,
    F: FnMut(B, Self::Item) -> B,
  • Melipat setiap elemen ke dalam akumulator dengan menerapkan operasi, mengembalikan hasil akhir.

  • fold() mengambil dua argumen: nilai awal, dan closure dengan dua argumen: sebuah 'accumulator', dan sebuah elemen. Closure mengembalikan nilai yang harus dimiliki akumulator untuk iterasi berikutnya.

  • Nilai awal adalah nilai yang akan dimiliki akumulator pada panggilan pertama.

  • Setelah menerapkan closure ini ke setiap elemen iterator, fold() mengembalikan akumulator.

  • Contoh:

    fn main() {
        let numbers = vec![1, 2, 3];
        let iter = numbers.iter();
        let x: i32 = iter.fold(0, |acc, &x| acc + x * x);
    
        println!("{:?}", x); // 14
    }

Iterator Adapters

Iterator adapters adalah method yang didefinisikan pada Iterator trait yang tidak mengonsumsi iterator. Sebaliknya, mereka menghasilkan iterator yang berbeda dengan mengubah beberapa aspek dari iterator asli.

Beberapa method iterator adapters yang umum digunakan: map, filter, take, skip, enumerate, dll. Sekali lagi, Anda dapat menemukan lebih banyak method yang disediakan di sini: https://doc.rust-lang.org/std/iter/trait.Iterator.html#provided-methods.

Contoh

Map

fn map<B, F>(self, f: F) -> Map<Self, F> 
where
    Self: Sized,
    F: FnMut(Self::Item) -> B,
  • Mengambil sebuah closure dan membuat iterator yang memanggil closure tersebut pada setiap elemen.

  • map() mengubah satu iterator menjadi iterator lain, melalui argumennya: sesuatu yang mengimplementasikan FnMut. Ia menghasilkan iterator baru yang memanggil closure ini pada setiap elemen dari iterator asli.

  • Jika Anda terbiasa berpikir dalam tipe, Anda dapat memikirkan map() seperti ini: Jika Anda memiliki iterator yang memberi Anda elemen dari beberapa tipe A, dan Anda menginginkan iterator dari beberapa tipe lain B, Anda dapat menggunakan map(), dengan memberikan closure yang mengambil A dan mengembalikan B.

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

Filter

fn filter<P>(self, predicate: P) -> Filter<Self, P> 
where
    Self: Sized,
    P: FnMut(&Self::Item) -> bool,
  • Membuat iterator yang menggunakan closure untuk menentukan apakah suatu elemen harus dihasilkan.

  • Untuk suatu elemen, closure harus mengembalikan true atau false. Iterator yang dikembalikan hanya akan menghasilkan elemen-elemen yang closure-nya mengembalikan true.

  • Contoh:

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

Kekuatan collect()

fn collect<B>(self) -> B
where
    B: FromIterator<Self::Item>,
    Self: Sized,

Method collect adalah bagian dari Iterator trait dan digunakan untuk mengubah iterator menjadi sebuah collection. Ia bergantung pada FromIterator trait untuk menentukan bagaimana membangun tipe target.

Contoh Vec::from_iter:

fn main() {
    let five_fives = std::iter::repeat(5).take(5);
    let v = Vec::from_iter(five_fives);

    println!("{:?}", v); // [5, 5, 5, 5, 5]
}

collect() dapat mengambil apa pun yang iterable, dan mengubahnya menjadi collection yang relevan. Ini adalah salah satu method yang paling kuat di standard library, digunakan dalam berbagai konteks.

Pola paling dasar di mana collect() digunakan adalah mengubah satu collection menjadi collection lain. Anda mengambil sebuah collection, memanggil iter padanya, melakukan serangkaian transformasi, dan kemudian collect() di akhir.

Contoh collect:

fn main() {
    fn take_vec(v: Vec<i32>) {
        println!("{:?}", v) // [2, 4, 6]
    }
    
    let numbers = vec![1, 2, 3];
    let doubled = numbers.iter().map(|x| x * 2).collect();
    take_vec(doubled); // Compiler menyimpulkan tipe target sebagai Vec<i32>
}
  • Jadi di sini Rust akan memeriksa tipe target saat collect dipanggil.
  • Seperti yang kita lihat, fungsi take_vec mengharapkan tipe Vec<i32>.
  • Compiler Rust kemudian akan menyimpulkan bahwa target collect adalah Vec<i32>.
  • Dan karena Vec memiliki FromIterator trait, ia akan memanggil fungsi Vec::from_iter dan menghasilkan hasilnya.

Kenapa collect Kadang Gagal?

Ada berbagai alasan mengapa terkadang collect tidak dapat membangun tipe yang ditargetkan, di antaranya:

  • Tipe Target Ambigu: Jika tipe target tidak jelas dari konteks.

  • Ketidakcocokan Tipe: Jika iterator menghasilkan item yang tidak dapat dikonversi ke tipe target.

  • Tipe Target Tidak Didukung: Jika tipe target tidak mengimplementasikan FromIterator.

    Meskipun demikian, kita dapat mengimplementasikan FromIterator jika kita memiliki tipe kustom sendiri seperti ini:

    fn main() {
        use std::iter::FromIterator;
    
        #[derive(Debug)]
        struct MyType(Vec<i32>);
        
        impl FromIterator<i32> for MyType {
            fn from_iter<I: IntoIterator<Item = i32>>(iter: I) -> Self {
                MyType(iter.into_iter().collect())
            }
        }
        
        let numbers = vec![1, 2, 3];
        let result: MyType = numbers.into_iter().collect();
        println!("{:?}", result); // MyType([1, 2, 3])
    }

iter vs iter_mut vs into_iter

.iter()

  • Mengembalikan iterator dari references (&T) ke elemen-elemen collection.
  • Collection tidak dikonsumsi.
  • Tidak dapat memodifikasi elemen collection asli (kecuali digabungkan dengan .iter_mut()).

Contoh:

fn main() {
    let numbers = vec![1, 2, 3];
    let sum: i32 = numbers.iter().sum();
    println!("Sum: {:?}", sum); // Sum: 6 
    println!("Vec: {:?}", numbers); // Vec: [1, 2, 3]
}

.iter_mut()

  • Mengembalikan iterator dari mutable references (&mut T) ke elemen-elemen collection.
  • Untuk memodifikasi elemen collection di tempat.

Contoh:

fn main() {
    let mut numbers = vec![1, 2, 3];
    println!("Vec: {:?}", numbers); // Vec: [1, 2, 3]
    numbers.iter_mut().for_each(|x| *x *= 2);
    println!("Vec: {:?}", numbers); // Vec: [2, 4, 6]
}

.into_iter()

  • Mengonsumsi collection dan mengembalikan iterator dari owned values (T).
  • Memindahkan kepemilikan setiap elemen.
  • Setelah memanggil .into_iter(), collection asli tidak dapat digunakan lagi karena kepemilikan telah ditransfer ke iterator.

Contoh:

fn main() {
    let numbers = vec![1, 2, 3];
    let doubled: Vec<i32> = numbers.into_iter().map(|x| x * 2).collect();
    println!("Vec: {:?}", doubled); // Vec: [2, 4, 6]
    println!("Vec: {:?}", numbers); // error[E0382]: borrow of moved value: `numbers`
}

Referensi


Edit on GitHub

Last updated on

Iterators | Faisal Affan