std::unordered_map


C++ std::unordered_map

std::unordered_map is an associative container in the C++ Standard Template Library (STL) that stores key-value pairs in no particular order. It uses a hash table for its internal implementation, providing average constant-time complexity for insertion, deletion, and search operations.

Key Characteristics

Example 1: Basic Usage

#include <iostream>
#include <unordered_map>
#include <string>

int main() {
    std::unordered_map<std::string, int> ages;

    // Inserting elements
    ages["Alice"] = 30;
    ages.insert({"Bob", 25});
    ages.emplace("Charlie", 35);

    // Accessing elements
    std::cout << "Alice's age: " << ages["Alice"] << std::endl;

    // Checking if a key exists
    if (ages.find("David") == ages.end()) {
        std::cout << "David is not in the map" << std::endl;
    }

    // Iterating through the map
    for (const auto& pair : ages) {
        std::cout << pair.first << " is " << pair.second << " years old" << std::endl;
    }

    // Size of the map
    std::cout << "Number of entries: " << ages.size() << std::endl;

    // Removing an element
    ages.erase("Bob");

    return 0;
}

Explanation:

Example 2: Custom Hash Function and Key Equality

#include <iostream>
#include <unordered_map>
#include <string>

struct Person {
    std::string name;
    int age;

    bool operator==(const Person& other) const {
        return name == other.name && age == other.age;
    }
};

// Custom hash function for Person
struct PersonHash {
    std::size_t operator()(const Person& p) const {
        return std::hash<std::string>()(p.name) ^ std::hash<int>()(p.age);
    }
};

int main() {
    std::unordered_map<Person, std::string, PersonHash> personDescriptions;

    personDescriptions[{"Alice", 30}] = "Software Engineer";
    personDescriptions[{"Bob", 25}] = "Data Analyst";
    personDescriptions[{"Charlie", 35}] = "Project Manager";

    for (const auto& pair : personDescriptions) {
        std::cout << pair.first.name << " (Age " << pair.first.age << "): " 
                  << pair.second << std::endl;
    }

    return 0;
}

Explanation:

Example 3: Performance Comparison with std::map

#include <iostream>
#include <unordered_map>
#include <map>
#include <chrono>
#include <random>
#include <string>

// Function to generate random strings
std::string random_string(std::size_t length) {
    const std::string characters = "abcdefghijklmnopqrstuvwxyz";
    std::random_device rd;
    std::mt19937 generator(rd());
    std::uniform_int_distribution<> distribution(0, characters.size() - 1);

    std::string result;
    for (std::size_t i = 0; i < length; ++i) {
        result += characters[distribution(generator)];
    }
    return result;
}

// Function to measure execution time
template<typename Func>
long long measureTime(Func func) {
    auto start = std::chrono::high_resolution_clock::now();
    func();
    auto end = std::chrono::high_resolution_clock::now();
    return std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
}

int main() {
    const int NUM_ELEMENTS = 1000000;
    const int NUM_SEARCHES = 1000000;

    std::unordered_map<std::string, int> umap;
    std::map<std::string, int> map;

    std::vector<std::string> keys;
    for (int i = 0; i < NUM_ELEMENTS; ++i) {
        keys.push_back(random_string(10));
    }

    // Insertion
    auto insertUnordered = [&]() {
        for (const auto& key : keys) {
            umap[key] = 1;
        }
    };
    auto insertOrdered = [&]() {
        for (const auto& key : keys) {
            map[key] = 1;
        }
    };

    std::cout << "Insertion time (microseconds):" << std::endl;
    std::cout << "unordered_map: " << measureTime(insertUnordered) << std::endl;
    std::cout << "map: " << measureTime(insertOrdered) << std::endl;

    // Search
    auto searchUnordered = [&]() {
        for (int i = 0; i < NUM_SEARCHES; ++i) {
            umap.find(keys[i % NUM_ELEMENTS]);
        }
    };
    auto searchOrdered = [&]() {
        for (int i = 0; i < NUM_SEARCHES; ++i) {
            map.find(keys[i % NUM_ELEMENTS]);
        }
    };

    std::cout << "Search time (microseconds):" << std::endl;
    std::cout << "unordered_map: " << measureTime(searchUnordered) << std::endl;
    std::cout << "map: " << measureTime(searchOrdered) << std::endl;

    return 0;
}

Explanation:

Example 4: Bucket Interface and Load Factor

#include <iostream>
#include <unordered_map>
#include <string>

int main() {
    std::unordered_map<std::string, int> wordCount = {
        {"apple", 3}, {"banana", 2}, {"cherry", 5},
        {"date", 1}, {"elderberry", 4}, {"fig", 3}
    };

    // Print bucket information
    std::cout << "Bucket count: " << wordCount.bucket_count() << std::endl;
    std::cout << "Max bucket count: " << wordCount.max_bucket_count() << std::endl;
    std::cout << "Load factor: " << wordCount.load_factor() << std::endl;
    std::cout << "Max load factor: " << wordCount.max_load_factor() << std::endl;

    // Print contents of each bucket
    for (size_t i = 0; i < wordCount.bucket_count(); ++i) {
        std::cout << "Bucket " << i << " contains:";
        for (auto it = wordCount.begin(i); it != wordCount.end(i); ++it) {
            std::cout << " " << it->first << "(" << it->second << ")";
        }
        std::cout << std::endl;
    }

    // Find which bucket an element is in
    std::string search = "cherry";
    size_t bucket = wordCount.bucket(search);
    std::cout << "'" << search << "' is in bucket " << bucket << std::endl;

    // Rehash the container
    std::cout << "\nRehashing to 20 buckets..." << std::endl;
    wordCount.rehash(20);
    std::cout << "New bucket count: " << wordCount.bucket_count() << std::endl;

    return 0;
}

Explanation:

Additional Considerations

  1. Iterator Stability: Iterators and references to elements in an unordered_map remain valid after insertion or deletion of other elements.

  2. Rehashing: When the load factor exceeds the max load factor, the container automatically increases the number of buckets and rehashes the elements.

  3. Custom Types: When using custom types as keys, you need to provide a hash function and an equality comparison function.

  4. No Duplicates: unordered_map automatically handles duplicate keys by not inserting them.

  5. Memory Usage: unordered_map typically uses more memory than map due to its hash table structure, but this trade-off allows for faster access times.

Summary

std::unordered_map is a powerful container in C++ for storing key-value pairs with fast access times. Key points to remember:

std::unordered_map is ideal for scenarios where fast lookup and uniqueness of keys are required, and the order of elements is not important. It's commonly used in situations like: - Implementing caches - Counting occurrences of elements - Fast key-value lookups in large datasets - Implementing certain algorithms that require quick element access

Understanding when to use std::unordered_map versus other containers like std::map or std::vector is crucial for writing efficient and clear C++ code in various application domains. Its constant-time average complexity for key operations makes it a go-to choice for many performance-critical applications.

Related

Previous Page | Course Schedule | Course Content