采用hash 封装 unordered_map&set [万字详解,速来一看]

目录

  1. HashFunc 模板类
    1.1 HashFunc 通用模板
    1.2 HashFunc 模板特化(string)

  2. closehash 命名空间
    2.1 State 枚举类
    2.2 HashData 结构体
    2.3 HashTable 类
    2.3.1 Insert 方法
    2.3.2 Find 方法
    2.3.3 Erase 方法

  3. buckethash 命名空间
    3.1 HashNode 结构体
    3.2 HashTable 类
    3.3 Insert 方法

  4. __HTIterator 类

  5. unordered_map 类
    5.1 MapKeyOfT 仿函数结构体
    5.2 unordered_map 中的 iterator 类型定义
    5.3 begin 和 end 方法
    5.4 insert 方法

  6. unordered_set 类
    6.1 SetKeyOfT 仿函数结构体
    6.2 unordered_set 中的 iterator 类型定义
    6.3 Insert 方法
    6.4 test_unordered_set 测试函数

  7. 全部代码
    7.1 HashTable.hpp
    7.2 UnorderedMap.hpp
    7.3 UnorderedSet.hpp
    7.4 test_02.cc


简要介绍

本文详细探讨了C++中的哈希表实现,涵盖了数据结构的基本概念、常用操作、冲突处理方法、负载因子控制及扩容机制。

首先,哈希表是一种以键值对形式存储数据的高效数据结构。其核心思想是通过哈希函数将键映射到表的索引,以实现快速的查找、插入和删除操作。平均时间复杂度接近O(1),使其在处理大量数据时表现优异。

本文实现了两种主要的冲突处理方法:闭散列法和拉链法。闭散列法通过开放寻址处理冲突,使用线性探测的方式寻找空位,适用于元素较少且冲突不多的情况。相对而言,拉链法则通过为每个哈希表位置维护一个链表来存储冲突的元素,能够有效应对高负载因子的情况,在元素数量较多时表现更好。

为了保持哈希表的高效性能,负载因子的控制至关重要。负载因子是指哈希表中元素数量与表容量的比值。当负载因子过高时,哈希表的性能会显著下降,导致更多的冲突和更长的查找时间。因此,本文设计了一个动态扩容机制,当负载因子达到一定阈值时,自动调整哈希表的容量,并重新计算元素的哈希位置。通过使用质数作为哈希表的容量,能够有效减少冲突,并提高哈希函数的分布均匀性,进一步优化性能。

本文还实现了质数容量调整函数,通过预定义的一系列质数,在需要扩容时选择合适的下一个质数作为新的容量,从而保证哈希表的性能不会受到影响。

总的来说,本文提供了哈希表的完整实现代码,并详细解释了每个模块的功能和逻辑,使读者能够更深入地理解哈希表的设计与实现。同时,本文也强调了哈希表在现代应用中的重要性,尤其是在缓存系统、数据库索引和符号表等领域。希望这篇文章能为读者在数据结构的学习和实际应用中提供帮助。

如果不想看分块代码,可以划至文末查看全部代码


1. HashFunc 模板类

1.1 HashFunc 通用模板
template <class K>
struct HashFunc
{
    size_t operator()(const K& key)
    {
        return (size_t)key;
    }
};
  • 逻辑说明:这个模板类 HashFunc 定义了一个默认的哈希函数,用于将任意类型的键 K 转换为 size_t 类型的哈希值。
    • 操作符 () 重载:它接收一个 K 类型的键,并将其强制转换为 size_t。这种实现适用于可以直接进行类型转换的基础类型,比如 intchar
    • 用途:该哈希函数可以在 unordered_mapunordered_set 等哈希容器中作为默认的哈希计算方式。
1.2 HashFunc 模板特化(string)
template <>
struct HashFunc<string>
{
    size_t operator()(const string& key)
    {
        size_t hash = 0;
        for (auto ch : key)
        {
            hash = hash * 131 + ch;
        }
        return hash;
    }
};
  • 逻辑说明:该部分是针对 string 类型的模板特化,使用了经典的 BKDR 哈希算法来计算字符串的哈希值。
    • BKDR 算法:这个算法通过逐字符遍历字符串,每个字符的 ASCII 值乘以 131 累加到哈希值中。131 是一个常用的素数,能够有效减少哈希冲突。
    • 用途:适用于存储字符串类型的键时的哈希计算,特别在需要高效字符串处理的 unordered_mapunordered_set 中使用。

2. closehash 命名空间

2.1 State 枚举类
enum State
{
    EMPTY,
    EXIST,
    DELETE,
};
  • 逻辑说明State 枚举类定义了哈希表中每个槽位可能的三种状态:
    • EMPTY:该槽位为空,没有存储数据。
    • EXIST:该槽位存储了有效的数据。
    • DELETE:该槽位曾经存储数据,但已经被删除。
2.2 HashData 结构体
template <class K, class V>
struct HashData
{
    pair<K, V> _kv;
    State _state = EMPTY;
};
  • 逻辑说明HashData 结构体用于存储哈希表中的键值对及其对应的状态。
    • _kv:存储键值对,pair<K, V>
    • _state:表示该槽位当前的状态,默认为 EMPTY
    • 用途:这个结构体是哈希表中每个槽位的基本单元,既保存数据也保存该槽位的状态。
2.3 HashTable 类
template <class K, class V, class Hash = HashFunc<K>>
class HashTable
{
    typedef HashData<K, V> Data;
    private:
        vector<Data> _table;
        size_t _n = 0;

    public:
        HashTable() : _n(0) { _table.resize(10); }
  • 逻辑说明HashTable 类是基于闭散列实现的哈希表,支持插入、查找、删除操作。
    • _table:一个 vector,用于存储哈希表的槽位,每个槽位是一个 HashData<K, V> 对象。
    • _n:当前存储的有效数据数量。
    • 构造函数:初始化哈希表,默认大小为 10。
2.3.1 Insert 方法
bool Insert(const pair<K, V> &kv)
{
    if (Find(kv.first)) return false;
    if (_n * 10 / _table.size() >= 7)
    {
        HashTable<K, V, Hash> newHT;
        for (auto &e : _table)
        {
            if (e._state == EXIST)
                newHT.Insert(e._kv);
        }
        _table.swap(newHT._table);
    }

    Hash hf;
    size_t hashi = hf(kv.first) % _table.size();
    while (_table[hashi]._state == EXIST)
    {
        ++hashi;
        hashi %= _table.size();
    }

    _table[hashi]._kv = kv;
    _table[hashi]._state = EXIST;
    ++_n;
    return true;
}
  • 逻辑说明
    • 插入逻辑:首先检查是否已经存在该键,如果不存在则进行插入。如果负载因子超过 0.7,进行扩容并重新哈希。
    • 扩容:扩容时,会创建一个新的哈希表 newHT,并将所有旧表中的有效数据重新插入新表中,最后通过 swap 替换表。
    • 线性探测法:插入时,若当前槽位被占用,使用线性探测法寻找下一个可用的槽位。
2.3.2 Find 方法
Data *Find(const K& key)
{
    Hash hf;
    size_t hashi = hf(key) % _table.size();
    while (_table[hashi]._state != EMPTY)
    {
        if (_table[hashi]._state == EXIST && _table[hashi]._kv.first == key)
            return &_table[hashi];
        ++hashi;
        hashi %= _table.size();
    }
    return nullptr;
}
  • 逻辑说明Find 方法用于查找键对应的槽位。
    • 查找逻辑:通过哈希

值找到初始槽位,并通过线性探测法寻找是否存在目标键。如果找到则返回对应的 HashData 指针,否则返回 nullptr

2.3.3 Erase 方法
bool Erase(const K& key)
{
    Data *ret = Find(key);
    if (ret)
    {
        ret->_state = DELETE;
        --_n;
        return true;
    }
    return false;
}
  • 逻辑说明Erase 方法用于删除哈希表中的指定键值对。
    • 删除逻辑:通过 Find 方法找到目标键值对,将其状态标记为 DELETE,并减少有效数据数量。

3. buckethash 命名空间

3.1 HashNode 结构体
template <class T>
struct HashNode
{
    T _data;
    HashNode<T> *_next;

    HashNode(const T& data) : _data(data), _next(nullptr) {}
};
  • 逻辑说明HashNode 结构体定义了哈希表中链表节点的结构。
    • 数据域 _data:存储节点中的数据。
    • 指针域 _next:指向下一个节点的指针,支持链表形式的哈希表处理冲突。
3.2 HashTable 类
template <class K, class T, class Hash, class KeyOfT>
class HashTable
{
    typedef HashNode<T> Node;
    vector<Node *> _table;
    size_t _n = 0;
};
  • 逻辑说明:这是拉链法实现的哈希表。每个槽位存储一个链表的头节点,哈希冲突时将新元素插入链表中。
3.3 Insert 方法
pair<iterator, bool> Insert(const T &data)
{
    Hash hash;
    KeyOfT kof;
    iterator it = Find(kof(data));
    if (it != end()) return make_pair(it, false);

    if (_table.size() == _n)
    {
        vector<Node *> newtable;
        newtable.resize(__stl_next_prime(_table.size()), nullptr);
        for (size_t i = 0; i < _table.size(); ++i)
        {
            Node *cur = _table[i];
            while (cur)
            {
                Node *next = cur->_next;
                size_t hashi = hash(kof(cur->_data)) % newtable.size();
                cur->_next = newtable[hashi];
                newtable[hashi] = cur;
                cur = next;
            }
            _table[i] = nullptr;
        }
        _table.swap(newtable);
    }

    size_t hashi = hash(kof(data)) % _table.size();
    Node *newnode = new Node(data);
    newnode->_next = _table[hashi];
    _table[hashi] = newnode;
    ++_n;
    return make_pair(iterator(newnode, this), true);
}
  • 逻辑说明:拉链法哈希表的插入方法,支持扩容并重新分配节点到新表。

4. __HTIterator 类

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

    typedef HashTable<K, T, Hash, KeyOfT> HT;

    Node *_node;
    HT *_ht;

    __HTIterator(Node *node, HT *ht) : _node(node), _ht(ht) {}

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

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

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

    Self &operator++()
    {
        if (_node->_next)
        {
            _node = _node->_next;
        }
        else
        {
            KeyOfT kof;
            Hash hash;
            size_t hashi = hash(kof(_node->_data)) % _ht->_table.size();
            ++hashi;
            while (hashi < _ht->_table.size())
            {
                if (_ht->_table[hashi])
                {
                    _node = _ht->_table[hashi];
                    break;
                }
                ++hashi;
            }
            if (hashi == _ht->_table.size())
            {
                _node = nullptr;
            }
        }
        return *this;
    }
};
  • 逻辑说明:这是 HashTable 的迭代器实现,支持遍历链表和不同的哈希槽。

5. unordered_map 类

5.1 MapKeyOfT 仿函数结构体
struct MapKeyOfT
{
    const K &operator()(const std::pair<const K, V> &kv)
    {
        return kv.first;
    }
};
  • 逻辑说明:这是一个仿函数,用于从 pair<const K, V> 中提取 K 作为键。
5.2 unordered_map 中的 iterator 类型定义
typedef typename buckethash::HashTable<K, pair<const K, V>, Hash, MapKeyOfT>::iterator iterator;
  • 逻辑说明:定义了 unordered_map 的迭代器类型,基于哈希表的迭代器实现。
5.3 begin 和 end 方法
iterator begin()
{
    return _ht.begin();
}

iterator end()
{
    return _ht.end();
}
  • 逻辑说明:返回 unordered_map 的起始和结束迭代器。
5.4 insert 方法
pair<iterator, bool> insert(const pair<K, V> &data)
{
    return _ht.Insert(data);
}
  • 逻辑说明:向 unordered_map 中插入键值对,返回插入的迭代器和成功标志。

6. unordered_set 类

6.1 SetKeyOfT 仿函数结构体
struct SetKeyOfT
{
    const K &operator()(const K &key)
    {
        return key;
    }
};
  • 逻辑说明:仿函数用于从键 K 中提取自身,unordered_set 中键和值相同。
6.2 unordered_set 中的 iterator 类型定义
typedef typename buckethash::HashTable<K, K, Hash, SetKeyOfT>::iterator iterator;
  • 逻辑说明:定义 unordered_set 的迭代器类型,基于哈希表实现。
6.3 Insert 方法
pair<iterator, bool> Insert(const K &key)
{
    return _ht.Insert(key);
}
  • 逻辑说明:向 unordered_set 中插入元素,返回插入结果和迭代器。
6.4 test_unordered_set 测试函数
void test_unordered_set()
{
    unordered_set<int> us;
    us.Insert(13);
    us.Insert(3);
    us.Insert(23);
    us.Insert(5);
    us.Insert(5);  // 重复插入不会被添加
    us.Insert(6);
    us.Insert(15);
    us.Insert(223342);
    us.Insert(22);

    unordered_set<int>::iterator it = us.begin();
    while (it != us.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    for (auto e : us)
    {
        cout << e << " ";
    }
    cout << endl;
}
  • 逻辑说明:这是一个测试函数,用于验证 unordered_set 的插入和遍历操作。

7. 全部代码

7.1 HashTable.hpp

#pragma once
#include <vector>
#include <string>
#include <utility>
#include <iostream>
#include <cassert>

using namespace std;

//下面写hashfunc模板类,用于计算任意类型key的哈希值
template <class K>
struct HashFunc
{
    size_t operator()(const K& key)
    {
        //这里返回的是size_t的类型是因为,后面需要进行计算位置索引,需要用到整形
        return (size_t)key;//强转//默认返回key的本身,如果key是char类型会进行隐式转换为size_t类型
    }
};

//当我们传入string的时候,我们可以进行模板特化
template <>
struct HashFunc<string>
{
    size_t operator()(const string& key)
    {
        size_t hash = 0;

        // 使用BKDR哈希算法
        for(auto ch : key)
        {
            hash = hash * 131 + ch;
        }
        return hash;
    }

};

namespace closehash//闭散列
{
    enum State
    {
        EMPTY,
        EXIST,
        DELETE,
    };

    template <class K, class V>
    struct HashData
    {
        pair<K, V> _kv;
        State _state = EMPTY;
    };

    template <class K, class V, class Hash = HashFunc<K>>
    class HashTable
    {
        typedef HashData<K, V> Data;

    private:
        vector<Data> _table;
        size_t _n = 0; // 表中存贮的有效数据的元素

    public:
        HashTable()
            : _n(0)
        {
            _table.resize(10);
        }

        bool Insert(const pair<K, V> &kv)
        {
            if (Find(kv.first))
            {
                return false;
            }
            
            // 大于标定负载因子,就需要扩容
            if (_n * 10 / _table.size() >= 7)
            {
                HashTable<K, V, Hash> newHT;
                for(auto &e : _table)
                {
                    if(e._state == EXIST)
                    {
                        newHT.Insert(e._kv);
                    }
                }
                _table.swap(newHT._table);//swap之后,newHT会析构,_table指向了newHT的内存空间
            }

            // 现在我们可以往表里面插入数据了
            Hash hf;//哈希函数对象,便于后面计算索引值
            //计算索引值
            size_t hashi = hf(kv.first) % _table.size();
            while(_table[hashi]._state ==EXIST)
            {
                ++hashi;
                hashi %= _table.size();//防止索引值越界
                
            }

            //找到空位置
            _table[hashi]._kv = kv;
            _table[hashi]._state = EXIST;
            ++_n;

            return true;
        }

        Data *Find(const K& key)
        {
            Hash hf;
            size_t hashi = hf(key) % _table.size();
            while(_table[hashi]._state != EMPTY)
            {
                if(_table[hashi]._state == EXIST && _table[hashi]._kv.first == key)
                {
                    return &_table[hashi];//找到目标元素,返回指针
                }
                //下面这两步其实包括了两种情况,一种是存在但不是,一种是DELETE
                ++hashi;
                hashi %= _table.size();//只要进行线性探测,都要确保索引值不要越界
            }

            return nullptr;//没找到
        }

        bool Erase(const K& key)
        {
            Data *ret = Find(key);
            if(ret)//找到传一个指针
            {
                ret->_state = DELETE;
                --_n;
                return true;
            }
            else//没找到
            {
                return false;

            }
        }
    };

     void testHT1()
    {
        HashTable<int, int> ht;
        int a[] = {18, 8, 7, 27, 57, 3, 38, 18};
        for (auto e : a)
        {
            ht.Insert(make_pair(e, e));
        }

        ht.Insert(make_pair(17, 17));
        ht.Insert(make_pair(5, 5));

        cout << ht.Find(7) << endl;
        cout << ht.Find(8) << endl;

        ht.Erase(7);
        cout << ht.Find(7) << endl;
        cout << ht.Find(8) << endl;
    }

    void TestHT2()
    {
        string arr[] = {"苹果", "西瓜", "香蕉", "草莓", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉"};

        // HashTable<string, int, HashFuncString> countHT;
        HashTable<string, int> countHT;
        for(auto &e : arr)
        {
            HashData<string, int> *ret = countHT.Find(e);
            if (ret) // 有的话+1
            {
                ret->_kv.second++;
            }
            else // 没有的话置1
            {
                countHT.Insert(make_pair(e, 1));
            }
        }

        HashFunc<string> hf;
        cout << hf("abc") << endl;
        cout << hf("bac") << endl;
        cout << hf("cba") << endl;
        cout << hf("aad") << endl;
    }
};


//哈希桶(拉链法)
namespace buckethash
{
    
    //先定义哈希表节点的结构体
    template <class T>
    struct HashNode
    {
        T _data;
        HashNode<T> *_next;

        //构造函数
        HashNode(const T& data)
            : _data(data)
            , _next(nullptr)
        {
        }
        
    };

    //前置声明
    //在hash桶这个命名空间中,我们写迭代器结构体的时候,之前是没有HashTable的,所以我们需要前置声明,
    //相当于告诉迭代器这是个哈希表类
    template <class K, class T, class Hash, class KeyOfT>
    class HashTable;

    //下面我们写迭代器
    template <class K, class T, class Hash, class KeyOfT>
    struct __HTIterator
    {
        typedef HashNode<T> Node;
        typedef __HTIterator<K, T, Hash, KeyOfT> Self;

        typedef HashTable<K, T, Hash, KeyOfT> HT;

        //迭代器为了封装节点
        Node *_node;
        HT *_ht;

        //写构造函数
        __HTIterator(Node *node, HT *ht)
              : _node(node)
              , _ht(ht)
        {
        }

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

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

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

        }

        //前置++(重点)
        Self &operator++()
        {
            if(_node->_next)
            {
                _node = _node->_next;
            }
            else//如果当前节点没有下一个节点,则说明当前桶已经遍历完了,需要遍历下一个桶
            {
                KeyOfT kof;
                Hash hash;
                // 这里的kof是为了用于map和set的用于提取key的仿函数,然后调用hash函数,获得key的哈希值(key可能是int,char之类的)
                size_t hashi = hash(kof(_node->_data)) % _ht->_table.size();
                ++hashi;
                while (hashi < _ht->_table.size())//确保hashi不会越界
                {
                    if(_ht->_table[hashi])//指向当前节点,如果非空,则++到当前节点
                    {
                        _node = _ht->_table[hashi];
                        break;
                    }
                    else
                    {
                        ++hashi;//否则继续++,直到找到下一个非空的桶

                    }

                }
                //如果从这里出来了,说明已经遍历完了所有桶,则将_node置为空
                if(hashi == _ht->_table.size())
                {
                    _node = nullptr;
                }
            }
            return *this;
        }
    };

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

        friend struct __HTIterator<K, T, Hash, KeyOfT>;

    private:
        vector<Node *> _table; // 指针数组
        size_t _n = 0;         // 记录当前元素个数

    public:
        typedef __HTIterator<K, T, Hash, KeyOfT> iterator;

        iterator begin()
        {
            //我们需指向数组的第一个元素,确保非空
            for(size_t i = 0; i < _table.size(); ++i)
            {
                if(_table[i])
                {
                    return iterator(_table[i], this);
                }
            }

            return iterator(nullptr, this);

        }

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

        HashTable()
            : _n(0)
        {
            _table.resize(__stl_next_prime(0));

        }

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

                _table[i] = nullptr;//防止野指针
                
            }
        }

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

            iterator it = Find(kof(data));
            if(it != end())
            {
                return make_pair(it, false);
            }

            //负载因子控制在1, 超过就扩容
            if(_table.size() == _n)
            {
                //原始写法,在上面那个hashtable 即 开2倍原始空间新表 传旧表 交换表
                //优化写法,

                vector<Node *> newtable;
                newtable.resize(__stl_next_prime(_table.size()), nullptr);

                //这里是将旧表数据迁移到新表
                for(size_t i = 0; i < _table.size(); ++i)
                {
                    Node *cur = _table[i];
                    while(cur)
                    {
                        Node *next = cur->_next;
                        size_t hashi = hash(kof(cur->_data)) % newtable.size();
                       //size_t hashi = Hash()(cur->_data) % newtable.size();

                        //以上获得索引值,下面直接头插到桶中
                        cur->_next = newtable[hashi];
                        newtable[hashi] = cur;

                        cur = next;
                    }
                    // 处理完当前链表,将原链表中的指针置为空
                    _table[i] = nullptr;
                }

                _table.swap(newtable);//交换两个vector

            }

            //下面部分是无需扩容的
            size_t hashi = hash(kof(data)) % _table.size();
            Node *newnode = new Node(data);
            newnode->_next = _table[hashi];
            _table[hashi] = newnode;
            ++_n;

            return make_pair(iterator(newnode, this), true);//重点
        }    

        iterator Find(const K &key)
        {
            KeyOfT kof;
            Hash hash;
            //size_t hashi = hash(kof(key)) % _table.size();
            size_t hashi = Hash()(key) % _table.size();//?
            Node *cur = _table[hashi];
            while (cur)
            {
                if(kof(cur->_data) == key)
                {
                    return iterator(cur, this);
                }
                else
                {
                    cur = cur->_next;
                }
            }
            return end();
            
        }

        bool Erase(const K &key)
        {
            Hash hash;
            KeyOfT kof;
            size_t hashi = Hash()(key) % _table.size();
            //size_t hashi = hash(kof(key)) % _table.size();
            Node *cur = _table[hashi];
            Node *prev = nullptr;
            while(cur)//当cur存在时
            {
                if(kof(cur->_data) == key)//找到对应节点
                {
                    if(cur == _table[hashi])
                    {
                        _table[hashi] = cur->_next;//如果cur是第一个节点,则直接将第一个节点指向cur的下一个节点
                    }
                    else//当cur不是第一个节点时,我们需要将前后节点连接起来
                    {
                        //这里的的prev是cur的前一个节点,cur是当前节点,赋值是通过后面的操作
                        prev->_next = cur->_next;
                    }

                    delete cur;
                    --_n;

                    return true;
                }
                else
                {
                    prev = cur;
                    cur = cur->_next;//如果没找到,则将cur指向下一个节点
                }
            }
            
            //如果走出来了,说明没有找到对应的节点,返回false
            return false;
        }

        //原来我们对拉链法的hash表操作时,当负载因子到一时我们会对其扩容即变为原来size的两倍
        //然后再将旧表数据映射到新表
        //但是,这样操作我们遇到的hash冲突依旧是很多,所以我们根据前人研究出来的最佳扩容方法
        //我们每次扩容会变到当前size的大约2倍左右,同时是质数。以及我们将其设为内联函数以减少调用开销

        inline unsigned long __stl_next_prime(unsigned long n)
        {
            static const int __stl_num_primes = 28;
            static const unsigned long __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 (int i = 0; i < __stl_num_primes; ++i)
            {
                if(__stl_prime_list[i] > n)
                {
                    return __stl_prime_list[i];
                }
            }

            return __stl_prime_list[__stl_num_primes - 1];
        }
    };
};

7.2 UnorderedMap.hpp

#include "HashTable.hpp"

namespace YU
{
    template <class K, class V, class Hash = HashFunc<K>>
    class unordered_map
    {
        //我们先写map的仿函数对应KeyOfT
        struct MapKeyOfT
        {
            //现在对()进行重载
            const K &operator()(const std::pair<const K, V> &kv) 
            {
                //这里注意key是不能修改的,所以用const引用
                return kv.first;
            }
        };//小提示:一般来说,只有函数可以不加:。例如class, struct , enum,namspace等都需要加:。

    public:
        // 我们这里对迭代器起别名,同时这句话有几个细节
        // 1. typename 编译器是从前往后看的,所以这里需要typename告诉编译器,HashFunc<K>是类型,先用着,后面就知道了
        // 2.MapKeyOfT代替了原有的KeyOfT,因为我们要对HashTable进行代码复用可以用于Map和Set。
        // 同时我们要记住,在原有的HashTable中,我们并没有对KeyOfT进行定义,也是为了代码的复用
        typedef typename buckethash::HashTable<K, pair<const K, V>, Hash, MapKeyOfT>::iterator iterator;
        
        iterator begin()
        {
            return _ht.begin();//这里我们直接调用HashTable中的begin函数,因为HashTable中的begin函数已经帮我们写好了
        }

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

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

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

        bool Erase(const K &key)
        {
            return _ht.Erase(key);
        }

        // iterator Find(const K &key)
        // {
        //     return _ht.Find(key);
        // }

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

    void test_unordered_map()
    {
        string arr[] = {"ƻ", "", "㽶", "ݮ", "ƻ", "", "ƻ", "ƻ", "", "ƻ", "㽶", "ƻ", "㽶"};

        unordered_map<string, int> countMap;
        for (auto &e : arr)
        {
            countMap[e]++;
        }

        for (const auto &kv : countMap)
        {
            cout << kv.first << ":" << kv.second << endl;
        }

        cout << "_____________" << endl;

        YU::unordered_map<int, std::string> map4;
        map4.insert(std::make_pair(1, "apple"));
        map4.insert(std::make_pair(2, "banana"));
        map4.insert(std::make_pair(3, "cherry"));
        cout << map4.Erase(2) << endl;

        //assert(map4.Find(2) == map4.end());
    }
};

7.3 UnorderedSet.hpp

#include "HashTable.hpp"

#pragma once

namespace YU
{
    template <class K, class Hash = HashFunc<K>>
    class unordered_set
    {
        struct SetKeyOfT
        {
            const K &operator()(const K &key)
            {
                return key;
            }
        };

    public:
        typedef typename buckethash::HashTable<K, K, Hash, SetKeyOfT>::iterator iterator;
        iterator begin()
        {
            return _ht.begin();
        }

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

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

    private:
        buckethash::HashTable<K, K, Hash, SetKeyOfT> _ht;
    };

    void test_unordered_set()
    {
        unordered_set<int> us;
        us.Insert(13);
        us.Insert(3);
        us.Insert(23);
        us.Insert(5);
        us.Insert(5);
        us.Insert(6);
        us.Insert(15);
        us.Insert(223342);
        us.Insert(22);

        unordered_set<int>::iterator it = us.begin();
        while (it != us.end())
        {
            cout << *it << " ";
            ++it;
        }
        cout << endl;

        for (auto e : us)
        {
            cout << e << " ";
        }
        cout << endl;
    }
};

7.4 test_02.cc

#include "HashTable.hpp"
#include "UnorderedMap.hpp"
#include "UnorderedSet.hpp"

int main()
{
    YU::test_unordered_map();
    YU::test_unordered_set();

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值