std::unordered_set 的第三个模板参数必须是比较对象的类型。 table_key_equiv_pred 不是类型。默认为std::equal_to,其定义类似于:
template <typename T>
struct equal_to {
bool operator()(const T &lhs, const T &rhs) const {
return lhs == rhs;
}
};
您希望能够使用ints 或任何其他类型在集合中搜索nodes。这样做std::unordered_set 并非不可能,只是有点痛苦。问题是std::unordered_set 首先比较哈希值,然后再比较实际对象。如果您想使用int 搜索node,那么两者必须具有相同的哈希值并且比较相等。
从问题中的代码中,我收集到 node 不仅仅是 level,因此 4 的哈希值不会等于 node 的哈希值level == 4(除非您已将 std::hash<node> 定义为仅返回 level)。这意味着即使是第 4 级节点,在确定 4 不在集合中之前,甚至不会使用自定义比较对象。
能够搜索与任何东西进行比较的节点是很棘手的,std::unordered_set 因为您需要一个“透明”哈希函数。我建议您切换到二叉树集 (std::set)。此集合比较元素的顺序(使用小于比较)。这意味着您只需要定义一个小于比较函数。
实际创建自定义比较对象并执行以下操作是相当少见的:std::set<node, MyCustomLessThan>。通常所做的是为该类型定义一个自定义的小于运算符,然后让默认的比较对象(在本例中为 std::less)调用该运算符。现在你要少用“透明”了。
std::less<int> 比较两个整数。 std::less<node> 比较两个节点。 std::less<> 将任何事物与任何事物进行比较,称为不透明。 std::less<> 的定义类似于:
template <>
struct less<void> {
template <typename Left, typename Right>
bool operator()(const Left &lhs, const Right &rhs) const {
return lhs < rhs;
}
};
所以你想要的数据结构是std::set<node, std::less<>> 但是你仍然需要定义整数和节点之间的比较。您可以通过重载小于运算符来做到这一点。
// I'm not confident that these implementations are correct
// Nullable objects make this pretty tricky!
bool operator<(const node &lhs, const int rhs) {
return !lhs || lhs->level < rhs;
}
bool operator<(const int lhs, const node &rhs) {
return rhs && lhs < rhs->level;
}
bool operator<(const node &lhs, const node &rhs) {
// I'll probably get this wrong so I'll leave this up to you!
}
这是一个完整的例子(注意这使用了 C++14 的特性):
#include <set>
#include <iostream>
struct Node {
int level;
int thing;
};
bool operator<(const Node lhs, const int rhs) {
return lhs.level < rhs;
}
bool operator<(const int lhs, const Node rhs) {
return lhs < rhs.level;
}
bool operator<(const Node lhs, const Node rhs) {
// A better way to do this is with std::tie
// see https://stackoverflow.com/a/16090720/4093378
if (lhs.level < rhs.level) return true;
if (lhs.level > rhs.level) return false;
if (lhs.thing < rhs.thing) return true;
if (lhs.thing > rhs.thing) return false;
return false;
}
int main() {
std::set<Node, std::less<>> set;
set.insert({1, 91});
set.insert({1, 87});
set.insert({2, 43});
set.insert({2, 10});
// find a level 2 node
// there's more than 1 level 2 node so you'll get the first one
// see https://en.cppreference.com/w/cpp/container/set/find
if (auto iter = set.find(2); iter != set.end()) {
// prints "2 - 10"
std::cout << iter->level << " - " << iter->thing << '\n';
}
// get a range of all the level 1 nodes
// see https://en.cppreference.com/w/cpp/container/set/equal_range
auto range = set.equal_range(1);
for (auto iter = range.first; iter != range.second; ++iter) {
// prints "1 - 87" then "1 - 91"
std::cout << iter->level << " - " << iter->thing << '\n';
}
// a neat thing about std::set is that all the elements are kept in order
// that's how std::set differs from std::unordered_set!
// this prints "1 - 87", "1 - 91", "2 - 10" and then "2 - 43"
for (const Node node : set) {
std::cout << node.level << " - " << node.thing << '\n';
}
}
如果您更愿意使用std::unordered_set,则必须使用透明散列函数。 这是 C++20 的一项功能。请阅读我链接的文档以获取更多信息。
为了搜索具有给定级别的节点,级别和节点的哈希必须兼容。这意味着第 5 级的哈希必须与第 5 级节点的哈希相同。
这是一个完整的例子(注意这使用了 C++20 的特性):
#include <iostream>
#include <unordered_set>
struct Node {
int level;
int thing;
};
struct NodeEqual {
struct is_transparent {};
bool operator()(const Node lhs, const int rhs) const noexcept {
return lhs.level == rhs;
}
bool operator()(const int lhs, const Node rhs) const noexcept {
return lhs == rhs.level;
}
bool operator()(const Node lhs, const Node rhs) const noexcept {
return lhs.level == rhs.level && lhs.thing == rhs.thing;
}
};
struct NodeHash {
using transparent_key_equal = NodeEqual;
size_t operator()(const Node node) const noexcept {
// note that the hash depends only on level
// if lots of nodes have the same level,
// then lots of nodes will have the same hash
// this could lead to lots of equality comparisons
return node.level;
}
size_t operator()(const int level) const noexcept {
return level;
}
};
int main() {
std::unordered_set<Node, NodeHash> set;
set.insert({1, 91});
set.insert({1, 87});
set.insert({2, 43});
set.insert({2, 10});
// find a level 2 node
// see https://en.cppreference.com/w/cpp/container/unordered_set/find
if (auto iter = set.find(2); iter != set.end()) {
// prints either "2 - 43" or "2 - 10"
std::cout << iter->level << " - " << iter->thing << '\n';
}
// getting a range of all the level 1 nodes isn't actually possible
// there are a couple of tweaks required to make it possible
// however, these tweaks will further reduce the performance of std::unordered_set
}
我实际上无法测试这个示例(因为它是 C++20),所以我不确定它是否正确。我只能希望我已经正确阅读了文档!
std::set 实际上可能比std::unordered_set 快,除非我对level 的独特性有更多了解。