web-dev-qa-db-fra.com

Unordered_map C++ utilisant un type de classe personnalisé comme clé

J'essaie d'utiliser une classe personnalisée comme clé pour un unordered_map, comme suit:

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

Cependant, g ++ me donne l'erreur suivante:

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

J'imagine que j'ai besoin du tell C++ pour savoir comment hacher la classe Node. Cependant, je ne sais pas trop comment faire. Comment puis-je accomplir cette tâche?

225
Alfred Zhong

Pour pouvoir utiliser std::unordered_map (ou l'un des autres conteneurs associatifs non ordonnés) avec un type de clé défini par l'utilisateur, vous devez définir deux éléments:

  1. Une fonction hash; il doit s'agir d'une classe qui remplace operator() et calcule la valeur de hachage en fonction d'un objet du type-clé. Une méthode particulièrement simple consiste à spécialiser le modèle std::hash pour votre type de clé.

  2. Une fonction de comparaison pour l'égalité; cela est nécessaire parce que le hachage ne peut pas compter sur le fait que la fonction de hachage fournira toujours une valeur de hachage unique pour chaque clé distincte (c.-à-d. qu'elle doit pouvoir gérer les collisions), de sorte qu'il faut un moyen de comparer deux clés données. pour une correspondance exacte. Vous pouvez l'implémenter soit en tant que classe remplaçant operator(), ou en tant que spécialisation de std::equal, ou - le plus simple - en surchargeant operator==() pour votre type de clé (comme vous l'avez déjà fait).

La difficulté avec la fonction de hachage est que si votre type de clé est composé de plusieurs membres, vous aurez généralement la fonction de hachage calculer les valeurs de hachage pour les membres individuels, puis les combiner d'une manière ou d'une autre en une seule valeur de hachage pour l'objet entier. Pour obtenir de bonnes performances (c'est-à-dire peu de collisions), réfléchissez bien à la manière de combiner les valeurs de hachage individuelles afin d'éviter de générer trop souvent le même résultat pour différents objets.

Un assez bon point de départ pour une fonction de hachage est celui qui utilise le décalage de bits et le bit à bit XOR pour combiner les valeurs de hachage individuelles. Par exemple, supposons un type de clé comme ceci:

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

Voici une fonction de hachage simple (adaptée de celle utilisée dans l'exemple cppreference pour les fonctions de hachage définies par l'utilisateur ):

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

}

Avec ceci en place, vous pouvez instancier un std::unordered_map pour le type de clé:

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

Il utilisera automatiquement std::hash<Key> tel que défini ci-dessus pour les calculs de valeur de hachage et le operator== défini comme fonction membre de Key pour les contrôles d'égalité.

Si vous ne souhaitez pas spécialiser le modèle dans l'espace de noms std (bien que ce soit parfaitement légal dans ce cas), vous pouvez définir la fonction de hachage en tant que classe séparée et l'ajouter à la liste des arguments de modèle pour la carte:

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

Comment définir une meilleure fonction de hachage? Comme indiqué ci-dessus, il est important de définir une bonne fonction de hachage pour éviter les collisions et obtenir de bonnes performances. Pour une qualité réelle, vous devez prendre en compte la distribution des valeurs possibles de tous les champs et définir une fonction de hachage qui projette cette distribution dans un espace de résultats possibles aussi large et uniforme que possible.

Cela peut être difficile. la méthode de décalage XOR/bit ci-dessus n'est probablement pas un mauvais début. Pour un meilleur départ, vous pouvez utiliser les modèles de fonction hash_value et hash_combine de la bibliothèque Boost. Le premier fonctionne de la même manière que std::hash pour les types standard (incluant récemment les n-uplets et d’autres types standard utiles); ce dernier vous aide à combiner des valeurs de hachage individuelles en une. Voici une réécriture de la fonction de hachage qui utilise les fonctions d'assistance 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;
  }
};

Et voici une réécriture qui n’utilise pas de boost, mais utilise une bonne méthode pour combiner les hachages:

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;
        }
    };
}
387
jogojapan

Je pense que jogojapan a donné une très bonne et exhaustive réponse . Vous devriez définitivement y jeter un coup d'œil avant de lire mon post. Cependant, j'aimerais ajouter ce qui suit:

  1. Vous pouvez définir une fonction de comparaison pour un unordered_map séparément, au lieu d'utiliser l'opérateur de comparaison d'égalité (operator==). Cela peut être utile, par exemple, si vous souhaitez utiliser ce dernier pour comparer tous les membres de deux objets Node l'un à l'autre, mais uniquement certains membres spécifiques en tant que clé d'un unordered_map.
  2. Vous pouvez également utiliser expressions lambda au lieu de définir les fonctions de hachage et de comparaison.

Au total, pour votre classe Node, le code pourrait être écrit comme suit:

using h = std::hash<int>;
auto hash = [](const Node& n){return ((17 * 31 + h()(n.a)) * 31 + h()(n.b)) * 31 + h()(n.c);};
auto equal = [](const Node& l, const Node& r){return l.a == r.a && l.b == r.b && l.c == r.c;};
std::unordered_map<Node, int, decltype(hash), decltype(equal)> m(8, hash, equal);

Remarques:

  • Je viens de réutiliser la méthode de hachage à la fin de la réponse de jogojapan, mais vous pouvez trouver l’idée d’une solution plus générale ici (si vous ne voulez pas utiliser Boost).
  • Mon code est peut-être un peu trop précis. Pour une version un peu plus lisible, veuillez voir ce code sur Ideone .
0
honk