Collections

Collections #

Hampir setiap program yang berguna menyimpan dan memproses sekumpulan data. Rust menyediakan beberapa struktur data koleksi di standard library, masing-masing dengan karakteristik performa dan use case yang berbeda. Berbeda dengan bahasa yang menyediakan satu tipe “list” serbaguna, Rust mengharuskan kamu memilih koleksi yang paling tepat untuk kebutuhan — pilihan yang berdampak langsung pada performa dan kejelasan kode. Artikel ini membahas koleksi yang paling sering digunakan: Vec untuk data berurutan, HashMap dan HashSet untuk lookup cepat, BTreeMap dan BTreeSet untuk data terurut, serta VecDeque untuk kebutuhan antrean.

Vec<T> — Koleksi Berurutan yang Paling Umum #

Vec<T> adalah array dinamis yang menyimpan elemen bertipe T secara berurutan di heap. Ini adalah koleksi yang paling sering digunakan di Rust — pilihan default saat kamu membutuhkan daftar elemen.

fn main() {
    // Membuat Vec
    let mut v: Vec<i32> = Vec::new();
    let v2 = vec![1, 2, 3, 4, 5];          // macro vec! — cara paling umum
    let v3: Vec<i32> = (1..=5).collect();   // dari iterator
    let v4 = vec![0i32; 10];               // 10 elemen bernilai 0

    // Dengan kapasitas — hindari realokasi jika ukuran akhir diketahui
    let mut v5: Vec<i32> = Vec::with_capacity(100);
    println!("len: {}, capacity: {}", v5.len(), v5.capacity());  // 0, 100

    // Menambah elemen
    v.push(1);
    v.push(2);
    v.push(3);

    // Menambah banyak elemen sekaligus
    v.extend([4, 5, 6]);
    v.extend(v2.iter().copied());

    // Menyisipkan di posisi tertentu — O(n), geser semua elemen setelahnya
    v.insert(0, 99);  // sisipkan 99 di indeks 0
    println!("{:?}", v);  // [99, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]

    // Menghapus elemen
    let terakhir = v.pop();          // hapus dan kembalikan elemen terakhir: Some(5)
    let di_posisi = v.remove(0);    // hapus di indeks 0, geser elemen lain: 99
    v.swap_remove(0);               // hapus di indeks 0, isi dengan elemen terakhir — O(1)

    // Akses elemen
    let pertama: Option<&i32> = v.first();
    let terakhir: Option<&i32> = v.last();
    let ketiga: Option<&i32> = v.get(2);    // aman — mengembalikan Option
    let langsung: &i32 = &v[2];             // panic jika di luar batas

    // ANTI-PATTERN: akses langsung tanpa pengecekan batas
    // let x = v[100];  // panic jika v punya < 101 elemen

    // BENAR: gunakan get() untuk akses yang aman
    if let Some(x) = v.get(100) {
        println!("{}", x);
    }
}

Operasi Slice pada Vec #

Vec<T> bisa di-deref menjadi &[T] (slice), yang membuka akses ke semua method slice.

fn main() {
    let mut v = vec![3, 1, 4, 1, 5, 9, 2, 6, 5, 3];

    // Pengurutan
    v.sort();                              // urut ascending: [1, 1, 2, 3, 3, 4, 5, 5, 6, 9]
    v.sort_by(|a, b| b.cmp(a));           // urut descending
    v.sort_by_key(|&x| std::cmp::Reverse(x)); // cara lain descending

    // Untuk float (tidak implements Ord)
    let mut float_v = vec![3.1f64, 1.4, 2.7, 0.8];
    float_v.sort_by(|a, b| a.partial_cmp(b).unwrap());

    // Pencarian — binary search hanya untuk Vec yang sudah terurut
    v.sort();
    match v.binary_search(&5) {
        Ok(idx) => println!("Ditemukan di indeks: {}", idx),
        Err(idx) => println!("Tidak ada, bisa disisipkan di indeks: {}", idx),
    }

    // Deduplikasi — hapus duplikat berurutan (efektif setelah sort)
    v.dedup();
    println!("{:?}", v);  // [1, 2, 3, 4, 5, 6, 9]

    // Retain — pertahankan hanya yang memenuhi kondisi (in-place filter)
    let mut v2 = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    v2.retain(|&x| x % 2 == 0);
    println!("{:?}", v2);  // [2, 4, 6, 8, 10]

    // Membalik urutan
    v2.reverse();
    println!("{:?}", v2);  // [10, 8, 6, 4, 2]

    // Slice — referensi ke bagian Vec
    let v3 = vec![1, 2, 3, 4, 5];
    let tengah: &[i32] = &v3[1..4];  // [2, 3, 4]
    println!("{:?}", tengah);

    // Windows dan chunks
    for window in v3.windows(3) {
        println!("{:?}", window);  // [1,2,3], [2,3,4], [3,4,5]
    }
    for chunk in v3.chunks(2) {
        println!("{:?}", chunk);   // [1,2], [3,4], [5]
    }

    // Split — bagi Vec menjadi dua di posisi tertentu
    let (kiri, kanan) = v3.split_at(2);
    println!("{:?} {:?}", kiri, kanan);  // [1, 2] [3, 4, 5]

    // Truncate dan clear
    let mut v4 = vec![1, 2, 3, 4, 5];
    v4.truncate(3);   // pertahankan hanya 3 elemen pertama: [1, 2, 3]
    v4.clear();       // hapus semua elemen, kapasitas tetap
}

Manajemen Memori Vec #

fn main() {
    // Vec mengalokasikan ulang saat kapasitas habis
    // Strategi: kapasitas digandakan setiap kali habis (amortized O(1))
    let mut v: Vec<i32> = Vec::new();
    for i in 0..10 {
        v.push(i);
        println!("len: {}, cap: {}", v.len(), v.capacity());
        // cap: 1, 2, 4, 4, 8, 8, 8, 8, 16, 16 — tumbuh eksponensial
    }

    // with_capacity jika ukuran akhir diketahui
    let n = 1000;
    let mut v_efisien: Vec<i32> = Vec::with_capacity(n);
    for i in 0..n as i32 {
        v_efisien.push(i);  // tidak pernah realokasi
    }

    // shrink_to_fit — kembalikan memori yang tidak terpakai
    v_efisien.truncate(10);
    println!("Sebelum shrink — cap: {}", v_efisien.capacity()); // masih 1000
    v_efisien.shrink_to_fit();
    println!("Setelah shrink — cap: {}", v_efisien.capacity()); // sekitar 10

    // drain — hapus range elemen, kembalikan sebagai iterator
    let mut v5 = vec![1, 2, 3, 4, 5, 6, 7, 8];
    let diambil: Vec<i32> = v5.drain(2..5).collect();
    println!("Diambil: {:?}", diambil);  // [3, 4, 5]
    println!("Sisa: {:?}", v5);          // [1, 2, 6, 7, 8]
}

HashMap<K, V> — Lookup Cepat dengan Kunci #

HashMap<K, V> menyimpan pasangan kunci-nilai dengan waktu lookup rata-rata O(1). Ini adalah pilihan default saat kamu perlu mencari nilai berdasarkan kunci.

use std::collections::HashMap;

fn main() {
    // Membuat HashMap
    let mut skor: HashMap<String, i32> = HashMap::new();

    // Menambah entri
    skor.insert(String::from("Alice"), 85);
    skor.insert(String::from("Bob"), 92);
    skor.insert(String::from("Carol"), 78);

    // Dari array of tuples
    let map: HashMap<&str, i32> = [("satu", 1), ("dua", 2), ("tiga", 3)]
        .into_iter()
        .collect();

    // Membaca nilai
    let alice_skor = skor.get("Alice");          // Option<&i32> — aman
    let bob_skor = skor["Bob"];                  // i32 — panic jika tidak ada
    println!("{:?}", alice_skor);                // Some(85)

    // Pengecekan keberadaan kunci
    println!("{}", skor.contains_key("Alice"));  // true
    println!("{}", skor.contains_key("Dave"));   // false

    // Menghapus entri
    let dihapus = skor.remove("Carol");          // mengembalikan Option<V>
    println!("{:?}", dihapus);                   // Some(78)

    // Iterasi
    for (nama, nilai) in &skor {
        println!("{}: {}", nama, nilai);
    }

    // Hanya kunci atau hanya nilai
    let semua_nama: Vec<&String> = skor.keys().collect();
    let semua_skor: Vec<&i32> = skor.values().collect();

    // Ukuran
    println!("Jumlah entri: {}", skor.len());
    println!("Kosong: {}", skor.is_empty());
}

Entry API — Pola yang Paling Penting di HashMap #

Entry API adalah cara idiomatik untuk menangani situasi “insert jika belum ada” atau “update jika sudah ada” tanpa double lookup.

use std::collections::HashMap;

fn main() {
    let mut skor: HashMap<&str, i32> = HashMap::new();

    // ANTI-PATTERN: double lookup — cek keberadaan lalu insert
    if !skor.contains_key("Alice") {
        skor.insert("Alice", 0);
    }
    *skor.get_mut("Alice").unwrap() += 10;

    // BENAR: entry API — satu lookup, lebih efisien
    skor.entry("Alice").or_insert(0);
    *skor.entry("Alice").or_insert(0) += 10;

    // or_insert_with — lazy, value hanya dibuat jika belum ada
    skor.entry("Bob").or_insert_with(|| {
        println!("Membuat nilai default untuk Bob");
        calculate_default_score()
    });

    // or_default — gunakan nilai Default trait
    skor.entry("Carol").or_default();  // 0 untuk i32

    // and_modify — modifikasi jika ada, tanpa insert jika tidak ada
    skor.entry("Alice").and_modify(|v| *v += 5);

    // Kombinasi: modifikasi jika ada, insert dengan default jika tidak
    skor.entry("Dave")
        .and_modify(|v| *v += 10)
        .or_insert(50);

    println!("{:?}", skor);

    // Use case klasik: menghitung frekuensi
    let teks = "apel pisang apel jeruk pisang apel mangga";
    let mut frekuensi: HashMap<&str, u32> = HashMap::new();

    for kata in teks.split_whitespace() {
        *frekuensi.entry(kata).or_insert(0) += 1;
    }
    println!("{:?}", frekuensi);
    // {"apel": 3, "pisang": 2, "jeruk": 1, "mangga": 1}

    // Use case: grouping — kelompokkan berdasarkan kunci
    let data = vec![
        ("backend", "Alice"),
        ("frontend", "Bob"),
        ("backend", "Carol"),
        ("frontend", "Dave"),
        ("backend", "Eve"),
    ];

    let mut per_tim: HashMap<&str, Vec<&str>> = HashMap::new();
    for (tim, nama) in &data {
        per_tim.entry(tim).or_insert_with(Vec::new).push(nama);
    }
    println!("{:?}", per_tim);
    // {"backend": ["Alice", "Carol", "Eve"], "frontend": ["Bob", "Dave"]}
}

fn calculate_default_score() -> i32 { 50 }

Kustomisasi Hasher #

HashMap menggunakan hasher yang aman secara kriptografis secara default (SipHash), yang tahan terhadap HashDoS attacks. Untuk performa maksimum di konteks yang tidak membutuhkan keamanan tersebut, kamu bisa mengganti hasher.

use std::collections::HashMap;
use std::hash::BuildHasherDefault;
use std::collections::hash_map::DefaultHasher;

// Menggunakan FxHashMap dari crate rustc-hash untuk performa lebih tinggi
// Tambahkan ke Cargo.toml: rustc-hash = "1"
// use rustc_hash::FxHashMap;

fn main() {
    // HashMap standar — aman, performa baik untuk kebanyakan kasus
    let mut map: HashMap<String, i32> = HashMap::new();
    map.insert("kunci".to_string(), 42);

    // Kapan pertimbangkan hasher alternatif:
    // - Key bertipe integer atau string pendek yang terprediksi
    // - Hot path yang perlu throughput maksimum
    // - Konteks non-public (tidak menerima input dari luar)
}

HashSet<T> — Koleksi Nilai Unik #

HashSet<T> adalah HashMap<T, ()> — ia menyimpan nilai unik tanpa value. Gunakan saat kamu perlu mengetahui apakah suatu elemen ada, tanpa peduli urutannya.

use std::collections::HashSet;

fn main() {
    // Membuat HashSet
    let mut set: HashSet<i32> = HashSet::new();
    let set2: HashSet<i32> = vec![1, 2, 3, 2, 1].into_iter().collect(); // duplikat dihapus

    // Menambah dan menghapus
    set.insert(1);
    set.insert(2);
    set.insert(2);  // diabaikan — sudah ada
    set.remove(&1);

    // Pengecekan
    println!("{}", set.contains(&2));  // true

    // Operasi himpunan — ini adalah kekuatan utama HashSet
    let a: HashSet<i32> = vec![1, 2, 3, 4, 5].into_iter().collect();
    let b: HashSet<i32> = vec![3, 4, 5, 6, 7].into_iter().collect();

    // Irisan (intersection) — elemen yang ada di keduanya
    let irisan: HashSet<&i32> = a.intersection(&b).collect();
    println!("Irisan: {:?}", irisan);  // {3, 4, 5}

    // Gabungan (union) — semua elemen dari kedua himpunan
    let gabungan: HashSet<&i32> = a.union(&b).collect();
    println!("Gabungan: {:?}", gabungan);  // {1, 2, 3, 4, 5, 6, 7}

    // Selisih (difference) — elemen di a tapi tidak di b
    let selisih: HashSet<&i32> = a.difference(&b).collect();
    println!("Selisih a-b: {:?}", selisih);  // {1, 2}

    // Selisih simetris — elemen yang ada di salah satu tapi tidak di keduanya
    let sym_diff: HashSet<&i32> = a.symmetric_difference(&b).collect();
    println!("Selisih simetris: {:?}", sym_diff);  // {1, 2, 6, 7}

    // Pengecekan relasi himpunan
    let sub: HashSet<i32> = vec![1, 2].into_iter().collect();
    println!("sub bagian dari a: {}", sub.is_subset(&a));    // true
    println!("a superset dari sub: {}", a.is_superset(&sub)); // true
    println!("a dan b disjoint: {}", a.is_disjoint(&b));      // false

    // Use case: deduplikasi dengan mempertahankan urutan
    let dengan_duplikat = vec![3, 1, 4, 1, 5, 9, 2, 6, 5, 3];
    let mut seen = HashSet::new();
    let unik_berurutan: Vec<i32> = dengan_duplikat
        .into_iter()
        .filter(|x| seen.insert(*x))  // insert mengembalikan false jika sudah ada
        .collect();
    println!("{:?}", unik_berurutan);  // [3, 1, 4, 5, 9, 2, 6] — urutan terjaga
}

BTreeMap<K, V> dan BTreeSet<T> — Koleksi Terurut #

BTreeMap dan BTreeSet menyimpan elemen dalam urutan terurut berdasarkan kunci. Berbeda dengan HashMap/HashSet yang tidak menjamin urutan, BTreeMap selalu mengiterasi dalam urutan kunci.

use std::collections::BTreeMap;

fn main() {
    let mut map: BTreeMap<String, i32> = BTreeMap::new();
    map.insert("charlie".to_string(), 3);
    map.insert("alice".to_string(), 1);
    map.insert("bob".to_string(), 2);

    // Iterasi selalu dalam urutan kunci (alphabetical untuk String)
    for (k, v) in &map {
        println!("{}: {}", k, v);
    }
    // alice: 1
    // bob: 2
    // charlie: 3

    // Range query — ini yang tidak bisa dilakukan HashMap
    for (k, v) in map.range("alice".to_string()..="bob".to_string()) {
        println!("{}: {}", k, v);
    }
    // alice: 1
    // bob: 2

    // Elemen terkecil dan terbesar
    println!("{:?}", map.first_key_value());  // Some(("alice", 1))
    println!("{:?}", map.last_key_value());   // Some(("charlie", 3))

    // Hapus elemen terkecil atau terbesar
    map.pop_first();  // hapus "alice"
    map.pop_last();   // hapus "charlie"

    // Entry API tersedia sama seperti HashMap
    map.entry("dave".to_string()).or_insert(4);
}
use std::collections::BTreeSet;

fn main() {
    let mut set: BTreeSet<i32> = BTreeSet::new();
    for x in [5, 2, 8, 1, 9, 3, 7, 4, 6] {
        set.insert(x);
    }

    // Iterasi selalu dalam urutan terurut
    println!("{:?}", set.iter().collect::<Vec<_>>());  // [1, 2, 3, 4, 5, 6, 7, 8, 9]

    // Range query
    let rentang: Vec<&i32> = set.range(3..=7).collect();
    println!("{:?}", rentang);  // [3, 4, 5, 6, 7]

    // Nilai terkecil dan terbesar
    println!("{:?}", set.first());  // Some(1)
    println!("{:?}", set.last());   // Some(9)

    // Operasi himpunan (sama seperti HashSet)
    let a: BTreeSet<i32> = vec![1, 2, 3, 4].into_iter().collect();
    let b: BTreeSet<i32> = vec![3, 4, 5, 6].into_iter().collect();
    let irisan: BTreeSet<i32> = a.intersection(&b).copied().collect();
    println!("{:?}", irisan);  // {3, 4}
}
flowchart TD
    A{Perlu lookup berdasarkan kunci?} -- Ya --> B{Perlu urutan terurut?}
    A -- Tidak --> C{Perlu nilai unik saja?}

    B -- Ya --> D["BTreeMap<K,V>\nO(log n), terurut, range query"]
    B -- Tidak --> E["HashMap<K,V>\nO(1) rata-rata, tidak terurut"]

    C -- Ya, terurut --> F["BTreeSet<T>\nO(log n), terurut, range query"]
    C -- Ya, tidak terurut --> G["HashSet<T>\nO(1) rata-rata, tidak terurut"]
    C -- Tidak --> H["Vec<T>\nO(1) akses indeks, berurutan"]

    style D fill:#e8f5e9
    style E fill:#e3f2fd
    style F fill:#e8f5e9
    style G fill:#e3f2fd
    style H fill:#fff3e0

VecDeque<T> — Antrean Dua Ujung #

VecDeque<T> adalah double-ended queue — ia mendukung penambahan dan penghapusan di kedua ujung secara efisien (O(1)). Gunakan saat kamu membutuhkan antrean (FIFO) atau stack (LIFO) yang efisien di kedua ujung.

use std::collections::VecDeque;

fn main() {
    let mut deque: VecDeque<i32> = VecDeque::new();

    // Menambah di depan dan belakang
    deque.push_back(1);
    deque.push_back(2);
    deque.push_back(3);
    deque.push_front(0);
    deque.push_front(-1);
    println!("{:?}", deque);  // [-1, 0, 1, 2, 3]

    // Menghapus dari depan dan belakang
    let depan = deque.pop_front();   // Some(-1)
    let belakang = deque.pop_back(); // Some(3)
    println!("{:?}", deque);  // [0, 1, 2]

    // Akses tanpa menghapus
    println!("{:?}", deque.front());  // Some(0)
    println!("{:?}", deque.back());   // Some(2)

    // Konversi ke Vec (linearisasi buffer ring)
    let sebagai_vec: Vec<i32> = Vec::from(deque.clone());
    // Atau: deque.make_contiguous() untuk mendapat &mut [T]

    // Use case: sliding window / buffer dengan ukuran tetap
    fn rata_rata_bergerak(data: &[f64], ukuran_window: usize) -> Vec<f64> {
        let mut window: VecDeque<f64> = VecDeque::with_capacity(ukuran_window);
        let mut hasil = Vec::new();

        for &nilai in data {
            window.push_back(nilai);
            if window.len() > ukuran_window {
                window.pop_front();
            }
            if window.len() == ukuran_window {
                let rata: f64 = window.iter().sum::<f64>() / ukuran_window as f64;
                hasil.push(rata);
            }
        }
        hasil
    }

    let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0];
    let ma = rata_rata_bergerak(&data, 3);
    println!("{:?}", ma);  // [2.0, 3.0, 4.0, 5.0, 6.0]

    // Use case: BFS (Breadth-First Search)
    fn bfs(graf: &Vec<Vec<usize>>, mulai: usize) -> Vec<usize> {
        let n = graf.len();
        let mut dikunjungi = vec![false; n];
        let mut antrean: VecDeque<usize> = VecDeque::new();
        let mut urutan = Vec::new();

        antrean.push_back(mulai);
        dikunjungi[mulai] = true;

        while let Some(node) = antrean.pop_front() {
            urutan.push(node);
            for &tetangga in &graf[node] {
                if !dikunjungi[tetangga] {
                    dikunjungi[tetangga] = true;
                    antrean.push_back(tetangga);
                }
            }
        }
        urutan
    }
}

Perbandingan Performa Koleksi #

Memilih koleksi yang tepat berdampak signifikan pada performa. Berikut perbandingan kompleksitas operasi utama:

OperasiVecHashMapBTreeMapVecDeque
Akses per indeksO(1)O(1)
Lookup per kunciO(n)O(1) avgO(log n)O(n)
Insert di akhirO(1) amortizedO(1) avgO(log n)O(1)
Insert di depanO(n)O(1)
Insert di tengahO(n)O(n)
Hapus di akhirO(1)O(1)
Hapus di depanO(n)O(1)
Iterasi terurutO(n)O(n) tidak terurutO(n) terurutO(n)
Range queryO(n)O(log n + k)
Memori overheadRendahSedangTinggiRendah
flowchart TD
    A[Butuh koleksi apa?] --> B{Akses berdasarkan posisi/indeks?}
    B -- Ya --> C{Insert/hapus di depan sering?}
    C -- Ya --> D["VecDeque — antrean dua ujung"]
    C -- Tidak --> E["Vec — array dinamis"]

    B -- Tidak --> F{Butuh lookup cepat O(1)?}
    F -- Ya, kunci ke nilai --> G{Urutan penting?}
    F -- Ya, hanya keanggotaan --> H{Urutan penting?}
    F -- Tidak --> I["Vec dengan linear search"]

    G -- Ya --> J["BTreeMap — terurut, range query"]
    G -- Tidak --> K["HashMap — tercepat untuk lookup"]
    H -- Ya --> L["BTreeSet — set terurut"]
    H -- Tidak --> M["HashSet — set tercepat"]

    style D fill:#fff3e0
    style E fill:#e3f2fd
    style J fill:#e8f5e9
    style K fill:#e3f2fd
    style L fill:#e8f5e9
    style M fill:#e3f2fd

Pola Idiomatik dengan Collections #

Beberapa pola yang sering muncul di kode Rust produksi saat bekerja dengan koleksi.

use std::collections::HashMap;

fn main() {
    // 1. Transformasi HashMap — map values
    let harga: HashMap<&str, f64> = [("apel", 5000.0), ("pisang", 3000.0)].into_iter().collect();

    let setelah_pajak: HashMap<&str, f64> = harga.iter()
        .map(|(&k, &v)| (k, v * 1.1))
        .collect();

    // 2. Filter HashMap
    let mahal: HashMap<&&str, &f64> = harga.iter()
        .filter(|(_, &v)| v > 4000.0)
        .collect();

    // 3. Invert HashMap — tukar kunci dan nilai
    let kode: HashMap<&str, u32> = [("merah", 1), ("hijau", 2), ("biru", 3)].into_iter().collect();
    let terbalik: HashMap<u32, &str> = kode.iter().map(|(&k, &v)| (v, k)).collect();
    println!("{:?}", terbalik);  // {1: "merah", 2: "hijau", 3: "biru"}

    // 4. Merge dua HashMap — nilai dari map kedua menang jika ada konflik
    let mut map1: HashMap<&str, i32> = [("a", 1), ("b", 2)].into_iter().collect();
    let map2: HashMap<&str, i32> = [("b", 20), ("c", 3)].into_iter().collect();
    map1.extend(map2);
    println!("{:?}", map1);  // {"a": 1, "b": 20, "c": 3}

    // 5. Menghitung top-N dari frekuensi
    let teks = "apel pisang apel jeruk pisang apel mangga jeruk apel";
    let mut frekuensi: Vec<(&str, usize)> = teks.split_whitespace()
        .fold(HashMap::<&str, usize>::new(), |mut map, kata| {
            *map.entry(kata).or_insert(0) += 1;
            map
        })
        .into_iter()
        .collect();

    frekuensi.sort_by(|a, b| b.1.cmp(&a.1));  // urut dari terbanyak
    let top_2: Vec<_> = frekuensi.iter().take(2).collect();
    println!("{:?}", top_2);  // [("apel", 4), ("pisang", 2)] atau ("jeruk", 2)

    // 6. Vec of struct — sort berdasarkan field
    #[derive(Debug)]
    struct Produk { nama: String, harga: f64, stok: u32 }

    let mut produk = vec![
        Produk { nama: "Apel".to_string(), harga: 5000.0, stok: 100 },
        Produk { nama: "Pisang".to_string(), harga: 3000.0, stok: 50 },
        Produk { nama: "Jeruk".to_string(), harga: 7000.0, stok: 75 },
    ];

    // Sort berdasarkan harga ascending
    produk.sort_by(|a, b| a.harga.partial_cmp(&b.harga).unwrap());

    // Sort berdasarkan beberapa kriteria
    produk.sort_by(|a, b| {
        b.stok.cmp(&a.stok)                           // stok descending
            .then(a.harga.partial_cmp(&b.harga).unwrap()) // lalu harga ascending
    });

    // 7. Flatten dan deduplikasi dari banyak sumber
    let sumber1 = vec![1, 2, 3, 4];
    let sumber2 = vec![3, 4, 5, 6];
    let sumber3 = vec![5, 6, 7, 8];

    use std::collections::HashSet;
    let semua_unik: HashSet<i32> = [sumber1, sumber2, sumber3]
        .into_iter()
        .flatten()
        .collect();
    let mut terurut: Vec<i32> = semua_unik.into_iter().collect();
    terurut.sort();
    println!("{:?}", terurut);  // [1, 2, 3, 4, 5, 6, 7, 8]
}

Kapan Menggunakan Koleksi Mana #

Gunakan Vec jika:
  ✓ Kamu menyimpan daftar elemen berurutan
  ✓ Akses berdasarkan indeks adalah operasi utama
  ✓ Insert dan hapus hampir selalu di akhir
  ✓ Kamu membutuhkan representasi memori yang compact
  ✓ Kamu perlu mengirim data ke fungsi yang menerima slice &[T]

Gunakan HashMap jika:
  ✓ Kamu perlu lookup O(1) berdasarkan kunci
  ✓ Urutan kunci tidak penting
  ✓ Kunci adalah tipe yang implements Hash + Eq

Gunakan BTreeMap jika:
  ✓ Kamu perlu iterasi dalam urutan kunci terurut
  ✓ Kamu perlu range query (ambil semua entri dalam rentang kunci)
  ✓ Kamu perlu min/max kunci secara efisien
  ✓ Kunci tidak implements Hash (misalnya float)

Gunakan HashSet jika:
  ✓ Kamu hanya perlu mengetahui apakah nilai ada atau tidak
  ✓ Kamu perlu operasi himpunan (irisan, gabungan, selisih)
  ✓ Deduplikasi cepat dari koleksi besar

Gunakan VecDeque jika:
  ✓ Kamu membutuhkan antrean FIFO (First In, First Out)
  ✓ Insert dan hapus terjadi di kedua ujung
  ✓ Implementasi sliding window atau buffer circular

Jangan gunakan LinkedList kecuali:
  ✗ Kamu perlu O(1) insert/hapus di tengah dengan cursor yang sudah ada
  ✗ (Sangat jarang di Rust — Vec hampir selalu lebih baik karena cache locality)
LinkedList ada di standard library Rust tapi jarang menjadi pilihan terbaik. Akses sequential pada Vec jauh lebih cache-friendly dibanding LinkedList karena data tersimpan berdekatan di memori. Bahkan untuk operasi insert di tengah, Vec sering lebih cepat dalam praktik karena cache locality yang lebih baik — kecuali ukuran datanya sangat besar dan operasi insert di tengah sangat sering.

Ringkasan #

  • Vec<T> adalah pilihan default — gunakan untuk daftar berurutan, akses per indeks, dan saat insert/hapus hampir selalu di akhir. Gunakan with_capacity jika ukuran akhir diketahui untuk menghindari realokasi.
  • HashMap<K,V> untuk lookup O(1) — entry API (or_insert, and_modify) adalah cara idiomatik untuk insert-or-update tanpa double lookup. Gunakan untuk penghitungan frekuensi dan grouping.
  • BTreeMap<K,V> jika urutan penting — satu-satunya koleksi map yang menjamin iterasi terurut dan mendukung range query. Trade-off: O(log n) vs O(1) untuk HashMap.
  • HashSet<T> untuk keanggotaan dan operasi himpunaninsert mengembalikan false jika sudah ada, yang bisa dieksploitasi untuk deduplikasi sambil mempertahankan urutan.
  • VecDeque<T> untuk antrean — O(1) di kedua ujung. Pilihan tepat untuk BFS, sliding window, dan buffer dengan ukuran tetap.
  • Entry API adalah kunci HashMap — hindari pola contains_key + insert yang melakukan dua lookup. Gunakan entry().or_insert() atau entry().and_modify().or_insert().
  • Pilih berdasarkan operasi dominan — O(1) vs O(log n) terdengar kecil, tapi di hot path dengan jutaan operasi perbedaannya signifikan. Profil dulu sebelum optimasi prematur.
  • retain untuk filter in-place — lebih efisien dari filter + collect ke Vec baru saat kamu ingin memodifikasi koleksi yang ada tanpa alokasi baru.

← Sebelumnya: Iterators   Berikutnya: Sync →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact