Kalau urutan tidak penting
Kamu baru saja melihat std::map, yang menjaga key-nya tetap terurut memakai balanced tree dan memberi operasi O(log n). Tapi mengurutkan itu ada biayanya, dan sering kali kamu tidak peduli dalam urutan apa key-nya keluar. Kamu hanya ingin bertanya "apakah key ini ada, dan berapa nilainya?" secepat mungkin.
Untuk itulah std::unordered_map ada. Ia adalah hash table: setiap key dimasukkan ke fungsi hash untuk menentukan di mana key itu disimpan, sehingga penambahan, pencarian, dan penghapusan rata-rata O(1), bukan O(log n). Gantinya, urutan iterasinya tidak ditentukan: key-key keluar dalam urutan apa pun yang kebetulan dimiliki bucket-nya.
Antarmukanya sengaja dibuat hampir sama dengan map, jadi sering kali kamu bisa menukar keduanya hanya dengan mengubah tipenya. Sertakan <unordered_map> (bukan <map>), dan key-nya akan keluar tidak terurut.
Menambah dan memperbarui
Ada beberapa cara memasukkan entri, dan tidak semuanya berperilaku sama. Dua yang paling sering kamu pakai adalah operator[] dan insert:
Perbedaan utamanya: operator[] menimpa nilai yang sudah ada, sedangkan insert membiarkan key yang sudah ada tanpa perubahan. Kalau kamu memakai C++17, insert_or_assign(key, value) memberimu perilaku "isi nilai ini apa pun yang terjadi", dan try_emplace(key, args...) membuat nilai di tempatnya hanya kalau key-nya baru, berguna untuk nilai yang mahal dibuat.
Jebakan operator[]: menambahkan saat membaca
Ini bug unordered_map yang paling umum, jadi ia mendapat bagiannya sendiri. m[key] bukan sekadar membaca. Kalau key-nya tidak ada, ia membuat nilai default dan menambahkannya (int menjadi 0, string menjadi ""), lalu mengembalikan reference. Jadi kode yang kelihatannya hanya mencari diam-diam memperbesar map:
seen["y"] membuat entri "y" -> 0 hanya karena disebut. Untuk mengecek keberadaan tanpa mengubah map, pakai count (mengembalikan 0 atau 1) atau find:
Aturan praktisnya: pakai [] hanya kalau kamu memang ingin membuat atau memperbarui. Untuk membaca saja, pakai count, contains (C++20), atau find.
find dan at: pencarian yang aman
find mengembalikan iterator ke entrinya, atau end() kalau key-nya tidak ada. Ia tidak pernah menambahkan apa pun, dan memungkinkan kamu menjangkau key sekaligus nilainya lewat it->first dan it->second dalam satu pencarian:
Kalau kamu tahu key-nya seharusnya ada dan ingin program gagal dengan tegas kalau ternyata tidak, pakai at. Berbeda dengan [], at tidak menambahkan apa pun: ia melempar std::out_of_range untuk key yang tidak ada:
int p = prices.at("pen"); // fine
int q = prices.at("hat"); // throws std::out_of_range - key absent
Jadi kamu punya tiga gaya pencarian: [] (menambahkan), at (melempar exception), dan find/count (melaporkan keberadaan tanpa menyentuh map). Pilih yang cara gagalnya sesuai dengan maksudmu.
Iterasi dan penghapusan
Range-based for loop dengan structured binding adalah cara rapi untuk menelusuri setiap entri. Ingat bahwa urutannya acak, jadi jangan pernah mengandalkannya:
Untuk menghapus entri, erase langsung menerima sebuah key dan mengembalikan berapa yang dihapus (0 atau 1). Ada satu jebakan penting: menghapus sambil melakukan iterasi pada unordered_map hanya membuat iterator elemen yang dihapus menjadi tidak valid, jadi pakai nilai kembalian erase(it) untuk maju dengan aman:
Menulis gaya wins.erase(it++) atau ++it setelah erase(it) biasa adalah jebakan klasik dangling iterator: iterator yang dihapus sudah mati, jadi selalu ambil iterator yang dikembalikan erase.
map atau unordered_map?
Keduanya menyimpan pasangan key-value dengan API yang mirip, jadi pilihannya bergantung pada apa yang kamu butuhkan:
// std::map -> sorted keys, O(log n), range queries (lower_bound)
// std::unordered_map -> no order, O(1) avg, fastest plain lookup
Pakai unordered_map kalau kamu hanya butuh pencarian cepat berdasarkan key dan urutan tidak relevan (menghitung frekuensi kata, caching, menghapus duplikat). Pilih map kalau kamu butuh key dalam urutan terurut, ingin iterasi berurutan, atau melakukan query rentang. Ada dua catatan untuk unordered_map: O(1)-nya adalah rata-rata, dan hash yang buruk bisa menurunkannya; selain itu, tipe key buatan sendiri butuh spesialisasi hash atau hash functor, sedangkan map hanya butuh operator<.
Berikutnya: set
Sekarang kamu sudah melihat kedua jenis container key-value. Tapi kadang kamu sama sekali tidak butuh nilai, kamu hanya peduli apakah sesuatu ada, seperti kumpulan tag unik atau ID yang sudah dikunjungi. Berikutnya kita akan membahas std::set (dan saudaranya yang berbasis hash, unordered_set), yang hanya menyimpan key dan otomatis menjaganya tetap unik.
Pertanyaan yang sering diajukan
Apa perbedaan map dan unordered_map di C++?
std::map adalah balanced binary tree: key-nya tetap terurut dan operasinya O(log n). std::unordered_map adalah hash table: key-nya tidak dalam urutan tertentu, tapi penambahan dan pencariannya rata-rata O(1). Pakai unordered_map kalau kamu hanya butuh pencarian key yang cepat dan tidak peduli urutan; pakai map kalau kamu butuh iterasi terurut atau query rentang.
Apakah unordered_map[] menambahkan key kalau key itu belum ada?
Ya. m[key] membuat nilai default dan menambahkan key itu kalau belum ada, lalu mengembalikan reference ke nilainya. Artinya, bahkan if (m[key] == ...) yang kelihatannya hanya membaca pun diam-diam memperbesar map. Untuk mengecek keberadaan tanpa menambahkan, pakai m.count(key) atau m.find(key).
Apakah unordered_map selalu lebih cepat daripada map di C++?
Tidak. Ia O(1) secara rata-rata, tapi hashing punya biaya tambahan, dan hash yang buruk (atau key yang sengaja dibuat bermasalah) bisa menurunkan pencarian menjadi O(n). Untuk map kecil, faktor konstanta dan perilaku cache yang lebih buruk bisa membuat map yang terurut sama cepat atau bahkan lebih cepat. Lakukan benchmark kalau hal ini penting, tapi pakai unordered_map sebagai default kalau kamu hanya butuh pencarian key.