hash — C ++ unordered_map с использованием пользовательского типа класса в качестве ключа

Я пытаюсь использовать пользовательский класс в качестве ключа для unordered_map, как показано ниже,

#include <iostream>
#include <algorithm>
#include <unordered_map>
//#include <map>

using namespace std;

class node;
class Solution;

class Node {
public:
int a;
int b;
int c;
Node(){}
Node(vector<int> v) {
sort(v.begin(), v.end());
a = v[0];
b = v[1];
c = v[2];
}

bool operator==(Node i) {
if ( i.a==this->a && i.b==this->b &&i.c==this->c ) {
return true;
} else {
return false;
}
}
};

int main() {

unordered_map<Node, int> m;

vector<int> v;
v.push_back(3);
v.push_back(8);
v.push_back(9);
Node n(v);

m[n] = 0;

return 0;
}

Я думаю, мне нужно сказать C ++, как хэшировать класс Node, однако я не совсем уверен, как это сделать. Есть ли пример для такого рода задач?

Вот ошибка из g ++:

In file included from /usr/include/c++/4.6/string:50:0,
from /usr/include/c++/4.6/bits/locale_classes.h:42,
from /usr/include/c++/4.6/bits/ios_base.h:43,
from /usr/include/c++/4.6/ios:43,
from /usr/include/c++/4.6/ostream:40,
from /usr/include/c++/4.6/iostream:40,
from 3sum.cpp:4:
/usr/include/c++/4.6/bits/stl_function.h: In member function ‘bool std::equal_to<_Tp>::operator()(const _Tp&, const _Tp&) const [with _Tp = Node]’:
/usr/include/c++/4.6/bits/hashtable_policy.h:768:48:   instantiated from ‘bool std::__detail::_Hash_code_base<_Key, _Value, _ExtractKey, _Equal, _H1, _H2, std::__detail::_Default_ranged_hash, false>::_M_compare(const _Key&, std::__detail::_Hash_code_base<_Key, _Value, _ExtractKey, _Equal, _H1, _H2, std::__detail::_Default_ranged_hash, false>::_Hash_code_type, std::__detail::_Hash_node<_Value, false>*) const [with _Key = Node, _Value = std::pair<const Node, int>, _ExtractKey = std::_Select1st<std::pair<const Node, int> >, _Equal = std::equal_to<Node>, _H1 = std::hash<Node>, _H2 = std::__detail::_Mod_range_hashing, std::__detail::_Hash_code_base<_Key, _Value, _ExtractKey, _Equal, _H1, _H2, std::__detail::_Default_ranged_hash, false>::_Hash_code_type = long unsigned int]’
/usr/include/c++/4.6/bits/hashtable.h:897:2:   instantiated from ‘std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::_Node* std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::_M_find_node(std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::_Node*, const key_type&, typename std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::_Hash_code_type) const [with _Key = Node, _Value = std::pair<const Node, int>, _Allocator = std::allocator<std::pair<const Node, int> >, _ExtractKey = std::_Select1st<std::pair<const Node, int> >, _Equal = std::equal_to<Node>, _H1 = std::hash<Node>, _H2 = std::__detail::_Mod_range_hashing, _Hash = std::__detail::_Default_ranged_hash, _RehashPolicy = std::__detail::_Prime_rehash_policy, bool __cache_hash_code = false, bool __constant_iterators = false, bool __unique_keys = true, std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::_Node = std::__detail::_Hash_node<std::pair<const Node, int>, false>, std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::key_type = Node, typename std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::_Hash_code_type = long unsigned int]’
/usr/include/c++/4.6/bits/hashtable_policy.h:546:53:   instantiated from ‘std::__detail::_Map_base<_Key, _Pair, std::_Select1st<_Pair>, true, _Hashtable>::mapped_type& std::__detail::_Map_base<_Key, _Pair, std::_Select1st<_Pair>, true, _Hashtable>::operator[](const _Key&) [with _Key = Node, _Pair = std::pair<const Node, int>, _Hashtable = std::_Hashtable<Node, std::pair<const Node, int>, std::allocator<std::pair<const Node, int> >, std::_Select1st<std::pair<const Node, int> >, std::equal_to<Node>, std::hash<Node>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, false, false, true>, std::__detail::_Map_base<_Key, _Pair, std::_Select1st<_Pair>, true, _Hashtable>::mapped_type = int]’
3sum.cpp:149:5:   instantiated from here
/usr/include/c++/4.6/bits/stl_function.h:209:23: error: passing ‘const Node’ as ‘this’ argument of ‘bool Node::operator==(Node)’ discards qualifiers [-fpermissive]
make: *** [threeSum] Error 1

217

Решение

Чтобы иметь возможность использовать std::unordered_map (или один из других неупорядоченных ассоциативных контейнеров) с определяемым пользователем типом ключа, вам нужно определить две вещи:

  1. хэш-функция; это должен быть класс, который переопределяет operator() и вычисляет значение хеш-функции, заданное для объекта типа ключа. Одним из особенно простых способов сделать это является специализация std::hash шаблон для вашего типа ключа.

  2. функция сравнения на равенство; это необходимо, поскольку хеш-функция не может полагаться на тот факт, что хеш-функция всегда будет предоставлять уникальное хеш-значение для каждого отдельного ключа (т. е. она должна иметь возможность обрабатывать коллизии), поэтому ей нужен способ сравнения двух заданных ключей. для точного соответствия. Вы можете реализовать это либо как класс, который переопределяет operator()или как специализация std::equalили — проще всего — перегрузкой operator==() для вашего типа ключа (как вы уже сделали).

Сложность хеш-функции заключается в том, что если ваш тип ключа состоит из нескольких элементов, у вас обычно есть хеш-функция, которая вычисляет значения хеш-функции для отдельных элементов, а затем каким-то образом объединяет их в одно значение хеш-функции для всего объекта. Для обеспечения хорошей производительности (т. Е. Нескольких коллизий) вы должны тщательно продумать, как объединить отдельные значения хеш-функции, чтобы избежать слишком частого получения одного и того же вывода для разных объектов.

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

struct Key
{
std::string first;
std::string second;
int         third;

bool operator==(const Key &other) const
{ return (first == other.first
&& second == other.second
&& third == other.third);
}
};

Вот простая хеш-функция (адаптированная от той, которая используется в Пример cppreference для пользовательских хеш-функций):

namespace std {

template <>
struct hash<Key>
{
std::size_t operator()(const Key& k) const
{
using std::size_t;
using std::hash;
using std::string;

// Compute individual hash values for first,
// second and third and combine them using XOR
// and bit shifting:

return ((hash<string>()(k.first)
^ (hash<string>()(k.second) << 1)) >> 1)
^ (hash<int>()(k.third) << 1);
}
};

}

С этим на месте, вы можете создать экземпляр std::unordered_map для типа ключа:

int main()
{
std::unordered_map<Key,std::string> m6 = {
{ {"John", "Doe", 12}, "example"},
{ {"Mary", "Sue", 21}, "another"}
};
}

Это будет автоматически использовать std::hash<Key> как определено выше для вычислений значения хеша, и operator== определяется как функция-член Key для проверки на равенство.

Если вы не хотите специализировать шаблон внутри std Пространство имен (хотя в данном случае это совершенно законно), вы можете определить хеш-функцию как отдельный класс и добавить ее в список аргументов шаблона для карты:

struct KeyHasher
{
std::size_t operator()(const Key& k) const
{
using std::size_t;
using std::hash;
using std::string;

return ((hash<string>()(k.first)
^ (hash<string>()(k.second) << 1)) >> 1)
^ (hash<int>()(k.third) << 1);
}
};

int main()
{
std::unordered_map<Key,std::string,KeyHasher> m6 = {
{ {"John", "Doe", 12}, "example"},
{ {"Mary", "Sue", 21}, "another"}
};
}

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

Это может быть сложно; описанный выше метод XOR / битового сдвига, вероятно, неплохое начало. Для лучшего начала вы можете использовать hash_value а также hash_combine шаблон функции из библиотеки Boost. Первый действует так же, как std::hash для стандартных типов (недавно также включая кортежи и другие полезные стандартные типы); последнее помогает вам объединить отдельные значения хеша в одно. Вот переписать хеш-функцию, которая использует вспомогательные функции Boost:

#include <boost/functional/hash.hpp>

struct KeyHasher
{
std::size_t operator()(const Key& k) const
{
using boost::hash_value;
using boost::hash_combine;

// Start with a hash value of 0    .
std::size_t seed = 0;

// Modify 'seed' by XORing and bit-shifting in
// one member of 'Key' after the other:
hash_combine(seed,hash_value(k.first));
hash_combine(seed,hash_value(k.second));
hash_combine(seed,hash_value(k.third));

// Return the result.
return seed;
}
};

И вот переписывание, которое не использует boost, но использует хороший метод объединения хэшей:

namespace std
{
template <>
struct hash<Key>
{
size_t operator()( const Key& k ) const
{
// Compute individual hash values for first, second and third
// http://stackoverflow.com/a/1646913/126995
size_t res = 17;
res = res * 31 + hash<string>()( k.first );
res = res * 31 + hash<string>()( k.second );
res = res * 31 + hash<int>()( k.third );
return res;
}
};
}
379

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]