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.