Làm cách nào để kết hợp các giá trị băm trong C ++ 0x?


87

C ++ 0x thêm vào hash<...>(...).

hash_combineMặc dù vậy, tôi không thể tìm thấy một chức năng như đã trình bày trong phần boost . Cách sạch nhất để thực hiện một cái gì đó như thế này là gì? Có lẽ, sử dụng C ++ 0x xor_combine?

Câu trả lời:


93

Chà, cứ làm như những người tăng đã làm:

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
vâng, đó là điều tốt nhất tôi có thể làm. Tôi không hiểu làm thế nào mà ủy ban tiêu chuẩn lại từ chối một thứ quá rõ ràng.
Neil G

13
@Neil: Tôi đồng ý. Tôi nghĩ rằng một giải pháp đơn giản cho họ là yêu cầu thư viện phải có một hàm băm cho std::pair(hoặc tuple, thậm chí). Nó sẽ tính toán hàm băm của mỗi phần tử, sau đó kết hợp chúng. (Và theo tinh thần của thư viện tiêu chuẩn, theo một cách triển khai được xác định.)
GManNickG

3
Có rất nhiều điều hiển nhiên bị bỏ qua khỏi tiêu chuẩn. Quá trình đánh giá đồng nghiệp chuyên sâu khiến việc đưa những điều nhỏ nhặt đó ra ngoài trở nên khó khăn.
stinky472,

15
Tại sao những con số kỳ diệu ở đây? Và điều trên không phụ thuộc vào máy (ví dụ: nó sẽ không khác nhau trên nền tảng x86 và x64)?
einpoklum

3
Có một bài báo đề xuất bao gồm hash_combine ở đây
SSJ_GZ

35

Tôi sẽ chia sẻ nó ở đây vì nó có thể hữu ích cho những người khác đang tìm kiếm giải pháp này: bắt đầu từ câu trả lời @KarlvonMoor , đây là phiên bản mẫu đa dạng, sẽ gọn gàng hơn trong cách sử dụng nếu bạn phải kết hợp nhiều giá trị với nhau:

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

Sử dụng:

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

Điều này ban đầu được viết để triển khai macro thay đổi để dễ dàng làm cho các loại tùy chỉnh có thể băm (mà tôi nghĩ là một trong những cách sử dụng chính của một hash_combinehàm):

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

Sử dụng:

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

Tại sao hạt giống luôn luôn được bithift lần lượt là 6 và 2?
j00hi

5

Điều này cũng có thể được giải quyết bằng cách sử dụng một mẫu đa dạng như sau:

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

Sử dụng:

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

Người ta chắc chắn có thể tạo một hàm mẫu, nhưng điều này có thể gây ra một số loại trừ khó chịu, ví dụ: hash("Hallo World!") sẽ tính toán một giá trị băm trên con trỏ chứ không phải trên chuỗi. Đây có lẽ là lý do, tại sao tiêu chuẩn sử dụng cấu trúc.


4

Một vài ngày trước, tôi đã đưa ra phiên bản cải tiến một chút của câu trả lời này (yêu cầu hỗ trợ 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), ...);
}

Đoạn mã trên tốt hơn về mặt tạo mã. Tôi đã sử dụng hàm qHash từ Qt trong mã của mình, nhưng cũng có thể sử dụng bất kỳ hàm băm nào khác.


Viết biểu thức gấp dưới dạng (int[]){0, (hashCombine(seed, rest), 0)...};và nó cũng sẽ hoạt động trong C ++ 11.
Henri Menke

3

Tôi thực sự thích cách tiếp cận C ++ 17 từ câu trả lời của vt4a2h , tuy nhiên nó gặp phải một vấn đề: Giá trị Restđược truyền theo giá trị trong khi sẽ mong muốn hơn nếu chuyển chúng bằng tham chiếu const (đó là điều bắt buộc nếu phải sử dụng được với các loại chỉ di chuyển).

Đây là phiên bản đã điều chỉnh vẫn sử dụng biểu thức gấp (đó là lý do tại sao nó yêu cầu C ++ 17 trở lên) và sử dụng std::hash(thay vì hàm băm 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), ...);
}

Vì lợi ích hoàn chỉnh: Tất cả các loại có thể sử dụng được với phiên bản này của hash_combinephải có chuyên môn hóa khuôn mẫu để hashđưa vàostd không gian tên.

Thí dụ:

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

Vì vậy, kiểu đó Btrong ví dụ trên cũng có thể sử dụng được trong một kiểu khác A, như ví dụ sử dụng sau đây cho thấy:

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

Theo ý kiến ​​của tôi, tốt hơn là sử dụng các Hashđối số mẫu của các vùng chứa tiêu chuẩn để chỉ định hàm băm tùy chỉnh của bạn thay vì đưa nó vào stdkhông gian tên.
Henri Menke

3

Câu trả lời của vt4a2h chắc chắn là hay nhưng sử dụng biểu thức C ++ 17 gấp và không phải ai cũng có thể chuyển sang chuỗi công cụ mới hơn một cách dễ dàng. Phiên bản bên dưới sử dụng thủ thuật mở rộng để mô phỏng biểu thức gấp và hoạt động trong C ++ 11C ++ 14 .

Ngoài ra, tôi đã đánh dấu hàm inlinevà sử dụng chuyển tiếp hoàn hảo cho các đối số mẫu khác nhau.

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

Ví dụ trực tiếp trên Compiler Explorer


Trông đẹp hơn nhiều, cảm ơn bạn! Tôi có lẽ không quan tâm đến việc chuyển theo giá trị, bởi vì tôi đã sử dụng một số đối tượng được chia sẻ ngầm, chẳng hạn như QString.
vt4a2h
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.