دوال التجزئة المخصّصة
أنشئ تجزئة لأنواعك الخاصة
دوال التجزئة المخصّصة درس مجاني في C++ Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C++ Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C++ Academy 4 دروس في المجموع.
لماذا نستخدم التجزئة المخصصة؟
تحتاج الحاويات غير المرتبة إلى طريقة لتجزئة مفاتيحها. تملك الأنواع المضمنة وstd::string دوال تجزئة جاهزة، لكن أنواعك الخاصة لا تملكها. وعليك توفير واحدة.
#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;
}إنشاء struct للتجزئة
لنفترض أن لدينا Point يحتوي على عددين صحيحين. ولتخزينه في unordered_set، نحتاج إلى كل من المساواة ودالة تجزئة.
#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;
}كتابة كائن دالي للتجزئة
كائن التجزئة الدالي هو struct يحتوي على operator() ويعيد size_t. ادمج تجزئات الحقول، غالباً باستخدام 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;
}استخدام كائن التجزئة الدالي
مرر كائن التجزئة الدالي باعتباره المعامل الثاني في قالب الحاوية غير المرتبة.
#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;
}المساواة مطلوبة أيضاً
ينتهي مفتاحان في الحاوية نفسها إذا اصطدمت تجزئتاهما. ثم تستخدم الحاوية 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;
}استخدام التجزئة كمفتاح map
تتيح دالة التجزئة المخصصة نفسها استخدام struct كمفتاح في 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;
}دمج حقول متعددة
تجمع أداة مساعدة شائعة التجزئات، حقلاً تلو الآخر، باستخدام نمط الضرب والجمع المشابه لـ 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;
}توزيع التجزئة الجيد
تضع دالة التجزئة السيئة التي تعيد قيمة ثابتة كل العناصر في حاوية واحدة، مما يؤدي إلى تدهور الأداء إلى 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;
}استخدام Lambda كتجزئة
في C++20، يمكنك حتى استخدام lambda عديمة الحالة كتجزئة من خلال تمرير نوعها.
#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;
}تحقق سريع
اختبر مدى فهمك للتجزئة المخصصة.
مراجعة
تعلمت كيفية تجزئة الأنواع المخصصة:
- وفّر كائن تجزئة دالياً، أو خصص
std::hash، ليعيدsize_t - وفّر أيضاً operator== للتمييز بين المفاتيح المتصادمة
- ادمج تجزئات الحقول جيداً للحصول على توزيع جيد
بعد ذلك، ستستكشف الحاويات وعامل التحميل وتأثيرهما في الأداء.
الأسئلة الشائعة
هل درس «دوال التجزئة المخصّصة» مجاني؟
نعم — نص درس «دوال التجزئة المخصّصة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C++ Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C++ Academy 4 دروس في المجموع.
ماذا ستتعلم في «دوال التجزئة المخصّصة»؟
أنشئ تجزئة لأنواعك الخاصة تتمرن على C++ Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ C++ Academy؟
لا تُشترط خبرة سابقة. C++ Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.
كم من الوقت يستغرق درس «دوال التجزئة المخصّصة»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس C++ Academy هذا؟
نعم. كل درس في C++ Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- std::unordered_map
- unordered_set
- دوال التجزئة المخصّصة
- اعتبارات الأداء