Start learning free

LESSON 254 OF 409 · HASH TABLE

Hash Map in C++: unordered_map Basics

Lesson Read the full explanation

Hash Tables, better known as Hash Maps, are just collections of key-value pairs. In other words, they are pieces of data (values) mapped to unique identifiers called properties (keys).

Hash Maps were designed to give us a way of, given a key, associating a value with it for very quick lookups.

In C++, the Hash Map is implemented as an unordered_map from the Standard Library.


Creation:

You create an empty Hash Map by declaring an unordered_map<keyType, valueType>, where the keyType and valueType are the data types of the keys and values.

Let's declare a Hash Map that maps text values represing digits to their numerical value (e.g. "two" => 2). We wil map strings to integers, so the two types are string and int. We will name the map digitValue

unordered_map<string, int> digitValue;

Creating an empty dictionary takes O(1) time.


Initialization:

You can also add some key-value pairs in your dictionary at creation:

unordered_map<string, int> digitValue({
	{"zero", 0},
	{"one", 1}
});

Accessing values:

A value is retrieved from a map by specifying its corresponding key in square brackets ([]):

unordered_map<string, int> digitValue({
	{"zero", 0},
	{"one", 1}
});

// Accesing  values:
cout << digitValue["zero"]; // prints 0
cout << digitValue["one"]; // prins 1

cout << digitValue["three"]; // prints 0

As you can see, if you refer to a key that is not in the map, C++ returns a default value (0 for numbers, empty string for strings). Also when accesing an non existing value, it also adds it to the map!

Accessing a value from a map takes O(1) time.


Adding an entry:

Adding an entry to an existing map is simply a matter of assigning a new key and value, via the assignment operator:

unordered_map<string, int> digitValue({
	{"zero", 0},
	{"one", 1}
});

// Adding new entries:
digitValue["three"] = 3;

//The new Hash Map:
// {
//	"zero": 0,
//  "one":  1,
//	"three": 3
//}

Adding a new entry to a map takes O(1) time.


Updating an entry:

If you want to update an entry, you can just assign a new value to an existing key:

unordered_map<string, int> digitValue({
	{"zero", 0},
	{"one", 1}
});

// Updating entries:
digitValue["zero"] = 5;

//The new Hash Map:
// {
//	"zero": 5,
//  "one":  1
//}

Updating an entry from a map takes O(1) time.


Deleting entries:

The remove() method allows you to remove a key from a map:

unordered_map<string, int> digitValue({
	{"zero", 0},
	{"one", 1}
});

// Removing an entry
digitValue.erase("zero");

//The new Hash Map:
// {
//  "one":  1
//}

If you try to delete a key that is not in the map, it won't do anything.

Deleting an entry from a map takes O(1) time.


Checking if a key exists:

You can check if a key exists in a map using the count() method:

unordered_map<string, int> digitValue({
	{"zero", 0},
	{"one", 1}
});

cout << digitValue.count("zero"); // prints 1 since the key appears once

cout << digitValue.count("three"); // print 0 since the key does not exist in the map

Checking if a key exists in the map is O(1) time.


Iterating over the map keys:

If we want to iterate over keys of the map, we can use the for loop along with the : operator:

unordered_map<string, int> digitValue({
	{"zero", 0},
	{"one", 1}
});

for (string key : digitValue) {
    cout << key << " is " << digitValue[key] << endl;
}

// This will print:
// one is 1
// zero is 0

Notice the order is not the same as initiated. unordered_map keeps the data in random order

Iterating over a dictionary takes O(n) time.


Iterating using both the key and the value for simplicity:

If we want to iterate over both keys and values of the map, we can use the for loop along with the : operator as follows:

unordered_map<string, int> digitValue({
	{"zero", 0},
	{"one", 1}
});

for (auto [key, val] : digitValue) {
    cout << key << " is " << val << endl;
}

// This will print:
// one is 1
// zero is 0

Notice the order is not the same as initiated. unordered_map keeps the data in random order

Iterating over an unordered_map takes O(n) time.


Assignment
Follow the Coding Tutorial and let's practice with unordered_maps.


Hint
Look at the examples above if you get stuck.


STEP 1 OF 6
    Ctrl + Enter to check

    C++ · HASH TABLE

    Hash Map in C++, explained

    What a hash map is, and what C++ calls it

    A hash map stores key-value pairs and finds the value for a key quickly, without scanning everything. It hashes the key to decide where to look, so a lookup, an insert or a delete takes constant time on average, however many entries the map holds.

    C++ has no type called hashmap or HashMap. The hash map is std::unordered_map, declared in the <unordered_map> header. The older hash_map that some tutorials mention is a pre-standard extension; use unordered_map instead. Its sibling std::map keeps keys sorted, is built on a balanced tree, and takes O(log n) per operation.

    The examples below assume these lines at the top of the file:

    #include <iostream>
    #include <string>
    #include <unordered_map>
    using namespace std;
    

    Declaring and initializing a hash map in C++

    The type takes a key type and a value type. An empty map that maps strings to integers:

    unordered_map<string, int> digitValue;
    

    To start with some entries, pass a brace list of {key, value} pairs. Both of these forms work:

    unordered_map<string, int> a({
        {"zero", 0},
        {"one", 1}
    });
    unordered_map<string, int> b = {{"zero", 0}, {"one", 1}};
    

    Accessing values

    There are three ways to read a value, and they differ in what happens when the key is missing.

    • m[key] returns a reference to the value. If the key is absent, it inserts the key with a default value (0 for numbers, an empty string for strings) and returns that.
    • m.at(key) returns the value, and throws std::out_of_range if the key is absent. It never inserts.
    • m.find(key) returns an iterator, which equals m.end() when the key is absent. The pair is reached with it->first and it->second.
    unordered_map<string, int> ages = {{"Ana", 31}, {"Ben", 27}};
    
    auto it = ages.find("Ben");
    if (it != ages.end()) cout << it->first << " is " << it->second << "\n";   // Ben is 27
    
    cout << ages.size() << "\n";   // 2
    cout << ages["Dan"] << "\n";   // 0
    cout << ages.size() << "\n";   // 3: reading "Dan" added it
    

    The insertion behaviour of [] is the most common source of bugs with maps. A lookup that you meant as a question quietly grows the map. To ask without changing anything, use count, find or at.

    Adding and updating entries

    Assigning through [] adds the key when it is new and overwrites the value when it already exists:

    digitValue["three"] = 3;   // adds
    digitValue["zero"] = 5;    // updates
    digitValue["one"] += 10;   // read and update in one step
    

    insert behaves differently: m.insert({"one", 100}) does nothing if "one" is already there. Use it when you do not want to overwrite.

    Because m[key] starts a missing value at zero, counting is a one-liner: freq[word]++ works whether or not the word was seen before. This is the standard way to build a frequency table.

    Removing entries

    The method is erase, not remove. It returns the number of entries removed, so erasing a missing key is harmless and returns 0:

    digitValue.erase("zero");     // returns 1
    digitValue.erase("missing");  // returns 0, nothing happens
    

    Checking whether a key exists

    count(key) returns 1 if the key is present and 0 otherwise, because a map holds each key at most once. It converts to a boolean, so you can write if (m.count(key)). find does the same check and also gives you the entry. The member function contains exists only from C++20, so it does not compile in C++17.

    Iterating over a hash map

    A range-based for loop visits every entry. Each element is a pair<const Key, Value>, so the loop variable has to be a pair (or auto), not just the key:

    for (const auto &[key, value] : digitValue) {
        cout << key << " is " << value << "\n";
    }
    

    The form [key, value] is a structured binding (C++17). Writing for (string key : digitValue) is a compile error, since a pair cannot convert to a string. To change values while looping, drop the const and bind by reference: for (auto &[k, v] : m) v++;.

    The order of iteration is unspecified. It is not insertion order and it is not sorted, and it can change after inserts. If you need a stable order, use std::map, or copy the keys into a vector and sort them. Iterating takes O(n).

    Time complexity

    OperationAverageWorst case
    Insert, update, [], at, find, count, eraseO(1)O(n)
    Iterate over all entriesO(n)O(n)

    The worst case happens when many keys land in the same bucket, which a good hash function makes unlikely. The standard library provides hash functions for integers, strings and other basic types. A pair or a struct as a key does not work out of the box: unordered_map<pair<int,int>, int> fails to compile until you supply your own hash function. Use std::map for such keys when that is simpler.

    The exercise, step by step

    The lesson tracks a fruit inventory in an unordered_map<string, int> named fruits.

    1. Create the map with three entries: apples 3, bananas 5, grapes 2, using the brace list.
    2. Add 4 apples by assigning fruits["apples"] its current value plus 4. It becomes 7.
    3. Report a missing fruit. Print "Out of pears" when fruits.count("pears") is 0. Testing with fruits["pears"] would insert pears with a count of 0.
    4. Remove the bananas with erase.
    5. Print the inventory with a range-based loop and a structured binding.
    unordered_map<string, int> fruits({
        {"apples", 3},
        {"bananas", 5},
        {"grapes", 2}
    });
    
    
    fruits["apples"] = fruits["apples"] + 4;
    
    if (!fruits.count("pears")) {
       cout << "Out of pears" << endl;
    }
    
    fruits.erase("bananas");
    
    for (const auto &[fruit, counter] : fruits) {
        cout << "We have " << counter << " "<< fruit << "." << endl;
    }
    

    The output is "Out of pears", then the two remaining lines, "We have 7 apples." and "We have 2 grapes.", in either order, because iteration order is unspecified.

    Key takeaways

    • The C++ hash map is unordered_map; include <unordered_map>.
    • Declare it as unordered_map<KeyType, ValueType> and fill it with a brace list or with m[key] = value.
    • m[key] inserts a missing key. Use count, find or at to look without inserting.
    • Remove with erase, and iterate with for (const auto &[k, v] : m); the order is not defined.
    • Operations are O(1) on average.
    Keep going with C++About 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free