异构查找意味着我们可以使用另一种有意义的兼容类型(如std::string )索引到包含std::string类型键的散列映射中。例如,以下代码工作正常(出于某些兼容性的原因,我在代码中使用Abseil库而不是C++20 ):
std::string word = "bird";
absl::flat_hash_map<std::string, int> word_map;
word_map[word] = 1;
std::cout << word_map[absl::string_view(word)] << std::endl;这是有意义的,因为我们需要处理哈希表的所有功能是计算哈希函数的能力,以及比较相等的能力。因此,使用此方法读取哈希表应该很简单,编写表也是有意义的,因为哈希表可以创建一个新的std::string,保存字符串视图的内容。
std::vector<T>还具有字符串视图的轻量级模拟,即absl::Span<T>类型。但是,相应的查找不起作用:
std::vector<int> nums = {1, 2, 3, 4};
absl::flat_hash_map<std::vector<int>, int> int_map;
int_map[nums] = 1;
std::cout << int_map[absl::Span<int>(nums)] << std::endl;编译器在最后一行中抱怨operator[]没有匹配项。
问题:如何实现这种异构查找,以便它能够以与字符串和字符串视图相同的方式对向量和跨进行工作?
我可以看到,absl::Hash<std::vector<int>>和absl::Hash<absl::Span<int>>产生了相同的结果,因此不应该有太多的障碍来完成这项工作。
发布于 2020-11-04 09:29:51
您可以通过定义类型来覆盖散列和比较来实现Abseil的异构查找功能。根据文档,必须用is_transparent特征标记它们以支持转换。
struct VectorHash {
using is_transparent = void;
size_t operator()(absl::Span<int> v) const {
return absl::Hash<absl::Span<const int>>{}(v);
}
size_t operator()(const std::vector<int>& v) const {
return absl::Hash<absl::Span<const int>>{}(absl::Span<const int>{ v.data(), v.size() });
}
};
struct VectorEq {
using is_transparent = void;
bool operator()(const std::vector<int>& a, absl::Span<int> b) const {
return std::equal(a.begin(), a.end(), b.begin(), b.end());
}
bool operator()(absl::Span<int> b, const std::vector<int>& a) const {
return std::equal(a.begin(), a.end(), b.begin(), b.end());
}
bool operator()(const std::vector<int>& a, const std::vector<int>& b) const {
return std::equal(a.begin(), a.end(), b.begin(), b.end());
}
bool operator()(absl::Span<int> b, absl::Span<int> a) const {
return std::equal(a.begin(), a.end(), b.begin(), b.end());
}
};
using int_map_t = absl::flat_hash_map<std::vector<int>, int, VectorHash, VectorEq>;这将使使用at或find进行查找工作。但[]仍将失败。为什么?因为[]运算符是一个新插入-如果它不存在,它会创建键。absl::string_view有一个显式的转换操作符到std::string,因此,创建一个新的std::string键是可行的。absl::Span<int>没有将运算符转换为std::vector<int>,因此操作失败。
如果使用at而不是[]不是选项,则仍然可以扩展类型:
struct int_map_t : absl::flat_hash_map<std::vector<int>, int, VectorHash, VectorEq> {
using absl::flat_hash_map<std::vector<int>, int, VectorHash, VectorEq>::flat_hash_map;
using absl::flat_hash_map<std::vector<int>, int, VectorHash, VectorEq>::operator [];
int& operator [](absl::Span<int> v) {
return operator [](std::vector<int> { v.begin(), v.end() });
}
};演示:https://godbolt.org/z/dW4av7
在注释中,您询问如果映射条目存在,是否可以实现不复制向量的operator []覆盖,同时仍然只执行一次哈希操作。这有点麻烦,而且可能还会进行额外的比较,但我认为您可以通过一个同时存储一个键和一个已经计算的散列的助手类型来完成这个任务:
struct VectorHashMemo {
size_t hash;
absl::Span<int> key;
explicit operator std::vector<int>() const {
return { key.begin(), key.end() };
}
};
struct VectorHash {
/* ...existing overloads... */
size_t operator()(VectorHashMemo v) const {
return v.hash;
}
};
struct VectorEq {
/* ...existing overloads... */
bool operator()(const std::vector<int>& a, VectorHashMemo b) const {
return operator()(a, b.key);
}
bool operator()(VectorHashMemo a, const std::vector<int>& b) const {
return operator()(a.key, b);
}
bool operator()(VectorHashMemo b, VectorHashMemo a) const {
return operator()(a.key, b.key);
}
};然后,您可以只显式计算哈希一次,同时访问映射两次:
struct int_map_t : absl::flat_hash_map<std::vector<int>, int, VectorHash, VectorEq> {
using absl::flat_hash_map<std::vector<int>, int, VectorHash, VectorEq>::flat_hash_map;
using absl::flat_hash_map<std::vector<int>, int, VectorHash, VectorEq>::operator [];
int& operator [](absl::Span<int> v) {
VectorHashMemo hash = { absl::Hash<absl::Span<int>>{}(v), v };
auto it = find(hash);
if (it != end()) {
return it->second;
} else {
// calls the explicit conversion operator
return operator [](hash);
}
return operator [](std::vector<int> { v.begin(), v.end() });
}
};https://stackoverflow.com/questions/64673031
复制相似问题