异构查找意味着我们可以使用另一个有意义的兼容类型(例如std::string
)索引到包含absl::string_view
类型的键的哈希映射中。例如,以下代码有效(出于兼容性原因,我在代码中使用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
来保存字符串 View 的内容。std::vector<T>
也具有字符串 View 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>>
产生了相同的结果,因此进行这项工作不应有太多障碍。 最佳答案
您可以通过定义重写哈希和比较的类型来实现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
的查找有效。但是[]
仍然会失败。为什么?因为[]
运算符是upsert-如果密钥不存在,它将创建密钥。 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 []
覆盖,如果 map 条目存在,该覆盖不复制 vector ,而仍然仅进行一次哈希处理。这有点棘手,仍然可能需要进行额外的比较,但是我认为您可以使用存储密钥和已经计算出的哈希值的辅助类型来完成此操作: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://godbolt.org/z/fecevE关于c++ - 在C++中实现跨度的异构查找,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/64673031/