C++ STL和泛型编程(四)----一个万用的hash function

一、Hash Function通用版型

#inlcude <functional>
class Customer{ // 自定义一个存放于容器的元素的类
public:
	string fname;
	string lname;
	long no;
	Customer(const string _fname, const string _lname, long _no) //constructor
		:fname(_fname), lname(_lname), no(_no) {}

}// 版型1
class CustomerHash{ // 内部operator(),是个仿函数类型
public:
	std::size operator() (const Customer& c) const{
		return ...
	}
};
unordered_set<Customer, CustomerHash> custset; // 使用时第一模板参数只需传入元素类名,
											   // 第二模板参数传入HashFunction类名即可(其里头会自动产生函数对象而被调用)

// 版型2
size_t customer_hash_func(const Customer& c){
	return...
}
unordered_set<Customer, size_t(*)(const Customer&)> custset(20, customer_hash_func);
// 使用时第一模板参数传入元素类名,但第二模板参数传入函数类型
// 然后在创建对象时调用构造函数时,还需传入参数元素值和函数地址(函数名 即到底是哪个函数)

// 版型3(类似系统的已经写好的struct hash<T>{...}的偏特化版本)
template<>
struct hash<Customer>{
	size_t operator() (const Customer& c) const noexcept{
		...
	}
};

二、实现操作

// a native approach
class CustomerHash{
public:
	std::size_t operator() (const Customer& c) const{
		return std::hash<std::string>()(c.fname) + 
			   std::hash<std::string>()(c.lname) +
			   std::hash<long>()(c.no);
	}
}

上述操作只是单纯地将元素的内部成员data机械地拆分为基本类型,然后调用对应的 hash<类型>版本(系统已经写好的关于 基本类型的HashFcn 得出各自的hash-code,然后相加起来。但这样加出来,会容易产生比较多的碰撞,造成串在同一个bucket上的链过长导致搜查较慢。

因而,考虑通过variadic templates(未定型模板)来实现求取hash-code的操作:

#include<functional>
template<typename T>
inline void hash_combine(size_t& seed, const T& val){ // 4,求取seed
	seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
template<typename T>
inline void hash_val(size_t& seed, const T& val){ // 3,最后一步剩余一个传入模板参数时则调用此函数
	hash_combine(seed, val);
}
template<typename T, typename... Types>
inline void hash_val(size_t& seed, const T& val, Types&... args){ // 2,通过自调用不断地将传入的模板参数分割
	hash_combine(seed, val);   // 逐一将传入参数args拆分为val和args‘,并且调用4改变seed
	hash_val(seed, args...);
}
template<typename... Types>
inline size_t hash_val(const Types&... args){ // 1,一开始调用此函数
	size_t seed = 0;
	hash_val(seed, args...);
	return seed;  // seed最终返回的结果是改元素的hash-code
}

class CustomerHash{
public:
	std::size_t operator() (const Customer& c) const{
		return hash_val(c.fname, c.lname, c.no); // 先调用1
	}
};

注意:这里的typename…Types&… 以及 args… 中的 应该看成一个操作符

由上知,hash_val()有三个版本,具体调用哪一个得看对应的传入参数的个数和类型情况。

hash_val(c.fname, c.lname, c.no);
传入三个参数,且第一参数不是size_t类型,所以调用的是1。进入1内部,先声明定义seed=0,然后进入hash_val()函数,并传入参数seed和args…(此时的参数依然没有被拆分)。因为传入第一参数为size_t类型,且后面参数有多个,则调用的是2。

由hash_val(seed, args…);进入2
在调用2时,先将原传入的arg…拆分为val和新的arg’…。然后进入2内部操作,hash_combine(seed, val)调用4,val即为第一个c.fname,然后调用基本类型的HashFcn即==hash<string>()(c.fname)==求得其对应的hash-code,然后再加上一个黄金比例数0x9e3779b9,再加上seed左移6位和右移2位的结果,最终seed再 ‘’^=’’ 操作一下返回则得到最终的seed【这一通操作下来可保证得到的seed足够乱!】。而且此时因为seed的传入是引用传入,所以再4中改变seed也即在全范围内改变了seed。
执行完hash_combine(seed, val)后,退回到2中,将新得到的seed和拆分后比原先传入少一个的args…继续传入hash_val(seed, args…),此时因为arg…还有两个即c.lname和c.no,所以继续调用2。

此时继续将args…拆分为val和args’…【即此时val为c.lname,args为c.no】,继续调用4又获得新修改的seed。然后再回退到2中,继续执行hash_val(seed, args…),但此时,因为args只剩1个传入参数即c.no,所以此时调用的是3的hash_val(size_t& seed, const T& val)。然后3中也是调用4,得到一个最终修改的seed。最终回退到1中,返回最终的seed值即为改元素的hash-code

  • 操作例子:

这里使用的是版型1情况的HashFcn即 unordered_set<Customer, CustomerHash> custset
在这里插入图片描述
然后,通过上述的万用hash function模板class CustomerHash去创建一个仿函数对象hh,然后去求取对应的customers的hash-code,并将得到的对应的 hash-code%11 便可得到最终放置于bucket的编号情况:
在这里插入图片描述

三、以struct hash偏特化形式实现 Hash Function

前面说过在G2.9中,系统并没有默认写好struct hash<string>{…}的偏特化版本,需要自己去编写。但G4.9下,系统已经写好struct hash<string>{…}的偏特化版本在这里插入图片描述

像前面的系统已经写好的基本类型的特化版本的

template<> struct hash<int>{…}
template<> struct hash<char>{…}
template<> struct hash<long>{…}
。。。

为特定的class MyString也写一个特化版本

template<> struct hash<MyString>{…}
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值