C ++ 0x जोड़ता है hash<...>(...)
।
मैं एक hash_combine
समारोह नहीं मिल सका , हालांकि के रूप में बढ़ावा दिया । ऐसा कुछ लागू करने का सबसे साफ तरीका क्या है? शायद, सी ++ 0x का उपयोग कर xor_combine
?
C ++ 0x जोड़ता है hash<...>(...)
।
मैं एक hash_combine
समारोह नहीं मिल सका , हालांकि के रूप में बढ़ावा दिया । ऐसा कुछ लागू करने का सबसे साफ तरीका क्या है? शायद, सी ++ 0x का उपयोग कर xor_combine
?
जवाबों:
ठीक है, बस इसे ऐसे करें जैसे बूस्ट लोगों ने किया:
template <class T>
inline void hash_combine(std::size_t& seed, const T& v)
{
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
std::pair
(या tuple
, यहां तक कि) के लिए हैश । यह प्रत्येक तत्व के हैश की गणना करेगा, फिर उन्हें संयोजित करेगा। (और मानक पुस्तकालय की भावना में, एक क्रियान्वित तरीके से।)
मैं इसे यहाँ साझा करूँगा क्योंकि यह इस समाधान की तलाश करने वाले अन्य लोगों के लिए उपयोगी हो सकता है: @KarlvonMoor उत्तर से शुरू होकर , यहाँ एक वैरेडिक टेम्प्लेट संस्करण है, जो यदि आपके उपयोग में एक साथ कई मानों को संयोजित करता है:
inline void hash_combine(std::size_t& seed) { }
template <typename T, typename... Rest>
inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) {
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
hash_combine(seed, rest...);
}
उपयोग:
std::size_t h=0;
hash_combine(h, obj1, obj2, obj3);
यह मूल रूप से कस्टम प्रकार को आसानी से धोने योग्य बनाने के लिए एक वैरेडिक मैक्रो को लागू करने के लिए लिखा गया था (जो मुझे लगता है कि एक hash_combine
समारोह के प्राथमिक उपयोगों में से एक है):
#define MAKE_HASHABLE(type, ...) \
namespace std {\
template<> struct hash<type> {\
std::size_t operator()(const type &t) const {\
std::size_t ret = 0;\
hash_combine(ret, __VA_ARGS__);\
return ret;\
}\
};\
}
उपयोग:
struct SomeHashKey {
std::string key1;
std::string key2;
bool key3;
};
MAKE_HASHABLE(SomeHashKey, t.key1, t.key2, t.key3)
// now you can use SomeHashKey as key of an std::unordered_map
इसे निम्नानुसार एक वैचारिक टेम्पलेट का उपयोग करके भी हल किया जा सकता है:
#include <functional>
template <typename...> struct hash;
template<typename T>
struct hash<T>
: public std::hash<T>
{
using std::hash<T>::hash;
};
template <typename T, typename... Rest>
struct hash<T, Rest...>
{
inline std::size_t operator()(const T& v, const Rest&... rest) {
std::size_t seed = hash<Rest...>{}(rest...);
seed ^= hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
return seed;
}
};
उपयोग:
#include <string>
int main(int,char**)
{
hash<int, float, double, std::string> hasher;
std::size_t h = hasher(1, 0.2f, 2.0, "Hello World!");
}
कोई निश्चित रूप से एक टेम्पलेट फ़ंक्शन कर सकता है, लेकिन यह कुछ बुरा प्रकार की कटौती का कारण बन सकता है उदाहरण के hash("Hallo World!")
लिए स्ट्रिंग पर बजाय सूचक पर एक हैश मान की गणना करेगा। शायद यही कारण है, क्यों मानक एक संरचना का उपयोग करता है।
कुछ दिनों पहले मैं इस उत्तर के थोड़े बेहतर संस्करण के साथ आया था (C ++ 17 समर्थन की आवश्यकता है):
template <typename T, typename... Rest>
void hashCombine(uint& seed, const T& v, Rest... rest)
{
seed ^= ::qHash(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
(hashCombine(seed, rest), ...);
}
कोड जनरेशन के लिहाज से ऊपर का कोड बेहतर है। मैंने अपने कोड में Qt से qHash फ़ंक्शन का उपयोग किया, लेकिन किसी अन्य हैशर्स का उपयोग करना भी संभव है।
(int[]){0, (hashCombine(seed, rest), 0)...};
और यह C ++ 11 में भी काम करेगा।
मैं वास्तव में vt4a2h द्वारा उत्तर से C ++ 17 दृष्टिकोण को पसंद करता हूं , हालांकि यह एक समस्या से ग्रस्त है: Rest
मूल्य द्वारा पारित किया गया है जबकि उन्हें कॉन्स्टिट्यूशन संदर्भों द्वारा पारित करना अधिक वांछनीय होगा (जो कि एक होना चाहिए चाल-प्रकार के साथ प्रयोग करने योग्य)।
यहाँ अनुकूलित संस्करण है जो अभी भी एक गुना अभिव्यक्ति का उपयोग करता है (यही कारण है कि इसके लिए C ++ 17 या इसके बाद के संस्करण की आवश्यकता होती है) और std::hash
(Qt हैश फ़ंक्शन के बजाय ) का उपयोग करता है :
template <typename T, typename... Rest>
void hash_combine(std::size_t& seed, const T& v, const Rest&... rest)
{
seed ^= std::hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
(hash_combine(seed, rest), ...);
}
पूर्णता के लिए: सभी प्रकार जो इस संस्करण के साथ उपयोग करने योग्य होंगे , उनके नाम में इंजेक्शन के लिए टेम्पलेट विशेषज्ञताhash_combine
होनी चाहिए ।hash
std
उदाहरण:
namespace std // Inject hash for B into std::
{
template<> struct hash<B>
{
std::size_t operator()(B const& b) const noexcept
{
std::size_t h = 0;
cgb::hash_combine(h, b.firstMember, b.secondMember, b.andSoOn);
return h;
}
};
}
ताकि B
ऊपर के उदाहरण में भी एक और प्रकार के भीतर प्रयोग करने योग्य हो A
, जैसे निम्न उपयोग उदाहरण दिखाता है:
struct A
{
std::string mString;
int mInt;
B mB;
B* mPointer;
}
namespace std // Inject hash for A into std::
{
template<> struct hash<A>
{
std::size_t operator()(A const& a) const noexcept
{
std::size_t h = 0;
cgb::hash_combine(h,
a.mString,
a.mInt,
a.mB, // calls the template specialization from above for B
a.mPointer // does not call the template specialization but one for pointers from the standard template library
);
return h;
}
};
}
Hash
मानक कंटेनरों के टेम्प्लेट तर्कों का उपयोग करने के लिए है, जो आपके कस्टम हैशर को नामांकित स्थान पर इंजेक्ट करने के बजाय निर्दिष्ट करता std
है।
Vt4a2h द्वारा जवाब निश्चित रूप से अच्छा है, लेकिन सी ++ 17 गुना अभिव्यक्ति का उपयोग करता है और हर कोई आसानी से एक नए toolchain करने के लिए स्विच करने में सक्षम है। नीचे दिया गया संस्करण गुना अभिव्यक्ति का अनुकरण करने के लिए विस्तारक चाल का उपयोग करता है और C ++ 11 और C ++ 14 में भी काम करता है ।
इसके अतिरिक्त, मैंने फ़ंक्शन को चिह्नित किया inline
और varadic टेम्पलेट तर्क के लिए एकदम सही अग्रेषण का उपयोग किया।
template <typename T, typename... Rest>
inline void hashCombine(std::size_t &seed, T const &v, Rest &&... rest) {
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
(int[]){0, (hashCombine(seed, std::forward<Rest>(rest)), 0)...};
}