如何在c++中生成整数对的无序集合?

How can I make an unordered set of pairs of integers in C++?

本文关键字:无序 集合 整数 c++      更新时间:2023-10-16

下面的程序不编译一组无序的整数对,但它可以编译整数对。unordered_set及其成员函数可以用于用户定义类型吗?如何定义它?

#include <unordered_set>
...
class A{
...
private: 
    std::unordered_set< std::pair<int, int> > u_edge_;
};

编译错误:

错误:调用std::unordered_set>::unordered_set()'没有匹配的函数

没有计算一对哈希的标准方法。将这个定义添加到您的文件中:

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>定义了相等。对于不提供测试相等性方法的自定义类,您可能需要提供一个单独的函数来测试两个实例是否彼此相等。

当然这个解仅限于一对两个整数。下面是一个答案的链接,它可以帮助您定义为多个对象创建散列的更通用的方法。

您的代码可以在VS2010 SP1 (VC10)上编译,但无法在GCC g++ 4.7.2上编译。

但是,您可能想要考虑Boost中的boost::hash。用于散列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_;
};

问题是std::unordered_set使用std::hash模板为其条目计算哈希值,并且没有std::hash专门化对。所以你需要做两件事:

  1. 决定你想使用什么哈希函数
  2. 使用该函数为您的密钥类型(std::pair<int, int>)专门化std::hash

下面是一个简单的例子:

#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::pair<int, int>提供一个散列函数。但是,从c++ 11开始,您也可以使用lambda表达式来代替定义散列函数。下面的代码以Sergey给出的解为基础:

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);

Ideone代码

我想重复Sergey的免责声明:此解决方案仅限于一对两个整数。这个答案为更通用的解决方案提供了思路。

这是一个保证不发生碰撞的简单解决方案。只需将您的问题简化为现有的解决方案,即将您的int对转换为string,如下所示:

 auto stringify = [](const pair<int, int>& p, string sep = "-")-> string{
    return to_string(p.first) + sep + to_string(p.second);
 }
 unordered_set<string> myset;
 myset.insert(stringify(make_pair(1, 2)));
 myset.insert(stringify(make_pair(3, 4)));
 myset.insert(stringify(make_pair(5, 6)));

享受吧!

您需要为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_;
};

这里的其他答案都建议构建一个散列函数,以某种方式将两个整数组合在一起。

这将工作,但产生非唯一哈希。虽然这对于您使用unordered_set来说很好,但对于某些应用程序来说,这可能是不可接受的。在你的例子中,如果你碰巧选择了一个不好的哈希函数,它可能会导致许多不必要的冲突。

但是你可以产生唯一的哈希值!

int通常为4字节。您可以通过使用int32_t使其显式。

哈希的数据类型是std::size_t。在大多数机器上,这是8字节。您可以在编译时检查。

由于一对由两个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>>的哈希函数。例如,

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>>,在这种情况下,您可以省略第二个模板参数。

要创建一个unordered_set对,您可以创建一个自定义散列函数,也可以创建一个unordered_set字符串。

  1. 创建自定义哈希函数:根据数据创建自定义哈希。所以没有放之四海而皆准的哈希函数。一个好的哈希函数必须有更少的冲突,所以在创建哈希函数时需要考虑冲突计数。

  2. 使用字符串:使用字符串非常简单,花费的时间更少。它还保证很少或没有碰撞。而不是使用unordered_set<int,>>我们使用了一个unordered_set。我们可以通过用分隔符(字符或字符串)分隔数字来表示这对。下面给出的示例显示了如何使用分隔符(";")插入一对整数。

    auto StringPair = [](const pair<int,>&X){返回to_string(x.first) + ";"+ to_string (x.second);};unordered_set;

    vector<pair&lt>比;Nums = {{1,2}, {2,3}, {4,5}, {1,2}};

    (汽车,对:num){Set.insert (StringPair(一对);}

只是在这里添加我的2美分,奇怪的是,要使用unordered_set,您需要指定一个外部哈希函数。封装原则更倾向于在类中有一个返回哈希值的'hash()'函数,而unordered_set将调用该函数。您应该有一个Hashable接口,并且您的类(在本例中是std::pair)将实现该接口。我认为这是Java等语言所遵循的方法。不幸的是,c++不遵循这种逻辑。你能模仿的最接近的是:

  1. 从std::pair派生一个类(无论如何,这允许你有更可读的代码)
  2. 将哈希函数传递给unordered_set模板

代码示例

class Point : public pair<int, int> {
   public:
   Point() {};
   Point(int first, int second) : pair{first, second}{};
   class Hash {
      public:
      auto operator()(const Point &p) const -> size_t {
         return ((size_t)p.first) << 32 | ((size_t)p.second);
      }
   };
 };
int main()
{
    unordered_set< Point, Point::Hash > us;
    Point mypoint(1000000000,1);
    size_t res = Point::Hash()(mypoint);
    cout<<"Hello World " << res << " " << mypoint.first;
    return 0;
}

如果size_t为64位,int为32位,则使用的简单哈希函数有效,在这种情况下,该哈希函数保证没有冲突,这是理想的