Построение неупорядоченной карты с кортежами в качестве ключей
В программе на C++ с Boost я пытаюсь создать неупорядоченную карту, ключи которой являются кортежами типа double:
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 ответов
Вам нужно немного переднего вопроса. Из-за базовой реализации 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;
На самом деле, вы можете прекрасно определить общую хэш-функцию для 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;
}
} }
Примечание: я только доказал, что это правильно, я не проверял это.
Этот код из универсального хэша для кортежей в 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://stackru.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;
}
};
}
Вы пробовали использовать это:
#include "boost/functional/hash.hpp"
#include <unordered_map>
#include <tuple>
usong Edge = std::tuple<double, double, double, double>;
struct KeyHash {
std::size_t operator()(const Edge & key) const
{
return boost::hash_value(key);
}
};
using EdgeMap = std::unordered_map<Edge, int, KeyHash>;
обратите внимание, я использую std
за tuple
а также unordered_map
.
Это все в документах...
- http://www.boost.org/doc/libs/1_38_0/doc/html/hash/custom.html
- http://www.boost.org/doc/libs/1_38_0/doc/html/hash/combine.html
Вам нужно что-то вроде:
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;
В документации Boost указан необходимый интерфейс. Не зная больше о задействованных ценностях, трудно сказать намного больше. Учитывая ключевой объект в качестве входных данных, он должен генерировать size_t, который является детерминированным, то есть это чистая функция, в которой результат зависит исключительно от входного значения, поэтому предоставление одного и того же ввода всегда будет приводить к одному и тому же хеш-коду.