我知道这可能是一个模糊的问题,但是我想知道当自定义比较器对std中的哈希容器有用时在现实情况下是什么情况。 我知道它在有序容器中很有用,但是对于哈希容器,这似乎有点不可思议。
这样做的原因是,根据比较器相等的元素的哈希值必须相同,并且我相信,在大多数情况下,这实际上意味着将lookup / insert元素转换为某种通用表示形式(更快,更容易实行)。
例如:
因此,我想知道是否有人可以提供一些自定义std::unordered_
模板参数有用性的示例,以便在以后编写的代码中识别出这些模式。
注1:“对称参数”(std::map
允许自定义比较器,因此std::unordred_
也应自定义)是我考虑的,但我认为这没有说服力。
注2:为了简洁起见,我在帖子中混合了两种比较器(<
和==
),我知道std::map
使用<
和{{1} }使用std::unordered_map
。
答案 0 :(得分:10)
按照https://en.cppreference.com/w/cpp/container/unordered_set
在内部,元素不是按任何特定顺序排序的,而是 整理成水桶。元素放入哪个存储桶取决于 完全取决于其价值。这样可以快速访问 各个元素,因为一旦计算出哈希值,它就是指 元素所在的确切存储桶。
因此,哈希函数定义了元素将最终存储在的存储桶中,但是一旦确定存储桶,为了找到元素,将使用operator ==
。
基本上,operator ==
用于解决哈希冲突,因此,您需要哈希函数和operator ==
保持一致。此外,如果运算符operator ==
说两个元素相等,则该集合将不允许重复。
关于自定义方面,我认为set
中不区分大小写的string
的想法是一个好主意:给定两个字符串,您将需要提供不区分大小写的哈希函数来允许set
确定存储字符串的存储桶。然后,您将需要提供一个自定义KeyEqual
,以允许集合实际检索元素。
过去,我不得不处理的一种情况是允许用户插入字符串,跟踪其插入顺序但避免重复的一种方式。因此,给定这样的结构:
struct keyword{
std::string value;
int sequenceCounter;
};
您只想根据value
检测重复项。我想到的解决方案之一是带有自定义比较器/哈希函数的unordered_set
,该函数仅使用value
。这使我可以在允许插入之前检查密钥是否存在。
答案 1 :(得分:2)
一种有趣的用法是为一组给定的对象定义内存有效索引(术语的数据库意义)。
假设我们有一个程序,其中包含此类的N
个对象的集合:
struct Person {
// each object has a unique firstName/lastName pair
std::string firstName;
std::string lastName;
// each object has a unique ssn value
std::string socialSecurityNumber;
// each object has a unique email value
std::string email;
}
我们需要通过任何唯一属性的值来有效地检索对象。
假设字符串比较是恒定时间(字符串长度有限),则给出时间复杂度。
unordered_map
使用由单个键索引的单个map
(例如:email
):
std::unordered_map<std::string,Person> indexedByEmail;
email
以外的任何唯一属性进行查询都需要遍历地图:平均O(N)。email
值重复。可以通过使用带有自定义哈希和比较的单个set
来避免这种情况(请参见3)。unordered_map
,没有自定义哈希和比较具有每个唯一属性的映射,以及默认的哈希和比较:
std::unordered_map<std::pair<std::string,std::string>, Person> byName;
std::unordered_map<std::string, const Person*> byEmail;
std::unordered_map<std::string, const Person*> bySSN;
string
重复,内存使用效率低下。unordered_set
,自定义哈希和比较:使用自定义哈希和比较,我们定义了不同的unordered_set
,它将仅哈希和比较对象的特定字段。这些集合可用于执行查找,就像将项目存储在map
中一样(如2)一样,但是无需重复任何字段。
using StrHash = std::hash<std::string>;
// --------------------
struct PersonNameHash {
std::size_t operator()(const Person& p) const {
// not the best hashing function in the world, but good enough for demo purposes.
return StrHash()(p.firstName) + StrHash()(p.lastName);
}
};
struct PersonNameEqual {
bool operator()(const Person& p1, const Person& p2) const {
return (p1.firstName == p2.firstName) && (p1.lastName == p2.lastName);
}
};
std::unordered_set<Person, PersonNameHash, PersonNameEqual> byName;
// --------------------
struct PersonSsnHash {
std::size_t operator()(const Person* p) const {
return StrHash()(p->socialSecurityNumber);
}
};
struct PersonSsnEqual {
bool operator()(const Person* p1, const Person* p2) const {
return p1->socialSecurityNumber == p2->socialSecurityNumber;
}
};
std::unordered_set<const Person*, PersonSsnHash, PersonSsnEqual> bySSN;
// --------------------
struct PersonEmailHash {
std::size_t operator()(const Person* p) const {
return StrHash()(p->email);
}
};
struct PersonEmailEqual {
bool operator()(const Person* p1, const Person* p2) const {
return p1->email == p2->email;
}
};
std::unordered_set<const Person*,PersonEmailHash,PersonEmailEqual> byEmail;
string
复制。答案 2 :(得分:-1)
哈希函数本身以某种方式提取特征,比较器的工作是区分特征是否相同
使用“外壳”数据,您可能不需要修改比较器
简要地说:在数据上放一个功能外壳。功能负责比较
事实上,我不太了解您对问题的描述。我的演讲不可避免地在逻辑上感到困惑。敬请谅解。 :)