البرمجة

مفهوم واستخدامات std::map في C++

النوع std::map في C++: القواميس

يعتبر النوع std::map في C++ واحداً من أنواع البيانات المتقدمة التي توفرها مكتبة القوالب القياسية (STL) في C++. يسمح هذا النوع بتخزين مجموعات من الأزواج المفتاحية (key-value) حيث كل مفتاح (key) يكون فريدًا، ويقترن بقيمة (value) مرتبطة به. تُستخدم هذه الهياكل البيانات في العديد من التطبيقات، بدءًا من قواعد البيانات وصولًا إلى الخوارزميات المعقدة التي تتطلب عمليات بحث سريعة ومنظمة.

هيكل std::map

في لغة C++، يعد std::map نوعًا من القواميس الذي يوفر خوارزميات مرتبة وفعّالة للبحث، الإدراج، والحذف. يتم تخزين البيانات في هيكل شجري (عادةً ما يكون شجرة بحث ثنائية متوازنة)، ما يضمن أن العمليات مثل البحث، الإضافة، والحذف تتم في وقت O(logn)O(\log n)، حيث nn هو عدد العناصر في الخريطة.

بنية البيانات الأساسية

كل عنصر في std::map هو زوج مكون من مفتاح وقيمة، ويُرمز له عادة بالصورة التالية:

cpp
std::map

حيث KeyType هو نوع المفتاح وValueType هو نوع القيمة المرتبطة بالمفتاح. يُخزن العنصر في std::map بشكل مرتب بناءً على قيمة المفتاح، لذا تكون جميع المفاتيح في الخريطة مُرتبة بترتيب تصاعدي افتراضي أو باستخدام دالة مقارنة مخصصة إذا لزم الأمر.

كيفية استخدام std::map

لاستعمال std::map، يجب أولاً تضمين رأس المكتبة المناسبة:

cpp
#include

وبعد ذلك يمكن تعريف خريطة باستخدام نوع المفتاح والقيمة المحددة. على سبيل المثال، إذا كنت ترغب في تخزين أزواج من الأسماء والعمر، يمكنك استخدام الشيفرة التالية:

cpp
std::mapint> ageMap;

في هذه الحالة، std::string هو نوع المفتاح (الاسم) وint هو نوع القيمة (العمر).

إضافة عناصر إلى std::map

لإضافة عنصر إلى std::map، يمكن استخدام المشغل [] أو دالة insert().

  • استخدام المشغل []: عند استخدام هذا المشغل، إذا لم يكن المفتاح موجودًا بالفعل، سيتم إنشاء عنصر جديد تلقائيًا.

cpp
ageMap["Alice"] = 25; ageMap["Bob"] = 30;
  • استخدام insert(): تُعتبر هذه الطريقة أكثر أمانًا حيث أنه إذا كان المفتاح موجودًا مسبقًا في الخريطة، فلن يتم إضافة العنصر الجديد.

cpp
ageMap.insert(std::make_pair("Charlie", 35));

الوصول إلى العناصر

يمكن الوصول إلى العناصر في std::map باستخدام المشغل [] أو دالة find().

  • استخدام المشغل []: يعمل المشغل [] على إرجاع قيمة مرتبطة بالمفتاح المحدد. إذا كان المفتاح غير موجود، سيتم إضافته مع قيمة مبدئية.

cpp
int aliceAge = ageMap["Alice"]; // يعطي 25
  • استخدام find(): توفر هذه الدالة طريقة أكثر أمانًا للوصول إلى العناصر، لأنها لا تضيف أي عنصر جديد إذا كان المفتاح غير موجود.

cpp
auto it = ageMap.find("Bob"); if (it != ageMap.end()) { std::cout << "Bob's age is " << it->second << std::endl; }

حذف العناصر

لحذف عنصر من std::map، يمكن استخدام دالة erase(). هذه الدالة تقبل إما المفتاح أو مؤشر إلى العنصر الذي يجب حذفه.

cpp
ageMap.erase("Alice"); // يحذف العنصر الذي يحتوي على المفتاح "Alice"

التكرار عبر العناصر

يمكن التكرار عبر عناصر std::map باستخدام مؤشرات أو الحلقات مثل for:

cpp
for (auto it = ageMap.begin(); it != ageMap.end(); ++it) { std::cout << it->first << " is " << it->second << " years old" << std::endl; }

أو باستخدام النطاق المدي (range-based) في C++11 وما بعده:

cpp
for (const auto& pair : ageMap) { std::cout << pair.first << " is " << pair.second << " years old" << std::endl; }

مقارنة std::map مع std::unordered_map

من المهم ملاحظة الفرق بين std::map و std::unordered_map، وهما نوعان شائعان لتخزين أزواج من المفاتيح والقيم في C++.

  • std::map: يعتمد على شجرة بحث ثنائية متوازنة (مثل شجرة AVL أو Red-Black Tree)، مما يضمن ترتيب العناصر وفقًا للمفاتيح. تعمل العمليات الأساسية (البحث، الإدراج، الحذف) في وقت O(logn)O(\log n).

  • std::unordered_map: يعتمد على الجدول المبعثر (hash table)، مما يوفر عمليات بحث، إدراج وحذف في وقت O(1)O(1) في المتوسط. لا يتم ترتيب العناصر في std::unordered_map.

الاختيار بين هذين النوعين يعتمد على الحاجة إلى ترتيب العناصر في std::map أو السرعة في عمليات الوصول في std::unordered_map.

التعامل مع المفاتيح المكررة

في std::map، لا يمكن أن يكون هناك مفتاح مكرر. إذا حاولت إدراج مفتاح موجود بالفعل، فسيتم تجاهل إدخالك ولن يحدث تغيير في القيمة المرتبطة به. على سبيل المثال:

cpp
ageMap["Bob"] = 30; ageMap["Bob"] = 40; // القيمة القديمة تُستبدل بالقيمة الجديدة

ولكن، إذا أردت التعامل مع المفاتيح المكررة بطريقة معينة (على سبيل المثال، بتخزين قائمة من القيم لكل مفتاح)، فيمكنك استخدام std::multimap أو هياكل بيانات أخرى مثل std::vector.

ترتيب المفاتيح في std::map

بحسب تصميم std::map، يتم ترتيب المفاتيح في الخريطة بترتيب تصاعدي افتراضي. إذا أردت ترتيب المفاتيح بترتيب مخصص، يمكنك استخدام دالة مقارنة (comparator) عند تعريف الخريطة:

cpp
std::map<int, std::string, std::greater<int>> reverseMap; reverseMap[3] = "three"; reverseMap[1] = "one"; reverseMap[2] = "two";

في هذه الحالة، سيتم ترتيب المفاتيح بترتيب تنازلي.

فوائد std::map

  • الترتيب التلقائي: يسمح std::map بتخزين المفاتيح في ترتيب معين (افتراضيًا تصاعدي)، ما يسهل عمليات البحث والاسترجاع.

  • البحث السريع: توفر العمليات الأساسية في std::map وقت تنفيذ O(logn)O(\log n) بفضل الشجرة الثنائية المتوازنة.

  • الأمان: يوفر std::map طريقة أكثر أمانًا للوصول إلى العناصر مقارنةً بـ std::unordered_map لأن ترتيب العناصر محفوظ، ما يعني أن العمليات قد تكون أكثر قابلية للتنبؤ.

الحالات التي يُفضل فيها استخدام std::map

  1. عندما تحتاج إلى ترتيب العناصر: إذا كان لديك حاجة لتنظيم البيانات بشكل معين (مثلاً، ترتيب الكلمات حسب تكرارها في نص ما).

  2. البحث السريع: عند الحاجة إلى إجراء عمليات بحث بسرعة عالية، مثل البحث عن مفتاح في بيانات ضخمة.

  3. الذاكرة الموجهة: عند التعامل مع البيانات المرتبطة بمفاتيح فريدة، مثل قواميس أو خرائط لربط المفتاح بالقيمة في التطبيقات المختلفة.

الختام

النوع std::map في C++ يعد أداة قوية ومرنة لتخزين الأزواج المفتاحية (key-value)، حيث يوفر ميزات متعددة مثل البحث السريع، الترتيب التلقائي للعناصر، وأداء جيد في معظم العمليات. نظرًا لأنه يعتمد على هيكل شجري متوازن، فإنه يضمن عمليات فعّالة في معظم الحالات. وعلى الرغم من توفر أنواع أخرى مثل std::unordered_map التي توفر أداء أفضل في بعض الحالات، يظل std::map الخيار المثالي عندما يكون الترتيب والتدرج مهمين في تطبيقاتك.