unordered_set unordered_map example ejemplo c++ hash g++ unordered-map hashtree

example - C++ unordered_map usando un tipo de clase personalizado como clave



unordered_map c++ example (1)

Estoy tratando de usar una clase personalizada como clave para unordered_map, como la siguiente,

#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; }

Supongo que necesito decirle a C ++ cómo hash Nodo de clase, sin embargo, no estoy muy seguro de cómo hacerlo. ¿Hay algún ejemplo para este tipo de tareas?

El siguiente es el error de 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


Para poder usar std::unordered_map (o uno de los otros contenedores asociativos desordenados) con un tipo de clave definido por el usuario, necesita definir dos cosas:

  1. Una función hash ; esta debe ser una clase que anula el operator() y calcula el valor de hash dado un objeto del tipo clave. Una forma particularmente sencilla de hacerlo es especializar la plantilla std::hash para su tipo de clave.

  2. Una función de comparación para la igualdad ; esto es necesario porque el hash no puede confiar en el hecho de que la función hash siempre proporcionará un valor de hash único para cada clave distinta (es decir, debe ser capaz de lidiar con colisiones), por lo que necesita una forma de comparar dos claves dadas para una coincidencia exacta. Puede implementarlo como una clase que reemplaza a operator() , o como una especialización de std::equal , o, lo más fácil de todo, al sobrecargar a operator==() para su tipo de clave (como ya hizo).

La dificultad con la función hash es que si su tipo de clave consta de varios miembros, generalmente la función hash calculará los valores hash para los miembros individuales y, de alguna manera, los combinará en un valor hash para todo el objeto. Para obtener un buen rendimiento (es decir, pocas colisiones), debe pensar detenidamente cómo combinar los valores hash individuales para asegurarse de evitar la misma salida para diferentes objetos con demasiada frecuencia.

Un punto de partida bastante bueno para una función hash es aquel que utiliza el desplazamiento de bits y XOR a nivel de bits para combinar los valores hash individuales. Por ejemplo, asumiendo un tipo de clave como este:

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); } };

Aquí hay una función hash simple (adaptada de la utilizada en el ejemplo de cppreference para las funciones hash definidas por el usuario ):

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); } }; }

Con esto en su lugar, puede instanciar un std::unordered_map para el tipo de clave:

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

Utilizará automáticamente std::hash<Key> como se definió anteriormente para los cálculos del valor de hash, y el operator== definido como función miembro de Key para verificaciones de igualdad.

Si no desea especializar una plantilla dentro del std nombres std (aunque es perfectamente legal en este caso), puede definir la función hash como una clase separada y agregarla a la lista de argumentos de la plantilla para el mapa:

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"} }; }

¿Cómo definir una mejor función hash? Como se dijo anteriormente, definir una buena función hash es importante para evitar colisiones y obtener un buen rendimiento. Para uno realmente bueno, debe tener en cuenta la distribución de los valores posibles de todos los campos y definir una función hash que proyecta esa distribución en un espacio de resultados posibles tan amplio y uniformemente distribuido como sea posible.

Esto puede ser difícil; el método anterior de XOR / desplazamiento de bits probablemente no sea un mal comienzo. Para un comienzo un poco mejor, puede usar la plantilla de función hash_value y hash_combine de la biblioteca Boost. El primero actúa de manera similar a std::hash para tipos estándar (recientemente también incluye tuplas y otros tipos estándar útiles); el último le ayuda a combinar valores hash individuales en uno. Aquí hay una reescritura de la función hash que utiliza las funciones de ayuda de 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; } };

Y aquí hay una reescritura que no usa boost, pero que usa un buen método para combinar los hashes:

namespace std { template <> struct hash<Key> { size_t operator()( const Key& k ) const { // Compute individual hash values for first, second and third // http://.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; } }; }