Как сделать неупорядоченный набор пар целых чисел в C++?

программа не компилирует неупорядоченный набор пар целых чисел, но для целых чисел. Можно ли использовать unordered_set и его функции-члены для пользовательских типов и как его определить?

#include <unordered_set>
...

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

error: no matching function for call to 'std::unordered_set<std::pair<unsigned int, unsigned int> >::unordered_set()'

6 ответов


ваш код компилируется на VS2010 SP1 (VC10), но он не компилируется с GCC g++ 4.7.2.

однако, вы можете рассмотреть 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_;
};

нет стандартного способа вычисления хэша на паре. Добавьте это определение в свой файл:

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> определяет равенство. Для пользовательских классов, которые не предоставляют способ проверки равенства, может потребоваться предоставить отдельную функцию для проверки, равны ли два экземпляра друг другу.

конечно, это решение ограничено парой двух целых чисел. Вот ссылка на ответ это поможет вам определить более общий способ создания хэша для нескольких объектов.


проблема в том, что std::unordered_set использует std::hash шаблон для вычисления хэшей для его записи и нет std::hash специализация для пар. Поэтому вам придется сделать две вещи:--11-->

  1. решите, какую хэш-функцию вы хотите использовать.
  2. Specialize 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>> - в таком случае вы можете опустить второй параметр шаблона.


другие ответы здесь все предлагают построить хэш-функцию, которая каким-то образом объединяет ваши два целых числа.

это будет работать, но производит не уникальный хеш. Хотя это прекрасно для вашего использования 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);
}