Estou tentando usar uma classe personalizada como chave para um unordered_map
, como o seguinte:
#include <iostream>
#include <algorithm>
#include <unordered_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;
}
No entanto, o g ++ me fornece o seguinte erro:
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
Eu acho que, eu preciso dizer ao C ++ como classe de hash Node
, no entanto, não tenho muita certeza de como fazê-lo. Como posso realizar essas tarefas?
Respostas:
Para poder usar
std::unordered_map
(ou um dos outros contêineres associativos não ordenados) com um tipo de chave definido pelo usuário, você precisa definir duas coisas:Uma função de hash ; deve ser uma classe que substitua
operator()
e calcule o valor do hash, dado um objeto do tipo de chave. Uma maneira particularmente direta de fazer isso é especializar ostd::hash
modelo para o seu tipo de chave.Uma função de comparação para igualdade ; isso é necessário porque o hash não pode confiar no fato de que a função hash sempre fornecerá um valor de hash exclusivo para cada chave distinta (ou seja, precisa ser capaz de lidar com colisões), portanto, é necessário uma maneira de comparar duas chaves fornecidas para uma correspondência exata. Você pode implementar isso como uma classe que substitui
operator()
, ou como uma especialização destd::equal
, ou - o mais fácil de tudo - sobrecarregandooperator==()
seu tipo de chave (como você já fez).A dificuldade com a função de hash é que, se o seu tipo de chave consistir em vários membros, você normalmente terá a função de hash para calcular valores de hash para os membros individuais e, de alguma forma, combiná-los em um valor de hash para o objeto inteiro. Para um bom desempenho (ou seja, poucas colisões), você deve pensar cuidadosamente sobre como combinar os valores de hash individuais para garantir que você evite obter a mesma saída para objetos diferentes com muita frequência.
Um ponto de partida bastante bom para uma função de hash é aquele que usa XOR de deslocamento de bits e bit a bit para combinar os valores individuais de hash. Por exemplo, assumindo um tipo de chave como este:
Aqui está uma função hash simples (adaptada da usada no exemplo cppreference para funções hash definidas pelo usuário ):
Com isso, você pode instanciar a
std::unordered_map
para o tipo de chave:Ele será usado automaticamente
std::hash<Key>
como definido acima para os cálculos de valor de hash e ooperator==
definido como função de membroKey
para verificações de igualdade.Se você não deseja se especializar no modelo dentro do
std
namespace (embora seja perfeitamente legal nesse caso), defina a função hash como uma classe separada e adicione-a à lista de argumentos do modelo para o mapa:Como definir uma melhor função de hash? Como dito acima, é importante definir uma boa função de hash para evitar colisões e obter um bom desempenho. Para um valor realmente bom, é necessário levar em consideração a distribuição dos possíveis valores de todos os campos e definir uma função de hash que projete essa distribuição em um espaço de resultados possíveis da forma mais ampla e distribuída possível.
Isso pode ser difícil; o método XOR / deslocamento de bits acima provavelmente não é um começo ruim. Para um início um pouco melhor, você pode usar o modelo de função
hash_value
ehash_combine
da biblioteca Boost. O primeiro atua de maneira semelhantestd::hash
à dos tipos padrão (incluindo recentemente tuplas e outros tipos padrão úteis); o último ajuda a combinar valores individuais de hash em um. Aqui está uma reescrita da função hash que usa as funções auxiliares do Boost:E aqui está uma reescrita que não usa impulso, mas usa um bom método de combinar os hashes:
fonte
KeyHasher
?std::hash
método especializado para minha chave, como você fez. Eu coloquei isso na parte inferior do meu arquivo Key.cpp mas estou recebendo o seguinte erro:Error 57 error C2440: 'type cast' : cannot convert from 'const Key' to 'size_t' c:\program files (x86)\microsoft visual studio 10.0\vc\include\xfunctional
. Eu estou supondo que o compilador não está encontrando meu método hash? Devo adicionar algo ao meu arquivo Key.h?std::hash
não é realmente uma estrutura, mas um modelo (especialização) para uma estrutura . Portanto, não é uma implementação - será transformada em uma implementação quando o compilador precisar. Os modelos sempre devem ir para os arquivos de cabeçalho. Veja também stackoverflow.com/questions/495021/…find()
retorna um iterador e esse apontador aponta para uma "entrada" do mapa. Uma entrada éstd::pair
composta por chave e valor. Então, se você fizer issoauto iter = m6.find({"John","Doe",12});
, receberá a chaveiter->first
e o valor (ou seja, a string"example"
)iter->second
. Se você deseja a string diretamente, pode usarm6.at({"John","Doe",12})
(isso gerará uma exceção se a chave não sair) oum6[{"John","Doe",12}]
(que criará um valor vazio se a chave não existir).Eu acho que o jogojapan deu uma resposta muito boa e exaustiva . Você definitivamente deve dar uma olhada antes de ler meu post. No entanto, gostaria de adicionar o seguinte:
unordered_map
separadamente, em vez de usar o operador de comparação de igualdade (operator==
). Isso pode ser útil, por exemplo, se você quiser usá-lo para comparar todos os membros de doisNode
objetos entre si, mas apenas alguns membros específicos como chave de umunordered_map
.Em resumo, para sua
Node
classe, o código pode ser escrito da seguinte maneira:Notas:
fonte
unordered_map
construtor . O8
representa a chamada "contagem de baldes". Um balde é um slot na tabela de hash interna do contêiner, consulte, por exemplo,unordered_map::bucket_count
para obter mais informações.8
aleatoriamente. Dependendo do conteúdo que você deseja armazenarunordered_map
, a contagem de buckets pode afetar o desempenho do contêiner.