C ++ unordered_map कुंजी के रूप में एक कस्टम वर्ग प्रकार का उपयोग कर रहा है


285

मैं unordered_mapनिम्नलिखित की तरह एक कस्टम वर्ग को कुंजी के रूप में उपयोग करने की कोशिश कर रहा हूं :

#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 ++ मुझे निम्न त्रुटि देता है:

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

मुझे लगता है, मुझे यह बताने की आवश्यकता है कि C ++ को हैश क्लास कैसे किया जाता है Node, हालांकि, मुझे यह सुनिश्चित नहीं है कि यह कैसे करना है। मैं इस कार्य को कैसे पूरा कर सकता हूं?


2
तीसरे टेम्पलेट तर्क हैश फंक्शन आपूर्ति की जरूरत है।
क्रिसकॉक

3
cppreference का एक सरल और व्यावहारिक उदाहरण है कि यह कैसे करना है: en.cppreference.com/w/cpp/container/unordered_map/unordered_map
jogojapan

जवाबों:


486

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फंक्शन टेम्पलेट का उपयोग कर सकते हैं । पूर्व std::hashमानक प्रकारों के लिए एक समान तरीके से कार्य करता है (हाल ही में ट्यूपल्स और अन्य उपयोगी मानक प्रकारों सहित); उत्तरार्द्ध आपको व्यक्तिगत हैश मानों को एक में मिलाने में मदद करता है। यहाँ हैश फ़ंक्शन का पुनर्लेखन है जो बूस्ट सहायक कार्यों का उपयोग करता है:

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

और यहाँ एक फिर से लिखना है कि बढ़ावा का उपयोग नहीं करता है, फिर भी हैश के संयोजन की अच्छी विधि का उपयोग करता है:

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

11
क्या आप बता सकते हैं कि बिट्स को शिफ्ट करना क्यों आवश्यक है KeyHasher?
चनी

45
यदि आप बिट्स को स्थानांतरित नहीं करते हैं और दो तार समान थे, तो xor उन्हें एक दूसरे को रद्द करने का कारण होगा। तो हैश ("ए", "ए", 1) हैश ("बी", "बी", 1) के समान होगा। आदेश भी मायने नहीं रखेगा, इसलिए हैश ("ए", "बी", 1) हैश ("बी", "ए", 1) के समान होगा।
ब्यूज

1
मैं सिर्फ C ++ सीख रहा हूं और एक चीज जो मैं हमेशा संघर्ष करता हूं वह है: कोड कहां डालूं? मैंने std::hashअपनी कुंजी के लिए एक विशेषज्ञ विधि लिखी है जैसा आपने किया है। मैंने इसे अपने Key.cpp फ़ाइल के नीचे रखा है लेकिन मुझे निम्न त्रुटि मिल रही है 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:। मैं अनुमान लगा रहा हूं कि संकलक मेरी हैश विधि नहीं पा रहा है? क्या मुझे अपनी Key.h फ़ाइल में कुछ भी जोड़ना चाहिए?
बेन

4
@ इसे .h फ़ाइल में डालना सही है। std::hashवास्तव में एक संरचना नहीं है, लेकिन एक संरचना के लिए एक टेम्पलेट (विशेषज्ञता) है । इसलिए यह एक कार्यान्वयन नहीं है - यह एक कार्यान्वयन में बदल जाएगा जब कंपाइलर को इसकी आवश्यकता होगी। टेम्प्लेट हमेशा हेडर फाइलों में जाने चाहिए। यह भी देखें stackoverflow.com/questions/495021/…
jogojapan

3
@ नाइटफ़ेयर find()एक पुनरावृत्तिकर्ता लौटाता है, और यह पुनरावृत्ति मानचित्र की "प्रविष्टि" की ओर इशारा करता है। एक प्रविष्टि std::pairकुंजी और मूल्य से मिलकर होती है। इसलिए यदि आप करते हैं auto iter = m6.find({"John","Doe",12});, तो आपको कुंजी iter->first(और स्ट्रिंग "example") में कुंजी मिल जाएगी iter->second। यदि आप सीधे स्ट्रिंग चाहते हैं, तो आप या तो उपयोग कर सकते हैं m6.at({"John","Doe",12})(यदि कुंजी बाहर नहीं निकलती है तो एक अपवाद फेंक देगा), या m6[{"John","Doe",12}](यदि कुंजी मौजूद नहीं है तो एक खाली मान पैदा करेगा)।
जोगोजपन

16

मुझे लगता है, जोगोजापान ने बहुत अच्छा और संपूर्ण जवाब दिया । आप निश्चित रूप से मेरी पोस्ट पढ़ने से पहले इस पर एक नज़र रखना चाहिए। हालाँकि, मैं निम्नलिखित जोड़ना चाहूँगा:

  1. आप unordered_mapसमानता तुलना ऑपरेटर ( operator==) का उपयोग करने के बजाय एक तुलनात्मक फ़ंक्शन को अलग से परिभाषित कर सकते हैं । यह सहायक हो सकता है, उदाहरण के लिए, यदि आप दो Nodeवस्तुओं के सभी सदस्यों की एक दूसरे से तुलना करने के लिए उत्तरार्द्ध का उपयोग करना चाहते हैं , लेकिन केवल कुछ विशिष्ट सदस्यों की कुंजी के रूप में unordered_map
  2. आप हैश और तुलना कार्यों को परिभाषित करने के बजाय लैम्ब्डा अभिव्यक्तियों का भी उपयोग कर सकते हैं ।

सभी में, आपकी Nodeकक्षा के लिए, कोड इस प्रकार लिखा जा सकता है:

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

टिप्पणियाँ:

  • मैंने सिर्फ जोगोजपन के जवाब के अंत में हैशिंग विधि का पुन: उपयोग किया है, लेकिन आप यहां अधिक सामान्य समाधान के लिए विचार पा सकते हैं (यदि आप बूस्ट का उपयोग नहीं करना चाहते हैं)।
  • मेरा कोड शायद बहुत छोटा है। थोड़े अधिक पठनीय संस्करण के लिए, कृपया इस कोड को आइडोन पर देखें

8 कहां से आया और इसका क्या मतलब है?
एंडीचिन

@WhalalalalalalaCHen: कृपया पर एक नज़र के प्रलेखन unordered_mapनिर्माता8तथाकथित "बकेट काउंट" का प्रतिनिधित्व करता है। एक बाल्टी कंटेनर की आंतरिक हैश तालिका में एक स्लॉट है, उदाहरण के unordered_map::bucket_countलिए अधिक जानकारी के लिए देखें।
हार्न

@ हीरालाललालचेन: मैंने 8यादृच्छिक रूप से चुना । आपके द्वारा संग्रहित की जाने वाली सामग्री के आधार पर unordered_map, बाल्टी गणना कंटेनर के प्रदर्शन को प्रभावित कर सकती है।
हार्न
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.