为什么我可以用一对键编译unordered_map?

发布于 2025-01-23 14:01:55 字数 674 浏览 3 评论 0原文

我正在尝试创建一个unordered_map与整数映射:

#include <unordered_map>

using namespace std;
using Vote = pair<string, string>;
using Unordered_map = unordered_map<Vote, int>;

我有一个类,其中我将unordered_map为私人成员。

但是,当我尝试编译时,我会遇到以下错误:

/applications/xcode.app/contents/developer/toolchains/xcodedefault.xctoolchain/usr/include/c ++/c ++/v1/type_traits:948:38:38:demit Intantiatiation' :__ 1 :: basic_string&gt; &gt;'

如果我使用常规地图,例如map&lt; pair&lt; string,string&gt; gt;而不是unordered_map

是否不可能将Pair用作无序地图中的键?

I am trying to create an unordered_map to map pairs with integers:

#include <unordered_map>

using namespace std;
using Vote = pair<string, string>;
using Unordered_map = unordered_map<Vote, int>;

I have a class where I have declared an Unordered_map as a private member.

However, I am getting the following error when I try to compile it:

/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 > >'

I am not getting this error if I use a regular map like map<pair<string, string>, int> instead of an unordered_map.

Is it not possible to use pair as key in unordered maps?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

著墨染雨君画夕 2025-01-30 14:01:55

您需要为密钥类型提供合适的哈希功能。一个简单的例子:

#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;
}

这将起作用,但没有最好的Hash-Properties 。您可能想看一下 BOOST.HASH_COMBINE在结合哈希时为更高质量的结果。还可以更详细地讨论这一点 - 包括BOOST的上述解决方案 - 在此答案

对于现实世界的使用:boost还提供功能集 <代码> hash_value 已经为std ::配对以及std :: Tuple和大多数标准容器提供了哈希功能。


更确切地说,它会产生太多的碰撞。例如,每个对称对将哈希至0,而仅因排列而不同的对将具有相同的哈希。这对于您的编程练习可能很好,但可能会严重损害现实世界法规的性能。

You need to provide a suitable hash function for your key type. A simple example:

#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;
}

This will work, but not have the best hash-properties. You might want to have a look at something like 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.

For real world use: Boost also provides the function set hash_value which already provides a hash function for std::pair, as well as std::tuple and most standard containers.


More precisely, it will produce too many collisions. E.g., every symmetric pair will hash to 0 and pairs that differ only by permutation will have the same hash. This is probably fine for your programming exercise, but might seriously hurt performance of real world code.

趴在窗边数星星i 2025-01-30 14:01:55

解决此问题的我首选的方法是定义函数,该功能将您的配对转换为唯一的整数(或任何可用的数据类型)。该密钥不是哈希键。这是一对数据的唯一ID,然后将通过unordered_map最佳地哈希。例如,您想定义类型的unordered_map

  unordered_map<pair<int,int>,double> Map;

,并且要使用map [make_pair(i,j)] = valuemap.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)] = valuemap.find(key(i,(i,) j))要在地图上操作。现在,每个make_pair现在都变为呼叫inline 函数。

此方法可以确保将密钥最佳地放置,因为现在哈希零件是由系统完成的,它将始终选择内部哈希表尺寸以确保每个存储桶的可能性同样可能。但是,您必须使自己100%确定是每对唯一的,即,没有两个不同的对可以具有相同的键,否则可能会有很难找到的错误。

My preferred way of solving this problem is to define a key function that transforms your pair into a unique integer (or any hashable data type). This key is not the hash key. It is the unique ID of the pair of data that will then be optimally hashed by the unordered_map. For example, you wanted to define an unordered_map of the type

  unordered_map<pair<int,int>,double> Map;

And you want to use Map[make_pair(i,j)]=value or Map.find(make_pair(i,j)) to operate on the map. Then you'll have to tell the system how to hash a pair of integers make_pair(i,j). Instead of that, we can define

  inline size_t key(int i,int j) {return (size_t) i << 32 | (unsigned int) j;}

and then change the type of the map to

  unordered_map<size_t,double> Map;

We can now use Map[key(i,j)]=value or Map.find(key(i,j)) to operate on the map. Every make_pair now becomes calling the inline key function.

This method guarantees that the key will be optimally hashed, because now the hashing part is done by the system, which will always choose the internal hash table size to be prime to make sure every bucket is equally likely. But you have to make yourself 100% sure that the key is unique for every pair, i.e., no two distinct pairs can have the same key, or there can be very difficult bugs to find.

浮华 2025-01-30 14:01:55

如果使用Pair不是严格的要求,则只需两次使用地图即可。

#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

If using pair is not a strict requirement, you can simply use map twice.

#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
疯了 2025-01-30 14:01:55

对于配对键,我们可以使用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;
}

For pair key, we can use boost pair hash function:

#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;
}

Similarly we can use boost hash for vectors,

#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;
}
空心空情空意 2025-01-30 14:01:55

参考: c ++标准库:教程和参考,第二版第7.9.2章:创建和控制无序的容器,

我在Google中找到的所有解决方案XOR XOR 生成对,这完全不好。请参阅 why-is-is-xor-the-default-to-to -combine-hashes 。但是,这本书为我们提供了最佳的解决方案,使用hash_combine,它取自boost。当我在在线法官中对其进行测试时,该解决方案要比XOR好得多( noreflow noreferrer“> atcoder )。我将代码作为模板组织为以下模板。您可以尽可能复制和粘贴它。更改它以适合任何自定义结构/类是很方便的。

更新:添加元组的哈希模板。

#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;
}

Reference: C++ Standard Library: A tutorial and reference, Second version Chapter 7.9.2: Creating and Controlling unordered Container

All solutions I found in Google use XOR to generate hashcode of pair, which is totally bad. see 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). I organized the code as a template as follow. You can copy and paste it as much as you can. And it is convenient to change it to fit any custom struct/class.

Update: add hash template for tuple.

#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;
}

给我一枪 2025-01-30 14:01:55

正如您的汇编错误所示,在您的std nesspace中没有有效的std :: std :: hash&lt; std :: pair&lt; std :: string,std :: string&gt;&gt;

根据我的编译器的说法:

错误C2338 C ++标准无法为此提供哈希
类型。 C:\ Program Files(X86)\ Microsoft Visual Studio
14.0 \ vc \ include \ xstddef 381

您可以为std :: Hash&lt; pote&gt;提供自己的专业化,如下:

#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;
}

As your compilation error indicates, there is no valid instantiation of std::hash<std::pair<std::string, std::string>> in your std namespace.

According to my compiler:

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

You can provide your own specialization for std::hash<Vote> as follows:

#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;
}
心凉怎暖 2025-01-30 14:01:55

我已经简化了 @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版本更好)。

I've simplified @YoungForest's answer to only work with pairs (= not with arbitrary length tuples) as was requested by the OP. I've also minimized the boilerplate code:

#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;
}

It uses the same logic as in the boost library (that is better than the xor version).

眼波传意 2025-01-30 14:01:55

要求使用一个示例 lambda表达式而不是定义哈希功能。我同意 MIT Augen ,这可能会损害可读性,尤其是如果您想实现更普遍的解决方案。因此,我想通过专注于std :: pair&lt; std :: String,std :: String&gt;的特定解决方案来简短示例。该示例还使用a 手工制作 std :: Hash&lt; std :: String&gt;函数调用的组合:

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);

In the comments on the answer by Baum mit Augen, the user Joe Black asked for an example on using a lambda expressions instead of defining a hash function. I agree with the opinion 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 combination of std::hash<std::string> function calls:

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);

Code on Ideone

翻身的咸鱼 2025-01-30 14:01:55

此类问题有一个 hack

使用std:unordered_map string> string

查看以下示例 -

我需要哈希矩形

错误方法

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]++;

甚至有效,即使您需要创建十进制或双重键的哈希:)

There is a hack to such problems

Use a std:unordered_map of string

Look at the following example-

I am required to hash the endpoint(corner) of a rectangle

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]++;

Such hack even works if you are required to create a hash of decimal or double as a key :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文