Map

Map #

HashMap<K, V> adalah koleksi kunci-nilai standar di Rust — setara dengan dict di Python, Map di JavaScript, atau HashMap di Java. Ia menyimpan pasangan kunci-nilai dengan waktu akses rata-rata O(1). Yang membedakan HashMap Rust dari bahasa lain adalah ketegasannya soal ownership: ketika sebuah nilai di-insert, HashMap mengambil alih ownership-nya. Dan cara mengupdate nilai yang sudah ada — lewat Entry API — jauh lebih elegan dan idiomatis dari yang tersedia di banyak bahasa lain. Artikel ini membahas HashMap secara menyeluruh, termasuk BTreeMap untuk kebutuhan urutan terurut, HashSet sebagai koleksi tanpa nilai, dan panduan memilih di antara ketiganya.

Membuat HashMap #

use std::collections::HashMap;

fn main() {
    // Kosong — tipe diinfer dari insert pertama
    let mut skor: HashMap<String, i32> = HashMap::new();

    // Insert pasangan kunci-nilai
    skor.insert(String::from("Tim Merah"), 85);
    skor.insert(String::from("Tim Biru"), 92);
    skor.insert(String::from("Tim Hijau"), 78);

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

    // Dari dua Vec dengan zip + collect
    let tim = vec!["Merah", "Biru", "Hijau"];
    let nilai = vec![85, 92, 78];
    let map: HashMap<_, _> = tim.iter().zip(nilai.iter()).collect();
    println!("{:?}", map);

    // Dari array of tuple
    let konfigurasi: HashMap<&str, &str> = [
        ("host", "localhost"),
        ("port", "8080"),
        ("mode", "debug"),
    ].into_iter().collect();
    println!("{:?}", konfigurasi);

    // Dengan kapasitas awal untuk menghindari realokasi
    let mut cache: HashMap<u64, String> = HashMap::with_capacity(1000);
    println!("Kapasitas awal: {}", cache.capacity());
}

Mengakses dan Mencari #

use std::collections::HashMap;

fn main() {
    let mut populasi: HashMap<&str, u64> = HashMap::new();
    populasi.insert("Jakarta", 10_500_000);
    populasi.insert("Surabaya", 2_900_000);
    populasi.insert("Bandung", 2_500_000);

    // get() — mengembalikan Option<&V>
    match populasi.get("Jakarta") {
        Some(p) => println!("Jakarta: {} jiwa", p),
        None    => println!("Tidak ditemukan"),
    }

    // get() dengan if let — lebih ringkas
    if let Some(p) = populasi.get("Surabaya") {
        println!("Surabaya: {} jiwa", p);
    }

    // unwrap_or — nilai default jika kunci tidak ada
    let malang = populasi.get("Malang").copied().unwrap_or(0);
    println!("Malang: {} jiwa", malang);

    // contains_key() — cek keberadaan tanpa ambil nilainya
    println!("Ada Jakarta: {}", populasi.contains_key("Jakarta")); // true
    println!("Ada Malang: {}", populasi.contains_key("Malang"));   // false

    // Akses langsung via [] — panic jika kunci tidak ada
    // println!("{}", populasi["Malang"]); // panic!

    // BENAR: gunakan get() untuk akses aman
    let kota = "Bandung";
    if let Some(&p) = populasi.get(kota) {
        println!("{}: {}", kota, p);
    }

    // len() dan is_empty()
    println!("Jumlah kota: {}", populasi.len());
    println!("Kosong: {}", populasi.is_empty());
}

Entry API — Pola Idiomatis untuk Update #

Entry API adalah salah satu fitur paling elegan di HashMap Rust. Ia menangani tiga skenario umum — insert baru, skip jika ada, update berdasarkan nilai lama — dalam satu ekspresi tanpa lookup ganda:

use std::collections::HashMap;

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

    // or_insert — insert jika belum ada, kembalikan referensi mutable ke nilai
    skor.entry(String::from("Tim A")).or_insert(0);
    skor.entry(String::from("Tim A")).or_insert(999); // diabaikan, Tim A sudah ada
    skor.entry(String::from("Tim B")).or_insert(0);

    println!("{:?}", skor);  // {"Tim A": 0, "Tim B": 0}

    // or_insert_with — insert dengan nilai dari closure (lazy evaluation)
    // Closure hanya dipanggil jika kunci belum ada
    skor.entry(String::from("Tim C")).or_insert_with(|| {
        println!("Menghitung skor awal Tim C...");
        50  // nilai awal yang "mahal" dihitung
    });

    // Modifikasi nilai via referensi mutable yang dikembalikan or_insert
    let nilai_a = skor.entry(String::from("Tim A")).or_insert(0);
    *nilai_a += 10;
    println!("{:?}", skor);  // Tim A: 10

    // Pola umum: hitung frekuensi kata
    let teks = "apel mangga apel jeruk mangga apel";
    let mut frekuensi: HashMap<&str, u32> = HashMap::new();

    for kata in teks.split_whitespace() {
        let counter = frekuensi.entry(kata).or_insert(0);
        *counter += 1;
    }

    println!("{:?}", frekuensi);  // {"apel": 3, "mangga": 2, "jeruk": 1}

    // Pola umum: kelompokkan elemen ke dalam Vec
    let data = vec![
        ("kategori-a", "item-1"),
        ("kategori-b", "item-2"),
        ("kategori-a", "item-3"),
        ("kategori-b", "item-4"),
        ("kategori-c", "item-5"),
    ];

    let mut kelompok: HashMap<&str, Vec<&str>> = HashMap::new();
    for (kategori, item) in data {
        kelompok.entry(kategori).or_insert_with(Vec::new).push(item);
    }

    for (k, v) in &kelompok {
        println!("{}: {:?}", k, v);
    }
}

Memperbarui dan Menghapus #

use std::collections::HashMap;

fn main() {
    let mut harga: HashMap<&str, f64> = HashMap::new();
    harga.insert("kopi", 25_000.0);
    harga.insert("teh", 15_000.0);
    harga.insert("jus", 30_000.0);

    // insert() dengan kunci yang sama menimpa nilai lama
    // dan mengembalikan nilai lama yang ditimpa
    let harga_lama = harga.insert("kopi", 28_000.0);
    println!("Harga kopi lama: {:?}", harga_lama);  // Some(25000.0)
    println!("Harga kopi baru: {}", harga["kopi"]);  // 28000.0

    // remove() — hapus dan kembalikan nilai yang dihapus
    let harga_teh = harga.remove("teh");
    println!("Teh dihapus: {:?}", harga_teh);  // Some(15000.0)
    println!("Setelah hapus: {:?}", harga);

    // retain() — pertahankan hanya pasangan yang memenuhi kondisi
    let mut inventori: HashMap<&str, u32> = [
        ("apel", 50), ("mangga", 0), ("jeruk", 30), ("durian", 0),
    ].into_iter().collect();

    inventori.retain(|_, &mut stok| stok > 0);
    println!("Stok tersedia: {:?}", inventori);

    // clear() — hapus semua pasangan
    inventori.clear();
    println!("Setelah clear: {:?}", inventori);
}

Iterasi #

Urutan iterasi HashMap tidak terjamin — ini adalah konsekuensi dari hashing. Jika butuh urutan tertentu, gunakan BTreeMap atau sort hasilnya:

use std::collections::HashMap;

fn main() {
    let mut kota: HashMap<&str, u64> = [
        ("Jakarta", 10_500_000u64),
        ("Surabaya", 2_900_000),
        ("Bandung", 2_500_000),
        ("Medan", 2_100_000),
    ].into_iter().collect();

    // Iterasi kunci-nilai — urutan tidak terjamin
    println!("=== Semua Kota ===");
    for (nama, pop) in &kota {
        println!("{}: {}", nama, pop);
    }

    // Iterasi hanya kunci
    let mut nama_kota: Vec<&&str> = kota.keys().collect();
    nama_kota.sort();
    println!("\nKota (terurut): {:?}", nama_kota);

    // Iterasi hanya nilai
    let total: u64 = kota.values().sum();
    println!("Total populasi: {}", total);

    // Iterasi dengan modifikasi via values_mut()
    for pop in kota.values_mut() {
        *pop = (*pop as f64 * 1.02) as u64;  // naik 2%
    }

    // Iterasi terurut — sort by key
    let mut vec_kota: Vec<(&&str, &u64)> = kota.iter().collect();
    vec_kota.sort_by_key(|&(k, _)| *k);
    println!("\nUrut nama:");
    for (nama, pop) in vec_kota {
        println!("  {}: {}", nama, pop);
    }

    // Sort by value
    let mut vec_pop: Vec<(&&str, &u64)> = kota.iter().collect();
    vec_pop.sort_by(|a, b| b.1.cmp(a.1));  // descending
    println!("\nUrut populasi (besar ke kecil):");
    for (nama, pop) in vec_pop {
        println!("  {}: {}", nama, pop);
    }
}

Ownership di HashMap #

Tipe yang mengimplementasikan Copy (integer, float, bool) di-copy saat di-insert. Tipe owned seperti String di-move:

use std::collections::HashMap;

fn main() {
    let kunci = String::from("nama");
    let nilai = String::from("Budi");

    let mut map = HashMap::new();
    map.insert(kunci, nilai);  // kunci dan nilai di-MOVE ke map

    // ANTI-PATTERN: menggunakan variabel setelah di-move
    // println!("{}", kunci);  // error: kunci sudah di-move
    // println!("{}", nilai);  // error: nilai sudah di-move

    // BENAR: gunakan referensi jika masih butuh variabel asli
    let kunci2 = String::from("usia");
    let nilai2 = 30u32;  // Copy type
    let mut map2: HashMap<&str, u32> = HashMap::new();
    map2.insert(&kunci2, nilai2);   // &kunci2 = borrow
    println!("kunci2 masih valid: {}", kunci2);  // ✓
    println!("nilai2 masih valid: {}", nilai2);  // ✓ karena Copy

    // Referensi sebagai kunci — perlu lifetime yang valid selama HashMap hidup
    let teks = String::from("hello world");
    let mut frekuensi: HashMap<&str, u32> = HashMap::new();
    for kata in teks.split_whitespace() {
        *frekuensi.entry(kata).or_insert(0) += 1;
    }
    println!("{:?}", frekuensi);
    // teks harus hidup setidaknya selama frekuensi
}

Kunci Kustom #

Tipe yang digunakan sebagai kunci HashMap harus mengimplementasikan Hash dan Eq. Cara termudah adalah lewat #[derive]:

use std::collections::HashMap;

// Derive Hash dan Eq — cukup untuk kasus umum
#[derive(Debug, Hash, PartialEq, Eq)]
struct KoordinatGrid {
    x: i32,
    y: i32,
}

// Tipe kustom yang lebih kompleks
#[derive(Debug, Hash, PartialEq, Eq)]
struct IdProduk {
    kategori: String,
    kode: u32,
}

fn main() {
    // HashMap dengan kunci struct
    let mut peta: HashMap<KoordinatGrid, &str> = HashMap::new();
    peta.insert(KoordinatGrid { x: 0, y: 0 }, "Titik asal");
    peta.insert(KoordinatGrid { x: 1, y: 0 }, "Kanan");
    peta.insert(KoordinatGrid { x: 0, y: 1 }, "Atas");

    let posisi = KoordinatGrid { x: 1, y: 0 };
    println!("{:?}", peta.get(&posisi));  // Some("Kanan")

    // HashMap dengan kunci enum — derive Hash dan Eq pada enum
    #[derive(Debug, Hash, PartialEq, Eq)]
    enum Mata {
        Matematika,
        IPA,
        Bahasa(String),
    }

    let mut nilai: HashMap<Mata, u8> = HashMap::new();
    nilai.insert(Mata::Matematika, 90);
    nilai.insert(Mata::IPA, 85);
    nilai.insert(Mata::Bahasa(String::from("Inggris")), 92);

    println!("{:?}", nilai.get(&Mata::Matematika));  // Some(90)
}

BTreeMap — Map Terurut #

BTreeMap<K, V> menyimpan pasangan kunci-nilai dalam struktur B-tree yang selalu terurut berdasarkan kunci. Operasinya O(log n) — lebih lambat dari HashMap yang O(1), tapi menjamin urutan iterasi:

use std::collections::BTreeMap;

fn main() {
    let mut harga: BTreeMap<&str, f64> = BTreeMap::new();
    harga.insert("pisang", 5_000.0);
    harga.insert("apel", 15_000.0);
    harga.insert("mangga", 20_000.0);
    harga.insert("jeruk", 10_000.0);

    // Iterasi selalu terurut berdasarkan kunci (alphanumerik)
    println!("=== Daftar Harga (Terurut Abjad) ===");
    for (nama, harga) in &harga {
        println!("  {:10}: Rp{:.0}", nama, harga);
    }

    // range() — query rentang kunci
    println!("\nBuah antara 'j' dan 'p':");
    for (nama, h) in harga.range("j"..="p") {
        println!("  {}: Rp{:.0}", nama, h);
    }

    // first_key_value() dan last_key_value()
    println!("\nPertama: {:?}", harga.first_key_value());
    println!("Terakhir: {:?}", harga.last_key_value());

    // Kasus nyata: log terurut berdasarkan timestamp
    let mut log: BTreeMap<u64, String> = BTreeMap::new();
    log.insert(1_700_000_100, String::from("Server dimulai"));
    log.insert(1_700_000_050, String::from("Konfigurasi dimuat"));
    log.insert(1_700_000_200, String::from("Koneksi diterima"));

    println!("\n=== Log (Terurut Waktu) ===");
    for (ts, pesan) in &log {
        println!("  {} | {}", ts, pesan);
    }
}

HashSet — Koleksi Kunci Unik Tanpa Nilai #

HashSet<T> adalah HashMap<T, ()> yang disederhanakan — menyimpan nilai unik tanpa data tambahan. Berguna untuk cek keberadaan O(1) dan operasi himpunan:

use std::collections::HashSet;

fn main() {
    let mut set: HashSet<i32> = HashSet::new();
    set.insert(1);
    set.insert(2);
    set.insert(3);
    set.insert(2);  // duplikat — diabaikan
    set.insert(1);  // duplikat — diabaikan
    println!("{:?}", set);  // {1, 2, 3} — urutan tidak terjamin

    // contains() — O(1) cek keberadaan
    println!("Ada 2: {}", set.contains(&2));  // true
    println!("Ada 5: {}", set.contains(&5));  // false

    // Dari Vec — cara mudah menghapus duplikat
    let angka_duplikat = vec![1, 2, 3, 2, 1, 4, 3, 5];
    let unik: HashSet<i32> = angka_duplikat.into_iter().collect();
    println!("Unik: {:?}", unik);

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

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

    // Gabungan (union) — ada di salah satu atau keduanya
    let gabungan: HashSet<_> = a.union(&b).collect();
    println!("Gabungan: {:?}", gabungan);  // {1, 2, 3, 4, 5, 6, 7}

    // Selisih (difference) — ada di a tapi tidak di b
    let selisih: HashSet<_> = a.difference(&b).collect();
    println!("A \\ B: {:?}", selisih);  // {1, 2}

    // Subset dan superset
    let c: HashSet<i32> = [3, 4].into_iter().collect();
    println!("c ⊆ a: {}", c.is_subset(&a));    // true
    println!("a ⊇ c: {}", a.is_superset(&c));  // true
}

Perbandingan HashMap, BTreeMap, dan HashSet #

AspekHashMap<K,V>BTreeMap<K,V>HashSet<T>
KompleksitasO(1) rata-rataO(log n)O(1) rata-rata
Urutan iterasiTidak terjaminTerurut by keyTidak terjamin
Constraint kunciHash + EqOrdHash + Eq
Query rentangTidakYa (.range())Tidak
Penggunaan memoriLebih efisienLebih banyakLebih efisien
Kapan digunakanDefault mapMap urutan pentingCek keanggotaan unik
Gunakan HashMap jika:
  ✓ Kebutuhan map standar — lookup, insert, delete
  ✓ Urutan iterasi tidak penting
  ✓ Performa O(1) yang optimal

Gunakan BTreeMap jika:
  ✓ Perlu iterasi dalam urutan terurut
  ✓ Perlu query rentang kunci (range queries)
  ✓ Kunci harus selalu dalam urutan tertentu

Gunakan HashSet jika:
  ✓ Hanya perlu tahu apakah elemen ada atau tidak
  ✓ Perlu operasi himpunan (union, intersection, difference)
  ✓ Hapus duplikat dari koleksi

Ringkasan #

  • Entry API adalah cara idiomatis untuk update.entry(k).or_insert(v) menangani insert-jika-tidak-ada, .or_insert_with(||) untuk nilai yang mahal dihitung, dan mutable ref yang dikembalikan untuk update berdasarkan nilai lama.
  • HashMap mengambil ownership kunci dan nilaiString yang di-insert tidak bisa digunakan lagi setelah itu. Gunakan &str sebagai kunci jika variabel asli masih dibutuhkan.
  • Urutan iterasi tidak terjamin — jika butuh iterasi terurut, gunakan BTreeMap atau collect ke Vec dan sort.
  • Gunakan get() bukan [] untuk akses amanmap[k] panic jika kunci tidak ada; map.get(k) mengembalikan Option.
  • contains_key() untuk cek keberadaan tanpa mengambil nilai — lebih ekspresif dari get().is_some().
  • retain() untuk filter in-place — lebih efisien dari filter + collect ke HashMap baru.
  • Kunci kustom butuh Hash + Eq — gunakan #[derive(Hash, PartialEq, Eq)] untuk struct atau enum sederhana.
  • BTreeMap untuk urutan terjamin — O(log n) lebih lambat tapi mendukung .range() dan iterasi selalu terurut by key.
  • HashSet untuk keanggotaan unik — O(1) lookup dan mendukung operasi himpunan (union, intersection, difference, subset).

← Sebelumnya: List   Berikutnya: Date & Time →

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