C ++ 0x에서 해시 값을 어떻게 결합합니까?


87

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

hash_combine하지만 boost에 제시된 기능을 찾을 수 없습니다 . 이와 같은 것을 구현하는 가장 깨끗한 방법은 무엇입니까? 아마도 C ++ 0x를 사용 xor_combine합니까?

답변:


93

글쎄, 그냥 부스트 녀석들이했던 것처럼하세요.

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

26
네, 그게 제가 할 수있는 최선입니다. 나는 표준위원회가 그렇게 명백한 것을 어떻게 거부했는지 이해하지 못한다.
Neil G

13
@ 닐 : 동의합니다. 나는 그들에 대한 간단한 솔루션은 해시 가지고있는 라이브러리의 요구 사항이 될 것이라고 생각 std::pair(또는 tuple심지어을). 각 요소의 해시를 계산 한 다음 결합합니다. (그리고 정의 된 구현 방식으로 표준 라이브러리의 정신으로.)
GManNickG 2010

3
표준에서 생략 된 명백한 것들이 많이 있습니다. 집중적 인 동료 검토 과정은 이러한 사소한 일을 꺼내는 것을 어렵게 만듭니다.
stinky472

15
왜 이런 마법의 숫자가 여기에 있습니까? 그리고 위의 시스템에 따라 다르지 않습니까 (예 : x86 및 x64 플랫폼에서 다르지 않음)?
einpoklum 2014 년

3
hash_combine의 포함을 제안 종이가있다 여기
SSJ_GZ

35

이 솔루션을 찾는 다른 사람들에게 유용 할 수 있으므로 여기에서 공유하겠습니다. @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

시드가 항상 각각 6과 2 씩 비트 시프트되는 이유는 무엇입니까?
j00hi

5

이는 다음과 같이 가변 템플릿을 사용하여 해결할 수도 있습니다.

#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!")문자열이 아닌 포인터에서 해시 값을 계산합니다. 이것이 아마도 표준이 구조체를 사용하는 이유 일 것입니다.


4

며칠 전에이 답변 의 약간 개선 된 버전을 생각해 냈습니다 (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에서도 작동합니다.
Henri Menke

3

나는 vt4a2h답변 에서 C ++ 17 접근 방식을 정말 좋아 하지만 문제가 있습니다 .TheRest 는 값으로 전달되는 반면 const 참조로 전달하는 것이 더 바람직합니다 이동 전용 유형과 함께 사용 가능).

다음은 여전히 fold 표현식 (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템플릿 전문화 가 있어야합니다 .hashstd

예:

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네임 스페이스 에 주입하는 대신 표준 컨테이너 의 템플릿 인수를 사용하여 사용자 지정 해시를 지정 하는 것이 더 좋습니다 .
Henri Menke

3

vt4a2h대답 은 확실히 좋지만 C ++ 17 접기 표현식을 사용하며 모든 사람이 새로운 도구 모음으로 쉽게 전환 할 수있는 것은 아닙니다. 아래 버전은 확장기 트릭을 사용하여 접기 표현식을 에뮬레이트하고 C ++ 11C ++ 14 에서도 작동합니다.

또한 함수를 표시 inline하고 가변 템플릿 인수에 대해 완벽한 전달을 사용합니다.

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

컴파일러 탐색기의 라이브 예제


훨씬 좋아 보인다, 감사합니다! 예를 들어 QString과 같은 일부 암시 적으로 공유 된 객체를 사용했기 때문에 값 전달에 대해서는 신경 쓰지 않았을 것입니다.
vt4a2h
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.