【C++】unordered_map和unordered_set的模拟实现

news2024/11/24 5:06:55

一、改造HashTable

实现了哈希表(开散列),再将其封装为unordered_mapunordered_set

HashTable的改造与RBTree的改造大致相同:

  1. 改造节点
template<class T>
struct HashNode
{
	//std::pair<K, V> _kv;
	//HashNode<K, V>* _next;
	T _data;
	HashNode<T>* _next;
	HashNode(const T& data)
		:_data(data)
		, _next(nullptr)
	{}
};
  1. 改造HashTable
// 这里的Hash可以不用给缺省参数,由上层传递
template<class K, class T, class Hash, class KeyOfT>
class HashTable
{
public:
	typedef HashNode<T> Node;
	// ……
	bool Insert(const T& data)
	{
		KeyOfT kot; // --> 用kot获取data中的key
		if (Find(kot(data)))
			return false;
		// ……
	}
	Node* Find(const K& key)
	{
		// ……
		while (cur)
		{
			if (KeyOfT()(cur->_data) == key)
			{
				return cur;
			}
			else
			{
				cur = cur->_next;
			}
		}
		return nullptr;
	}
	bool Erase(const K& key)
	{
		// ……
		while (cur)
		{
			if (KeyOfT()(cur->_data) == key)
			{
				// ……
			}
			// ……
		}
	}
private:
	std::vector<Node*> _tables;// 指针数组
	size_t _n = 0;
};
  1. unordered_map:
namespace nb
{
	template<class K, class V, class Hash = HashFunc<K>>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const std::pair<const K, V>& kv)
			{
				return kv.first;
			}
		};
	private:
		bucketHash::HashTable < K, std::pair<const K, V>, Hash, MapKeyOfT> _ht;
	};
};
  1. unordered_set:
namespace nb
{
	template<class K, class Hash = HashFunc<K>>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	private:
		bucketHash::HashTable <K, K, Hash, SetKeyOfT> _ht;
	};
};

二、实现迭代器

首先要知道的是哈希表的迭代器是单向迭代器,先看源码的实现:

迭代器类:
在这里插入图片描述

operator++:

在这里插入图片描述

  • 迭代器类
// 类的前置声明
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;

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

	Self& operator++()
	{
		if (_node->_next) // node下一个不为空走到下一个节点
		{
			_node = _node->_next;
		}
		else // node的下一个为空找非空桶
		{
			// 计算哈希地址
			size_t hashi = Hash()(KeyOfT()(_node->_data)) % _ht->_tables.size();
			++hashi;
			while (hashi < _ht->_tables.size())
			{
				if (_ht->_tables[hashi])//找到非空桶
				{
					_node = _ht->_tables[hashi];
					break;
				}
				else
				{
					++hashi;
				}
			}
			// 所有桶走完了,走到end
			if (hashi == _ht->_tables.size())
				_node = nullptr;
			return *this;
		}
	}

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

	Node* _node;
	HT* _ht;
};

HashTable层:

template<class K, class T, class Hash, class KeyOfT>
class HashTable
{
	// 声明 _HTIterator 为HashTable类的友元
	// 可以让 _HTIterator 结构体访问哈希表中的私有成员和保护成员
	template<class K, class T, class Hash, class KeyOfT>
	friend struct _HTIterator;
public:
	typedef HashNode<T> Node;
	typedef _HTIterator<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 iterator(nullptr, this);
	}

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

源码中const迭代器并没有复用普通迭代器的代码:

在这里插入图片描述

为什么不复用?为什么不按照之前map和set迭代器的实现方式呢?实现一遍看看有什么问题

  • 迭代器类:
//template<class K, class T, class Hash, class KeyOfT>
template<class K, class T, class Ref, class Ptr, class Hash, class KeyOfT>
struct _HTIterator
{
	typedef HashNode<T> Node;
	//typedef _HTIterator<K, T, Hash, KeyOfT> Self;
	typedef _HTIterator<K, T, Ref, Ptr, Hash, KeyOfT> Self;
	typedef _HTIterator<K, T, T&, T*, Hash, KeyOfT> iterator;
	typedef HashTable<K, T, Hash, KeyOfT> HT;

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

	//T& operator*()
	Ref operator*()
	{
		return _node->_data;
	}
	//T* operator->()
	Ptr operator->()
	{
		return &_node->_data;
	}
	// ……
	Node* _node;
	HT* _ht;
};

  • HashTable:
template<class K, class T, class Hash, class KeyOfT>
class HashTable
{
	// 声明 _HTIterator 为HashTable类的友元
	// 可以让 _HTIterator 结构体访问哈希表中的私有成员和保护成员
	//template<class K, class T, class Hash, class KeyOfT>
	//friend struct _HTIterator;
	template<class K, class T, class Ref, class Ptr, class Hash, class KeyOfT>
	friend struct _HTIterator;
public:
	typedef HashNode<T> Node;
	//typedef _HTIterator<K, T, Hash, KeyOfT> iterator;
	typedef _HTIterator<K, T, T&, T*, Hash, KeyOfT> iterator;
	// const迭代器复用普通迭代器代码
	typedef _HTIterator<K, T, const T&, const T*, Hash, KeyOfT> const_iterator;

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

	iterator end()
	{
		return iterator(nullptr, this);
	}
	// const迭代器
	const_iterator begin() const
	{
		for (size_t i = 0; i < _tables.size(); ++i)
		{
			if (_tables[i])
				return const_iterator(_tables[i], this);
		}
		return const_iterator(nullptr, this);
	}

	const_iterator end() const
	{
		return const_iterator(nullptr, this);
	}
	// ……
	std::vector<Node*> _tables;// 指针数组
	size_t _n = 0;
};
  • unordered_map:
template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
	struct MapKeyOfT
	{
		const K& operator()(const std::pair<const K, V>& kv)
		{
			return kv.first;
		}
	};

public:
	// 普通迭代器
	typedef typename bucketHash::HashTable<K, std::pair<const K, V>, Hash, MapKeyOfT>::iterator iterator;
	// const迭代器
	typedef typename bucketHash::HashTable<K, std::pair<const K, V>, Hash, MapKeyOfT>::const_iterator const_iterator;

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

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

	// const迭代器
	const_iterator begin() const
	{
		return _ht.begin();
	}
	const_iterator end() const
	{
		return _ht.end();
	}
	// ……
private:
	bucketHash::HashTable < K, std::pair<const K, V>, Hash, MapKeyOfT> _ht;
};
  • unordered_set:
template<class K, class Hash = HashFunc<K>>
class unordered_set
{
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};

public:
	// 注意typename的使用
	typedef typename bucketHash::HashTable<K, K, Hash, SetKeyOfT>::const_iterator iterator;
	typedef typename bucketHash::HashTable<K, K, Hash, SetKeyOfT>::const_iterator const_iterator;
	iterator begin()
	{
		return _ht.begin();
	}
	iterator end()
	{
		return _ht.end();
	}

	const_iterator begin() const
	{
		return _ht.begin();
	}
	const_iterator end() const
	{
		return _ht.end();
	}

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

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

unordered_set的普通迭代器与set的迭代器一样也不能修改key,所以普通迭代器(unordered_set层)也是const迭代器(HashTable层)

在这里插入图片描述

测试unordered_map:

void TestMap()
{
	std::string arr[] = { "cherry" };
	unordered_map<std::string, int> countMap;
	for (auto& e : arr)
	{
		countMap[e]++;
	}
	// it是const迭代器,countMap.begin()是普通迭代器无法转换
	nb::unordered_map<std::string, int>::const_iterator it = countMap.begin();// Error

	while (it != countMap.end())// Error:没有匹配的运算符
	{
		std::cout << it->first << " " << it->second << std::endl;
		++it;
	}
}

在这里插入图片描述

这里如果我们实现一个像RBTree迭代器一样的特殊构造那么就可以解决。

在这里插入图片描述

再测试就没有问题:

在这里插入图片描述

再来测试unordered_set:

void test()
{
	nb::unordered_set<int> us;
	us.insert(13);

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

也没问题:

在这里插入图片描述

再看一段测试代码:

void func(const unordered_set<int>& us)
{
	unordered_set<int>::const_iterator it = us.begin();
	while (it != us.end())
	{
		cout << *it << endl;
		++it;
	}
}

void test()
{
	unordered_set<int> us;
	us.insert(10);
	func(us);
}

在这里插入图片描述

在这里插入图片描述

再看看我们模拟实现的unordered_set:

void func(const unordered_set<int>& us)
{
	nb::unordered_set<int>::const_iterator it = us.begin();
	while (it != us.end())
	{
		std::cout << *it << " ";
		++it;
	}
	std::cout << std::endl;
}
void test()
{
	nb::unordered_set<int> us;
	us.insert(10);
}

结果编译错误:

在这里插入图片描述

在这里插入图片描述

错误分析:

在这里插入图片描述

那把迭代器的构造改成const Node*const HT*
答案是不行,因为这样一改,迭代器的成员变量也要跟着改,这样迭代器就只能被const对象使用了。

在这里插入图片描述

所以普通迭代器和const迭代器需要分开实现。这样一来有多处代码需要改动具体见代码,当然本文的实现肯定是没有STL库中的好,我们只需要知道底层实现逻辑即可,而不是造一个更好的轮子。

// const迭代器类
template<class K, class T, class Hash, class KeyOfT>
//template<class K, class T, class Ref, class Ptr, class Hash, class KeyOfT>
struct const_HTIterator
{
	typedef HashNode<T> Node;
	typedef _HTIterator<K, T, Hash, KeyOfT> iterator;
	typedef const_HTIterator<K, T, Hash, KeyOfT> const_iterator;
	typedef const_HTIterator<K, T, Hash, KeyOfT> Self;
	//typedef _HTIterator<K, T, Ref, Ptr, Hash, KeyOfT> Self;
	//typedef _HTIterator<K, T, T&, T*, Hash, KeyOfT> iterator;
	typedef HashTable<K, T, Hash, KeyOfT> HT;

	const_HTIterator(const Node* node, const HT* ht)
		:_node(node)
		, _ht(ht)
	{
		//std::cout << "_HTIterator()" << std::endl;
	}

	// 普通迭代器构造const迭代器
	const_HTIterator(const iterator& it)
		:_node(it._node)
		, _ht(it._ht)
	{
		std::cout << "iterator --> const_iterator" << std::endl;
	}

	const T& operator*()
		//Ref operator*()
	{
		return _node->_data;
	}
	const T* operator->()
		//Ptr operator->()
	{
		return &_node->_data;
	}

	Self& operator++()
	{
		if (_node->_next) // node下一个不为空走到下一个节点
		{
			_node = _node->_next;
		}
		else // node的下一个为空找非空桶
		{
			// 计算哈希地址
			size_t hashi = Hash()(KeyOfT()(_node->_data)) % _ht->_tables.size();
			++hashi;
			while (hashi < _ht->_tables.size())
			{
				if (_ht->_tables[hashi])//找到非空桶
				{
					_node = _ht->_tables[hashi];
					break;
				}
				else
				{
					++hashi;
				}
			}
			// 所有桶走完了,走到end
			if (hashi == _ht->_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;
	}

	const Node* _node;
	const HT* _ht;
};

unordered_map和unordered_set的模拟实现-GitHub

如有错误,望指正🌹


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/531717.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【AI思维空间】ChatGPT纵横编程世界,点亮智慧火花 | 京东云技术团队

作者&#xff1a;京东零售 王英杰 概述 该文档记录云交易开发小伙伴儿们在开发过程中的实际应用案例&#xff0c;记录典型案例&#xff0c;以解决开发过程中的实际问题为主&#xff0c;涵盖设计方案、编码、测试、集成、部署等等。 目的&#xff1a;贡献最佳实践&#xff0c;…

案例6:Java社区志愿者服务系统设计与实现开题报告

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

全面升级:知否AI问答场景导航功能震撼登场

今日&#xff0c;知否AI问答平台推出全新的场景功能&#xff0c;为用户提供更全面、高效的智能问答服务&#xff0c;再也不用担心找不到适合自己的场景入口了。 此次升级涵盖了50多个场景&#xff0c;包括论文助手、公司文案、营销文案、多语言翻译、行政公文、科研课题、招投…

bat脚本、dos命令

bat脚本 bat脚本就是DOS批处理脚本&#xff0c;就是将一系列DOS命令按照一定顺序排列而形成的集合&#xff0c;运行在windows命令行环境上。这个文件的每一行都是一条DOS命令 在命令提示下键入批处理文件的名称&#xff0c;或者双击该批处理文件&#xff0c;系统就会调用Cmd.…

服务器信息收集

#Version&#xff1a;1.2 #Modify Date&#xff1a;2013-05-21 #说明&#xff1a; #该脚本可以获取计算机名,域名,IP地址,操作系统版本,CPU名称单颗CPU内核数量*CPU个数,内存大小(GB),单块磁盘大小,计算机序列号,制造商,计算机型号 #该脚本先将计算机信息输出到txt文件中&…

浅谈Gradle构建工具

一、序言 常见的项目构建工具有Ant、Maven、Gradle&#xff0c;以往项目常见采用Maven进构建&#xff0c;但随着技术的发展&#xff0c;越来越多的项目采用Gradle进行构建&#xff0c;例如 Spring-boot。Gradle站在了Ant和Maven构建工具的肩膀上&#xff0c;使用强大的表达式语…

千万不要乱操作了!医院机房这么做真高级

各类中心数据机房广泛分布于银行、库房、交通、电信、医院、教育等行业。系统故障和人为操作不当可能导致各种业务中断或数据丢失&#xff0c;进而影响企业业务的停滞和运行。 医院管理3大难题和挑战 01.缺乏预警、告警机制 医院在使用自动化监控系统之前&#xff0c;主要靠人…

springboot足球赛事安排球队管理系统

系统主要有球队赛程安排&#xff0c;包括比赛数据&#xff0c;球员信息&#xff0c;球员实时数据&#xff0c;球队纪念品售卖 Spring Boot 是 Spring 家族中的一个全新的框架&#xff0c;它用来简化Spring应用程序的创建和开发过程。也可以说 Spring Boot 能简化我们之前采用SS…

09——path的使用

一、path 是 svg 中最强大的图形 用于定义一个 路径所有命令均允许小写字母。大写 表示绝对定位&#xff0c;小写 表示 相对定位 &#xff08;相对于上一个结束的坐标&#xff09;d 属性中包含所有路径的点&#xff0c;可根据命令缩写 自由组合 命令 名称 …

Windows 11 反转鼠标和触摸板滚动方向

如果在使用 Windows 10 设备时不喜欢鼠标或触摸板的「下滚上移&#xff0c;上滚下移」方式&#xff0c;可以通过调整「Windows 设备」或更改注册表 2 种方式来反转滚动方向。 下面就为大家介绍详细步骤。 Windows 11反转触摸板滚动方向 要通过调整「Windows 设置」反转触摸板…

企业为什么需要一套CRM系统进行销售管理

随着市场竞争的日益激烈和消费者的日益挑剔&#xff0c;企业要想在市场中取得优势地位&#xff0c;就需要通过有效的销售管理来提高销售业绩。而CRM系统作为企业实现销售管理的最佳选择&#xff0c;越来越受到企业的重视和关注。 一、CRM系统的优势 1. 提高销售流程管理效率 C…

车载AUTOSAR和OSEK关系及网络管理的异同(NM)

AUTOSAR和OSEK关系及网络管理比较 AUTOSAR和OSEK关系及网络管理比较 AUTOSAR和OSEK关系及网络管理比较AUTOSAR与OSEK的关系AUTOSARAUTOSAR架构和标准的目标是&#xff1a;AUTOSAR架构的主要特点是&#xff1a;AUTOSAR标准有四个核心内容&#xff1a; OSEK其特点主要有以下几个方…

基于SSM+JSP的高校学生健康档案管理系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

【ChatGPT】ChatGPT-5 到底有多强?

目录 1、ChatGPT-5 到底有多强2、技术方向3、系统特点4、系统应用5、ChatGPT-5为什么停止训练&#xff1f; 1、ChatGPT-5 到底有多强 OpenAI 最新的自然语言处理技术 ChatGPT-5 近期发布&#xff0c;拥有过去版本的一系列升级和改进。那么&#xff0c;在 ChatGPT-4 强大的基础…

STM32 调试TM7711驱动原理图驱动代码

本文使用工程代码如下 (1条消息) STM32调试TM7711驱动原理图驱动源代码&#xff0c;参考如下博客&#xff0c;有原理图设计资源-CSDN文库 背景 项目选用TM7711&#xff0c;还是很令人吃惊的&#xff0c;主要是有如下几个理由 第一就是便宜 第二精度高 STM32的ADC精度不够…

STM32 学习笔记_8 定时器中断:输入捕获

输入捕获 输入引脚发生跳变时&#xff0c;cnt的值会被记录到ccr中&#xff0c;可以用于测量pwm信号等。配置成pwmi模式还可以同时测量频率和占空比。主从触发模式可以实现硬件全自动测量。 高级定时器和通用定时器才有的功能。 这个功能只能测数字信号&#xff0c;对于a信号…

【k8s概念】一文搞懂k8s核心概念,吐血整理~两万字~!!!

文章目录 1. k8s简介1.1 k8s概念1.2 作用/功能 2. k8s集群搭建方式3. k8s核心组件3.1 Master Node&#xff08;控制平面组件&#xff09;3.2 Worker Node 4. k8s核心概念4.1 容器4.2 工作负载——Pod4.3 Pod控制器4.3.1 ReplicationController(RC)4.3.2 ReplicaSet(RS)4.3.3 De…

四大关键举措高效管控企业税务风险

税务风险是指企业在税务管理中&#xff0c;由于涉税行为因未能正确有效地遵守税法规定&#xff0c;而导致企业出现经济损失以及企业形象受损。企业税务风险的来源主要有两方面&#xff1a;第一&#xff0c;企业的纳税行为不符合税收法律法规的规定或对相关的税务政策未能全面理…

隐私计算论文合集「多方安全计算系列」第一期

当前&#xff0c;隐私计算领域正处于快速发展的阶段&#xff0c;涌现出了许多前沿的SOTA算法和备受关注的顶会论文。为了方便社区小伙伴学习最新算法、了解隐私计算行业最新进展和应用&#xff0c;隐语开源社区在GitHub创建了Paper推荐项目awesome-PETs&#xff08;PETs即Priva…

生态伙伴 | 硬创大赛新起航!携手华强科创广场,助力硬科技创业者

01 大赛介绍 中国硬件创新创客大赛始于2015年&#xff0c;由深圳华秋电子有限公司主办&#xff0c;至今已经成功举办八届&#xff0c;赛事范围覆盖华南、华东、华北三大地区&#xff0c;超10个省市区域。 大赛影响了超过45万工程师群体&#xff0c;吸引了35000多名硬创先锋报…