C++ Academy · บทเรียน

ฟังก์ชันแฮชแบบกำหนดเอง

แฮชชิ่งชนิดของคุณเอง

บทเรียน 3 จาก 413 ขั้นตอน

ฟังก์ชันแฮชแบบกำหนดเอง เป็นบทเรียน C++ Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน C++ Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส C++ Academy มีบทเรียนทั้งหมด 4 บทเรียน

เหตุใดจึงต้องใช้ hash แบบกำหนดเอง

คอนเทนเนอร์แบบไม่มีลำดับจำเป็นต้องมีวิธีทำ hash ให้คีย์ ชนิดข้อมูลพื้นฐานและ std::string มี hash อยู่แล้ว แต่ ชนิดข้อมูลของคุณเอง ยังไม่มี คุณจึงต้องจัดเตรียม hash ให้ชนิดข้อมูลนั้น

#include <iostream>
#include <unordered_set>
#include <string>

int main() {
    std::unordered_set<std::string> s{"hi"};
    std::cout << s.count("hi") << '\n';
    return 0;
}

แม่แบบ std::hash

std::hash เป็นฟังก์ชันอ็อบเจ็กต์ที่แมปค่าหนึ่งไปเป็น size_t คุณเรียกใช้งานได้เหมือนฟังก์ชัน

#include <iostream>
#include <functional>
#include <string>

int main() {
    std::hash<std::string> h;
    std::cout << "hash exists and returns a size_t\n";
    std::size_t v = h("hello");
    std::cout << (v != 0 ? "non-zero hash" : "zero") << '\n';
    return 0;
}

โครงสร้างสำหรับทำ hash

สมมติว่าเรามี Point ที่ประกอบด้วย int สองค่า หากต้องการจัดเก็บใน unordered_set เราต้องมีทั้ง การตรวจสอบความเท่ากัน และ hash

#include <iostream>

struct Point {
    int x, y;
    bool operator==(const Point& o) const { return x == o.x && y == o.y; }
};

int main() {
    Point a{1, 2}, b{1, 2};
    std::cout << std::boolalpha << (a == b) << '\n';
    return 0;
}

การเขียนฟังก์ชันอ็อบเจ็กต์สำหรับ hash

ฟังก์ชันอ็อบเจ็กต์สำหรับ hash คือโครงสร้างที่มี operator() ซึ่งคืนค่าเป็น size_t ให้รวม hash ของแต่ละฟิลด์เข้าด้วยกัน โดยมักใช้ XOR และการเลื่อนบิต

#include <iostream>
#include <functional>

struct Point { int x, y; };

struct PointHash {
    std::size_t operator()(const Point& p) const {
        return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1);
    }
};

int main() {
    PointHash h;
    std::cout << "hashed: " << (h({3, 4}) != 0 ? "ok" : "zero") << '\n';
    return 0;
}

การใช้ฟังก์ชันอ็อบเจ็กต์สำหรับ hash

ส่งฟังก์ชันอ็อบเจ็กต์สำหรับ hash เป็นอาร์กิวเมนต์แม่แบบตัวที่สองของคอนเทนเนอร์แบบไม่มีลำดับ

#include <iostream>
#include <unordered_set>
#include <functional>

struct Point {
    int x, y;
    bool operator==(const Point& o) const { return x == o.x && y == o.y; }
};

struct PointHash {
    std::size_t operator()(const Point& p) const {
        return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1);
    }
};

int main() {
    std::unordered_set<Point, PointHash> pts;
    pts.insert({1, 2});
    pts.insert({1, 2});
    std::cout << pts.size() << '\n';
    return 0;
}

ต้องมีการตรวจสอบความเท่ากันด้วย

คีย์สองตัวจะอยู่ในบักเก็ตเดียวกันเมื่อ hash ของคีย์ทั้งสองชนกัน จากนั้นคอนเทนเนอร์จะใช้ operator== เพื่อแยกความแตกต่าง ดังนั้นการตรวจสอบความเท่ากันจึงเป็นสิ่งจำเป็น

#include <iostream>
#include <unordered_set>

struct Point {
    int x, y;
    bool operator==(const Point& o) const { return x == o.x && y == o.y; }
};

struct PointHash {
    std::size_t operator()(const Point& p) const {
        return std::hash<int>()(p.x * 31 + p.y);
    }
};

int main() {
    std::unordered_set<Point, PointHash> s{{1, 1}, {2, 2}};
    std::cout << s.count({1, 1}) << '\n';
    return 0;
}

การใช้ hash เป็นคีย์ของ map

hash แบบกำหนดเองแบบเดียวกันนี้ทำให้โครงสร้างสามารถใช้เป็นคีย์ใน unordered_map ได้

#include <iostream>
#include <unordered_map>
#include <functional>

struct Point {
    int x, y;
    bool operator==(const Point& o) const { return x == o.x && y == o.y; }
};

struct PointHash {
    std::size_t operator()(const Point& p) const {
        return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1);
    }
};

int main() {
    std::unordered_map<Point, std::string, PointHash> m;
    m[{0, 0}] = "origin";
    std::cout << m[{0, 0}] << '\n';
    return 0;
}

การรวมหลายฟิลด์

ตัวช่วยที่ใช้กันทั่วไปจะรวม hash ทีละฟิลด์ โดยใช้รูปแบบการคูณแล้วบวกที่คล้ายกับ boost::hash_combine

#include <iostream>
#include <functional>

std::size_t combine(std::size_t seed, std::size_t v) {
    return seed ^ (v + 0x9e3779b9 + (seed << 6) + (seed >> 2));
}

int main() {
    std::size_t h = 0;
    h = combine(h, std::hash<int>()(10));
    h = combine(h, std::hash<int>()(20));
    std::cout << (h != 0 ? "combined ok" : "zero") << '\n';
    return 0;
}

การกระจายของ hash แบบ Good

hash ที่ไม่ดีและคืนค่าคงที่ตลอดเวลาจะทำให้ทุกอย่างไปอยู่ในบักเก็ตเดียว ส่งผลให้ประสิทธิภาพลดลงเป็น O(n) ควรผสมบิตของทุกฟิลด์ให้ดี

#include <iostream>
#include <functional>

struct Bad { std::size_t operator()(int) const { return 0; } };
struct Good { std::size_t operator()(int x) const { return std::hash<int>()(x); } };

int main() {
    std::cout << Bad()(5) << ' ' << (Good()(5) != 0 ? "varies" : "0") << '\n';
    return 0;
}

การทำให้ std::hash เฉพาะทาง

อีกทางเลือกหนึ่งคือทำให้ std::hash เป็นชนิดเฉพาะสำหรับชนิดข้อมูลของคุณ เพื่อให้ใช้งานได้โดยไม่ต้องส่งฟังก์ชันอ็อบเจ็กต์อย่างชัดเจน

#include <iostream>
#include <unordered_set>

struct Point {
    int x, y;
    bool operator==(const Point& o) const { return x == o.x && y == o.y; }
};

namespace std {
    template <> struct hash<Point> {
        std::size_t operator()(const Point& p) const {
            return hash<int>()(p.x) ^ (hash<int>()(p.y) << 1);
        }
    };
}

int main() {
    std::unordered_set<Point> s{{1, 2}};
    std::cout << s.count({1, 2}) << '\n';
    return 0;
}

การใช้แลมบ์ดาเป็น hash

ใน C++20 คุณยังสามารถใช้แลมบ์ดาที่ไม่มีสถานะเป็น hash ได้ด้วยการส่งชนิดของแลมบ์ดา

#include <iostream>
#include <unordered_set>

int main() {
    auto h = [](int x) { return std::hash<int>()(x * 2654435761u); };
    std::unordered_set<int, decltype(h)> s(8, h);
    s.insert(42);
    std::cout << s.count(42) << '\n';
    return 0;
}

ตรวจสอบความเข้าใจอย่างรวดเร็ว

ทดสอบความเข้าใจของคุณเกี่ยวกับการทำ hash แบบกำหนดเอง

สรุป

คุณได้เรียนรู้การทำ hash ให้ชนิดข้อมูลที่กำหนดเอง:

  • จัดเตรียม ฟังก์ชันอ็อบเจ็กต์สำหรับ hash (หรือทำให้ std::hash เป็นชนิดเฉพาะ) ที่คืนค่าเป็น size_t
  • จัดเตรียม operator== ด้วย เพื่อแยกคีย์ที่ hash ชนกัน
  • รวม hash ของฟิลด์ต่าง ๆ อย่างเหมาะสมเพื่อให้เกิดการกระจายที่ดี

ต่อไป คุณจะสำรวจบักเก็ตและประสิทธิภาพของอัตราการบรรจุ

เริ่มต้นได้ฟรี

เรียนรู้ C++ ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
51
บทเรียน
203

คำถามที่พบบ่อย

บทเรียน “ฟังก์ชันแฮชแบบกำหนดเอง” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “ฟังก์ชันแฮชแบบกำหนดเอง” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C++ Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C++ Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “ฟังก์ชันแฮชแบบกำหนดเอง”

แฮชชิ่งชนิดของคุณเอง คุณปฏิบัติ C++ Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C++ Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน C++ Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน

บทเรียน “ฟังก์ชันแฮชแบบกำหนดเอง” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน C++ Academy นี้ได้ไหม

ได้ บทเรียน C++ Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. std::unordered_map
  2. unordered_set
  3. ฟังก์ชันแฮชแบบกำหนดเอง
  4. ข้อควรพิจารณาด้านประสิทธิภาพ
← กลับไปที่ C++ Academy