KeyEqual 在 std::unordered_set/std::unordered_map 中的有用性
Usefulness of KeyEqual in std::unordered_set/std::unordered_map
我知道这可能是一个模糊的问题,但我想知道当自定义比较器对 std 中的哈希容器有用时,现实世界的情况是什么。 我知道它在有序容器中很有用,但对于哈希容器来说,这似乎有点奇怪。
这样做的原因是,根据比较器相等的元素的哈希值需要相同,我相信在大多数情况下,这实际上意味着将查找/插入元素转换为一些常见的表示形式(它更快更容易实现)。
例如:
- 一组不区分大小写的字符串:如果要正确散列,无论如何都需要将整个字符串大写/小写。
- 分数集(其中 2/3 == 42/63):您需要将 42/63 转换为 2/3,然后对其进行哈希处理...
所以我想知道是否有人可以提供一些真实世界的例子来说明自定义std::unordered_
模板参数的有用性,以便我可以在我编写的未来代码中识别这些模式。
注1:"对称参数"(std::map
允许定制比较器,因此std::unordred_
也应该可定制)是我考虑过的东西,我认为它没有说服力。
注2:为了简洁起见,我在帖子中混合了2种比较器(<
和==
),我知道std::map
使用<
,std::unordered_map
使用==
。
根据 https://en.cppreference.com/w/cpp/container/unordered_set
在内部,元素不按任何特定顺序排序,但 组织到存储桶中。元素放入哪个存储桶取决于 完全基于其值的哈希值。这允许快速访问 单个元素,因为一旦计算了哈希,它就引用了 将元素放入的确切存储桶。
因此,哈希函数定义了您的元素最终将进入的存储桶,但是一旦确定了存储桶,为了找到该元素,将使用operator ==
。
基本上operator ==
用于解决哈希冲突,因此,您需要哈希函数和operator ==
保持一致。此外,如果您的运算符operator ==
说两个元素相等,则该集合将不允许重复。
对于有关自定义的内容,我认为对string
s 进行不区分大小写的set
的想法很好:给定两个字符串,您需要提供一个不区分大小写的哈希函数,以允许set
确定它必须存储字符串的存储桶。然后,您需要提供一个自定义KeyEqual
,以允许集合实际检索元素。
过去,我必须处理的一个案例是一种允许用户插入字符串的方法,跟踪他们的插入顺序,但避免重复。因此,给定一个结构,例如:
struct keyword{
std::string value;
int sequenceCounter;
};
您希望仅根据value
检测重复项。我想出的解决方案之一是具有自定义比较器/哈希函数的unordered_set
,该函数仅使用value
。这允许我在允许插入之前检查密钥是否存在。
一个有趣的用法是为一组给定的对象定义内存效率索引(术语的数据库意义)。
例
假设我们有一个程序,其中包含此类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;
}
我们需要通过任何唯一属性的值有效地检索对象。
实现比较
假设字符串比较是常量时间(字符串长度有限),则给出时间复杂度。
1) 单unordered_map
使用单个键索引的单个map
(例如:email
):
std::unordered_map<std::string,Person> indexedByEmail;
- 时间复杂度:通过除
email
以外的任何唯一属性进行查找都需要遍历映射:平均 O(N)。 - 内存使用情况:
email
值重复。这可以通过使用带有自定义哈希和比较的单个set
来避免(见3)。
2)多重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;
- 时间复杂度:通过使用适当的映射,任何唯一属性的查找都是平均值 O(1)。
- 内存使用:效率低下,因为所有
string
重复。
3)多重unordered_set
,自定义哈希和比较:
通过自定义哈希和比较,我们定义了不同的unordered_set
,这些将仅哈希和比较对象的特定字段。这些集可用于执行查找,就像项目存储在 2 中的map
中一样,但不重复任何字段。
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;
- 时间复杂度:任何唯一属性的查找仍然是 O(1) 平均值。
- 内存使用情况:比 2 好得多):没有
string
重复。
现场演示
哈希函数本身以某种方式提取特征,并且 比较器的工作是区分特征是否相同
使用数据的"外壳",您可能不需要修改比较器
简而言之:在数据上放置一个特征外壳。特征负责进行比较
事实上,我不太明白你的问题描述。我的演讲在逻辑上不可避免地是混乱的。请理解。 :)
- 使用std::multimap迭代器创建std::list
- C++中std::resize(n)和std::shrink_to_fit之间的区别
- 来自 std::list 的迭代器 .end() 按预期返回"0xcdcdcdcdcdcdcdcd"但 .begin()
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 如何导出包含具有"std::unique_ptr"值的"std::map"属性的
- 从持续时间构造std::chrono::system_clock::time_point
- std::具有相同基类的类的变体
- std::向量与传递值的动态数组
- 使用std::vector的OpenCL矩阵乘法
- std::map<struct,struct>::find 找不到匹配项,但是如果我循环通过 begin() 到 end(),我在那里看到匹配项
- std::condition_variable::wait()如何评估给定的谓词
- 如何获取std::result_of函数的返回类型
- std::原子加载和存储都需要吗
- 将对象移动到std::shared_ptr
- POCO::PostgreSQL:如何将std::vector支持添加到`Binder::bind`
- 使用一个考虑到std::map中键值的滚动或换行的键
- 如何从 std::atomic 中提取指针 T<T>?
- 为什么 std::unique 不调用 std::sort?
- 是否可以<int>在一行中将向量值分配给 std::unordered<int,std::vector<int>>?
- unordered map -在c++ std::unordered_map中预分配桶