Построение неупорядоченной карты с кортежами в качестве ключей - PullRequest
22 голосов
/ 31 августа 2010

В программе на C ++ с Boost я пытаюсь создать неупорядоченную карту, ключи которой являются кортежами двойных:

typedef boost::tuples::tuple<double, double, double, double> Edge;
typedef boost::unordered_map< Edge, int > EdgeMap;

Инициализация карты завершается нормально, однако, когда я пытаюсь заполнить ее ключамии значения

EdgeMap map;
Edge key (0.0, 0.1, 1.1, 1.1);
map[key] = 1;

Я сталкиваюсь со следующим сообщением об ошибке:

/usr/include/boost/functional/hash/extensions.hpp:176: error: no matching function for call to ‘hash_value(const boost::tuples::tuple<double, double, double, double, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type>&)’

Я предполагаю, что это потому, что мне нужно указать хэш-функцию для ключей кортежей.Как я могу это сделать?

РЕДАКТИРОВАТЬ:

Следуя приведенным ниже советам, я написал следующую реализацию:

#include <boost/tuple/tuple.hpp>
#include <boost/unordered_map.hpp>

typedef boost::tuples::tuple<double, double, double, double> Edge;

struct ihash
    : std::unary_function<Edge, std::size_t>
{
    std::size_t operator()(Edge const& e) const
    {
        std::size_t seed = 0;
        boost::hash_combine( seed, e.get<0>() );
        boost::hash_combine( seed, e.get<1>() );
        boost::hash_combine( seed, e.get<2>() );
        boost::hash_combine( seed, e.get<3>() );
        return seed;
    }
};

struct iequal_to
    : std::binary_function<Edge, Edge, bool>
{
    bool operator()(Edge const& x, Edge const& y) const
    {
        return ( x.get<0>()==y.get<0>() &&
                 x.get<1>()==y.get<1>() &&
                 x.get<2>()==y.get<2>() &&
                 x.get<3>()==y.get<3>());
    }
};

typedef boost::unordered_map< Edge, int, ihash, iequal_to > EdgeMap;

int main() {

    EdgeMap map;
    Edge key (0.0, 0.1, 1.1, 1.1);
    map[key] = 1;

    return 0;
}

Возможно лисократить его?

Ответы [ 5 ]

12 голосов
/ 01 сентября 2010

На самом деле, вы можете прекрасно определить общую хеш-функцию для boost::tuple. Единственное требование состоит в том, чтобы оно находилось в одном и том же пространстве имен, чтобы оно было подобрано ADL.

Я на самом деле удивлен, что они еще не написали.

namespace boost { namespace tuples {

  namespace detail {

    template <class Tuple, size_t Index = length<Tuple>::value - 1>
    struct HashValueImpl
    {
      static void apply(size_t& seed, Tuple const& tuple)
      {
        HashValueImpl<Tuple, Index-1>::apply(seed, tuple);
        boost::hash_combine(seed, tuple.get<Index>());
      }
    };

    template <class Tuple>
    struct HashValueImpl<Tuple,0>
    {
      static void apply(size_t& seed, Tuple const& tuple)
      {
        boost::hash_combine(seed, tuple.get<0>());
      }
    };
  } // namespace detail

  template <class Tuple>
  size_t hash_value(Tuple const& tuple)
  {
    size_t seed = 0;
    detail::HashValueImpl<Tuple>::apply(seed, tuple);
    return seed;
  }

} }

Примечание: я только доказал, что это правильно, я не проверял это.

10 голосов
/ 31 августа 2010

Вам нужно немного переднего вопроса . Из-за базовой реализации boost::tuples::tuple, сделайте Edge структуру, позволяющую корректно разрешать перегрузки. В противном случае вы не получите совпадений для

  • boost::hash_value(const Edge &)
  • operator==(const Edge &, const Edge &)

Код ниже:

struct Edge {
  Edge(double x1, double x2, double x3, double x4)
    : tuple(x1,x2,x3,x4) {}
  boost::tuples::tuple<double, double, double, double> tuple;
};

// XXX: less than ideal implementation!
bool operator==(const Edge &a, const Edge &b)
{
  return a.tuple.get<0>() == b.tuple.get<0>() &&
         a.tuple.get<1>() == b.tuple.get<1>() &&
         a.tuple.get<2>() == b.tuple.get<2>() &&
         a.tuple.get<3>() == b.tuple.get<3>();
}

// XXX: me too!
std::size_t hash_value(const Edge &e)
{
  std::size_t seed = 0;
  boost::hash_combine(seed, e.tuple.get<0>());
  boost::hash_combine(seed, e.tuple.get<1>());
  boost::hash_combine(seed, e.tuple.get<2>());
  boost::hash_combine(seed, e.tuple.get<3>());
  return seed;
}

typedef boost::unordered_map< Edge, int > EdgeMap;
1 голос
/ 31 августа 2010

Это все в документах ...

Вам нужно что-то вроде:

std::size_t hash_value(Edge const& e)
{
    std::size_t seed = 0;
    boost::hash_combine( seed, e.get<0>() );
    boost::hash_combine( seed, e.get<1>() );
    boost::hash_combine( seed, e.get<2>() );
    boost::hash_combine( seed, e.get<3>() );
    return seed;
}

... и тогда вы можете определить:

boost::unordered_map< Edge, int, boost::hash< Edge > > EdgeMap;

... на самом деле это значение по умолчанию, поэтому теперь оно должно работать без явного определения хеша:

boost::unordered_map< Edge, int > EdgeMap;
0 голосов
/ 29 января 2014

Этот код из Общий хэш для кортежей в unordered_map / unordered_set обеспечивает магическую поддержку всех кортежей c ++ 11 стандартных типов хэширования (строк, целых и т. Д.).

Неудивительно, что оно очень похоже на решение Мэтью М., описанное выше, но без буст-зависимостей.

Поместите код в заголовочный файл и включите его, и неупорядоченные наборы кортежей будут работать из коробки:

#include <tuple>
namespace std{
    namespace
    {

        // Code from boost
        // Reciprocal of the golden ratio helps spread entropy
        //     and handles duplicates.
        // See Mike Seymour in magic-numbers-in-boosthash-combine:
        //     https://stackoverflow.com/questions/4948780

        template <class T>
        inline void hash_combine(std::size_t& seed, T const& v)
        {
            seed ^= hash<T>()(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        }

        // Recursive template code derived from Matthieu M.
        template <class Tuple, size_t Index = std::tuple_size<Tuple>::value - 1>
        struct HashValueImpl
        {
          static void apply(size_t& seed, Tuple const& tuple)
          {
            HashValueImpl<Tuple, Index-1>::apply(seed, tuple);
            hash_combine(seed, get<Index>(tuple));
          }
        };

        template <class Tuple>
        struct HashValueImpl<Tuple,0>
        {
          static void apply(size_t& seed, Tuple const& tuple)
          {
            hash_combine(seed, get<0>(tuple));
          }
        };
    }

    template <typename ... TT>
    struct hash<std::tuple<TT...>> 
    {
        size_t
        operator()(std::tuple<TT...> const& tt) const
        {                                              
            size_t seed = 0;                             
            HashValueImpl<std::tuple<TT...> >::apply(seed, tt);    
            return seed;                                 
        }                                              

    };
}
0 голосов
/ 31 августа 2010

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

...