为什么我不能用一对作为键来编译unordered_map?

时间:2015-09-20 23:56:05

标签: c++ dictionary unordered-map keyvaluepair

我正在尝试创建一个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 ++ / v1 / type_traits:948:38:未定义模板的隐式实例化&#st; :: __ 1 :: hash ,std :: __ 1 :: basic_string&gt; &GT;&#39;

如果我使用map<pair<string, string>, int>之类的常规地图而不是unordered_map,我就不会收到此错误。

是否无法在无序地图中使用pair作为关键字?

7 个答案:

答案 0 :(得分:9)

我解决此问题的首选方法是定义一个key函数,将您的对转换为唯一的整数(或任何可哈希的数据类型)。此密钥不是哈希密钥。它是数据对的唯一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,j))来操作地图。现在每个make_pair都会调用内联key函数。

这种方法可以保证密钥的最佳散列,因为现在散列部分由系统完成,系统总是选择内部散列表大小来确保每个桶都具有相同的可能性。但是你必须让自己100%确定key对于每一对都是唯一的,即,没有两个不同的对可以拥有相同的密钥,或者可能存在非常难以找到的错误。

答案 1 :(得分:7)

对于配对密钥,我们可以使用增强对散列函数:

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

答案 2 :(得分:4)

参考:C++ Standard Library: A tutorial and reference, Second version第7.9.2章:创建和控制无序容器

我在Google中找到的所有解决方案都使用XOR生成pair的哈希码,这是非常糟糕的。参见why-is-xor-the-default-way-to-combine-hashes。但是,本书使用hash_combine(从Boost中提取)为我们提供了最佳解决方案。当我在Online Judge(Atcoder)中对其进行测试时,该解决方案比XOR更好。我将代码组织为模板,如下所示。您可以复制和粘贴它。更改它以适合任何自定义结构/类很方便。

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

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

答案 3 :(得分:0)

与其他答案相比,我知道这是过于幼稚的实现,但是有一种解决方法。

如果要获取对的输入,只需在获取输入时用unordered_hash中的另一个整数对对进行哈希处理,然后间接使用此整数值对对进行哈希处理,即

unordered_hash<int, Vote> h;
using Unordered_map = unordered_map<i, int>; // i is corresponding value to pair

答案 4 :(得分:0)

Baum mit Augen answer的评论中,用户 Joe Black asked for an example改用lambda expressions哈希函数的定义。我同意 Baum mit Augen opinion,认为这可能会损害可读性,特别是如果您想实现更通用的解决方案。因此,我想通过OP提出的std::pair<std::string, std::string>特定解决方案来简化我的示例。该示例还使用了std::hash<std::string>函数调用的handcrafted组合:

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

答案 5 :(得分:0)

如果不是严格要求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

答案 6 :(得分:0)

此类问题有hack

使用 std:unordered_mapstring

看下面的例子-

我需要散列矩形的端点(角)

<块引用>

错误处理

unordered_map<pair<int, int>, int> M;           //ERROR

pair<int, int> p;
M[p]++;
<块引用>

破解

unordered_map<string, int> M;

pair<int, int> p;
string s = to_string(p.first) + "_" + to_string(p.second);
M[s]++;

如果您需要创建十进制或双精度的哈希作为键,这种 hack 甚至可以工作:)