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

218

Решение

Чтобы иметь возможность использовать 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]