ฟังก์ชันแฮชแบบกำหนดเอง
แฮชชิ่งชนิดของคุณเอง
ฟังก์ชันแฮชแบบกำหนดเอง เป็นบทเรียน 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- std::unordered_map
- unordered_set
- ฟังก์ชันแฮชแบบกำหนดเอง
- ข้อควรพิจารณาด้านประสิทธิภาพ