C++ course menu
C++ unordered_map and unordered_set: Hash Tables for Fast Lookup
Learn std::unordered_map and std::unordered_set: O(1) average lookups with hashing, inserting, finding and erasing, counting frequencies, detecting duplicates, the two-sum problem, hashing your own types, and when to choose them over map and set.
On this page
std::unordered_map and std::unordered_set are hash tables. They give up keeping elements in order, and in exchange they find, insert and erase elements in constant time on average, no matter how many elements they hold. They are the go-to containers for "look this up fast" and "have I seen this before?".
How hashing works (briefly)#
A hash function turns a key into a number. That number picks a bucket where the element is stored. To find a key, the container hashes it again and looks only in that bucket, instead of searching every element. Occasionally two keys land in the same bucket (a collision); the container handles that, and grows its bucket array as it fills up to keep collisions rare.
unordered_map#
It has almost the same interface as std::map:
#include <iostream>
#include <string>
#include <unordered_map>
int main() {
std::unordered_map<std::string, int> stock = {{"pens", 40}, {"books", 12}};
stock["bags"] = 5; // insert
stock["pens"] -= 10; // update
if (auto it = stock.find("books"); it != stock.end()) {
std::cout << "books: " << it->second << '\n';
}
std::cout << std::boolalpha << stock.contains("caps") << '\n';
std::cout << "pens: " << stock.at("pens") << ", items: " << stock.size() << '\n';
}books: 12
false
pens: 30, items: 3The same warning applies as with std::map: stock["caps"] would insert "caps". Use find, contains or at to look up.
No order#
Iterating over an unordered container visits elements in an unspecified order, which can change between compilers, runs or after insertions. If you need sorted output, copy the elements into a vector and sort it, or use std::map:
#include <algorithm>
#include <iostream>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
int main() {
std::unordered_map<std::string, int> votes = {{"Ravi", 12}, {"Asha", 30}, {"Meera", 21}};
std::vector<std::pair<std::string, int>> ranking(votes.begin(), votes.end());
std::sort(ranking.begin(), ranking.end(),
[](const auto& a, const auto& b) { return a.second > b.second; });
for (const auto& [name, count] : ranking) {
std::cout << name << ' ' << count << '\n';
}
}Asha 30
Meera 21
Ravi 12Counting frequencies#
Counting is the classic use. Each ++counts[x] is O(1) on average, so counting a million items is fast:
#include <iostream>
#include <string>
#include <unordered_map>
int main() {
std::string text = "mississippi";
std::unordered_map<char, int> counts;
for (char c : text) {
++counts[c];
}
std::cout << "s appears " << counts['s'] << " times, p appears " << counts['p'] << " times\n";
}s appears 4 times, p appears 2 timesunordered_set: fast membership#
An unordered_set stores unique values. insert returns whether the value was new, which makes duplicate detection a one-liner:
#include <iostream>
#include <unordered_set>
#include <vector>
int main() {
std::vector<int> ids = {104, 221, 357, 221, 489, 104};
std::unordered_set<int> seen;
for (int id : ids) {
if (!seen.insert(id).second) {
std::cout << "duplicate: " << id << '\n';
}
}
std::cout << seen.size() << " unique ids\n";
}duplicate: 221
duplicate: 104
4 unique idsA classic: two-sum#
Given a list of numbers and a target, find two numbers that add up to the target. Checking every pair is O(n²). With a hash map from value to index, one pass is enough:
#include <iostream>
#include <unordered_map>
#include <utility>
#include <vector>
std::pair<int, int> two_sum(const std::vector<int>& nums, int target) {
std::unordered_map<int, int> index_of; // value -> index
for (int i = 0; i < static_cast<int>(nums.size()); ++i) {
int need = target - nums[i];
if (auto it = index_of.find(need); it != index_of.end()) {
return {it->second, i};
}
index_of[nums[i]] = i;
}
return {-1, -1};
}
int main() {
auto [i, j] = two_sum({2, 7, 11, 15}, 9);
std::cout << i << ' ' << j << '\n';
auto [a, b] = two_sum({3, 2, 4}, 6);
std::cout << a << ' ' << b << '\n';
}0 1
1 2This "remember what you have seen in a hash table" pattern solves a large number of interview problems in linear time.
Using your own type as a key#
Built-in types and std::string already have hash functions. For your own type, provide == and a hash. One way is a small hash struct:
#include <functional>
#include <iostream>
#include <unordered_set>
struct Point {
int x = 0;
int y = 0;
bool operator==(const Point&) const = default;
};
struct PointHash {
std::size_t operator()(const Point& p) const {
std::size_t hx = std::hash<int>{}(p.x);
std::size_t hy = std::hash<int>{}(p.y);
return hx ^ (hy << 1); // combine the two hashes
}
};
int main() {
std::unordered_set<Point, PointHash> visited;
visited.insert({0, 0});
visited.insert({1, 2});
visited.insert({0, 0}); // already there
std::cout << visited.size() << ' ' << std::boolalpha << visited.contains({1, 2}) << '\n';
}2 trueA hash only has to be consistent with == (equal objects must have equal hashes); a good one also spreads values out evenly.
Performance notes#
- Lookups are O(1) on average. In the rare worst case (many keys in one bucket) they become O(n).
reserve(n)sets up enough buckets fornelements in advance, avoiding repeated rehashing when you know the size.- For very small collections, a sorted
std::vectoror a plainstd::mapcan be just as fast.
Common mistakes#
Practice#
Practice exercise
Write char first_unique(const std::string& s) that returns the first character that appears exactly once, or '_' if there is none. Test it with "swiss" and "aabb".
Show solution
#include <iostream>
#include <string>
#include <unordered_map>
char first_unique(const std::string& s) {
std::unordered_map<char, int> counts;
for (char c : s) ++counts[c];
for (char c : s) {
if (counts[c] == 1) return c;
}
return '_';
}
int main() {
std::cout << first_unique("swiss") << ' ' << first_unique("aabb") << '\n';
}w _The second loop walks the string, not the map, so "first" means the first in the original order.
Key takeaway#
Finished this lesson?