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:#fff3e0VecDeque<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:
| Operasi | Vec | HashMap | BTreeMap | VecDeque |
|---|---|---|---|---|
| Akses per indeks | O(1) | — | — | O(1) |
| Lookup per kunci | O(n) | O(1) avg | O(log n) | O(n) |
| Insert di akhir | O(1) amortized | O(1) avg | O(log n) | O(1) |
| Insert di depan | O(n) | — | — | O(1) |
| Insert di tengah | O(n) | — | — | O(n) |
| Hapus di akhir | O(1) | — | — | O(1) |
| Hapus di depan | O(n) | — | — | O(1) |
| Iterasi terurut | O(n) | O(n) tidak terurut | O(n) terurut | O(n) |
| Range query | O(n) | — | O(log n + k) | — |
| Memori overhead | Rendah | Sedang | Tinggi | Rendah |
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:#e3f2fdPola 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)
LinkedListada di standard library Rust tapi jarang menjadi pilihan terbaik. Akses sequential padaVecjauh lebih cache-friendly dibandingLinkedListkarena data tersimpan berdekatan di memori. Bahkan untuk operasi insert di tengah,Vecsering 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. Gunakanwith_capacityjika 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 himpunan —insertmengembalikanfalsejika 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+insertyang melakukan dua lookup. Gunakanentry().or_insert()atauentry().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.
retainuntuk filter in-place — lebih efisien dari filter + collect ke Vec baru saat kamu ingin memodifikasi koleksi yang ada tanpa alokasi baru.