Karma değerlerini C ++ 0x'de nasıl birleştiririm?


87

C ++ 0x ekler hash<...>(...).

Ben bulamadım hash_combinesunulduğu gibi, çalışmakla birlikte boost . Böyle bir şeyi uygulamanın en temiz yolu nedir? Belki C ++ 0x kullanarak xor_combine?

Yanıtlar:


94

Pekala, sadece destekçilerin yaptığı gibi yapın:

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

27
evet, yapabileceğimin en iyisi bu. Standartlar komitesinin bu kadar bariz bir şeyi nasıl reddettiğini anlamıyorum.
Neil G

13
@Neil: Katılıyorum. Bence onlar için basit bir çözüm, kütüphanenin bir karma std::pair(veya tuplehatta) olması gerekliliği olacaktır . Her bir öğenin karmasını hesaplar ve ardından bunları birleştirir. (Ve standart kitaplığın ruhuna uygun olarak, tanımlanmış bir uygulama şeklinde.)
GManNickG

3
Standarttan çıkarılan birçok bariz şey var. Yoğun akran değerlendirmesi süreci, bu küçük şeyleri kapıdan çıkarmayı zorlaştırır.
stinky472

15
Neden bu sihirli sayılar burada? Ve yukarıdaki makineye bağlı değil mi (örneğin, x86 ve x64 platformlarında farklı olmayacak mı)?
einpoklum

3
Hash_combine'ın buraya
SSJ_GZ

37

Bu çözümü arayanlar için faydalı olabileceği için burada paylaşacağım: @KarlvonMoor cevabından başlayarak, birkaç değeri bir araya getirmeniz gerekiyorsa kullanımında terser olan değişken bir şablon sürümü burada:

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

Kullanım:

std::size_t h=0;
hash_combine(h, obj1, obj2, obj3);

Bu, orijinal olarak, özel türleri kolayca hashable hale getirmek için değişken bir makro uygulamak için yazılmıştır (ki bu, bir hash_combineişlevin birincil kullanımlarından biridir ):

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

Kullanım:

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

Tohum neden her zaman sırasıyla 6 ve 2 ile bit kaydırılır?
j00hi

5

Bu, aşağıdaki gibi bir değişken şablon kullanılarak da çözülebilir:

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

Kullanım:

#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!");
}

Kesinlikle bir şablon işlevi yapılabilir, ancak bu bazı kötü tür çıkarımlarına neden olabilir, örneğin hash("Hallo World!")dizge yerine işaretçi üzerinde bir karma değeri hesaplar. Muhtemelen standardın bir yapı kullanmasının nedeni budur.


5

Birkaç gün önce bu cevabın biraz geliştirilmiş versiyonunu buldum (C ++ 17 desteği gereklidir):

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

Yukarıdaki kod, kod üretimi açısından daha iyidir. Kodumda Qt'den qHash işlevini kullandım, ancak başka bir karma kullanmak da mümkün.


Katlama ifadesini olarak yazın, (int[]){0, (hashCombine(seed, rest), 0)...};C ++ 11'de de çalışacaktır.
Henri Menke

3

Vt4a2h'nin cevabından C ++ 17 yaklaşımını gerçekten seviyorum , ancak bir sorundan muzdarip : Değere göre aktarılırken , Restbunları const referanslarıyla aktarmak daha cazip olurdu (eğer olacaksa bir zorunluluktur) yalnızca taşıma türleriyle kullanılabilir).

İşte hala bir katlama ifadesi kullanan (C ++ 17 veya daha üstünü gerektirmesinin nedeni budur) ve kullanan std::hash(Qt hash işlevi yerine ) uyarlanmış sürüm :

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

Tamlık aşkına: bu sürümüyle kullanılabilir olacaktır her türlü hash_combineolması gerekir şablon ihtisas için hashenjekte stdad.

Misal:

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

Dolayısıyla, Byukarıdaki örnekteki bu tür A, aşağıdaki kullanım örneğinin gösterdiği gibi başka bir tür içinde de kullanılabilir :

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

Benim düşünceme göre Hash, stdad alanına enjekte etmek yerine, özel hasherinizi belirtmek için standart kapsayıcıların şablon argümanlarını kullanmak daha iyidir .
Henri Menke

3

Vt4a2h tarafından cevap kesinlikle güzel ama C ++ 17 kat ifadesini kullanan ve herkesin kolayca yeni toolchain geçmek yapabiliyor. Aşağıdaki sürüm, bir katlama ifadesini taklit etmek için genişletici hilesini kullanır ve C ++ 11 ve C ++ 14'te de çalışır.

Ek olarak, işlevi işaretledim inlineve çeşitli şablon argümanları için mükemmel iletimi kullandım.

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

Derleyici Gezgini'nde canlı örnek


Çok daha iyi görünüyor, teşekkürler! Muhtemelen değere göre geçiş yapmayı umursamıyordum çünkü örtük olarak paylaşılan bazı nesneler kullandım, örneğin QString gibi.
vt4a2h
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.