web-dev-qa-db-ja.com

unordered_setをカスタム構造体で使用するにはどうすればよいですか?

カスタムstructunordered_setを使用したいと思います。私の場合、カスタムstructは、ユークリッド平面の2D点を表します。ハッシュ関数とコンパレータ演算子を定義する必要があることはわかっています。以下のコードでわかるように、定義しました。

struct Point {
    int X;
    int Y;

    Point() : X(0), Y(0) {};
    Point(const int& x, const int& y) : X(x), Y(y) {};
    Point(const IPoint& other){
        X = other.X;
        Y = other.Y;
    };

    Point& operator=(const Point& other) {
        X = other.X;
        Y = other.Y;
        return *this;
    };

    bool operator==(const Point& other) {
        if (X == other.X && Y == other.Y)
            return true;
        return false;
    };

    bool operator<(const Point& other) {
        if (X < other.X )
            return true;
        else if (X == other.X && Y == other.Y)
            return true;

        return false;
    };

    size_t operator()(const Point& pointToHash) const {
        size_t hash = pointToHash.X + 10 * pointToHash.Y;
        return hash;
    };
};

ただし、セットを次のように定義すると、以下のエラーが発生します。

unordered_set<Point> mySet;

エラーC2280'std :: hash <_Kty> :: hash(const std :: hash <_Kty>&) ':削除された関数を参照しようとしています

何が足りないのですか?

9
SyTheSy

Std :: unordered_setの2番目のテンプレートパラメータは、ハッシュに使用するタイプです。あなたの場合、デフォルトでstd::hash<Point>になりますが、これは存在しません。したがって、ハッシャーが同じタイプの場合は、std::unordered_set<Point,Point>を使用できます。

または、ハッシャーを指定したくない場合は、Pointに対してstd::hashの特殊化を定義し、メンバー関数を削除して、特殊化のoperator()の本体にハッシュを実装します。 、またはstd :: hashスペシャライゼーションからメンバー関数を呼び出します。

#include <unordered_set>

struct Point {
    int X;
    int Y;

    Point() : X(0), Y(0) {};
    Point(const int& x, const int& y) : X(x), Y(y) {};
    Point(const Point& other){
        X = other.X;
        Y = other.Y;
    };

    Point& operator=(const Point& other) {
        X = other.X;
        Y = other.Y;
        return *this;
    };

    bool operator==(const Point& other) {
        if (X == other.X && Y == other.Y)
            return true;
        return false;
    };

    bool operator<(const Point& other) {
        if (X < other.X )
            return true;
        else if (X == other.X && Y == other.Y)
            return true;

        return false;
    };

    // this could be moved in to std::hash<Point>::operator()
    size_t operator()(const Point& pointToHash) const noexcept {
        size_t hash = pointToHash.X + 10 * pointToHash.Y;
        return hash;
    };

};

namespace std {
    template<> struct hash<Point>
    {
        std::size_t operator()(const Point& p) const noexcept
        {
            return p(p);
        }
    };
}


int main()
{
    // no need to specify the hasher if std::hash<Point> exists
    std::unordered_set<Point> p;
    return 0;
}

デモ

3
rmawatson

上記のソリューションではコードをコンパイルできますが、ポイントのハッシュ関数は避けてください。 bによってパラメータ化された1次元部分空間があり、そのために線上のすべての点がy = -x/10 + bは同じハッシュ値を持ちます。たとえば、上位32ビットがx座標で、下位32ビットがy座標である64ビットハッシュを使用したほうがよいでしょう。それは次のようになります

uint64_t hash(Point const & p) const noexcept
{
    return ((uint64_t)p.X)<<32 | (uint64_t)p.Y;
}
2
CJ13

rmawatsonの回答 をさらにいくつかのヒントを提供して拡張したいと思います。

  1. structについては、デフォルトの動作を(再)実装したため、_operator=_もPoint(const Point& other)も定義する必要はありません。
  2. 次のようにif句を削除することで、_operator==_を合理化できます。

    _bool operator==(const Point& other) { return X == other.X && Y == other.Y; };
    _
  3. _operator<_に誤りがあります。_else if_句で、両方の点が等しい場合はtrueを返します。これは、 厳密な弱順序 の要件に違反します。したがって、代わりに次のコードを使用することをお勧めします。

    _bool operator<(const Point& other) { return X < other.X || (X == other.X && Y < other.Y); };
    _

さらに、 C++ 11 なので、ハッシュ関数と比較関数を定義する代わりに ラムダ式 を使用できます。このように、他の方法で演算子が必要ない場合は、structの演算子を指定する必要はありません。すべてをまとめると、コードは次のように記述できます。

_struct Point {
    int X, Y;

    Point() : X(0), Y(0) {};
    Point(const int x, const int y) : X(x), Y(y) {};
};

int main() {
    auto hash = [](const Point& p) { return p.X + 10 * p.Y; };
    auto equal = [](const Point& p1, const Point& p2) { return p1.X == p2.X && p1.Y == p2.Y; };
    std::unordered_set<Point, decltype(hash), decltype(equal)> mySet(8, hash, equal);

    return 0;
}
_

ただし、 CJ13の回答 でも説明されているように、ハッシュ関数は最適な関数ではない可能性があります。 ハッシュ関数を手作りする の別の方法は次のとおりです。

_auto hash = [](const Point& p) { return std::hash<int>()(p.X) * 31 + std::hash<int>()(p.Y); };
_

ハッシュのより一般的な解決策のアイデアは、 ここ にあります。

Ideoneのコード

1
honk