次のプログラムは、整数のペアの順序付けられていないセットをコンパイルしませんが、整数に対してはコンパイルします。できる unordered_set
とそのメンバー関数はユーザー定義型で使用されますが、どのように定義できますか?
#include <unordered_set>
...
class A{
...
private:
std::unordered_set< std::pair<int, int> > u_Edge_;
};
コンパイラエラー:
エラー: 'std :: unordered_set> :: unordered_set()'への呼び出しに一致する関数がありません
コードはVS2010 SP1(VC10)でコンパイルされますが、GCC g ++ 4.7.2ではコンパイルできません。
ただし、boost::hash
from Boost.Functional ハッシュするstd::pair
(この追加により、コードはg ++でもコンパイルされます)。
#include <unordered_set>
#include <boost/functional/hash.hpp>
class A
{
private:
std::unordered_set<
std::pair<int, int>,
boost::hash< std::pair<int, int> >
> u_Edge_;
};
ペアのハッシュを計算する標準的な方法はありません。この定義をファイルに追加します。
struct pair_hash {
inline std::size_t operator()(const std::pair<int,int> & v) const {
return v.first*31+v.second;
}
};
これで、次のように使用できます。
std::unordered_set< std::pair<int, int>, pair_hash> u_Edge_;
これは、pair<T1,T2>
は平等を定義します。同等性をテストする方法を提供しないカスタムクラスの場合、2つのインスタンスが互いに等しいかどうかをテストするための個別の関数を提供する必要があります。
もちろん、このソリューションは2つの整数のペアに制限されます。 回答へのリンク は、複数のオブジェクトのハッシュを作成するより一般的な方法を定義するのに役立ちます。
問題は、 std::unordered_set
が std::hash
テンプレートを使用してそのエントリのハッシュを計算し、ペアのstd::hash
特殊化がないことです。したがって、次の2つのことを行う必要があります。
std::hash
をキータイプ(std::pair<int, int>
)に特化します。以下に簡単な例を示します。
#include <unordered_set>
namespace std {
template <> struct hash<std::pair<int, int>> {
inline size_t operator()(const std::pair<int, int> &v) const {
std::hash<int> int_hasher;
return int_hasher(v.first) ^ int_hasher(v.second);
}
};
}
int main()
{
std::unordered_set< std::pair<int, int> > Edge;
}
std::hash<>
で機能するstd::pair<int, int>
に特化する必要があります。特殊化を定義する方法の非常に簡単な例を次に示します。
#include <utility>
#include <unordered_set>
namespace std
{
template<>
struct hash<std::pair<int, int>>
{
size_t operator () (std::pair<int, int> const& p)
{
// A bad example of computing the hash,
// rather replace with something more clever
return (std::hash<int>()(p.first) + std::hash<int>()(p.second));
}
};
}
class A
{
private:
// This won't give you problems anymore
std::unordered_set< std::pair<int, int> > u_Edge_;
};
std::pair<int, int>>
のハッシュ関数がありません。例えば、
struct bad_hash
{
std::size_t operator()(const std::pair<int,int>& p) const
{
return 42;
}
};
....
std::unordered_set< std::pair<int, int>, bad_hash> u_Edge_;
std::hash<T>
をstd::hash<std::pair<int,int>>
に特化することもできます。この場合、2番目のテンプレートパラメーターを省略できます。
ここでの他の回答はすべて、何らかの方法で2つの整数を結合するハッシュ関数を作成することを示唆しています。
これは機能しますが、一意でないハッシュを生成します。これはunordered_set
の使用には適していますが、一部のアプリケーションでは受け入れられない場合があります。あなたの場合、間違ったハッシュ関数を選択すると、多くの不必要な衝突が発生する可能性があります。
ただし、独自のハッシュを生成できます!
int
は通常4バイトです。 int32_t
を使用してこれを明示的にすることができます。
ハッシュのデータ型はstd::size_t
です。ほとんどのマシンでは、これは8バイトです。これはコンパイル時に確認できます。
ペアは2つのint32_t
型で構成されているため、両方の数値をstd::size_t
に入れて一意のハッシュを作成できます。
これは次のようになります(ビット操作のために符号なしの値としてコンパイラに符号付きの値を処理させる方法を手っ取り早く思い出せないため、uint32_t
について次のように記述しました)。
#include <cassert>
#include <cstdint>
#include <unordered_set>
#include <utility>
struct IntPairHash {
std::size_t operator()(const std::pair<uint32_t, uint32_t> &p) const {
assert(sizeof(std::size_t)>=8); //Ensure that std::size_t, the type of the hash, is large enough
//Shift first integer over to make room for the second integer. The two are
//then packed side by side.
return (((uint64_t)p.first)<<32) | ((uint64_t)p.second);
}
};
int main(){
std::unordered_set< std::pair<uint32_t, uint32_t>, IntPairHash> uset;
uset.emplace(10,20);
uset.emplace(20,30);
uset.emplace(10,20);
assert(uset.size()==2);
}
この質問に関する他のほとんどの回答ですでに述べたように、std::pair<int, int>
のハッシュ関数を提供する必要があります。ただし、 C++ 11 なので、ハッシュ関数を定義する代わりに lambda expression を使用することもできます。次のコードは、ベースとして dasblinkenlightによって与えられた解決策 を取ります。
auto hash = [](const std::pair<int, int>& p){ return p.first * 31 + p.second; };
std::unordered_set<std::pair<int, int>, decltype(hash)> u_Edge_(8, hash);
Dasblinkenlightの免責事項を繰り返してください:このソリューションは2つの整数のペアに制限されています。 この回答 は、より一般的なソリューションのアイデアを提供します。