簡體   English   中英

為什么我不能編譯一個以一對為鍵的 unordered_map?

[英]Why can't I compile an unordered_map with a pair as key?

我正在嘗試創建一個unordered_map到 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: 未定義模板的隱式實例化'std::__1::hash, std::__1: :basic_string > >'

如果我使用像map<pair<string, string>, int>這樣的常規 map 而不是unordered_map ,我不會收到此錯誤。

在無序映射中不能使用pair作為鍵嗎?

您需要為您的密鑰類型提供合適的哈希函數。 一個簡單的例子:

#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以獲得更高質量的結果。 這個答案中也更詳細地討論了這一點 - 包括上述來自 boost 的解決方案。

對於現實世界的使用:Boost 還提供了函數 set 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)]=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對每一對都是唯一的,即,沒有兩個不同的對可以具有相同的密鑰,否則可能很難找到錯誤。

如果使用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

對於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 命名空間中沒有有效的std::hash<std::pair<std::string, std::string>>實例化。

根據我的編譯器:

錯誤 C2338 C++ 標准不提供此類型的散列。 c:\\程序文件 (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;
}

參考: C++ 標准庫:教程和參考,第二版第 7.9.2 章:創建和控制無序容器

我在谷歌找到的所有解決方案都使用XOR來生成pair哈希碼,這非常糟糕。 看看為什么是異或默認方式到組合哈希 然而,這本書給了我們最好的解決方案,使用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;
}

Baum mit Augen答案的評論中,用戶Joe Black 要求提供一個使用lambda 表達式而不是定義散列函數的示例 我同意Baum mit Augen觀點,這可能會損害可讀性,特別是如果您想實施更通用的解決方案。 因此,我想通過關注 OP 提出的std::pair<std::string, std::string>的特定解決方案來保持我的示例簡短。 該示例還使用了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);

Ideone 上的代碼

我已經按照 OP 的要求簡化了@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 版本更好)。

與其他答案相比,我知道這太幼稚了,但是有一種解決方法。

如果要獲取對的輸入,只需在獲取輸入時用unordered_hash中的另一個整數對對進行哈希處理,然后間接使用此整數值對對進行哈希處理,即

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

這些問題有一個技巧

使用stringstd:unordered_map

看下面的例子——

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

錯誤方法

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 甚至可以工作:)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM