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.
#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:
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 : 99Mari kita bedah:
- Baris 13-18 (memotong kata): kita mencari spasi berikutnya dengan
find, mengambil potongan dengansubstr, lalu++frekuensi[...]menambah hitungannya.operator[]inilah jagoannya: entri untuk kata baru dibuat bernilai0tepat sebelum dinaikkan. - Baris 20-21 (
contains): sejak C++20,containsmenjadi cara paling jelas menanyakan kunci. Alternatif klasik yang tetap sah:count(k) > 0ataufind(k) != end(). - Baris 22 (
count): pada map (termasuk versi unordered),counthanya menghasilkan0atau1karena kunci unik. - Baris 25-26 (
emplace):emplace("susu", 1)hanya bekerja bila kunci belum ada; kembalianpair<iterator, bool>melaporberhasil=yakarena"susu"memang belum terdaftar. Bila kunci sudah ada,emplacetidak menimpa — miripinsertpadastd::map. - 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:tehdiperbarui menjadi99,rotiditambahkan. - Baris 33-40 (output deterministik): kita menyalin seluruh kunci ke vector, mengurutkannya dengan
std::sort, lalu membaca nilainya kembali lewatat(). Dua baris pertama loop mengabaikan nilaivkarena hanya kuncinya yang dibutuhkan. Hasil cetak selalu terurutgula, kopi, roti, susu, tehdi 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:
| Aspek | std::map | std::unordered_map |
|---|---|---|
| Akses menurut kunci | O(log n) | O(1) rata-rata, O(n) terburuk |
| Iterasi | terurut menurut kunci | urutan tidak ditentukan |
| Butuh operator< pada kunci | ya | butuh std::hash (tersedia untuk tipe bawaan) |
| Lookup beruntun bervolume besar | baik | paling cepat |
| Butuh output/pembacaan terurut | langsung | perlu 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 .