ฉันจะรวมค่าแฮชใน C ++ 0x ได้อย่างไร


87

C ++ 0x hash<...>(...)เพิ่ม

ฉันไม่สามารถหาhash_combineแต่ฟังก์ชั่นที่นำเสนอในการเพิ่ม อะไรคือวิธีที่สะอาดที่สุดในการนำสิ่งนี้ไปใช้? บางทีอาจจะใช้ C ++ 0x xor_combine?

คำตอบ:


94

ก็แค่ทำเหมือนที่พวกเพิ่มพลังทำ:

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
ใช่นั่นเป็นสิ่งที่ดีที่สุดที่ฉันทำได้เช่นกัน ฉันไม่เข้าใจว่าคณะกรรมการมาตรฐานปฏิเสธสิ่งที่ชัดเจนขนาดนี้ได้อย่างไร
Neil G

13
@ นีล: ฉันเห็นด้วย ฉันคิดว่าวิธีแก้ปัญหาง่ายๆสำหรับพวกเขาคือข้อกำหนดของห้องสมุดที่จะต้องมีแฮชสำหรับstd::pair(หรือtupleแม้กระทั่ง) มันจะคำนวณแฮชของแต่ละองค์ประกอบจากนั้นรวมเข้าด้วยกัน (และด้วยจิตวิญญาณของไลบรารีมาตรฐานด้วยวิธีที่กำหนดไว้ในการนำไปใช้งาน)
GManNickG

3
มีหลายสิ่งที่ชัดเจนที่ถูกละเว้นจากมาตรฐาน กระบวนการทบทวนอย่างเข้มข้นทำให้ยากที่จะนำสิ่งเล็กน้อยเหล่านั้นออกไปจากประตู
stinky472

15
ทำไมต้องมีตัวเลขวิเศษเหล่านี้ที่นี่? และไม่ได้ขึ้นอยู่กับเครื่องข้างต้น (เช่นจะไม่แตกต่างกันบนแพลตฟอร์ม x86 และ x64)?
einpoklum

3
มีกระดาษแนะนำการรวม hash_combine ที่นี่
SSJ_GZ

37

ฉันจะแบ่งปันที่นี่เนื่องจากจะเป็นประโยชน์กับผู้อื่นที่กำลังมองหาวิธีแก้ปัญหานี้: เริ่มจากคำตอบ@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!")จะคำนวณค่าแฮชบนตัวชี้แทนที่จะเป็นสตริง นี่อาจเป็นเหตุผลว่าทำไมมาตรฐานจึงใช้โครงสร้าง


5

ไม่กี่วันที่ผ่านมาฉันได้พบกับคำตอบนี้ในเวอร์ชันปรับปรุงเล็กน้อย(จำเป็นต้องรองรับ 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), ...);
}

โค้ดด้านบนดีกว่าในแง่ของการสร้างโค้ด ฉันใช้ฟังก์ชัน qHash จาก Qt ในโค้ดของฉัน แต่ก็สามารถใช้แฮชอื่น ๆ ได้เช่นกัน


เขียนนิพจน์การพับเป็น(int[]){0, (hashCombine(seed, rest), 0)...};และมันจะทำงานใน C ++ 11 ด้วย
Henri Menke

3

ฉันชอบวิธี C ++ 17 จากคำตอบของ vt4a2hมาก แต่มันประสบปัญหา:Restนี้ถูกส่งต่อโดยค่าในขณะที่ควรส่งต่อโดยการอ้างอิง const (ซึ่งเป็นสิ่งที่ต้องทำหากเป็นเช่นนั้น ใช้ได้กับประเภทที่เคลื่อนย้ายเท่านั้น)

นี่คือเวอร์ชันดัดแปลงซึ่งยังคงใช้นิพจน์พับ (ซึ่งเป็นเหตุผลว่าทำไมจึงต้องใช้ 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เนมสเปซ
Henri Menke

3

คำตอบโดย vt4a2hเป็นสิ่งที่ดีอย่างแน่นอน แต่ใช้ c ++ 17 แสดงออกพับและทุกคนไม่สามารถที่จะเปลี่ยนไปใช้ toolchain ใหม่ได้อย่างง่ายดาย เวอร์ชันด้านล่างใช้เคล็ดลับตัวขยายเพื่อจำลองนิพจน์พับและทำงานในC ++ 11และC ++ 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)...};
}

ตัวอย่างสดบน Compiler Explorer


ดูดีขึ้นมากขอบคุณ! ฉันอาจไม่สนใจเกี่ยวกับการส่งผ่านค่าเพราะฉันใช้วัตถุที่แชร์โดยปริยายตัวอย่างเช่น QString
vt4a2h
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.