目录
-
closehash 命名空间
2.1 State 枚举类
2.2 HashData 结构体
2.3 HashTable 类
2.3.1 Insert 方法
2.3.2 Find 方法
2.3.3 Erase 方法 -
buckethash 命名空间
3.1 HashNode 结构体
3.2 HashTable 类
3.3 Insert 方法 -
unordered_map 类
5.1 MapKeyOfT 仿函数结构体
5.2 unordered_map 中的 iterator 类型定义
5.3 begin 和 end 方法
5.4 insert 方法 -
unordered_set 类
6.1 SetKeyOfT 仿函数结构体
6.2 unordered_set 中的 iterator 类型定义
6.3 Insert 方法
6.4 test_unordered_set 测试函数 -
全部代码
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
。这种实现适用于可以直接进行类型转换的基础类型,比如int
、char
。 - 用途:该哈希函数可以在
unordered_map
或unordered_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_map
和unordered_set
中使用。
- BKDR 算法:这个算法通过逐字符遍历字符串,每个字符的 ASCII 值乘以 131 累加到哈希值中。
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;
}