HashSet dan BTreeMap di Rust
HashSet adalah koleksi berisi nilai-nilai unik. Kalau kamu memasukkan nilai yang sama dua kali, nilai itu hanya disimpan sekali. Bayangkan daftar tamu di pintu masuk: penjaga pintu hanya peduli apakah namamu ada di daftar, bukan berapa kali nama itu ditulis.
Kamu sudah kenal Vec dan HashMap. Halaman ini menambahkan koleksi lain yang paling sering kamu butuhkan: HashSet, BTreeMap dan BTreeSet yang selalu terurut, serta VecDeque untuk antrean. Di bagian akhir ada tabel untuk membantumu memilih yang tepat.
Membuat HashSet
Section titled “Membuat HashSet”HashSet berada di std::collections, jadi kamu perlu baris use. Method insert mengembalikan bool: true kalau nilainya baru, false kalau nilainya sudah ada.
use std::collections::HashSet;
fn main() { let mut tags = HashSet::new();
println!("{}", tags.insert("rust")); println!("{}", tags.insert("web")); println!("{}", tags.insert("rust")); // sudah ada
println!("{} tag unik", tags.len());}Output:
truetruefalse2 tag unikbool itu lebih berguna daripada kelihatannya. Ia menjawab pertanyaan “apakah aku pernah melihat ini sebelumnya?” sekaligus saat menambahkannya, jadi kamu tidak perlu pengecekan terpisah.
Mengecek dan menghapus nilai
Section titled “Mengecek dan menghapus nilai”contains memberi tahu apakah sebuah nilai ada di dalam set. remove mengeluarkan nilai itu dan, sama seperti insert, mengembalikan bool yang memberi tahu apakah memang ada yang dihapus.
use std::collections::HashSet;
fn main() { let mut online: HashSet<&str> = HashSet::from(["ana", "ben", "chen"]);
println!("apakah ben online? {}", online.contains("ben"));
println!("ben dihapus: {}", online.remove("ben")); println!("ben dihapus lagi: {}", online.remove("ben"));
println!("apakah ben online? {}", online.contains("ben")); println!("{} orang online", online.len());}Output:
apakah ben online? trueben dihapus: trueben dihapus lagi: falseapakah ben online? false2 orang onlineKenapa memakai set, bukan Vec, di sini? Memanggil contains pada Vec akan memeriksa setiap item satu per satu. HashSet langsung melompat ke jawabannya, sebesar apa pun ukurannya. Dengan sejuta pengguna, perbedaannya sangat besar.
Menghapus duplikat dari Vec
Section titled “Menghapus duplikat dari Vec”Ini tugas yang sangat umum: kamu punya daftar dengan nilai berulang dan ingin setiap nilai muncul sekali saja. Ada dua cara yang bagus, tergantung apakah kamu peduli dengan urutan aslinya.
use std::collections::HashSet;
fn main() { let visits = vec![3, 1, 3, 2, 1, 5, 2];
// Cara 1: kumpulkan ke dalam set, lalu urutkan agar urutannya stabil let unique: HashSet<i32> = visits.iter().copied().collect(); let mut sorted: Vec<i32> = unique.into_iter().collect(); sorted.sort(); println!("unik terurut: {:?}", sorted);
// Cara 2: simpan kemunculan pertama setiap nilai, sesuai urutan asli let mut seen = HashSet::new(); let mut in_order = visits.clone(); in_order.retain(|x| seen.insert(*x)); println!("urutan asli: {:?}", in_order);}Output:
unik terurut: [1, 2, 3, 5]urutan asli: [3, 1, 2, 5]Cara 2 memanfaatkan bool dari insert. retain hanya menyimpan item ketika closure-nya mengembalikan true, dan insert hanya mengembalikan true saat pertama kali melihat sebuah nilai.
Operasi himpunan: union, intersection, difference
Section titled “Operasi himpunan: union, intersection, difference”Set bisa digabungkan seperti lingkaran-lingkaran di diagram Venn. Setiap method mengembalikan iterator, jadi kita kumpulkan hasilnya ke dalam Vec lalu urutkan sebelum dicetak, supaya output-nya selalu sama.
use std::collections::HashSet;
fn main() { let alice: HashSet<&str> = HashSet::from(["rust", "python", "go"]); let bob: HashSet<&str> = HashSet::from(["go", "java", "rust"]);
let mut both: Vec<_> = alice.intersection(&bob).collect(); both.sort(); println!("dikuasai keduanya: {:?}", both);
let mut either: Vec<_> = alice.union(&bob).collect(); either.sort(); println!("dikuasai salah satu: {:?}", either);
let mut only_alice: Vec<_> = alice.difference(&bob).collect(); only_alice.sort(); println!("hanya alice: {:?}", only_alice);
let mut only_one: Vec<_> = alice.symmetric_difference(&bob).collect(); only_one.sort(); println!("hanya satu orang: {:?}", only_one);}Output:
dikuasai keduanya: ["go", "rust"]dikuasai salah satu: ["go", "java", "python", "rust"]hanya alice: ["python"]hanya satu orang: ["java", "python"]difference hanya berlaku satu arah: alice.difference(&bob) artinya “bahasa yang dikuasai Alice tetapi tidak dikuasai Bob”. Kalau urutannya ditukar, kamu mendapat daftar milik Bob. symmetric_difference mencakup kedua sisi sekaligus: semua yang hanya dikuasai oleh salah satu dari mereka.
BTreeMap dan BTreeSet: selalu terurut
Section titled “BTreeMap dan BTreeSet: selalu terurut”BTreeMap bekerja persis seperti HashMap, dan BTreeSet bekerja persis seperti HashSet. Bedanya: keduanya menyimpan key dalam keadaan terurut. Saat kamu melakukan perulangan padanya, key-nya selalu keluar berurutan.
use std::collections::{BTreeMap, BTreeSet};
fn main() { let text = "ini budi ini ibu budi ini bapak budi";
let mut counts: BTreeMap<&str, u32> = BTreeMap::new(); for word in text.split_whitespace() { *counts.entry(word).or_insert(0) += 1; } for (word, count) in &counts { println!("{word}: {count}"); }
let scores: BTreeSet<u32> = BTreeSet::from([72, 95, 58, 88]); println!("terendah: {:?}", scores.first()); println!("tertinggi: {:?}", scores.last()); println!("semua: {:?}", scores);}Output:
bapak: 1budi: 3ibu: 1ini: 3terendah: Some(58)tertinggi: Some(95)semua: {58, 72, 88, 95}API entry di sini sama dengan yang kamu temui di halaman HashMap. Karena map-nya adalah BTreeMap, kata-katanya keluar dalam urutan abjad tanpa langkah pengurutan tambahan.
Mana yang sebaiknya kamu pilih?
- Pilih
HashMap/HashSetsebagai default. Pencariannya biasanya lebih cepat. - Pilih
BTreeMap/BTreeSetkalau kamu butuh datanya berurutan: mencetak laporan yang terurut, mencari key terkecil atau terbesar, atau mengambil rentang key tertentu.
VecDeque: antrean
Section titled “VecDeque: antrean”VecDeque (dibaca “vec dek”) adalah daftar yang bisa kamu tambah dan kurangi dengan cepat di kedua ujungnya. Karena itu, VecDeque pilihan alami untuk antrean: yang pertama masuk, pertama keluar, seperti orang yang antre.
use std::collections::VecDeque;
fn main() { let mut line: VecDeque<&str> = VecDeque::new(); line.push_back("ana"); line.push_back("ben"); line.push_back("chen");
while let Some(person) = line.pop_front() { println!("melayani {person}, {} masih menunggu", line.len()); }}Output:
melayani ana, 2 masih menunggumelayani ben, 1 masih menunggumelayani chen, 0 masih menungguKamu bisa saja memakai Vec::remove(0) untuk ini, tetapi cara itu menggeser semua item lain satu posisi. pop_front pada VecDeque tidak melakukannya, jadi tetap cepat walaupun antreannya panjang.
Koleksi mana yang sebaiknya kupakai?
Section titled “Koleksi mana yang sebaiknya kupakai?”| Koleksi | Pakai saat kamu butuh… | Contoh |
|---|---|---|
Vec<T> |
Daftar berurutan yang diakses berdasarkan posisi | Baris-baris sebuah file, daftar skor |
HashMap<K, V> |
Pencarian nilai yang cepat berdasarkan key | Username → profil pengguna |
HashSet<T> |
Nilai unik dan pengecekan cepat “apakah ini ada di sini?” | Halaman yang sudah dikunjungi, kata terlarang |
BTreeMap<K, V> |
Key → value, dengan key yang selalu terurut | Jumlah kata yang dicetak urut abjad |
VecDeque<T> |
Menambah/menghapus di kedua ujung | Antrean tugas atau pesan |
Kalau ragu, mulai saja dengan Vec. Ganti kalau kamu sadar sering mencarinya berulang-ulang (pakai set atau map) atau sering menghapus dari depan (pakai VecDeque).
Kesalahan umum
Section titled “Kesalahan umum”Memasukkan struct buatanmu sendiri ke HashSet tanpa derive yang tepat. HashSet perlu meng-hash nilainya dan membandingkan apakah dua nilai sama. Struct buatanmu tidak bisa melakukan itu secara otomatis:
use std::collections::HashSet;
struct Point { x: i32, y: i32,}
fn main() { let mut visited = HashSet::new(); visited.insert(Point { x: 0, y: 0 });}error[E0277]: the trait bound `Point: Eq` is not satisfied --> src/main.rs:10:13 |10 | visited.insert(Point { x: 0, y: 0 }); | ^^^^^^ the trait `Eq` is not implemented for `Point`
error[E0277]: the trait bound `Point: Hash` is not satisfied --> src/main.rs:10:13 |10 | visited.insert(Point { x: 0, y: 0 }); | ^^^^^^ the trait `Hash` is not implemented for `Point`Compiler memberi tahu bahwa Point belum mengimplementasikan trait Eq dan Hash. Solusinya cukup satu baris di atas struct: #[derive(PartialEq, Eq, Hash)]. Untuk BTreeSet, kamu juga perlu PartialOrd dan Ord, supaya ia tahu cara mengurutkannya. Lihat Trait yang sering dipakai untuk arti masing-masing.
use std::collections::HashSet;
#[derive(PartialEq, Eq, Hash)]struct Point { x: i32, y: i32,}
fn main() { let mut visited = HashSet::new(); visited.insert(Point { x: 0, y: 0 }); visited.insert(Point { x: 1, y: 0 }); visited.insert(Point { x: 0, y: 0 }); // duplikat, diabaikan println!("{} kotak sudah dikunjungi", visited.len());}Output:
2 kotak sudah dikunjungiLatihan
Section titled “Latihan”1. Pengulangan pertama
Section titled “1. Pengulangan pertama”Dari kata-kata ["merah", "biru", "hijau", "biru", "merah"], cetak kata pertama yang muncul untuk kedua kalinya. Petunjuk: insert mengembalikan false kalau nilainya sudah ada di set, dan break menghentikan perulangan.
Lihat solusi
use std::collections::HashSet;
fn main() { let words = ["merah", "biru", "hijau", "biru", "merah"]; let mut seen = HashSet::new();
for word in words { if !seen.insert(word) { println!("Pengulangan pertama: {word}"); break; } }}Output:
Pengulangan pertama: biru!seen.insert(word) bisa dibaca “insert-nya gagal”, artinya “aku sudah pernah melihat kata ini”. "biru" adalah kata pertama yang berulang. "merah" juga berulang, tetapi belakangan.
2. Huruf secara berurutan
Section titled “2. Huruf secara berurutan”Hitung berapa kali setiap huruf muncul di "mississippi", lalu cetak jumlahnya dalam urutan abjad. Petunjuk: pakai BTreeMap dengan trik entry(...).or_insert(0) yang sama seperti di bagian BTreeMap, dan lakukan perulangan pada word.chars().
Lihat solusi
use std::collections::BTreeMap;
fn main() { let word = "mississippi"; let mut counts = BTreeMap::new();
for letter in word.chars() { *counts.entry(letter).or_insert(0) += 1; }
for (letter, count) in &counts { println!("{letter}: {count}"); }}Output:
i: 4m: 1p: 2s: 4Dengan HashMap, keempat baris ini bisa keluar dalam urutan apa saja. BTreeMap menjaga key-nya tetap terurut, jadi i, m, p, s selalu tercetak sesuai abjad.
Ringkasan
Section titled “Ringkasan”HashSetmenyimpan nilai unik.insertdanremovemengembalikanboolyang memberi tahu apakah ada yang berubah.containspada set itu cepat, bahkan untuk set yang sangat besar. PadaVec, ia memeriksa setiap item.union,intersection,difference, dansymmetric_differencemenjalankan operasi himpunan. Urutkan hasilnya sebelum dicetak.BTreeMapdanBTreeSetmenjaga key tetap terurut; pakai keduanya kalau urutan itu penting.VecDequeadalah pilihan utama untuk antrean:push_backuntuk masuk antrean,pop_frontuntuk keluar.
Kuis singkat
Section titled “Kuis singkat”Kuis singkat
1. Apa yang dikembalikan set.insert(x) kalau x sudah ada di set?
2. Kamu ingin mencetak jumlah kata dalam urutan abjad tanpa mengurutkannya sendiri. Map mana yang paling cocok?
3. Kenapa mencetak HashSet dengan {:?} bisa memberi output berbeda di setiap run?
4. Koleksi mana yang paling cocok untuk antrean first-in, first-out (pertama masuk, pertama keluar)?
Skor: 0 / 4
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”- Ikhtisar
std::collections— dokumentasi standard library (bahasa Inggris): panduan lebih panjang untuk memilih koleksi, lengkap dengan kecepatan setiap operasinya. - HashSet — Rust by Example (bahasa Inggris): contoh yang bisa dijalankan untuk
union,intersection, dandifference. HashSet— dokumentasi standard library (bahasa Inggris): semua method set, termasukis_subsetdanretain.BTreeMap— dokumentasi standard library (bahasa Inggris): map yang terurut, termasukrangeuntuk mengambil sebagian rentang key.VecDeque— dokumentasi standard library (bahasa Inggris): semua method antrean, sepertipush_front,pop_back, danfront.