it-swarm.com.de

C++ unordered_map verwendet einen benutzerdefinierten Klassentyp als Schlüssel

Ich versuche, eine benutzerdefinierte Klasse als Schlüssel für einen unordered_map wie den folgenden zu verwenden:

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

G ++ gibt mir jedoch den folgenden Fehler:

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

Ich denke, ich brauche den C++ - Befehl, wie man die Klasse Node hash, aber ich bin mir nicht sicher, wie ich das machen soll. Wie kann ich diese Aufgaben erledigen?

225
Alfred Zhong

Um std::unordered_map (oder einen der anderen ungeordneten assoziativen Container) mit einem benutzerdefinierten Schlüsseltyp verwenden zu können, müssen Sie zwei Dinge definieren:

  1. Eine Hash-Funktion; Dies muss eine Klasse sein, die operator() überschreibt und den Hashwert eines Objekts vom Schlüsseltyp berechnet. Eine besonders einfache Möglichkeit, dies zu tun, besteht darin, die Vorlage std::hash für Ihren Schlüsseltyp zu spezialisieren.

  2. Eine Vergleichsfunktion für Gleichheit; Dies ist erforderlich, da der Hash sich nicht auf die Tatsache verlassen kann, dass die Hash-Funktion immer einen eindeutigen Hashwert für jeden eindeutigen Schlüssel bereitstellt (dh er muss in der Lage sein, mit Kollisionen fertig zu werden). Daher muss er zwei gegebene Schlüssel vergleichen für eine genaue Übereinstimmung. Sie können dies entweder als Klasse implementieren, die operator() überschreibt, oder als Spezialisierung von std::equal oder - am einfachsten - durch Überladen von operator==() für Ihren Schlüsseltyp (wie Sie es bereits getan haben).

Die Schwierigkeit bei der Hash-Funktion besteht darin, dass, wenn Ihr Schlüsseltyp aus mehreren Elementen besteht, die Hash-Funktion normalerweise Hash-Werte für die einzelnen Elemente berechnen und diese dann irgendwie zu einem Hash-Wert für das gesamte Objekt zusammenfassen muss. Für eine gute Leistung (d. H. Wenige Kollisionen) sollten Sie sorgfältig überlegen, wie Sie die einzelnen Hash-Werte kombinieren, um zu vermeiden, dass Sie zu oft die gleiche Ausgabe für verschiedene Objekte erhalten.

Ein ziemlich guter Ausgangspunkt für eine Hash-Funktion ist derjenige, der Bit-Shifting und bitweise XOR verwendet, um die einzelnen Hash-Werte zu kombinieren. Angenommen, ein Schlüsseltyp wie folgt:

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

Hier ist eine einfache Hash-Funktion (angepasst an die im cppreference-Beispiel für benutzerdefinierte Hash-Funktionen verwendete):

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

}

Damit können Sie einen std::unordered_map für den Schlüsseltyp instanziieren:

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

Es verwendet automatisch std::hash<Key> wie oben definiert für die Hashwertberechnungen und den operator==, der als Member-Funktion von Key für Gleichheitsprüfungen definiert ist.

Wenn Sie die Vorlage nicht innerhalb des Namespace std spezialisieren möchten (obwohl dies in diesem Fall absolut legal ist), können Sie die Hash-Funktion als separate Klasse definieren und sie der Vorlagenargumentliste für die Map hinzufügen:

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

Wie definiere ich eine bessere Hash-Funktion? Wie oben erwähnt, ist das Definieren einer guten Hash-Funktion wichtig, um Kollisionen zu vermeiden und eine gute Leistung zu erzielen. Für ein wirklich gutes Ergebnis müssen Sie die Verteilung möglicher Werte aller Felder berücksichtigen und eine Hash-Funktion definieren, die diese Verteilung auf einen Bereich möglicher Ergebnisse projiziert, der möglichst breit und gleichmäßig verteilt ist.

Das kann schwierig sein; Die oben beschriebene XOR/Bit-Shifting-Methode ist wahrscheinlich kein schlechter Start. Für einen etwas besseren Start können Sie die Funktionsvorlage hash_value und hash_combine aus der Boost-Bibliothek verwenden. Ersteres verhält sich ähnlich wie std::hash für Standardtypen (in letzter Zeit auch Tupel und andere nützliche Standardtypen). Letzteres hilft Ihnen, einzelne Hashwerte zu einem zu kombinieren. Hier ist eine Umschreibung der Hash-Funktion, die die Boost-Helfer-Funktionen verwendet:

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

Und hier ist eine Umschreibung, die keinen Boost verwendet, die Hashes jedoch mit einer guten Methode kombiniert:

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

Ich denke, jogojapan gab ein sehr gutes und erschöpfendes answer . Sie sollten es unbedingt lesen, bevor Sie meinen Beitrag lesen. Ich möchte jedoch Folgendes hinzufügen:

  1. Sie können eine Vergleichsfunktion für einen unordered_map separat definieren, anstatt den Gleichheitsvergleichsoperator (operator==) zu verwenden. Dies kann beispielsweise hilfreich sein, wenn Sie letztere verwenden möchten, um alle Elemente zweier Node-Objekte miteinander zu vergleichen, jedoch nur einige bestimmte Elemente als Schlüssel eines unordered_map.
  2. Sie können auch Lambda-Ausdrücke verwenden, anstatt die Hash- und Vergleichsfunktionen zu definieren.

Alles in allem könnte der Code für Ihre Node-Klasse folgendermaßen geschrieben werden:

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

Anmerkungen:

  • Ich habe die Hashmethode am Ende der Antwort von jogojapan gerade wieder verwendet, aber Sie können die Idee für eine allgemeinere Lösung hier finden (wenn Sie Boost nicht verwenden möchten).
  • Mein Code ist vielleicht ein bisschen zu sehr reduziert. Eine etwas lesbarere Version finden Sie unter dieser Code auf Ideone .
0
honk