تخطَّ إلى المحتوى

المجموعات في C++‎: map و set و pair

الدرس 11 من 11 · C++

vector الذي تعلمته ممتاز للقوائم المرتبة، لكنه ليس الحل لكل شيء. كيف تجد رقم هاتف باسم صاحبه دون المرور على القائمة كلها؟ وكيف تمنع تكرار العناصر؟ المكتبة القياسية في C++‎ فيها حاويات (Containers) جاهزة لكل مهمة. في هذا الدرس ستتقن map و set، وتتعرف على pair و queue و stack.

pair يجمع قيمتين من أي نوعين في متغير واحد. ستراه كثيراً لأن الخريطة مبنية عليه:

#include <utility>
pair<string, int> player = {"Omar", 95};
cout << player.first << " " << player.second << endl;
auto [name, score] = player;
cout << name << " -> " << score << endl;
الناتج
Omar 95
Omar -> 95
  • first و second: القيمة الأولى والثانية.
  • auto [name, score] = player;: تفكيك الزوج إلى متغيرين في سطر واحد (منذ C++17).

الخريطة (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;
الناتج
38
3
true
false
2
  • map<string, int>: نوع المفتاح ثم نوع القيمة.
  • stock[key] = value: تضيف زوجاً جديداً، أو تستبدل القيمة إذا كان المفتاح موجوداً. المفاتيح لا تتكرر.
  • contains (منذ C++20): هل المفتاح موجود؟
  • erase: تحذف الزوج بمفتاحه.
map<string, int> ages = {{"Omar", 15}, {"Ali", 13}, {"Sara", 14}};
for (const auto& [name, age] : ages) {
cout << name << " = " << age << endl;
}
الناتج
Ali = 13
Omar = 15
Sara = 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: 2
green: 1
red: 3

هنا استفدنا من سلوك الأقواس المربعة: أول ظهور للون ينشئه بقيمة صفر، ثم ++ تزيده واحداً.

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;
}
الناتج
Amman
3
غير موجودة

find تُرجع مؤشر تكرار (Iterator) يشير إلى العنصر، أو end() إذا لم تجده. وترتيب عناصر unordered_map عند المرور عليها غير مضمون، فلا تعتمد عليه.

المجموعة (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;
الناتج
3
false
1 2 3 5
true
  • التكرارات تُحذف تلقائياً: من خمسة أرقام بقيت ثلاثة.
  • 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;
الناتج
3

الطابور (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() ثم احذفه.

تحتاج إلى استخدم
عناصر مرتبة تصل إليها برقم الموضع vector
البحث عن قيمة بمفتاح، والمفاتيح مرتبة map
البحث بمفتاح بأقصى سرعة unordered_map
عناصر بلا تكرار set
معالجة بالدور queue
التراجع: الأحدث أولاً stack
  1. أنشئ map<string, double> يربط اسم المنتج بسعره، وأضف ثلاثة منتجات، ثم اطبعها كلها بحلقة.
  2. اطبع سعر منتج غير موجود بأمان: افحص بـ contains واطبع رسالة مناسبة.
  3. اقرأ جملة وقسّمها إلى كلمات بـ stringstream، واستخدم set لمعرفة عدد الكلمات المختلفة.
  4. تحدٍّ: استخدم stack<char> للتحقق من توازن الأقواس في نص مثل (a[b]c).
  • القراءة بـ [] من map: تنشئ مفتاحاً جديداً دون قصد، وتكبر الخريطة بعناصر وهمية. استخدم contains أو find أو at.
  • استخدام ناتج find دون فحص: إذا كان الناتج end() فالوصول إلى it->second سلوك غير معرَّف.
  • pop على حاوية فارغة: pop و top و front على queue أو stack فارغ سلوك غير معرَّف. افحص empty() أولاً.
  • انتظار ترتيب الإضافة: map ترتب بالمفتاح، و unordered_map بلا ترتيب. إذا أردت ترتيب الإضافة فاستخدم vector من pair.

سؤال

ماذا يحدث عند تنفيذ cout << m["x"] والمفتاح x غير موجود في map<string, int> m؟

سؤال

ما حجم set<int> s = {4, 4, 7, 7, 7}؟

  • pair يجمع قيمتين، ويُفكَّك بـ auto [a, b].
  • map أزواج مفتاح وقيمة مرتبة بالمفتاح، و unordered_map أسرع بلا ترتيب.
  • [] تضيف المفتاح إن لم يوجد، و contains و find و at للقراءة الآمنة.
  • set عناصر بلا تكرار، و queue و stack للمعالجة بالدور أو بالعكس.
  • مبروك! أنهيت كورس C++‎. جرّب الآن بناء لعبة نصية صغيرة تجمع كل ما تعلمته.