std::unordered_map(提供自己的Hash函数和等价准则)
来源:互联网 发布:网页美工设计工资多少 编辑:程序博客网 时间:2024/06/09 17:26
在使用容器std::unordered_map< key, value >时,当key是内置类型或者std::string时,容器都能正常使用,而且由于查找时间为O(1),在编程时,特别适合充当hash_table来使用。
如果key是自定义类型时,直接使用std::unordered_map,编译时会报错,错误信息为:”error C2338: The C++ Standard doesn’t provide a hash for this type.”大意是,C++标准库没有为该类型提供hash操作!因此,针对自定义类型,我们在使用std::unordered_map时必须提供自定义的Hash函数。
注:以下内容全部参考和引用自《C++标准库》(第二版)
提供自己的Hash函数
所有的hash table都需要一个hash函数,把你放进去的元素的value映射至某个相关的bucket(注:标准库的unordered_map,底层实现是基于hashtable的,其避免冲突的方法是使用开链(seperate chaining)法,这种做法是在每一个表格元素中维护一个list,每个表格元素称之为buket(桶),如下图(摘自《STL源码剖析》))。
它的目标是,两个相等的value总是导致相同的bucket索引,而不同的value理应导致不同的bucket索引。对于任何范围内的(被传入的)value,hash函数应该提供良好的hash value分布。
Hash函数必须是个函数,或function object,它接收一个元素类型下的value作为参数,并返回一个类型为std::size_t的value。因此,bucket的当前数量并未考虑。将其返回值映射至合法的bucket索引范围内,是由容器内部完成。因此,你的目标是提供一个函数,可以把不同的元素值均匀映射至区间[0, size_t)内。
下面示范的是如何提供你自己的hash函数:
#include <functional>class Customer{ ...};class CustomerHash{public: std::size_t operator()(const Customer& c) const { return ... }};
在这里,CustomerHash是一个function object,为class Customer定义出hash函数。
如果不愿意传递一个function object成为容器的一部分,你也可以传递一个hash函数作为构造函数实参。然而请注意,hash函数相应的template类型也必须对应设妥:
std::size_t customer_hash_func(const Customer& c){ return ...}std::unordered_set<Customer, std::size_t(*)(const Customer&)> custset(20, customer_hash_func);
在这里,customer_hash_func()被传递为构造函数第二个实参,其类型为”一个pointer,指向某函数,该函数接受一个Customer并返回一个std::size_t”,作为第二template实参。
如果没有给予特殊的hash函数,默认的hash函数是hash<>,这是< functional >提供的一个function object,可以对付常见类型:包括所有整数类型、所有浮点数类型、pointer、std::string,以及若干特殊类型。这些之外的类型,就必须提供你自己的hash函数。
提供一个好的hash函数,说起来容易做起来难。就像搭便车一样,可以使用默认的hash函数来完成自己的hash函数。一个天真(naive)的做法是,单纯把那些数据栏”由默认之hash函数产生”的所有hash value加起来。举个例子:
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); }};
这里,返回的hash value只不过是Customer的数据栏fname、lname和no的hash value总和。如果预定义的所有hash函数对这些数据栏的类型以及所给予的值都能运作良好,那么三个值的总和必然也在[0, size_t)范围内。根据一般溢出规则(common overflow rule),上述结果值应该也能够有良好的分布。
然而专家认为,这仍然是粗劣的hash函数。提供一个良好的hash函数可能是十分棘手的事,似乎不如想象中那么轻松。
一个较好的做法如下,使用由Boost提供的hash函数和一个便利的接口:
#include <functional>template<typename T>inline void hash_combine(std::size_t& seed, const T& val){ seed ^= std::hash<T>()(val)+0x9e3779b9 + (seed << 6) + (seed >> 2);}template<typename T>inline void hash_val(std::size_t& seed, const T& val){ hash_combine(seed, val);}template<typename T, typename... Types>inline void hash_val(std::size_t& seed, const T& val, const Types&... args){ hash_combine(seed, val); hash_val(seed, args...);}template<typename... Types>inline std::size_t hash_val(const Types& ...args){ std::size_t seed = 0; hash_val(seed, args...); return seed;}
这里实现出一个辅助函数hash_val(),使用variadic template(可变参数模板),允许调用时给予任意数量、任意类型的元素,然后逐一个别处理(计算)hash value。例如:
class CustomerHash{public: std::size_t operator()(const Customer& c) const { return hash_val(c.fname, c.lname, c.no); }};
在其内部,hash_combine()会被调用。若干实验证明,它是”一般性hash函数”的优秀候选。
提供自己的等价准则(Equivalence Criterion)
作为unordered容器类型的第三(set)或第四(map)template参数,可以传递等价准则(equivalence criterion),那应该是一个predicate(判别式),用以找出同一个bucket内的相等value。默认使用的是equal_to<>, 它以operator==进行比较。基于此,提供合法等价准则的最方便做法就是为自己的类型提供operator==(如果它没有预先被定义为成员函数或全局函数)。例如:
class Customer{ ...};bool operator==(const Customer& c1, const Customer& c2){ ...}std::unordered_set<Customer, CustomerHash> custset;std::unordered_map<Customer, std::string, CustomHash> custmap;
当然,也可以提供自己的等价准则,例如:
#include <functional>class Customer{ ...};class CustomerEqual{public: bool operator()(const Customer& c1, const Customer&c2) const { return ... }};std::unordered_set<Customer, CustomerHash, CustomerEqual> custset;std::unordered_map<Customer, std::string, CustomHash, CustomerEqual> custmap;
这里针对类型Customer定义了一个function object,必须在其中实现operator()使它能够比较两个元素(对map而言是两个key)并返回一个bool值指示他们是否相等。
只要value在当前的等价准则下被视为相等,他们也应该在当前的hash函数下产生相同的hash value。基于这个原因,一个unordered容器如果被实例化时带有一个非默认的等价准则,通常也需要一个非默认的hash函数。
提供自己的Hash函数和等价准则
下面的程序展示了如何为类型Customer定义及指定一个hash函数和一个等价准则。
#include <functional>template<typename T>inline void hash_combine(std::size_t& seed, const T& val){ seed ^= std::hash<T>()(val)+0x9e3779b9 + (seed << 6) + (seed >> 2);}template<typename T>inline void hash_val(std::size_t& seed, const T& val){ hash_combine(seed, val);}template<typename T, typename... Types>inline void hash_val(std::size_t& seed, const T& val, const Types&... args){ hash_combine(seed, val); hash_val(seed, args...);}template<typename... Types>inline std::size_t hash_val(const Types& ...args){ std::size_t seed = 0; hash_val(seed, args...); return seed;}class Customer{public: Customer(const std::string& fn, const std::string& ln, long no) : fname(fn), lname(ln), no(no) { } friend std::ostream& operator<<(std::ostream& strm, const Customer& c) { return strm << "[" << c.fname << "," << c.lname << "," << c.no << "]"; } friend class CustomerHash; friend class CustomerEqual;private: std::string fname; std::string lname; long no;};class CustomerHash{public: std::size_t operator()(const Customer& c) const { return hash_val(c.fname, c.lname, c.no); }};class CustomerEqual{public: bool operator()(const Customer& c1, const Customer& c2) const { return c1.no == c2.no; }};int main(){ std::unordered_map<Customer, int, CustomerHash, CustomerEqual> custmap; custmap.insert(std::pair<Customer, int>(Customer("nico", "journalist", 42), 1)); std::cout << custmap[Customer("nico", "journalist", 42)] << std::endl; return 0;}
使用Lambda作为Hash函数和等价准则
使用lambda具体指定hash函数和/或等价准则,例如:
...class Customer{public: Customer(const std::string& fn, const std::string& ln, long no) : fname(fn), lname(ln), no(no) { } std::string firstname() const { return fname; } std::string lastname() const { return lname; } long number() const { return no; } friend std::ostream& operator<<(std::ostream& strm, const Customer& c) { return strm << "[" << c.fname << "," << c.lname << "," << c.no << "]"; }private: std::string fname; std::string lname; long no;};int main(){ auto hash = [](const Customer& c) { return hash_val(c.firstname(), c.lastname(), c.number()); }; auto eq = [](const Customer& c1, const Customer& c2) { return c1.number() == c2.number(); }; std::unordered_map<Customer, int, decltype(hash) , decltype(eq)> custmap(10, hash, eq); custmap.insert(std::pair<Customer, int>(Customer("nico", "journalist", 42), 1)); std::cout << custmap[Customer("nico", "journalist", 42)] << std::endl; return 0;}
在VS2013中,以上代码会报错,错误信息为:”C3497: 无法构造 lambda 实例”,出现该错误的原因是->“lambda 的默认构造函数被隐式删除”,因此,在VS2013里无法使用Lambda函数作为Hash函数和等价准则。
修改办法如下,将Hash函数和等价准则作为普通函数,以函数指针的形式作为构造函数的一部分,具体如下:
...std::size_t hash(const Customer& c){ return hash_val(c.firstname(), c.lastname(), c.number());};bool eq(const Customer& c1, const Customer& c2){ return c1.number() == c2.number();};int main(){ std::unordered_map<Customer, int, decltype(&hash) , decltype(&eq)> custmap(10, hash, eq); custmap.insert(std::pair<Customer, int>(Customer("nico", "journalist", 42), 1)); std::cout << custmap[Customer("nico", "journalist", 42)] << std::endl;}
修改后的代码,可以正常运行。但有一个需要特别注意的地方,decltype(hash)与decltype(&hash)返回的类型是不一样的,取获取函数指针的方式为后者,这里我们是需要函数指针的,因此需要选择后者(decltype(&hash)),如果选择前者会导致运行错误。
- std::unordered_map(提供自己的Hash函数和等价准则)
- boost::unordered_map 和 std::map 的对比(包括速度和内存消耗)
- std:sort和字符串hash函数
- std::unordered_map::unordered_map
- C++11中std::unordered_map的使用
- boost:unordered_map和std::map的使用详解和性能比较
- C++11:基于std::unordered_map和共享锁构建线程安全的map
- c++ unordered_map/set自定义对象的hash
- std::unordered_map用法
- 等价类划分准则
- 函数的等价变形
- hash算法和常见的hash函数
- 用std::pair做Unordered_map的key(C++)
- C++学习笔记-----std::pair作为unordered_map的key
- NOIP 2005 等价表达式(hash算法)
- C++学习:hash(unordered_map)
- hash_map和unordered_map的使用
- unordered_map和map的区别
- 反射:使用反射调用方法
- 三.获取登陆cookie,并且利用cookie访问登陆后的界面
- linux下执行mysql的sql文件
- java编译原理2
- 文件格式之rmvb
- std::unordered_map(提供自己的Hash函数和等价准则)
- 程序员福利:当编程语言都变成女孩子
- Android ViewPager类
- jekins出现svn: E200015: Authentication cancelled
- Struts2笔记13 自定义拦截器
- 首页学习--3D轮播
- IDEA 创建spring boot项目无法启动
- RestFul
- STL之三:deque用法详解