125

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?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Marre
  • 1,253
  • 2
  • 9
  • 7

9 Answers9

133

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.

Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
  • You reference `hash_value` but the link goes to `hash`. I think `hash` is the correct location because docs for `hash_value` recommend using `hash`. I thought I would let you edit rather than doing it myself... – PeterVermont Jun 07 '17 at 19:31
  • @PeterVermont `hash_value` is documented on the page I link. Do you have a better link? – Baum mit Augen Aug 12 '17 at 15:58
  • @Baum mit Augean The link is correct but I suggest that the text of the link say 'hash' rather than 'hash_value' – PeterVermont Aug 13 '17 at 17:44
  • 2
    Careful: "pairs that differ only by permutation will have the same hash". To put it more bluntly: `pair_hash({a, b}) ==pair_hash({b, a})` is rarely what you want! (The culprit is the XOR. `return h1 ^(h2 << 1);` should fix it.) – Unapiedra Nov 13 '17 at 10:51
  • 1
    @Unapiedra That's slightly better, but still not great; e.g. the hashes of permuted will still be fairly close to each other. There are questions on this site on how to do it right. – Baum mit Augen Nov 21 '17 at 17:29
  • Is there an example of how 'pair_hash' comparator can be written as a Lambda function (c++11 onwards) in the declaration of unordered_map 'um' ??? – Joe Black May 20 '18 at 23:41
  • @JoeBlack I'm not so sure I would endorse that from a readability point of view, especially when we move from this trivial example to an actually useful hash combiner. But if you insist: [Lambda expressions as class template parameters](//stackoverflow.com/q/5849059) – Baum mit Augen May 21 '18 at 07:56
35

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.

Zhuoran He
  • 873
  • 9
  • 15
  • 8
    Good luck doing that for `pair` as OP wants. – riv Aug 03 '18 at 15:38
  • 12
    Even if the OP wants to do that for `pair` - this is nevertheless a good way doing this for `pair` because `key` is bijective. May help someone! – lukasl1991 May 21 '19 at 06:23
  • 1
    @Zhuoran He I just wanna confirm something, this inline function only works for 64 bit systems right? As far as my understanding goes, this will fail on a 32 bit system for a pair of ints, right? – Pranjal Verma Aug 07 '20 at 10:47
  • Since `size_t` isn't guaranteed to be 64-bit, you should use `uint64_t` instead. – Puddle Feb 03 '22 at 00:29
  • I agree with @lukasl1991, this is a good idea. I made it independent of the arch size like this. It works efficiently for the common case where the ints (generally) fit in a short on 32 bit systems or in 4 bytes on systems where size_t is 8 bytes: `code` struct Pair_Hash { inline size_t operator() (const std::pair &pair) const { return pair.first + (pair.second << (sizeof(size_t) * 8 / 2)); } }; `code` – Erik Mar 30 '22 at 22:43
21

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
Efreeto
  • 2,132
  • 1
  • 24
  • 25
  • how to write the range-based for loop here – uss Oct 07 '20 at 18:55
  • 2
    This will require two hash-table lookups instead of just one. Thus, it will significantly degrade performance if the table is accessed frequently, e.g. if used as a cache of some sorts. On the other hand, the readability is much better! – Arne L. Dec 03 '20 at 09:01
  • The solution is simple and reasonable hash functions are likely to be used for each hash-table lookup. While the performance hit from a poorly chosen or a poorly implemented custom hash function could be even worse than from the double lookup. – S.V Sep 20 '21 at 18:33
19

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;
}
Felix Guo
  • 193
  • 1
  • 4
6

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

YoungForest
  • 111
  • 1
  • 7
5

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;
}
bku_drytt
  • 3,169
  • 17
  • 19
  • 1
    I think you mean `const Vote& v` – Guy Kogus Feb 05 '16 at 15:37
  • 4
    @GuyKogus "const Vote&" and "Vote const&" are exactly the same.http://stackoverflow.com/questions/5503352/const-before-or-const-after – David Fooks Feb 11 '16 at 09:54
  • 3
    I think [this is Undefined Behavior](http://en.cppreference.com/w/cpp/language/extending_std): It is allowed to add template specializations for any standard library template to the namespace std only if the declaration depends on a user-defined type and the specialization satisfies all requirements for the original template. – Arnaud May 20 '16 at 17:39
3

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

Antony Hatchkins
  • 31,947
  • 10
  • 111
  • 111
2

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

honk
  • 9,137
  • 11
  • 75
  • 83
1

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

H Soora
  • 47
  • 5