المجموعات في C++: map و set و pair
vector الذي تعلمته ممتاز للقوائم المرتبة، لكنه ليس الحل لكل شيء. كيف تجد رقم هاتف باسم صاحبه دون المرور على القائمة كلها؟ وكيف تمنع تكرار العناصر؟ المكتبة القياسية في C++ فيها حاويات (Containers) جاهزة لكل مهمة. في هذا الدرس ستتقن map و set، وتتعرف على pair و queue و stack.
pair: قيمتان معاً
Section titled “pair: قيمتان معاً”pair يجمع قيمتين من أي نوعين في متغير واحد. ستراه كثيراً لأن الخريطة مبنية عليه:
#include <utility>
pair<string, int> player = {"Omar", 95};cout << player.first << " " << player.second << endl;
auto [name, score] = player;cout << name << " -> " << score << endl;Omar 95Omar -> 95firstوsecond: القيمة الأولى والثانية.auto [name, score] = player;: تفكيك الزوج إلى متغيرين في سطر واحد (منذ C++17).
الخريطة map
Section titled “الخريطة map”الخريطة (Map) تخزن أزواجاً: مفتاح وقيمته، مثل دليل الهاتف: تبحث بالاسم فتصل للرقم مباشرة.
#include <map>
map<string, int> stock;stock["pen"] = 40;stock["notebook"] = 15;stock["pen"] = 38;stock.insert({"ruler", 8});
cout << stock["pen"] << endl;cout << stock.size() << endl;cout << boolalpha << stock.contains("ruler") << endl;cout << stock.contains("eraser") << endl;
stock.erase("notebook");cout << stock.size() << endl;383truefalse2map<string, int>: نوع المفتاح ثم نوع القيمة.stock[key] = value: تضيف زوجاً جديداً، أو تستبدل القيمة إذا كان المفتاح موجوداً. المفاتيح لا تتكرر.contains(منذ C++20): هل المفتاح موجود؟erase: تحذف الزوج بمفتاحه.
المرور على الخريطة
Section titled “المرور على الخريطة”map<string, int> ages = {{"Omar", 15}, {"Ali", 13}, {"Sara", 14}};for (const auto& [name, age] : ages) { cout << name << " = " << age << endl;}Ali = 13Omar = 15Sara = 14لاحظ أمرين: كل عنصر في الخريطة pair فككناه إلى name و age. والعناصر خرجت مرتبة أبجدياً بالمفتاح، لا بترتيب الإضافة: map تحافظ على مفاتيحها مرتبة دائماً.
مثال عملي: عدّ التكرارات
Section titled “مثال عملي: عدّ التكرارات”vector<string> votes = {"red", "blue", "red", "green", "red", "blue"};map<string, int> counts;for (const string& vote : votes) { counts[vote]++;}for (const auto& [color, count] : counts) { cout << color << ": " << count << endl;}blue: 2green: 1red: 3هنا استفدنا من سلوك الأقواس المربعة: أول ظهور للون ينشئه بقيمة صفر، ثم ++ تزيده واحداً.
unordered_map: الأسرع
Section titled “unordered_map: الأسرع”map ترتب مفاتيحها، وهذا له ثمن بسيط في السرعة. إذا لم يهمك الترتيب فاستخدم unordered_map: نفس الدوال تماماً، لكن البحث والإضافة فيها أسرع مع البيانات الكبيرة:
#include <unordered_map>
unordered_map<string, string> capitals = { {"Egypt", "Cairo"}, {"Jordan", "Amman"},};capitals["Tunisia"] = "Tunis";
cout << capitals.at("Jordan") << endl;cout << capitals.size() << endl;
auto it = capitals.find("Morocco");if (it == capitals.end()) { cout << "غير موجودة" << endl;}Amman3غير موجودةfind تُرجع مؤشر تكرار (Iterator) يشير إلى العنصر، أو end() إذا لم تجده. وترتيب عناصر unordered_map عند المرور عليها غير مضمون، فلا تعتمد عليه.
المجموعة set
Section titled “المجموعة set”المجموعة (Set) تخزن عناصر بلا تكرار، وتحافظ عليها مرتبة:
#include <set>
set<int> numbers = {3, 1, 3, 2, 1};cout << numbers.size() << endl;
numbers.insert(5);auto result = numbers.insert(2);cout << boolalpha << result.second << endl;
for (int n : numbers) { cout << n << " ";}cout << endl;cout << numbers.contains(5) << endl;3false1 2 3 5true- التكرارات تُحذف تلقائياً: من خمسة أرقام بقيت ثلاثة.
insertتُرجعpair، والقيمةsecondفيه تخبرك هل تمت الإضافة فعلاً.- مثل الخرائط:
setمرتبة، وunordered_setأسرع بلا ترتيب.
المجموعة مثل سجل الحضور: الاسم يُكتب مرة واحدة مهما دخل صاحبه وخرج.
حيلة شائعة: حذف التكرارات من vector بتحويله إلى set:
vector<string> tags = {"cpp", "game", "cpp", "ai", "game"};set<string> unique(tags.begin(), tags.end());cout << unique.size() << endl;3queue و stack
Section titled “queue و stack”الطابور (Queue): من يدخل أولاً يخرج أولاً، مثل طابور المخبز. والمكدس (Stack): آخر من يدخل أول من يخرج، مثل كومة الأطباق:
#include <queue>#include <stack>
queue<string> orders;orders.push("طلب 1");orders.push("طلب 2");orders.push("طلب 3");cout << orders.front() << endl;orders.pop();cout << orders.front() << endl;
stack<string> history;history.push("الصفحة الرئيسية");history.push("صفحة الدرس");cout << history.top() << endl;history.pop();cout << history.top() << endl;طلب 1طلب 2صفحة الدرسالصفحة الرئيسيةانتبه: pop في C++ تحذف العنصر ولا تُرجعه. اقرأه أولاً بـ front() أو top() ثم احذفه.
أيها أختار؟
Section titled “أيها أختار؟”| تحتاج إلى | استخدم |
|---|---|
| عناصر مرتبة تصل إليها برقم الموضع | vector |
| البحث عن قيمة بمفتاح، والمفاتيح مرتبة | map |
| البحث بمفتاح بأقصى سرعة | unordered_map |
| عناصر بلا تكرار | set |
| معالجة بالدور | queue |
| التراجع: الأحدث أولاً | stack |
جرّب بنفسك
Section titled “جرّب بنفسك”- أنشئ
map<string, double>يربط اسم المنتج بسعره، وأضف ثلاثة منتجات، ثم اطبعها كلها بحلقة. - اطبع سعر منتج غير موجود بأمان: افحص بـ
containsواطبع رسالة مناسبة. - اقرأ جملة وقسّمها إلى كلمات بـ
stringstream، واستخدمsetلمعرفة عدد الكلمات المختلفة. - تحدٍّ: استخدم
stack<char>للتحقق من توازن الأقواس في نص مثل(a[b]c).
أخطاء شائعة
Section titled “أخطاء شائعة”- القراءة بـ
[]من map: تنشئ مفتاحاً جديداً دون قصد، وتكبر الخريطة بعناصر وهمية. استخدمcontainsأوfindأوat. - استخدام ناتج find دون فحص: إذا كان الناتج
end()فالوصول إلىit->secondسلوك غير معرَّف. - pop على حاوية فارغة:
popوtopوfrontعلىqueueأوstackفارغ سلوك غير معرَّف. افحصempty()أولاً. - انتظار ترتيب الإضافة:
mapترتب بالمفتاح، وunordered_mapبلا ترتيب. إذا أردت ترتيب الإضافة فاستخدمvectorمنpair.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
ماذا يحدث عند تنفيذ cout << m["x"] والمفتاح x غير موجود في map<string, int> m؟
الأقواس المربعة تنشئ المفتاح بالقيمة الافتراضية إذا لم يكن موجوداً. استخدم at أو contains للقراءة الآمنة.
سؤال
ما حجم set<int> s = {4, 4, 7, 7, 7}؟
المجموعة تحذف التكرارات، فيبقى العنصران 4 و 7 فقط.
الخلاصة
Section titled “الخلاصة”pairيجمع قيمتين، ويُفكَّك بـauto [a, b].mapأزواج مفتاح وقيمة مرتبة بالمفتاح، وunordered_mapأسرع بلا ترتيب.[]تضيف المفتاح إن لم يوجد، وcontainsوfindوatللقراءة الآمنة.setعناصر بلا تكرار، وqueueوstackللمعالجة بالدور أو بالعكس.- مبروك! أنهيت كورس C++. جرّب الآن بناء لعبة نصية صغيرة تجمع كل ما تعلمته.