为什么我不能用一对作为键来编译 unordered_map?
Why can't I compile an unordered_map with a pair as key?
我正在尝试创建一个 unordered_map
来映射整数对:
#include <unordered_map>
using namespace std;
using Vote = pair<string, string>;
using Unordered_map = unordered_map<Vote, int>;
我有一个 class,我已将一个 Unordered_map
声明为私有成员。
但是,当我尝试编译它时出现以下错误:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/type_traits:948:38: Implicit instantiation of undefined template 'std::__1::hash, std::__1::basic_string > >'
如果我使用像 map<pair<string, string>, int>
这样的常规地图而不是 unordered_map
.
,我不会收到此错误
是否不能在无序映射中使用 pair
作为键?
如您的编译错误所示,在您的 std 命名空间中没有 std::hash<std::pair<std::string, std::string>>
的有效实例化。
根据我的编译器:
Error C2338 The C++ Standard doesn't provide a hash for this
type. c:\program files (x86)\microsoft visual studio
14.0\vc\include\xstddef 381
您可以为 std::hash<Vote>
提供自己的专业化,如下所示:
#include <string>
#include <unordered_map>
#include <functional>
using namespace std;
using Vote = pair<string, string>;
using Unordered_map = unordered_map<Vote, int>;
namespace std
{
template<>
struct hash<Vote>
{
size_t operator()(Vote const& v) const
{
// ... hash function here ...
}
};
}
int main()
{
Unordered_map m;
}
您需要为您的密钥类型提供合适的哈希函数。一个简单的例子:
#include <unordered_map>
#include <functional>
#include <string>
#include <utility>
// Only for pairs of std::hash-able types for simplicity.
// You can of course template this struct to allow other hash functions
struct pair_hash {
template <class T1, class T2>
std::size_t operator () (const std::pair<T1,T2> &p) const {
auto h1 = std::hash<T1>{}(p.first);
auto h2 = std::hash<T2>{}(p.second);
// Mainly for demonstration purposes, i.e. works but is overly simple
// In the real world, use sth. like boost.hash_combine
return h1 ^ h2;
}
};
using Vote = std::pair<std::string, std::string>;
using Unordered_map = std::unordered_map<Vote, int, pair_hash>;
int main() {
Unordered_map um;
}
这会起作用,但没有最好的哈希属性†。你可能想看看像 boost.hash_combine
for higher quality results when combining the hashes. This is also discussed in greater detail – including the aforementioned solution from boost – in this answer.
这样的东西
对于现实世界的使用:Boost 还提供函数集 hash_value
,它已经为 std::pair
以及 std::tuple
和大多数标准容器提供哈希函数。
†更准确的说,会产生太多的碰撞。例如,每个对称对将散列为 0,而仅因排列不同的对将具有相同的散列。这可能适合您的编程练习,但可能会严重损害实际代码的性能。
我解决这个问题的首选方法是定义一个 key
函数,将你的对转换为唯一的整数(或任何可散列的数据类型)。此键不是散列键。它是一对数据的唯一 ID,然后将由 unordered_map
进行最佳哈希处理。例如,您想要定义
类型的 unordered_map
unordered_map<pair<int,int>,double> Map;
并且您想使用Map[make_pair(i,j)]=value
或Map.find(make_pair(i,j))
对地图进行操作。然后你必须告诉系统如何散列一对整数make_pair(i,j)
。相反,我们可以定义
inline size_t key(int i,int j) {return (size_t) i << 32 | (unsigned int) j;}
然后将地图类型更改为
unordered_map<size_t,double> Map;
我们现在可以使用Map[key(i,j)]=value
或Map.find(key(i,j))
对地图进行操作。每个 make_pair
现在变成调用内联 key
函数。
此方法保证密钥将被最佳散列,因为现在散列部分由系统完成,系统将始终选择内部散列 table 大小为素数以确保每个桶都是平等的可能。但是你必须让自己 100% 确定 key
对每一对都是唯一的,即没有两个不同的对可以有相同的密钥,否则可能很难找到错误。
对于pair key,我们可以使用boost pair hash函数:
#include <iostream>
#include <boost/functional/hash.hpp>
#include <unordered_map>
using namespace std;
int main() {
unordered_map<pair<string, string>, int, boost::hash<pair<string, string>>> m;
m[make_pair("123", "456")] = 1;
cout << m[make_pair("123", "456")] << endl;
return 0;
}
同样,我们可以对向量使用 boost hash,
#include <iostream>
#include <boost/functional/hash.hpp>
#include <unordered_map>
#include <vector>
using namespace std;
int main() {
unordered_map<vector<string>, int, boost::hash<vector<string>>> m;
vector<string> a({"123", "456"});
m[a] = 1;
cout << m[a] << endl;
return 0;
}
在 by Baum mit Augen, the user Joe Black on using a lambda expressions instead of defining a hash function. I agree with the of Baum mit Augen, that this might harm readability, especially if you want to implement a more universal solution. Therefore, I'd like to keep my example short by focusing on a specific solution for std::pair<std::string, std::string>
, as presented by the OP. The example also uses a handcrafted 组合 std::hash<std::string>
函数调用的评论中:
using Vote = std::pair<std::string, std::string>;
auto hash = [](const Vote& v){
return std::hash<std::string>()(v.first) * 31 + std::hash<std::string>()(v.second);
};
using Unordered_map = std::unordered_map<Vote, int, decltype(hash)>;
Unordered_map um(8, hash);
如果使用pair
不是严格要求,你可以简单地使用map两次。
#include <unordered_map>
using namespace std;
using Unordered_map = unordered_map<string, unordered_map<string, int>>;
Unordered_map um;
um["Region1"]["Candidate1"] = 10;
cout << um["Region1"]["Candidate1"]; // 10
参考:C++ Standard Library: A tutorial and reference, Second version第7.9.2章:创建和控制无序容器
我在 Google 中找到的所有解决方案都使用 XOR
生成 pair
的哈希码,这非常糟糕。参见 why-is-xor-the-default-way-to-combine-hashes. However, the book has given us the best solution, using hash_combine
, which is taken from Boost
. The solution is much better than XOR when I tested it in Online Judge(Atcoder)。我将代码组织为模板如下。您可以尽可能多地复制和粘贴它。并且可以很方便地更改它以适应任何自定义 struct/class.
更新:为元组添加哈希模板。
#include <functional>
namespace hash_tuple {
template <typename TT> struct hash {
size_t operator()(TT const &tt) const { return std::hash<TT>()(tt); }
};
// from boost (functional/hash):
// see http://www.boost.org/doc/libs/1_35_0/doc/html/hash/combine.html template
template <class T> inline void hash_combine(std::size_t &seed, T const &v) {
seed ^= hash_tuple::hash<T>()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
// Recursive template code derived from Matthieu M.
template <class Tuple, size_t Index = std::tuple_size<Tuple>::value - 1>
struct HashValueImpl {
void operator()(size_t &seed, Tuple const &tuple) const {
HashValueImpl<Tuple, Index - 1>{}(seed, tuple);
hash_combine(seed, std::get<Index>(tuple));
}
};
template <class Tuple> struct HashValueImpl<Tuple, 0> {
void operator()(size_t &seed, Tuple const &tuple) const {
hash_combine(seed, std::get<0>(tuple));
}
};
template <typename... TT> struct hash<std::tuple<TT...>> {
size_t operator()(std::tuple<TT...> const &tt) const {
size_t seed = 0;
HashValueImpl<std::tuple<TT...>>{}(seed, tt);
return seed;
}
};
// auxiliary generic functions to create a hash value using a seed
template <typename T> inline void hash_val(std::size_t &seed, const T &val) {
hash_combine(seed, val);
}
template <typename T, typename... Types>
inline void hash_val(std::size_t &seed, const T &val, const Types &... args) {
hash_combine(seed, val);
hash_val(seed, args...);
}
template <typename... Types>
inline std::size_t hash_val(const Types &... args) {
std::size_t seed = 0;
hash_val(seed, args...);
return seed;
}
struct pair_hash {
template <class T1, class T2>
std::size_t operator()(const std::pair<T1, T2> &p) const {
return hash_val(p.first, p.second);
}
};
} // namespace hash_tuple
#include <bits/stdc++.h>
int main() {
using ll = long long;
// std::unordered_map<std::pair<ll, ll>, ll, hash_tuple::pair_hash>
// hashmapPair; std::unordered_set<std::pair<ll, ll>, hash_tuple::pair_hash>
// hashsetPair;
std::unordered_map<std::pair<ll, ll>, ll, hash_tuple::pair_hash>
hashmapPair;
hashmapPair[{0, 0}] = 10;
std::unordered_set<std::pair<ll, ll>, hash_tuple::pair_hash> hashsetPair;
hashsetPair.insert({1, 1});
using TI = std::tuple<ll, ll, ll, ll>;
std::unordered_map<TI, ll, hash_tuple::hash<TI>> hashmapTuple;
hashmapTuple[{0, 1, 2, 3}] = 10;
std::unordered_set<TI, hash_tuple::hash<TI>> hashsetTuple;
hashsetTuple.emplace(0, 1, 2, 3);
return 0;
}
有一个 hack 解决此类问题
使用 std:unordered_map
个 string
看下面的例子-
我需要散列矩形的端点(角)
Error Approach
unordered_map<pair<int, int>, int> M; //ERROR
pair<int, int> p;
M[p]++;
Hack
unordered_map<string, int> M;
pair<int, int> p;
string s = to_string(p.first) + "_" + to_string(p.second);
M[s]++;
如果您需要创建小数或双精度的散列作为键,这种 hack 甚至可以工作:)
我已经简化了@YoungForest 的 以仅按照 OP 的要求使用对(= 不使用任意长度的元组)。我还最小化了样板代码:
#include <functional>
#include <iostream>
#include <unordered_map>
#include <utility> # pair
using namespace std;
// from boost (functional/hash):
// see http://www.boost.org/doc/libs/1_35_0/doc/html/hash/combine.html template
template <class T> inline void hash_combine(size_t &seed, T const &v) {
seed ^= hash<T>()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
struct pair_hash {
template <class T1, class T2>
size_t operator()(const pair<T1, T2> &p) const {
size_t seed = 0;
hash_combine(seed, p.first);
hash_combine(seed, p.second);
return seed;
}
};
int main() {
unordered_map<pair<int, int>, int, pair_hash> d;
d[{1, 2}] = 3;
cout << d.find({1, 2})->second << endl;
return 0;
}
它使用与 boost 库中相同的逻辑(比 xor 版本更好)。
我正在尝试创建一个 unordered_map
来映射整数对:
#include <unordered_map>
using namespace std;
using Vote = pair<string, string>;
using Unordered_map = unordered_map<Vote, int>;
我有一个 class,我已将一个 Unordered_map
声明为私有成员。
但是,当我尝试编译它时出现以下错误:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/type_traits:948:38: Implicit instantiation of undefined template 'std::__1::hash, std::__1::basic_string > >'
如果我使用像 map<pair<string, string>, int>
这样的常规地图而不是 unordered_map
.
是否不能在无序映射中使用 pair
作为键?
如您的编译错误所示,在您的 std 命名空间中没有 std::hash<std::pair<std::string, std::string>>
的有效实例化。
根据我的编译器:
Error C2338 The C++ Standard doesn't provide a hash for this type. c:\program files (x86)\microsoft visual studio 14.0\vc\include\xstddef 381
您可以为 std::hash<Vote>
提供自己的专业化,如下所示:
#include <string>
#include <unordered_map>
#include <functional>
using namespace std;
using Vote = pair<string, string>;
using Unordered_map = unordered_map<Vote, int>;
namespace std
{
template<>
struct hash<Vote>
{
size_t operator()(Vote const& v) const
{
// ... hash function here ...
}
};
}
int main()
{
Unordered_map m;
}
您需要为您的密钥类型提供合适的哈希函数。一个简单的例子:
#include <unordered_map>
#include <functional>
#include <string>
#include <utility>
// Only for pairs of std::hash-able types for simplicity.
// You can of course template this struct to allow other hash functions
struct pair_hash {
template <class T1, class T2>
std::size_t operator () (const std::pair<T1,T2> &p) const {
auto h1 = std::hash<T1>{}(p.first);
auto h2 = std::hash<T2>{}(p.second);
// Mainly for demonstration purposes, i.e. works but is overly simple
// In the real world, use sth. like boost.hash_combine
return h1 ^ h2;
}
};
using Vote = std::pair<std::string, std::string>;
using Unordered_map = std::unordered_map<Vote, int, pair_hash>;
int main() {
Unordered_map um;
}
这会起作用,但没有最好的哈希属性†。你可能想看看像 boost.hash_combine
for higher quality results when combining the hashes. This is also discussed in greater detail – including the aforementioned solution from boost – in this answer.
对于现实世界的使用:Boost 还提供函数集 hash_value
,它已经为 std::pair
以及 std::tuple
和大多数标准容器提供哈希函数。
†更准确的说,会产生太多的碰撞。例如,每个对称对将散列为 0,而仅因排列不同的对将具有相同的散列。这可能适合您的编程练习,但可能会严重损害实际代码的性能。
我解决这个问题的首选方法是定义一个 key
函数,将你的对转换为唯一的整数(或任何可散列的数据类型)。此键不是散列键。它是一对数据的唯一 ID,然后将由 unordered_map
进行最佳哈希处理。例如,您想要定义
unordered_map
unordered_map<pair<int,int>,double> Map;
并且您想使用Map[make_pair(i,j)]=value
或Map.find(make_pair(i,j))
对地图进行操作。然后你必须告诉系统如何散列一对整数make_pair(i,j)
。相反,我们可以定义
inline size_t key(int i,int j) {return (size_t) i << 32 | (unsigned int) j;}
然后将地图类型更改为
unordered_map<size_t,double> Map;
我们现在可以使用Map[key(i,j)]=value
或Map.find(key(i,j))
对地图进行操作。每个 make_pair
现在变成调用内联 key
函数。
此方法保证密钥将被最佳散列,因为现在散列部分由系统完成,系统将始终选择内部散列 table 大小为素数以确保每个桶都是平等的可能。但是你必须让自己 100% 确定 key
对每一对都是唯一的,即没有两个不同的对可以有相同的密钥,否则可能很难找到错误。
对于pair key,我们可以使用boost pair hash函数:
#include <iostream>
#include <boost/functional/hash.hpp>
#include <unordered_map>
using namespace std;
int main() {
unordered_map<pair<string, string>, int, boost::hash<pair<string, string>>> m;
m[make_pair("123", "456")] = 1;
cout << m[make_pair("123", "456")] << endl;
return 0;
}
同样,我们可以对向量使用 boost hash,
#include <iostream>
#include <boost/functional/hash.hpp>
#include <unordered_map>
#include <vector>
using namespace std;
int main() {
unordered_map<vector<string>, int, boost::hash<vector<string>>> m;
vector<string> a({"123", "456"});
m[a] = 1;
cout << m[a] << endl;
return 0;
}
在 std::pair<std::string, std::string>
, as presented by the OP. The example also uses a handcrafted 组合 std::hash<std::string>
函数调用的评论中:
using Vote = std::pair<std::string, std::string>;
auto hash = [](const Vote& v){
return std::hash<std::string>()(v.first) * 31 + std::hash<std::string>()(v.second);
};
using Unordered_map = std::unordered_map<Vote, int, decltype(hash)>;
Unordered_map um(8, hash);
如果使用pair
不是严格要求,你可以简单地使用map两次。
#include <unordered_map>
using namespace std;
using Unordered_map = unordered_map<string, unordered_map<string, int>>;
Unordered_map um;
um["Region1"]["Candidate1"] = 10;
cout << um["Region1"]["Candidate1"]; // 10
参考:C++ Standard Library: A tutorial and reference, Second version第7.9.2章:创建和控制无序容器
我在 Google 中找到的所有解决方案都使用 XOR
生成 pair
的哈希码,这非常糟糕。参见 why-is-xor-the-default-way-to-combine-hashes. However, the book has given us the best solution, using hash_combine
, which is taken from Boost
. The solution is much better than XOR when I tested it in Online Judge(Atcoder)。我将代码组织为模板如下。您可以尽可能多地复制和粘贴它。并且可以很方便地更改它以适应任何自定义 struct/class.
更新:为元组添加哈希模板。
#include <functional>
namespace hash_tuple {
template <typename TT> struct hash {
size_t operator()(TT const &tt) const { return std::hash<TT>()(tt); }
};
// from boost (functional/hash):
// see http://www.boost.org/doc/libs/1_35_0/doc/html/hash/combine.html template
template <class T> inline void hash_combine(std::size_t &seed, T const &v) {
seed ^= hash_tuple::hash<T>()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
// Recursive template code derived from Matthieu M.
template <class Tuple, size_t Index = std::tuple_size<Tuple>::value - 1>
struct HashValueImpl {
void operator()(size_t &seed, Tuple const &tuple) const {
HashValueImpl<Tuple, Index - 1>{}(seed, tuple);
hash_combine(seed, std::get<Index>(tuple));
}
};
template <class Tuple> struct HashValueImpl<Tuple, 0> {
void operator()(size_t &seed, Tuple const &tuple) const {
hash_combine(seed, std::get<0>(tuple));
}
};
template <typename... TT> struct hash<std::tuple<TT...>> {
size_t operator()(std::tuple<TT...> const &tt) const {
size_t seed = 0;
HashValueImpl<std::tuple<TT...>>{}(seed, tt);
return seed;
}
};
// auxiliary generic functions to create a hash value using a seed
template <typename T> inline void hash_val(std::size_t &seed, const T &val) {
hash_combine(seed, val);
}
template <typename T, typename... Types>
inline void hash_val(std::size_t &seed, const T &val, const Types &... args) {
hash_combine(seed, val);
hash_val(seed, args...);
}
template <typename... Types>
inline std::size_t hash_val(const Types &... args) {
std::size_t seed = 0;
hash_val(seed, args...);
return seed;
}
struct pair_hash {
template <class T1, class T2>
std::size_t operator()(const std::pair<T1, T2> &p) const {
return hash_val(p.first, p.second);
}
};
} // namespace hash_tuple
#include <bits/stdc++.h>
int main() {
using ll = long long;
// std::unordered_map<std::pair<ll, ll>, ll, hash_tuple::pair_hash>
// hashmapPair; std::unordered_set<std::pair<ll, ll>, hash_tuple::pair_hash>
// hashsetPair;
std::unordered_map<std::pair<ll, ll>, ll, hash_tuple::pair_hash>
hashmapPair;
hashmapPair[{0, 0}] = 10;
std::unordered_set<std::pair<ll, ll>, hash_tuple::pair_hash> hashsetPair;
hashsetPair.insert({1, 1});
using TI = std::tuple<ll, ll, ll, ll>;
std::unordered_map<TI, ll, hash_tuple::hash<TI>> hashmapTuple;
hashmapTuple[{0, 1, 2, 3}] = 10;
std::unordered_set<TI, hash_tuple::hash<TI>> hashsetTuple;
hashsetTuple.emplace(0, 1, 2, 3);
return 0;
}
有一个 hack 解决此类问题
使用 std:unordered_map
个 string
看下面的例子-
我需要散列矩形的端点(角)
Error Approach
unordered_map<pair<int, int>, int> M; //ERROR
pair<int, int> p;
M[p]++;
Hack
unordered_map<string, int> M;
pair<int, int> p;
string s = to_string(p.first) + "_" + to_string(p.second);
M[s]++;
如果您需要创建小数或双精度的散列作为键,这种 hack 甚至可以工作:)
我已经简化了@YoungForest 的
#include <functional>
#include <iostream>
#include <unordered_map>
#include <utility> # pair
using namespace std;
// from boost (functional/hash):
// see http://www.boost.org/doc/libs/1_35_0/doc/html/hash/combine.html template
template <class T> inline void hash_combine(size_t &seed, T const &v) {
seed ^= hash<T>()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
struct pair_hash {
template <class T1, class T2>
size_t operator()(const pair<T1, T2> &p) const {
size_t seed = 0;
hash_combine(seed, p.first);
hash_combine(seed, p.second);
return seed;
}
};
int main() {
unordered_map<pair<int, int>, int, pair_hash> d;
d[{1, 2}] = 3;
cout << d.find({1, 2})->second << endl;
return 0;
}
它使用与 boost 库中相同的逻辑(比 xor 版本更好)。