std::unordered_map<K, V> adalah sahabat hash dari std::map: pasangan kunci-nilai yang sama, tetapi disimpan berdasarkan hash kunci sehingga pencarian, penyisipan, dan penghapusan berlangsung O(1) rata-rata. Harga yang dibayar juga sama dengan unordered_set: urutan iterasi tidak ditentukan standar, sehingga output apa pun yang menampilkan isi unordered_map harus dibuat deterministik secara sadar — pola “salin kunci, urutkan, cetak” dari chapter sebelumnya kini menjadi kebiasaan kerja kita. Chapter ini membangun program penghitung frekuensi kata, alat klasik yang menunjukkan mengapa unordered_map begitu sering dipakai.

Kasus Klasik: Menghitung Frekuensi Kata #

Kombinasi unordered_map<std::string, int> dengan operator[] adalah idiom penghitung paling ringkas di C++: cukup ++peta[kata], dan entri baru otomatis mulai dari 0.

cpp
#include <algorithm>     // std::sort: urutkan salinan kunci agar output deterministik
#include <iostream>      // std::cout
#include <string>        // std::string
#include <unordered_map> // std::unordered_map
#include <vector>        // std::vector untuk salinan kunci

int main() {
    std::string kalimat = "kopi teh kopi gula teh kopi";
    std::unordered_map<std::string, int> frekuensi;

    // potong kalimat per kata, lalu hitung dengan operator[]
    std::size_t mulai = 0;
    while (mulai < kalimat.size()) {
        std::size_t spasi = kalimat.find(' ', mulai);
        if (spasi == std::string::npos) spasi = kalimat.size();
        ++frekuensi[kalimat.substr(mulai, spasi - mulai)];
        mulai = spasi + 1;
    }

    std::cout << "kopi ada?     : " << (frekuensi.contains("kopi") ? "ya" : "tidak") << "\n";
    std::cout << "susu ada?     : " << (frekuensi.contains("susu") ? "ya" : "tidak") << "\n";
    std::cout << "count(gula)   : " << frekuensi.count("gula") << "\n";

    // emplace: hanya menambah bila kunci BELUM ada
    auto hasil = frekuensi.emplace("susu", 1);
    std::cout << "emplace susu  : berhasil=" << (hasil.second ? "ya" : "tidak") << "\n";

    // insert_or_assign: menambah BILA belum ada, MENIMPA bila sudah
    frekuensi.insert_or_assign("teh", 99);
    frekuensi.insert_or_assign("roti", 4);

    // pola cetak deterministik: salin kunci, urutkan, akses nilainya lewat at()
    std::vector<std::string> kunci;
    for (const auto& [k, v] : frekuensi) kunci.push_back(k);
    std::sort(kunci.begin(), kunci.end());

    std::cout << "Frekuensi (terurut menurut kunci):\n";
    for (const std::string& k : kunci) {
        std::cout << "  " << k << " : " << frekuensi.at(k) << "\n";
    }

    return 0;
}

Output yang Dihasilkan:

text
kopi ada?     : ya
susu ada?     : tidak
count(gula)   : 1
emplace susu  : berhasil=ya
Frekuensi (terurut menurut kunci):
  gula : 1
  kopi : 3
  roti : 4
  susu : 1
  teh : 99

Mari kita bedah:

  1. Baris 13-18 (memotong kata): kita mencari spasi berikutnya dengan find, mengambil potongan dengan substr, lalu ++frekuensi[...] menambah hitungannya. operator[] inilah jagoannya: entri untuk kata baru dibuat bernilai 0 tepat sebelum dinaikkan.
  2. Baris 20-21 (contains): sejak C++20, contains menjadi cara paling jelas menanyakan kunci. Alternatif klasik yang tetap sah: count(k) > 0 atau find(k) != end().
  3. Baris 22 (count): pada map (termasuk versi unordered), count hanya menghasilkan 0 atau 1 karena kunci unik.
  4. Baris 25-26 (emplace): emplace("susu", 1) hanya bekerja bila kunci belum ada; kembalian pair<iterator, bool> melapor berhasil=ya karena "susu" memang belum terdaftar. Bila kunci sudah ada, emplace tidak menimpa — mirip insert pada std::map.
  5. Baris 29-30 (insert_or_assign): fungsi ini menimpa bila kunci sudah ada dan menambah bila belum — semantik yang paling sering kita maksud saat mengisi data: teh diperbarui menjadi 99, roti ditambahkan.
  6. Baris 33-40 (output deterministik): kita menyalin seluruh kunci ke vector, mengurutkannya dengan std::sort, lalu membaca nilainya kembali lewat at(). Dua baris pertama loop mengabaikan nilai v karena hanya kuncinya yang dibutuhkan. Hasil cetak selalu terurut gula, kopi, roti, susu, teh di mesin mana pun — padahal tata letak internalnya tidak.

Kapan unordered_map, Kapan map #

Keduanya menampung pasangan kunci-nilai unik, jadi pertimbangannya menumpuk pada pola akses:

Aspekstd::mapstd::unordered_map
Akses menurut kunciO(log n)O(1) rata-rata, O(n) terburuk
Iterasiterurut menurut kunciurutan tidak ditentukan
Butuh operator< pada kunciyabutuh std::hash (tersedia untuk tipe bawaan)
Lookup beruntun bervolume besarbaikpaling cepat
Butuh output/pembacaan terurutlangsungperlu salin-urutkan

Aturan praktis: untuk kebutuhan “cari dan simpan secepat mungkin” — cache, indeks ID, penghitung — pilih unordered_map. Begitu hasil harus dibaca urut (laporan, daftar alfabetis) atau butuh pencarian rentang, pilih map. Keduanya bisa hidup berdampingan dalam satu program; biayanya satu salinan data ketika kita perlu berpindah pandangan.

Keluarga container asosiatif kini lengkap di tangan kita. Chapter berikutnya beralih dari container ke tipe utilitas kecil yang sering menjadi kembalian fungsi: std::pair dan std::tuple, pembungkus beberapa nilai dalam satu objek.

Rujukan resmi: cppreference — std::unordered_map , cppreference — unordered_map::insert_or_assign , cppreference — unordered_map::contains .