An article to familiarize you with unordered_map and its simulation implementation

Insert image description here

Definition of unordered_map

The implementation of hash tables in the C++ standard library has a history. In the C++98/03 standard, there is no formal definition of a standard hash table container. However, many C++ standard library implementations (such as STLPort, SGI STL, etc.) provide extension containers such hash_mapas and hash_set, which provide hash table functions.

std::unordered_mapWith the introduction of the C++11 standard, hash table containers such as and are officially introduced std::unordered_setas part of the C++ standard library. These containers are standardized in the C++11 standard and provide a standard interface and syntax so that developers can more easily port code between different C++ standard library implementations.

Therefore, although the hash table container existed before the C++98/03 standard, it underwent some important improvements and standardization in the C++11 standard, became part of the C++ standard library, and was therefore more widely used. to use.

1. Template definition of unordered_map

template < class Key,                                    // unordered_map::key_type
           class T,                                      // unordered_map::mapped_type
           class Hash = hash<Key>,                       // unordered_map::hasher
           class Pred = equal_to<Key>,                   // unordered_map::key_equal
           class Alloc = allocator< pair<const Key,T> >  // unordered_map::allocator_type
           > class unordered_map;

The template definition in C++ std::unordered_mapis an associative container (Associative Container) in the C++ standard library, which is used to implement a hash table of key-value pairs. Here's an explanation of the template parameters:

  1. Key : Indicates the key type in the hash table, which is the type used to find the value.
  2. T : Represents the value type in the hash table, that is, the type of value associated with the key.
  3. Hash : Represents the type of hash function used to calculate the hash value of a key. By default, uses std::hash<Key>as the hash function.
  4. Pred : A predicate representing a key comparison, used to determine whether two keys are equal. By default, uses std::equal_to<Key>as key comparison predicate.
  5. Alloc : Represents the allocator type, used to allocate memory. By default, uses std::allocator<std::pair<const Key, T>>as the allocator type.

std::unordered_mapIs a container for storing key-value pairs that uses a hash table for fast lookups and insertions. By providing these template parameters, you can customize keys, value types, hash functions, key comparison methods, and memory allocation methods to meet different application needs.

For example, you can create an std::unordered_mapinstance that takes strings as keys, integers as values, and uses custom hash functions and key comparisons. Here's an example:

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

// 自定义哈希函数
struct MyHash {
    
    
    size_t operator()(const std::string& key) const {
    
    
        // 简单示例:将字符串的长度作为哈希值
        return key.length();
    }
};

// 自定义键比较谓词
struct MyEqual {
    
    
    bool operator()(const std::string& lhs, const std::string& rhs) const {
    
    
        // 比较字符串是否相等
        return lhs == rhs;
    }
};

int main() {
    
    
    std::unordered_map<std::string, int, MyHash, MyEqual> myMap;
    myMap["apple"] = 5;
    myMap["banana"] = 3;

    std::cout << "apple: " << myMap["apple"] << std::endl;
    std::cout << "banana: " << myMap["banana"] << std::endl;

    return 0;
}

In this example, we create a custom hash function MyHashand key comparison predicate MyEqualand use them std::unordered_mapin the template parameters of to customize how keys are hashed and compared. This allows us to calculate a hash based on the length of the string and check if the strings are equal.

2. Member types of unordered_map

The following aliases are member types of unordered_map. They are widely used as parameter and return types by member functions

Please add image description

unordered_map constructor

empty (1)	
explicit unordered_map ( size_type n = /* see below */,
                         const hasher& hf = hasher(),
                         const key_equal& eql = key_equal(),
                         const allocator_type& alloc = allocator_type() );
explicit unordered_map ( const allocator_type& alloc );
range (2)	
template <class InputIterator>
unordered_map ( InputIterator first, InputIterator last,
                size_type n = /* see below */,
                const hasher& hf = hasher(),
                const key_equal& eql = key_equal(),
                const allocator_type& alloc = allocator_type() );
copy (3)	
unordered_map ( const unordered_map& ump );
unordered_map ( const unordered_map& ump, const allocator_type& alloc );
move (4)	
unordered_map ( unordered_map&& ump );
unordered_map ( unordered_map&& ump, const allocator_type& alloc );
initializer list (5)	
unordered_map ( initializer_list<value_type> il,
                size_type n = /* see below */,
                const hasher& hf = hasher(),
                const key_equal& eql = key_equal(),
                const allocator_type& alloc = allocator_type() );
  1. Default constructor(1) :
    • explicit unordered_map ( size_type n = /* see below */, const hasher& hf = hasher(), const key_equal& eql = key_equal(), const allocator_type& alloc = allocator_type() );
    • Creates an empty one unordered_map, optionally specifying the container's initial bucket number n, hash function hf, key comparison predicate eql, and allocator alloc.
  2. Using the allocator's constructor(1) :
    • explicit unordered_map ( const allocator_type& alloc );
    • Creates an empty one unordered_map, using the specified allocator alloc.
  3. Range constructor(2) :
    • template <class InputIterator> unordered_map ( InputIterator first, InputIterator last, size_type n = /* see below */, const hasher& hf = hasher(), const key_equal& eql = key_equal(), const allocator_type& alloc = allocator_type() );
    • [first, last)Initialized by elements in the iterator range unordered_map, optionally specifying an initial number of buckets n, a hash function hf, a key comparison predicate eql, and an allocator alloc.
  4. Copy constructor(3) :
    • unordered_map ( const unordered_map& ump );
    • Create a new one unordered_mapand unordered_map umpcopy-construct it using the contents of another.
    • unordered_map ( const unordered_map& ump, const allocator_type& alloc );
    • Creates a new one unordered_mapand unordered_map umpcopy-constructs it using the contents of another, specifying the allocator alloc.
  5. Move constructor(4) :
    • unordered_map ( unordered_map&& ump );
    • Create a new one unordered_mapand unordered_map umpmove content from another.
    • unordered_map ( unordered_map&& ump, const allocator_type& alloc );
    • Create a new one unordered_mapand unordered_map umpmove content from another one, specifying the allocator alloc.
  6. Initialization list constructor(5) :
    • unordered_map ( initializer_list<value_type> il, size_type n = /* see below */, const hasher& hf = hasher(), const key_equal& eql = key_equal(), const allocator_type& alloc = allocator_type() );
    • ilInitialized using the elements in the initialization list unordered_map, optionally specifying the initial number of buckets n, hash function hf, key comparison predicate eql, and allocator alloc.

Here are std::unordered_mapsome examples of how to use the constructor:

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

int main() {
    
    
    // 示例 1: 默认构造函数
    std::unordered_map<int, std::string> myMap;  // 创建一个空的 unordered_map

    // 示例 2: 范围构造函数
    std::unordered_map<char, int> charCount;
    std::string text = "hello world";
    for (char c : text) {
    
    
        charCount[c]++;
    }
    std::unordered_map<char, int> copyMap(charCount.begin(), charCount.end());

    // 示例 3: 拷贝构造函数
    std::unordered_map<int, double> sourceMap = {
    
    {
    
    1, 1.1}, {
    
    2, 2.2}, {
    
    3, 3.3}};
    std::unordered_map<int, double> copyOfSource(sourceMap);

    // 示例 4: 移动构造函数
    std::unordered_map<std::string, int> source;
    source["apple"] = 5;
    source["banana"] = 3;
    std::unordered_map<std::string, int> destination(std::move(source));

    // 示例 5: 初始化列表构造函数
    std::unordered_map<std::string, int> fruitCount = {
    
    
        {
    
    "apple", 5},
        {
    
    "banana", 3},
        {
    
    "cherry", 8}
    };

    // 输出示例
    for (const auto& pair : fruitCount) {
    
    
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    return 0;
}

unordered_map assignment operator overloading

copy (1)	
unordered_map& operator= ( const unordered_map& ump );
move (2)	
unordered_map& operator= ( unordered_map&& ump );
initializer list (3)	
unordered_map& operator= ( intitializer_list<value_type> il );

The assignment operator allows you to std::unordered_mapassign the contents of one to another std::unordered_map. Here is a brief description of these operators and how they are overloaded:

  1. Copy assignment operator(1) :
    • unordered_map& operator= (const unordered_map& ump);
    • Copy the contents of an existing std::unordered_mapto another existing std::unordered_map.
  2. Move assignment operator(2) :
    • unordered_map& operator= (unordered_map&& ump);
    • Move the content of an existing std::unordered_mapto another existing std::unordered_map. This operation will set the original std::unordered_mapto a valid but no longer usable state.
  3. Initialization list assignment operator (3) :
    • unordered_map& operator= (initializer_list<value_type> il);
    • Use ilelements in the initialization list to assign to std::unordered_map. This operation will replace the original content.

These assignment operators allow you to update the contents of a hash table in different situations std::unordered_mapand can be used to copy, move, or replace key-value pairs in a hash table. Here are some examples:

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

int main() {
    
    
    std::unordered_map<std::string, int> source = {
    
    {
    
    "apple", 5}, {
    
    "banana", 3}};
    std::unordered_map<std::string, int> destination;

    // 拷贝赋值操作符示例
    destination = source;

    // 移动赋值操作符示例
    std::unordered_map<std::string, int> otherSource = {
    
    {
    
    "cherry", 8}};
    destination = std::move(otherSource);

    // 初始化列表赋值操作符示例
    destination = {
    
    {
    
    "grape", 12}, {
    
    "orange", 6}};

    // 输出示例
    for (const auto& pair : destination) {
    
    
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    return 0;
}

These examples demonstrate how to use different assignment operators to update std::unordered_mapthe contents of . Whether copying, moving, or replacing with an initialization list, the contents of a hash table object can be easily updated as needed.

unordered_map capacity function (Capacity)

  1. empty()Function :
    • bool empty() const noexcept;
    • Used to check std::unordered_mapif is empty. Returns if the hash table does not contain any key-value pairs true; otherwise false. This function will not throw an exception.
  2. size()Function :
    • size_type size() const noexcept;
    • Returns std::unordered_mapthe number of key-value pairs stored in . That is, returns the size of the hash table. If the hash table is empty, 0 is returned. This function will not throw an exception.
  3. max_size()Function :
    • size_type max_size() const noexcept;
    • Returns std::unordered_mapthe maximum number of key-value pairs that can be accommodated, usually limited by system resources. This value may vary on different systems and compilers. This function will not throw an exception.

These functions enable you to query and manage std::unordered_mapthe status of . For example, you can use empty()to check if a hash table is empty, use size()to get its current size, and use max_size()to see the maximum size that a hash table can hold.

Example :

#include <iostream>
#include <unordered_map>

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

    // 使用 empty() 函数检查是否为空
    if (myMap.empty()) {
    
    
        std::cout << "Map is empty." << std::endl;
    } else {
    
    
        std::cout << "Map is not empty." << std::endl;
    }

    // 添加一些键值对
    myMap[1] = "one";
    myMap[2] = "two";
    myMap[3] = "three";

    // 使用 size() 函数获取大小
    std::cout << "Size of the map: " << myMap.size() << std::endl;

    // 使用 max_size() 函数获取最大容量
    std::cout << "Max size of the map: " << myMap.max_size() << std::endl;

    return 0;
}

unordered_map iterators (Iterators)

1. begin()

container iterator (1)	
      iterator begin() noexcept;
	  const_iterator begin() const noexcept;
bucket iterator (2)	
      local_iterator begin ( size_type n );
	  const_local_iterator begin ( size_type n ) const;
  1. begin()Function (container iterator):

    • iterator begin() noexcept;
    • const_iterator begin() const noexcept;
    • Both versions of begin()the function return std::unordered_mapan iterator pointing to the first element (key-value pair) of . The first version returns a non-const iterator, while the second version returns a const iterator. These functions will not throw exceptions.
  2. begin(size_type n)Function (bucket iterator):

    • local_iterator begin(size_type n);

    • const_local_iterator begin(size_type n) const;

    • Both versions of begin(size_type n)the function return an iterator pointing to the first element in a specific bucket, where nis the index of the bucket to be accessed. The first version returns a non-constant bucket iterator, while the second version returns a constant bucket iterator. These functions allow you to iterate over elements within specific buckets in a hash table.

2. end()

container iterator (1)	
      iterator end() noexcept;
	  const_iterator end() const noexcept;
bucket iterator (2)	
      local_iterator end (size_type n);
 	  const_local_iterator end (size_type n) const;
  1. end()function (container iterator)

    :

    • iterator end() noexcept;
    • const_iterator end() const noexcept;
    • These two versions of end()the function return std::unordered_mapan iterator pointing to the tail (end) of . The first version returns a non-const iterator, while the second version returns a const iterator. These functions will not throw exceptions.
  2. end(size_type n)Function (bucket iterator):

    • local_iterator end(size_type n);

    • const_local_iterator end(size_type n) const;

    • Both versions of end(size_type n)the function return an iterator pointing to the tail (end) of a specific bucket, where nis the index of the bucket to be accessed. The first version returns a non-constant bucket iterator, while the second version returns a constant bucket iterator. These functions allow you to iterate over the end of elements within a specific bucket in a hash table.

These iterator functions enable you to std::unordered_mapiterate over elements in a , not only over the entire container, but also over elements within a specific bucket. Here is some sample code:

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

int main() {
    
    
    std::unordered_map<int, std::string> myMap = {
    
    {
    
    1, "one"}, {
    
    2, "two"}, {
    
    3, "three"}};

    // 使用容器迭代器 begin()
    for (auto it = myMap.begin(); it != myMap.end(); ++it) {
    
    
        std::cout << "Key: " << it->first << ", Value: " << it->second << std::endl;
    }

    // 使用桶迭代器 begin(size_type n)
    size_t bucketIndex = myMap.bucket(2); // 获取键 2 所在的桶索引
    for (auto it = myMap.begin(bucketIndex); it != myMap.end(bucketIndex); ++it) {
    
    
        std::cout << "Key: " << it->first << ", Value: " << it->second << std::endl;
    }

    return 0;
}

In this example, we show how to use the container iterators begin()and end()to traverse the entire hash table, and the bucket iterators begin(size_type n)and end(size_type n)to iterate over the elements in a specific bucket. These iterator functions allow you to efficiently iterate and access std::unordered_mapthe elements in .

3. cbegin()

container iterator (1)	
	  const_iterator cbegin() const noexcept;
bucket iterator (2)	
	  const_local_iterator cbegin ( size_type n ) const;
  1. cbegin()Function (constant container iterator) :
    • const_iterator cbegin() const noexcept;
    • This function returns a std::unordered_mapconstant iterator pointing to the first element (key-value pair) of . It allows read-only traversal of the hash table and does not allow modification of the contents of the hash table. This function will not throw an exception.
  2. cbegin(size_type n)Function (constant bucket iterator) :
    • const_local_iterator cbegin(size_type n) const;
    • This function is used to return a constant bucket iterator pointing to the first element in a specific bucket (bucket), where nis the index of the bucket to be accessed. It allows read-only traversal of the elements in a specific bucket and does not allow modification of the contents of the hash table. This function will not throw an exception.

4. cend()

container iterator (1)	
	  const_iterator cend() const noexcept;
bucket iterator (2)	
	  const_local_iterator cend ( size_type n ) const;
  1. cend()Function (constant container iterator) :
    • const_iterator cend() const noexcept;
    • This function returns a std::unordered_mapconstant iterator pointing to the tail (end) of . It allows read-only traversal of the hash table and does not allow modification of the contents of the hash table. This function will not throw an exception.
  2. cend(size_type n)Function (constant bucket iterator) :
    • const_local_iterator cend(size_type n) const;
    • This function is used to return a constant bucket iterator pointing to the tail (end) of a specific bucket (bucket), where nis the index of the bucket to be accessed. It allows read-only traversal of the elements in a specific bucket and does not allow modification of the contents of the hash table. This function will not throw an exception.

These constant iterator functions std::unordered_mapare useful for accessing elements of in read-only mode. Here is a sample code:

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

int main() {
    
    
    std::unordered_map<int, std::string> myMap = {
    
    {
    
    1, "one"}, {
    
    2, "two"}, {
    
    3, "three"}};

    // 使用常量容器迭代器 cbegin()
    for (auto it = myMap.cbegin(); it != myMap.cend(); ++it) {
    
    
        std::cout << "Key: " << it->first << ", Value: " << it->second << std::endl;
    }

    // 使用常量桶迭代器 cbegin(size_type n)
    size_t bucketIndex = myMap.bucket(2); // 获取键 2 所在的桶索引
    for (auto it = myMap.cbegin(bucketIndex); it != myMap.cend(bucketIndex); ++it) {
    
    
        std::cout << "Key: " << it->first << ", Value: " << it->second << std::endl;
    }

    return 0;
}

In this example, we use the constant container iterators cbegin()and cend()to traverse the entire hash table, and the constant bucket iterators cbegin(size_type n)and cend(size_type n)to traverse the elements in a specific bucket. std::unordered_mapThese constant iterator functions allow you to access elements in read-only mode , ensuring that the contents of the hash table are not modified.

unordered_map element access and element search functions

1. operator[]

  1. mapped_type& operator[] ( const key_type& k );
    • This overloaded function accepts a constreference type key ( key_type) and returns mapped_typea reference to the value associated with the key ( ).
    • If the key ( k) is present unordered_map, it will return a reference to the value of that key, allowing you to modify the value.
    • If the key ( k) does not exist, it will unordered_mapinsert the key-value pair in and return a reference to a default-constructed value (usually the default value for the type).
    • This means that you can operator[]read or modify unordered_mapvalues ​​in without having to explicitly check whether the key exists or insert a key-value pair.
  2. mapped_type& operator[] ( key_type&& k );
    • This overloaded function accepts an rvalue reference type key ( key_type) and returns mapped_typea reference to the value ( ) associated with the key.
    • Similar to the first overloaded function, if the key ( k) is present unordered_map, it will return a reference to the value of that key, allowing you to modify that value.
    • If the key ( k) does not exist, it will unordered_mapinsert the key-value pair in and return a reference to the default-constructed value.

The following are examples of overloaded functions std::unordered_mapused :operator[]

#include <iostream>
#include <unordered_map>

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

    // 使用 operator[] 插入键-值对
    myMap["apple"] = 3;
    myMap["banana"] = 2;
    myMap["cherry"] = 5;

    // 访问键的值并修改它
    std::cout << "Number of apples: " << myMap["apple"] << std::endl;
    myMap["apple"] = 4;

    // 访问不存在的键,会插入默认值并返回引用
    std::cout << "Number of oranges: " << myMap["orange"] << std::endl;

    // 使用右值引用的 operator[] 插入键-值对
    std::string fruit = "grape";
    myMap[std::move(fruit)] = 6;

    // 遍历输出所有键-值对
    for (const auto& kv : myMap) {
    
    
        std::cout << kv.first << ": " << kv.second << std::endl;
    }

    return 0;
}

In this example, we use operator[]Insert key-value pairs, access and modify the value of an existing key, and access a non-existing key, which automatically inserts a default value. Also demonstrated is the use of rvalue references operator[]to insert new key-value pairs. Finally, we iterate through and output all key-value pairs.

2. at

  1. atFunctions that modify values :

    mapped_type& at(const key_type& k);
    mapped_type& at(key_type&& k);
    

    These two overloaded versions allow you kto modify the value in the associated container based on the key. If the key kexists in the container, it returns a reference to the corresponding value, allowing you to modify the value. If the key kdoes not exist, it will throw std::out_of_rangean exception.

    Sample code:

    std::unordered_map<std::string, int> myMap;
    myMap["apple"] = 3;
    
    // 使用 at 函数修改值
    myMap.at("apple") = 4;
    

    If the key "apple"exists, it will modify the value to 4.

  2. atFunctions for read-only access to values :

    const mapped_type& at(const key_type& k) const;
    

    This overloaded version allows you to read-only access the values ​​in the container. kIt returns a constant reference to the value associated with key . kIt will also throw an exception if the key does not exist std::out_of_range.

    Sample code:

    std::unordered_map<std::string, int> myMap;
    myMap["apple"] = 3;
    
    // 使用 at 函数只读访问值
    int numberOfApples = myMap.at("apple");
    

    If the key "apple"exists, it will return the value 3and you can store it in a variable numberOfApples.

In summary, ata function is a method for safely accessing elements in an associative container because it checks whether the key exists and throws an exception if necessary. This helps avoid undefined behavior caused by accessing non-existent keys.

3. find

  1. findFunctions for non-const containers :

    iterator find(const key_type& k);
    

    This version of findthe function works with non-const containers and returns an iterator. If the key exists in the container k, the iterator points to kthe element associated with the key; if the key kdoes not exist, it returns the container's end()iterator, indicating that it was not found.

    Sample code:

    std::unordered_map<std::string, int> myMap;
    myMap["apple"] = 3;
    
    // 使用 find 函数查找键 "apple"
    auto it = myMap.find("apple");
    if (it != myMap.end()) {
          
          
        // 找到了,输出值
        std::cout << "Value of 'apple': " << it->second << std::endl;
    } else {
          
          
        // 未找到
        std::cout << "Key 'apple' not found." << std::endl;
    }
    
  2. Constant container findfunctions :

    const_iterator find(const key_type& k) const;
    

    This version of findthe function works with constant containers and returns a const iterator (const_iterator). It functions the same as the non-const version, but does not allow modification of the elements in the container.

    Sample code:

    const std::unordered_map<std::string, int> myMap = {
          
          
        {
          
          "apple", 3},
        {
          
          "banana", 2},
        {
          
          "cherry", 5}
    };
    
    // 使用 const find 函数查找键 "banana"
    auto it = myMap.find("banana");
    if (it != myMap.end()) {
          
          
        // 找到了,输出值
        std::cout << "Value of 'banana': " << it->second << std::endl;
    } else {
          
          
        // 未找到
        std::cout << "Key 'banana' not found." << std::endl;
    }
    

In summary, findfunctions are a very useful way to find a specific key in an associative container. If you need to find out whether a key exists and access the value associated with it, findfunctions are a safe and efficient choice.

4. count

size_type count(const key_type& k) const;
  • k: The key to look for.
  • size_type: The return value type, usually an unsigned integer type, represents kthe number of occurrences of the key in the container.

countThe function looks for a key in the container kand returns the number of occurrences of the key. If the key kexists in the container, countthe function returns a positive integer of 1 or greater, representing the number of times the key occurs. If the key kdoes not exist, the function will return 0, indicating that the key does not appear.

Sample code:

std::unordered_map<std::string, int> myMap = {
    
    
    {
    
    "apple", 3},
    {
    
    "banana", 2},
    {
    
    "cherry", 5}
};

// 计算键 "apple" 在容器中的出现次数
size_t appleCount = myMap.count("apple");
std::cout << "The count of 'apple': " << appleCount << std::endl;

// 计算键 "grape" 在容器中的出现次数
size_t grapeCount = myMap.count("grape");
std::cout << "The count of 'grape': " << grapeCount << std::endl;

In the above example, countthe function first counts the number of occurrences of the key "apple" in the container (should be 1), and then counts the number of occurrences of the key "grape" in the container (should be 0). countFunctions are useful for checking whether a key exists in a container and counting the number of occurrences of a key. If you need to know whether a certain key exists and how many times it appears, you can use countthe function to query.

5. equal_range

pair<iterator,iterator>
   equal_range ( const key_type& k );
pair<const_iterator,const_iterator>
   equal_range ( const key_type& k ) const;
  • k: The key to look for.
  • pair: Return value type, a container containing two iterators representing the start and end positions of the range.
  • iteratorand const_iterator: iterator type, representing container iterators and constant iterators.

equal_rangeThe function looks up the key in the container kand returns one pairwhere the first element is an iterator pointing to the start of the range and the second element is an iterator pointing to the end of the range. This range includes all kelements with keys equal to .

Sample code:

std::map<int, std::string> myMap = {
    
    
    {
    
    1, "one"},
    {
    
    2, "two"},
    {
    
    2, "another two"}, // 注意:键 2 重复
    {
    
    3, "three"}
};

// 查找键 2 在容器中的范围
auto range = myMap.equal_range(2);

// 输出范围中的元素
for (auto it = range.first; it != range.second; ++it) {
    
    
    std::cout << it->first << ": " << it->second << std::endl;
}

In the example above, equal_rangethe function finds myMapthe range with key 2 in and returns an iterator containing the start and end of the range pair. We then use an iterator to traverse the range, outputting the elements in the range.

equal_rangeThe function is useful when dealing with duplicate keys in associative containers, as it allows you to find all elements with the same key and iterate over them.

unordered_map modifier function

1. location

template <class... Args>
pair<iterator, bool> emplace ( Args&&... args );

std::unordered_mapThe emplacefunction inserts a new key-value pair into a hash table and returns an std::pairiterator containing a boolean value.

  • Args&&... argsis a template parameter pack that allows you to pass any number of parameters.
  • pair<iterator, bool>Is the return value type, where iteratoris an iterator of insertion or existing key-value pairs, boolindicating whether the insertion is successful. If the key already exists, the iterator points to the existing key-value pair, the Boolean value is false; if the key does not exist, the iterator points to the newly inserted key-value pair, the Boolean value is true.

Here is emplacean example usage of the function:

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

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

    // 使用 emplace 插入键值对
    auto result1 = myMap.emplace(1, "One");
    if (result1.second) {
    
    
        std::cout << "Insertion successful. Key: " << result1.first->first << ", Value: " << result1.first->second << std::endl;
    } else {
    
    
        std::cout << "Key already exists. Key: " << result1.first->first << ", Value: " << result1.first->second << std::endl;
    }

    // 尝试再次插入相同的键值对
    auto result2 = myMap.emplace(1, "Another One");
    if (result2.second) {
    
    
        std::cout << "Insertion successful. Key: " << result2.first->first << ", Value: " << result2.first->second << std::endl;
    } else {
    
    
        std::cout << "Key already exists. Key: " << result2.first->first << ", Value: " << result2.first->second << std::endl;
    }

    return 0;
}

In the above example, emplacea key-value pair is first inserted using the function and then the same key-value pair is tried to be inserted again. Based on the returned Boolean value, you can determine whether the insertion was successful and whether the inserted key-value pair already exists or is newly inserted.

2. emplace_hint

Unlike emplace, emplace_hintallows you to provide a hint location to improve the performance of insert operations.

The following is emplace_hinta brief description of the function's parameters and usage:

  • position: This is an iterator that indicates the hint at the insertion position. The new element will be inserted positionbefore . This parameter helps the container insert elements more efficiently, but is not required.
  • Args&&... args: This is a variadic template used to pass the construction parameters of a new element. Depending on the element type's constructor, you can pass any number of arguments.
  • Return value: emplace_hintReturns an iterator pointing to the new element inserted, or if the insertion fails, an iterator pointing to the existing elements in the container.

Here's an example that demonstrates how to use to insert a new key-value pair into emplace_hint:std::unordered_map

#include <iostream>
#include <unordered_map>

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

    // 使用 emplace_hint 插入元素
    auto hint = myMap.emplace_hint(myMap.begin(), 1, "One");
    myMap.emplace_hint(hint, 2, "Two");
    myMap.emplace_hint(hint, 3, "Three");

    // 遍历 unordered_map 并打印键值对
    for (const auto& pair : myMap) {
    
    
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    return 0;
}

In this example, emplace_hintit is used to insert new key-value pairs by hintprompting the location to improve the efficiency of insertion. Note that emplace_hintelements can be inserted at any position, but typically you will use hint positions to avoid unnecessary search and move operations.

3. insert

  1. pair<iterator, bool> insert(const value_type& val): Insert a key-value valpair into unordered_map. If the insertion is successful, return one pair, in which iteratorpoints to the inserted element, boolindicating whether the insertion is successful.

    Example:

    std::unordered_map<int, std::string> myMap;
    std::pair<int, std::string> pairToInsert(1, "One");
    auto result = myMap.insert(pairToInsert);
    if (result.second) {
          
          
        std::cout << "Insertion successful." << std::endl;
    } else {
          
          
        std::cout << "Insertion failed (key already exists)." << std::endl;
    }
    
  2. template <class P> pair<iterator, bool> insert(P&& val): Use move semantics to valinsert a key-value pair unordered_mapinto . Also returns one pair, indicating the insertion result.

    Example:

    std::unordered_map<int, std::string> myMap;
    auto result = myMap.insert(std::make_pair(1, "One"));
    if (result.second) {
          
          
        std::cout << "Insertion successful." << std::endl;
    } else {
          
          
        std::cout << "Insertion failed (key already exists)." << std::endl;
    }
    
  3. iterator insert(const_iterator hint, const value_type& val)hint: Insert a key-value pair at the given position val. This function allows you to provide a position hint to improve insertion efficiency.

    Example:

    std::unordered_map<int, std::string> myMap;
    auto hint = myMap.begin(); // 可以是任何迭代器位置
    myMap.insert(hint, std::make_pair(1, "One"));
    
  4. template <class P> iterator insert(const_iterator hint, P&& val): Similar to the third function, key-value pairs are inserted using move semantics while providing location hints.

    Example:

    std::unordered_map<int, std::string> myMap;
    auto hint = myMap.begin(); // 可以是任何迭代器位置
    myMap.insert(hint, std::make_pair(1, "One"));
    
  5. template <class InputIterator> void insert(InputIterator first, InputIterator last)[first, last): Insert elements in the range specified by a pair of iterators into unordered_map.

    Example:

    std::unordered_map<int, std::string> myMap;
    std::vector<std::pair<int, std::string>> data = {
          
          {
          
          1, "One"}, {
          
          2, "Two"}, {
          
          3, "Three"}};
    myMap.insert(data.begin(), data.end());
    
  6. void insert(initializer_list<value_type> il): Insert into using elements from the initialization list unordered_map.

    Example:

    std::unordered_map<int, std::string> myMap;
    myMap.insert({
          
          {
          
          1, "One"}, {
          
          2, "Two"}, {
          
          3, "Three"}});
    

These functions provide multiple ways to insert key-value pairs, allowing you to choose the most appropriate method based on your needs.

4. erase

  1. iterator erase(const_iterator position)position: Delete the corresponding key-value pair through the iterator position . Returns an iterator pointing to the position after removing the element.

    Example:

    std::unordered_map<int, std::string> myMap = {
          
          {
          
          1, "One"}, {
          
          2, "Two"}, {
          
          3, "Three"}};
    auto it = myMap.find(2); // 找到键为2的位置
    if (it != myMap.end()) {
          
          
        myMap.erase(it); // 删除键为2的元素
    }
    
  2. size_type erase(const key_type& k)k: Delete the corresponding key-value pair by key . Returns the number of elements removed (0 or 1, since the key unordered_mapis unique in ).

    Example:

    std::unordered_map<int, std::string> myMap = {
          
          {
          
          1, "One"}, {
          
          2, "Two"}, {
          
          3, "Three"}};
    size_t erased = myMap.erase(2); // 删除键为2的元素
    std::cout << "Erased " << erased << " element(s)." << std::endl;
    
  3. iterator erase(const_iterator first, const_iterator last)[first, last): Delete key-value pairs in the specified range through a pair of iterators . Returns an iterator pointing to the position after the delete operation.

    Example:

    std::unordered_map<int, std::string> myMap = {
          
          {
          
          1, "One"}, {
          
          2, "Two"}, {
          
          3, "Three"}};
    auto first = myMap.begin(); // 范围起始位置
    auto last = myMap.find(2);  // 范围结束位置,找到键为2的位置
    if (last != myMap.end()) {
          
          
        myMap.erase(first, last); // 删除范围内的元素
    }
    

These functions provide different ways to delete unordered_mapelements in , and you can choose the most appropriate method according to your needs.

5. clear

  • void clear() noexcept;: Clear unordered_mapall key-value pairs in . This operation will unordered_mapempty the container but will not change the container's capacity.

    Example:

    std::unordered_map<int, std::string> myMap = {
          
          {
          
          1, "One"}, {
          
          2, "Two"}, {
          
          3, "Three"}};
    std::cout << "Size before clear: " << myMap.size() << std::endl;
    
    myMap.clear(); // 清空 unordered_map
    
    std::cout << "Size after clear: " << myMap.size() << std::endl;
    

    Output:

    Size before clear: 3
    Size after clear: 0
    

This function is a no-exception operation ( noexcept), meaning it does not throw exceptions. It can unordered_mapbe used when emptying is needed to release the resources occupied by the container.

6. swap

  • void swap ( unordered_map& ump );: Exchange the contents of the current unordered_mapand umprepresented by the parameter unordered_map. This operation does not change the container's capacity, just swaps their contents.

    Example:

    std::unordered_map<int, std::string> map1 = {
          
          {
          
          1, "One"}, {
          
          2, "Two"}};
    std::unordered_map<int, std::string> map2 = {
          
          {
          
          3, "Three"}, {
          
          4, "Four"}};
    
    std::cout << "map1 before swap:" << std::endl;
    for (const auto& pair : map1) {
          
          
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
    
    std::cout << "map2 before swap:" << std::endl;
    for (const auto& pair : map2) {
          
          
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
    
    map1.swap(map2); // 交换 map1 和 map2 的内容
    
    std::cout << "map1 after swap:" << std::endl;
    for (const auto& pair : map1) {
          
          
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
    
    std::cout << "map2 after swap:" << std::endl;
    for (const auto& pair : map2) {
          
          
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
    

    Output:

    map1 before swap:
    1: One
    2: Two
    map2 before swap:
    3: Three
    4: Four
    map1 after swap:
    3: Three
    4: Four
    map2 after swap:
    1: One
    2: Two
    

This function is very useful because it can efficiently exchange the contents of two without creating a temporary object unordered_map.

unordered_map bucket function

1. bucket_count

bucket_countThe function is used to obtain std::unordered_mapthe current number of buckets in the container. Buckets are storage locations in a hash table that store key-value pairs.

size_type bucket_count() const noexcept;
  • size_type: represents an unsigned integer type, usually size_t.
  • bucket_count(): This function has no parameters.
  • const: Indicates that this function will not modify the contents of the container.
  • noexcept: Indicates that the function will not throw an exception.

This function returns unordered_mapthe number of buckets in the current container.

Sample code:

#include <iostream>
#include <unordered_map>

int main() {
    
    
    std::unordered_map<int, std::string> map;
    map[1] = "One";
    map[2] = "Two";
    map[3] = "Three";
    map[4] = "Four";

    std::cout << "Bucket count: " << map.bucket_count() << std::endl;

    return 0;
}

In this example, bucket_countthe function returns the current std::unordered_mapnumber of buckets in , which depends on factors such as the hash function and load factor. This number is usually larger than the actual number of elements in the container to ensure that elements are evenly distributed in the bucket, thereby improving query performance.

2. max_bucket_count

max_bucket_countThe function is used to obtain std::unordered_mapthe maximum number of buckets supported by the container. Buckets are storage locations in a hash table that store key-value pairs.

size_type max_bucket_count() const noexcept;
  • size_type: represents an unsigned integer type, usually size_t.
  • max_bucket_count(): This function has no parameters.
  • const: Indicates that this function will not modify the contents of the container.
  • noexcept: Indicates that the function will not throw an exception.

This function returns std::unordered_mapthe maximum number of buckets supported by the container. This value is usually affected by the underlying hash table implementation and system resource limitations.

Sample code:

#include <iostream>
#include <unordered_map>

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

    // 获取最大桶数量并输出
    std::cout << "Max bucket count: " << map.max_bucket_count() << std::endl;

    return 0;
}

In this example, max_bucket_countthe function returns std::unordered_mapthe maximum number of buckets supported by the container. This value is compiler and system determined, and usually depends on the system's available memory and hash table implementation.

3. bucket_size

bucket_sizeThe function is used to get the number of elements in the specified bucket. It requires the index of a bucket to be passed in as a parameter.

size_type bucket_size(size_type n) const;
  • size_type: represents an unsigned integer type, usually size_t.
  • bucket_size(n): This function accepts a parameter nindicating the index of the bucket to be queried.
  • const: Indicates that this function will not modify the contents of the container.

This function returns the number of elements in the specified bucket.

Sample code:

#include <iostream>
#include <unordered_map>

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

    // 插入一些键值对
    map[1] = "one";
    map[2] = "two";
    map[3] = "three";

    // 获取第一个桶中的元素数量
    size_t bucketIndex = map.bucket(1); // 获取键为1的元素所在的桶的索引
    size_t bucketSize = map.bucket_size(bucketIndex); // 获取该桶的元素数量

    std::cout << "Bucket size for key 1: " << bucketSize << std::endl;

    return 0;
}

In this example, we create a std::unordered_mapcontainer, insert some key-value pairs, and use bucket_sizethe function to get the number of elements in a specific bucket. Please note that we first used bucketthe function to get the index of the bucket where the element with key 1 is located. Then, we use bucket_sizethe function to get the number of elements in the bucket.

4. bucket

bucketThe function is used to get the index of the bucket to which a given key belongs. It needs to pass in a key as a parameter.

size_type bucket(const key_type& k) const;
  • size_type: represents an unsigned integer type, usually size_t.
  • bucket(k): This function accepts a parameter kindicating the key to be queried.
  • const: Indicates that this function will not modify the contents of the container.

This function returns the index of the bucket to which the specified key belongs.

Sample code:

#include <iostream>
#include <unordered_map>

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

    // 插入一些键值对
    map["one"] = 1;
    map["two"] = 2;
    map["three"] = 3;

    // 获取键所属的桶的索引
    size_t bucketIndex1 = map.bucket("one");
    size_t bucketIndex2 = map.bucket("three");

    std::cout << "Bucket index for key 'one': " << bucketIndex1 << std::endl;
    std::cout << "Bucket index for key 'three': " << bucketIndex2 << std::endl;

    return 0;
}

In this example, we create a std::unordered_mapcontainer, insert some key-value pairs, and use bucketthe function to get the index of the bucket to which a specific key belongs. We get the indexes of the buckets where the keys "one" and "three" are located.

unordered_map hash strategy function

1. load_factor

float load_factor() const noexcept;

load_factorThe function is used to obtain the load factor of the current hash table, and it returns a floatvalue indicating the load factor.

The load factor is the ratio of the number of elements currently contained in the hash table to the total number of buckets. Generally, the smaller the load factor, the better the performance of the hash table because the probability of collision is lower.

Sample code:

#include <iostream>
#include <unordered_map>

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

    // 设置一些键值对
    map[1] = "one";
    map[2] = "two";
    map[3] = "three";

    // 获取当前负载因子
    float lf = map.load_factor();

    std::cout << "Load Factor: " << lf << std::endl;

    return 0;
}

In this example, we create a std::unordered_mapcontainer and insert some key-value pairs. Then, we load_factorget the current load factor using the function and print it out.

2. max_load_factor

max_load_factorFunction used to set or get the maximum load factor of the hash table. The maximum load factor is the maximum load factor allowed before rehashing occurs.

  • float max_load_factor() const noexcept;: Get the maximum load factor of the current hash table.
  • void max_load_factor(float z);: Set the maximum load factor of the hash table to z.

Sample code:

#include <iostream>
#include <unordered_map>

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

    // 获取当前最大负载因子
    float maxLF = map.max_load_factor();
    std::cout << "Current Max Load Factor: " << maxLF << std::endl;

    // 设置最大负载因子为新值
    map.max_load_factor(0.75);

    // 获取新的最大负载因子
    maxLF = map.max_load_factor();
    std::cout << "Updated Max Load Factor: " << maxLF << std::endl;

    return 0;
}

In this example, we first obtain the current maximum load factor of the hash table and then modify it to a new value. By calling max_load_factorthe function, you can control the load factor of the hash table before it is rehashed to optimize performance.

3. rehash

rehashThe function is used to re-adjust the number of buckets in the hash table to accommodate at least nelements to improve the performance of the hash table. Rehashing changes the number of buckets, redistributing elements, so it may take some time.

  • void rehash(size_type n);: Resize the number of buckets in the hash table so that it can accommodate at least nelements.

Sample code:

#include <iostream>
#include <unordered_map>

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

    // 添加一些元素
    map[1] = "One";
    map[2] = "Two";
    map[3] = "Three";

    // 获取当前桶的数量
    size_t currentBucketCount = map.bucket_count();
    std::cout << "Current Bucket Count: " << currentBucketCount << std::endl;

    // 重新调整桶的数量
    map.rehash(10);

    // 获取新的桶的数量
    currentBucketCount = map.bucket_count();
    std::cout << "Updated Bucket Count: " << currentBucketCount << std::endl;

    return 0;
}

In this example, we first add some elements to the hash table and then use rehashthe function to rescale the number of buckets to 10. Rehashing may be used to optimize performance after adding a large number of elements to ensure that the load factor remains within a suitable range.

4. reserve

reserveThe function is used to reserve nbucket space that can accommodate at least elements to improve the performance of the hash table. This function can help you allocate enough bucket space before inserting a large number of elements to avoid frequent rehash operations.

  • void reserve(size_type n);: Reserve bucket space that can accommodate at least nelements.

Sample code:

#include <iostream>
#include <unordered_map>

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

    // 预留足够的桶空间
    map.reserve(100);  // 预留至少能容纳 100 个元素的桶空间

    // 添加一些元素
    for (int i = 0; i < 100; ++i) {
    
    
        map[i] = "Value " + std::to_string(i);
    }

    // 获取当前桶的数量
    size_t currentBucketCount = map.bucket_count();
    std::cout << "Current Bucket Count: " << currentBucketCount << std::endl;

    return 0;
}

In this example, we use reservethe function to reserve a bucket that can hold at least 100 elements, and then add 100 elements to the hash table. This can help improve performance because enough bucket space is reserved, avoiding frequent rehashing operations when inserting elements.

Unordered_map opens hash form simulation implementation

Modify the hash table implementation of the previous hash blog

template<class K>
struct HashFunc
{
    
    
	size_t operator()(const K& key)
	{
    
    
		return (size_t)key;
	}
};
  • operator()Is an overload of the function call operator, allowing objects to be called like functions.
  • The function accepts a parameter keythat represents the key for which the hash value is to be calculated.
  • Inside the function body, cast (size_t)keythe key keyto size_ttype to get its hash value.
  • A hash value is a numerical value that identifies the key's position in the hash table. Typically, a hash function maps different keys to different hash values ​​to allow for efficient lookup operations in a hash table

hash iterator increment

template<class K, class T, class Hash, class KeyOfT>
struct __HashIterator
{
    
    
	typedef HashNode<T> Node;
	typedef HashTable<K, T, Hash, KeyOfT> HT;
	typedef __HashIterator<K, T, Hash, KeyOfT> Self;

	Node* _node;
	HT* _pht;

	__HashIterator(Node* node, HT* pht)
		:_node(node)
		, _pht(pht)
	{
    
    }

	T& operator*()
	{
    
    
		return _node->_data;
	}

	T* operator->()
	{
    
    
		return &_node->_data;
	}

	Self& operator++()
	{
    
    
		if (_node->_next)
		{
    
    
			// 当前桶中迭代
			_node = _node->_next;
		}
		else
		{
    
    
			// 找下一个桶
			Hash hash;
			KeyOfT kot;
			size_t i = hash(kot(_node->_data)) % _pht->_tables.size();
			++i;
			for (; i < _pht->_tables.size(); ++i)
			{
    
    
				if (_pht->_tables[i])
				{
    
    
					_node = _pht->_tables[i];
					break;
				}
			}

			// 说明后面没有有数据的桶了
			if (i == _pht->_tables.size())
			{
    
    
				_node = nullptr;
			}
		}

		return *this;
	}

	bool operator!=(const Self& s) const
	{
    
    
		return _node != s._node;
	}

	bool operator==(const Self& s) const
	{
    
    
		return _node == s._node;
	}
};
  1. typedef HashNode<T> Node;: Give HashNode<T>an alias to the type Node, HashNodeusually representing a node in a hash table.
  2. typedef HashTable<K, T, Hash, KeyOfT> HT;: Give HashTablean alias to the type after instantiation of the template class HT, HashTableusually representing a hash table.
  3. typedef __HashIterator<K, T, Hash, KeyOfT> Self;: Give an alias to the type of the iterator itself Self. This alias is used inside the iterator to define the iterator type.
  4. Node* _node;: Pointer to the current iteration node. The iterator is used to traverse the nodes in the hash table, and the information of the current node is stored in _node.
  5. HT* _pht;: Pointer to hash table. The information of the hash table is stored in _pht, and the iterator may need to access the properties of the hash table to implement the iteration operation.
  6. T& operator*(): Overload *the operator so that the iterator can be used like a pointer *to access the data of the current node.
  7. T* operator->(): Overload ->the operator so that the iterator can be used like a pointer ->to access the data of the current node.
  8. Self& operator++(): Overload the preceding increment operator ++so that the iterator can advance to the next node. This function will check whether there are nodes in the current bucket, and if so, move to the next node; otherwise, if the next non-empty bucket is found, it will _nodepoint to the first node of the bucket.
  9. bool operator!=(const Self& s) const: Overloaded inequality operator !=, used to compare whether two iterators are not equal.
  10. bool operator==(const Self& s) const: Overloaded equal operator ==, used to compare two iterators for equality.
typedef __HashIterator<K, T, Hash, KeyOfT> iterator;

iterator begin()
{
    
    
	for (size_t i = 0; i < _tables.size(); ++i)
	{
    
    
		if (_tables[i])
		{
    
    
			return iterator(_tables[i], this);
		}
	}

	return end();
}

iterator end()
{
    
    
	return iterator(nullptr, this);
}
  1. typedef __HashIterator<K, T, Hash, KeyOfT> iterator;: This line defines an alias iteratorfor type __HashIterator, allowing you to use it like a normal C++ iterator.
  2. iterator begin()Function: This function returns an iterator pointing to the first non-empty bucket in the hash table. It traverses _tablesthe buckets in the container, finds the first non-empty bucket, creates a corresponding iterator, and then returns the iterator. end()If no non-empty bucket is found, an iterator is returned , indicating the end of the traversal.
  3. iterator end()Function: This function returns an iterator indicating the end of the traversal. It returns an iterator whose _nodemember is nullptr, indicating that there are currently no valid nodes to iterate over. This is useful for identifying the end of an iteration.

Add __stl_num_primes function

inline size_t __stl_next_prime(size_t n)
{
    
    
	static const size_t __stl_num_primes = 28;
	static const size_t __stl_prime_list[__stl_num_primes] =
	{
    
    
		53, 97, 193, 389, 769,
		1543, 3079, 6151, 12289, 24593,
		49157, 98317, 196613, 393241, 786433,
		1572869, 3145739, 6291469, 12582917, 25165843,
		50331653, 100663319, 201326611, 402653189, 805306457,
		1610612741, 3221225473, 4294967291
	};

	for (size_t i = 0; i < __stl_num_primes; ++i)
	{
    
    
		if (__stl_prime_list[i] > n)
		{
    
    
			return __stl_prime_list[i];
		}
	}

	return -1;
}
  • __stl_num_primesDefines a constant array that contains a series of prime values. These prime values ​​are carefully chosen to be candidates for new capacity when the capacity of the data structure is expanded.
  • The function first iterates through the array of prime numbers, finds the first nprime number greater than the given number, and returns it. This operation ensures that the size of the container is always chosen to be a large enough prime number to reduce the probability of hash collisions.
  • If no suitable prime number is found, the function returns -1, which indicates that an exception has occurred and error handling can be performed as needed.

The main purpose of this function is to optimize the performance of the hash table and ensure that its capacity is always an appropriate prime number to improve the efficiency of the hash algorithm (refer to the STL source code) .

Modify insert function

pair<iterator, bool> Insert(const T& data)
{
    
    
	Hash hash;
	KeyOfT kot;

	// 去重
	iterator ret = Find(kot(data));
	if (ret != end())
	{
    
    
		return make_pair(ret, false);
	}

	// 负载因子到1就扩容
	if (_size == _tables.size())
	{
    
    
		vector<Node*> newTables;
		newTables.resize(__stl_next_prime(_tables.size()), nullptr);
		// 旧表中节点移动映射新表
		for (size_t i = 0; i < _tables.size(); ++i)
		{
    
    
			Node* cur = _tables[i];
			while (cur)
			{
    
    
				Node* next = cur->_next;

				size_t hashi = hash(kot(cur->_data)) % newTables.size();
				cur->_next = newTables[hashi];
				newTables[hashi] = cur;

				cur = next;
			}

			_tables[i] = nullptr;
		}

		_tables.swap(newTables);
	}

	size_t hashi = hash(kot(data)) % _tables.size();
	// 头插
	Node* newnode = new Node(data);
	newnode->_next = _tables[hashi];
	_tables[hashi] = newnode;
	++_size;

	return make_pair(iterator(newnode, this), true);
}
  1. First, the code creates an instance of Hashand an KeyOfT, which is to get the hash of the key and the key itself.
  2. Next, the code Find(kot(data))checks whether an element with the same key already exists by calling the function. If the same key is found, the insertion operation is not performed, but an element is returned pair, iteratorpart of which points to an existing element, and boolpart of which is set to falseindicate that the insertion failed.
  3. If the same key is not found, the insertion operation continues. First, the code checks whether the load factor of the current hash table has reached 1 (that is, the number of elements is equal to the size of the hash table). If so, it needs to be expanded.
  4. The expansion operation creates a new hash table newTableswhose size is __stl_next_prime(_tables.size())determined by calling , which ensures that the new table size is a prime number. The code then goes through the old hash table and remaps each node into the new hash table. This is to maintain an even distribution of hashes and reduce the possibility of hash collisions.
  5. Finally, the code calculates the hash value of the element to be inserted and inserts the new node into the new hash table. This is achieved by inserting at the head. _nextThe pointer of the new node points to the head node of the current bucket, and then the head node of the current bucket is updated as the new node. At the same time, the number of elements _sizewill increase by 1.
  6. Ultimately, the code returns one pairwith iteratorpart pointing to the newly inserted element and boolpart set to trueindicate successful insertion.

Add bucket function

size_t Size()
{
    
    
	return _size;
}

// 表的长度
size_t TablesSize()
{
    
    
	return _tables.size();
}

// 桶的个数
size_t BucketNum()
{
    
    
	size_t num = 0;
	for (size_t i = 0; i < _tables.size(); ++i)
	{
    
    
		if (_tables[i])
		{
    
    
			++num;
		}
	}

	return num;
}

size_t MaxBucketLenth()
{
    
    
	size_t maxLen = 0;
	for (size_t i = 0; i < _tables.size(); ++i)
	{
    
    
		size_t len = 0;
		Node* cur = _tables[i];
		while (cur)
		{
    
    
			++len;
			cur = cur->_next;
		}

		if (len > maxLen)
		{
    
    
			maxLen = len;
		}
	}

	return maxLen;
}
  1. Size(): This function returns the total number of elements in the hash table, which is the size of the hash table.
  2. TablesSize(): This function returns the number of buckets inside the hash table, which is the actual size of the hash table.
  3. BucketNum(): This function is used to calculate the number of non-empty buckets in the current hash table, that is, the number of buckets containing elements.
  4. MaxBucketLenth(): This function is used to find the length of the longest bucket in a hash table, that is, the number of elements in the bucket with the most elements.

All codes of HashTable.h after modification

#pragma once
#include<iostream>
#include<utility>
#include<vector>
#include<string>
using namespace std;
template<class K>
struct HashFunc
{
    
    
	size_t operator()(const K& key)
	{
    
    
		return (size_t)key;
	}
};

template<>
struct HashFunc<string>
{
    
    
	size_t operator()(const string& key)
	{
    
    
		size_t val = 0;
		for (auto ch : key)
		{
    
    
			val *= 131;
			val += ch;
		}

		return val;
	}
};
namespace Bucket
{
    
    
	template<class T>
	struct HashNode
	{
    
    
		T _data;
		HashNode<T>* _next;

		HashNode(const T& data)
			:_data(data)
			, _next(nullptr)
		{
    
    }
	};

	// 前置声明
	template<class K, class T, class Hash, class KeyOfT>
	class HashTable;

	template<class K, class T, class Hash, class KeyOfT>
	struct __HashIterator
	{
    
    
		typedef HashNode<T> Node;
		typedef HashTable<K, T, Hash, KeyOfT> HT;
		typedef __HashIterator<K, T, Hash, KeyOfT> Self;

		Node* _node;
		HT* _pht;

		__HashIterator(Node* node, HT* pht)
			:_node(node)
			, _pht(pht)
		{
    
    }

		T& operator*()
		{
    
    
			return _node->_data;
		}

		T* operator->()
		{
    
    
			return &_node->_data;
		}

		Self& operator++()
		{
    
    
			if (_node->_next)
			{
    
    
				// 当前桶中迭代
				_node = _node->_next;
			}
			else
			{
    
    
				// 找下一个桶
				Hash hash;
				KeyOfT kot;
				size_t i = hash(kot(_node->_data)) % _pht->_tables.size();
				++i;
				for (; i < _pht->_tables.size(); ++i)
				{
    
    
					if (_pht->_tables[i])
					{
    
    
						_node = _pht->_tables[i];
						break;
					}
				}

				// 说明后面没有有数据的桶了
				if (i == _pht->_tables.size())
				{
    
    
					_node = nullptr;
				}
			}

			return *this;
		}

		bool operator!=(const Self& s) const
		{
    
    
			return _node != s._node;
		}

		bool operator==(const Self& s) const
		{
    
    
			return _node == s._node;
		}
	};

	template<class K, class T, class Hash, class KeyOfT>
	class HashTable
	{
    
    
		typedef HashNode<T> Node;

		template<class K, class T, class Hash, class KeyOfT>
		friend struct __HashIterator;
	public:
		typedef __HashIterator<K, T, Hash, KeyOfT> iterator;

		iterator begin()
		{
    
    
			for (size_t i = 0; i < _tables.size(); ++i)
			{
    
    
				if (_tables[i])
				{
    
    
					return iterator(_tables[i], this);
				}
			}

			return end();
		}

		iterator end()
		{
    
    
			return iterator(nullptr, this);
		}

		~HashTable()
		{
    
    
			for (size_t i = 0; i < _tables.size(); ++i)
			{
    
    
				Node* cur = _tables[i];
				while (cur)
				{
    
    
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_tables[i] = nullptr;
			}
		}

		inline size_t __stl_next_prime(size_t n)
		{
    
    
			static const size_t __stl_num_primes = 28;
			static const size_t __stl_prime_list[__stl_num_primes] =
			{
    
    
				53, 97, 193, 389, 769,
				1543, 3079, 6151, 12289, 24593,
				49157, 98317, 196613, 393241, 786433,
				1572869, 3145739, 6291469, 12582917, 25165843,
				50331653, 100663319, 201326611, 402653189, 805306457,
				1610612741, 3221225473, 4294967291
			};

			for (size_t i = 0; i < __stl_num_primes; ++i)
			{
    
    
				if (__stl_prime_list[i] > n)
				{
    
    
					return __stl_prime_list[i];
				}
			}

			return -1;
		}

		pair<iterator, bool> Insert(const T& data)
		{
    
    
			Hash hash;
			KeyOfT kot;

			// 去重
			iterator ret = Find(kot(data));
			if (ret != end())
			{
    
    
				return make_pair(ret, false);
			}

			// 负载因子到1就扩容
			if (_size == _tables.size())
			{
    
    
				vector<Node*> newTables;
				newTables.resize(__stl_next_prime(_tables.size()), nullptr);
				// 旧表中节点移动映射新表
				for (size_t i = 0; i < _tables.size(); ++i)
				{
    
    
					Node* cur = _tables[i];
					while (cur)
					{
    
    
						Node* next = cur->_next;

						size_t hashi = hash(kot(cur->_data)) % newTables.size();
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;

						cur = next;
					}

					_tables[i] = nullptr;
				}

				_tables.swap(newTables);
			}

			size_t hashi = hash(kot(data)) % _tables.size();
			// 头插
			Node* newnode = new Node(data);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_size;

			return make_pair(iterator(newnode, this), true);
		}

		iterator Find(const K& key)
		{
    
    
			if (_tables.size() == 0)
			{
    
    
				return end();
			}

			Hash hash;
			KeyOfT kot;
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
    
    
				if (kot(cur->_data) == key)
				{
    
    
					return iterator(cur, this);
				}

				cur = cur->_next;
			}

			return end();
		}

		bool Erase(const K& key)
		{
    
    
			if (_tables.size() == 0)
			{
    
    
				return false;
			}

			Hash hash;
			KeyOfT kot;
			size_t hashi = hash(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
    
    
				if (kot(cur->_data) == key)
				{
    
    
					// 1、头删
					// 2、中间删
					if (prev == nullptr)
					{
    
    
						_tables[hashi] = cur->_next;
					}
					else
					{
    
    
						prev->_next = cur->_next;
					}

					delete cur;
					--_size;

					return true;
				}

				prev = cur;
				cur = cur->_next;
			}

			return false;
		}

		size_t Size()
		{
    
    
			return _size;
		}

		// 表的长度
		size_t TablesSize()
		{
    
    
			return _tables.size();
		}

		// 桶的个数
		size_t BucketNum()
		{
    
    
			size_t num = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
    
    
				if (_tables[i])
				{
    
    
					++num;
				}
			}

			return num;
		}

		size_t MaxBucketLenth()
		{
    
    
			size_t maxLen = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
    
    
				size_t len = 0;
				Node* cur = _tables[i];
				while (cur)
				{
    
    
					++len;
					cur = cur->_next;
				}

				if (len > maxLen)
				{
    
    
					maxLen = len;
				}
			}

			return maxLen;
		}

	private:
		vector<Node*> _tables;
		size_t _size = 0; // 存储有效数据个数
	};
};

Implement unordered_map using hash table encapsulation

#pragma once
#include "HashTable.h"

namespace yulao
{
    
    
	template<class K, class V, class Hash = HashFunc<K>>
	class unordered_map
	{
    
    
		struct MapKeyOfT
		{
    
    
			const K& operator()(const pair<K, V>& kv)
			{
    
    
				return kv.first;
			}
		};
	public:
		typedef typename Bucket::HashTable<K, pair<K, V>, Hash, MapKeyOfT>::iterator iterator;

		iterator begin()
		{
    
    
			return _ht.begin();
		}

		iterator end()
		{
    
    
			return _ht.end();
		}

		pair<iterator, bool> Insert(const pair<K, V>& kv)
		{
    
    
			return _ht.Insert(kv);
		}

		V& operator[](const K& key)
		{
    
    
			pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
			return ret.first->second;
		}

	private:
		Bucket::HashTable<K, pair<K, V>, Hash, MapKeyOfT> _ht;
	};
}

Here we will simply implement the basic functions of unordered_map! ! !

Guess you like

Origin blog.csdn.net/kingxzq/article/details/133364438