如何将unordered_set与自定义结构一起使用?

SyT*_*eSy 5 c++ struct set unordered-set c++11

我想使用unordered_set带有自定义的struct。在我的情况下,自定义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;
    };
};
Run Code Online (Sandbox Code Playgroud)

但是,如果我按以下方式定义集合,则会出现以下错误:

unordered_set<Point> mySet;
Run Code Online (Sandbox Code Playgroud)

错误C2280'std :: hash <_Kty> :: hash(const std :: hash <_Kty>&)':尝试引用已删除的函数

我想念什么?

rma*_*son 7

std :: unordered_set的第二个模板参数是用于哈希的类型。并且std::hash<Point>在您的情况下默认为不存在。因此,std::unordered_set<Point,Point>如果哈希器是相同类型,则可以使用 。

或者,如果您不想指定哈希器,则定义std::hashfor 的特殊化,Point并摆脱成员函数并在您的特殊化主体中实现哈希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;
}
Run Code Online (Sandbox Code Playgroud)

演示版


小智 6

虽然上述解决方案可以让您编译代码,但请避免使用点的散列函数。有一维子空间参数化为b,直线上的所有点y = -x/10 + b都将具有相同的哈希值。最好使用 64 位散列,其中前 32 位是 x 坐标,低 32 位是 y 坐标(例如)。那看起来像

uint64_t hash(Point const & p) const noexcept
{
    return ((uint64_t)p.X)<<32 | (uint64_t)p.Y;
}
Run Code Online (Sandbox Code Playgroud)


hon*_*onk 5

我想通过提供更多提示来扩展rmawatson 的回答

  1. 对于您的struct,您既不需要定义operator=也不需要Point(const Point& other),因为您(重新)实现了默认行为。
  2. 您可以operator==通过删除if子句进行简化,如下所示:

    bool operator==(const Point& other) { return X == other.X && Y == other.Y; };
    
    Run Code Online (Sandbox Code Playgroud)
  3. 您的operator<: 在else if子句中存在错误,true如果两个点相等,则返回。这违反了严格弱排序的要求。因此,我建议改用以下代码:

    bool operator<(const Point& other) { return X < other.X || (X == other.X && Y < other.Y); };
    
    Run Code Online (Sandbox Code Playgroud)

此外,从C++11 开始,您可以使用lambda 表达式而不是定义散列和比较函数。这样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;
}
Run Code Online (Sandbox Code Playgroud)

但是,正如CJ13 的回答中所解释的那样,您的哈希函数可能不是最好的。另一种手工制作散列函数的方法如下:

auto hash = [](const Point& p) { return std::hash<int>()(p.X) * 31 + std::hash<int>()(p.Y); };
Run Code Online (Sandbox Code Playgroud)

可以在此处找到更通用的散列解决方案的想法。

Ideone 上的代码