Menu
flag Ar iconالعربيةdown icon

‏C++ std::map: المفاتيح والقيم والبحث والإدراج

شرح std::map في C++ — حاوية مفتاح-قيمة مرتّبة حسب المفتاح مع بحث لوغاريتمي. تعلّم الإدراج والبحث والمرور على العناصر، وتجنّب فخّ operator[] الشهير الذي يدرج المفاتيح بصمت.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

البحث عن الأشياء عبر المفتاح

تُعدّ vector رائعة عندما تفهرس حسب الموضع — العنصر 0، العنصر 1، وهكذا. لكن في كثير من الأحيان لا يكون لديك موضع، بل لديك اسم وتريد ما يرتبط به: مستخدم ونتيجته، كلمة وعدد مرات ورودها، رمز دولة وعاصمتها. والمرور على vector للعثور على تطابق يكلّف O(n) ويصبح بطيئًا بسرعة.

تحلّ std::map هذه المشكلة. فهي تخزّن أزواج المفتاح-القيمة، وتُبقيها مرتّبة حسب المفتاح، وتتيح لك البحث عن قيمة عبر مفتاحها في زمن O(log n). ضمّن <map> لاستخدامها:

تُقرأ map<string, int> على أنها "map من مفاتيح string إلى قيم int". المفاتيح فريدة، فإذا أسندتَ إلى المفتاح نفسه مرتين فإن القيمة الثانية هي التي تسود. وداخليًا تكون map شجرة بحث ثنائية متوازنة، وهذا هو سبب بقاء كل شيء مرتّبًا وكون عمليات البحث لوغاريتمية لا ثابتة.

إدراج العناصر

هناك عدة طرق لإضافة المُدخلات، والفرق بينها مهم. أكثرها شيوعًا هو operator[]، الذي يُنشئ المفتاح إن لم يكن موجودًا ويُرجِع مرجعًا يمكنك الإسناد إليه:

إذا أردت إدراجًا يرفض الكتابة فوق مفتاح موجود، فاستخدم insert أو emplace. كلاهما يُرجِع pair يكون ‏.second فيه قيمة bool تخبرك بما إذا حدث الإدراج فعلًا:

استخدم [] عندما تريد أن "تسود آخر كتابة"، واستخدم insert/emplace عندما يجب ترك المفتاح الموجود دون مساس.

فخّ operator[]: يُدرِج عند القراءة

هذا هو خطأ map الأكثر شيوعًا على الإطلاق. فـoperator[] ليس قراءة خالصة — إذا كان المفتاح غائبًا فإنه يدرجه بصمت بقيمة مُنشأة افتراضيًا (0 للنوع int، و"" للنوع string، وهكذا) ويُرجِع مرجعًا إليها. لذا فإن مجرد التحقق من مفتاح باستخدام [] يغيّر الـmap:

map<string, int> m;
if (m["maybe"] == 0) {   // خطأ: هذا أنشأ للتو "maybe" -> 0
    // ...
}
cout << m.size();        // 1 وليس 0 - لقد أدرجتَ مفتاحًا عن غير قصد

وهذا يؤذيك أيضًا مع const map، حيث لا يُترجَم operator[] أصلًا لأنه قد يحتاج إلى الإدراج. ولكي تقرأ دون إدراج، استخدم find أو count/contains أو at:

قاعدة عامة: إذا كنت تنوي القراءة فلا تلجأ إلى [] أبدًا. استخدم at عندما يجب أن يكون المفتاح موجودًا، وfind/contains عندما قد لا يكون موجودًا.

المرور بترتيب مرتّب

بما أن map مبنية على شجرة، فإن المرور عليها يزور المفاتيح بترتيب تصاعدي مرتّب — دائمًا، ومجانًا. كل عنصر هو pair<const Key, Value>، لذا استخدم الربط البنيوي لتفكيك المفتاح والقيمة بأناقة:

لاحظ أن المفتاح في الربط هو const: يمكنك تغيير قيمة عبر الحلقة باستخدام auto&، لكن لا يمكنك أبدًا تغيير مفتاح في مكانه (فذلك سيكسر ترتيب الفرز). يخرج الناتج مرتّبًا أبجديًا (blue، sea، sky) دون أي خطوة فرز، وهذا بالضبط هو السبب الذي يجعل الناس يختارون map بدلًا من جدول التجزئة عندما يكون المرور المرتّب مهمًا.

كما أن الصيغة wordCount[w]++ هي الاستخدام النموذجي لسلوك الإدراج-عند-الوصول: هنا أنت تريد أن يبدأ المفتاح غير الموجود من 0، لذا فإن [] هي الأداة الصحيحة.

حذف العناصر وقياس الحجم

احذف عبر المفتاح باستخدام erase، الذي يُرجِع عدد العناصر المحذوفة (0 أو 1 في الـmap). يمكنك أيضًا الحذف عبر مكرِّر تحصل عليه من find. تحقّق من حالة الحاوية باستخدام size() وempty():

ثمة فخّ عند الحذف داخل حلقة: m.erase(it) يُبطِل it، لذا لا يمكنك تنفيذ ++it بعده. والنمط الآمن منذ C++11 هو it = m.erase(it)، الذي يُرجِع مكرِّرًا إلى العنصر التالي. أما للحذف الشرطي مرة واحدة عبر الـmap بأكملها، فإن std::erase_if(m, predicate) (في C++20) أنظف.

التالي: unordered_map

تمنحك std::map مفاتيح مرتّبة وعمليات O(log n) قابلة للتنبؤ، لكنك تدفع ثمن هذا الترتيب مع كل عملية بحث. وعندما لا تهتم بترتيب المفاتيح وتريد فقط أسرع بحث ممكن، فإن unordered_map تستبدل الشجرة المرتّبة بجدول تجزئة — بوصول بمتوسط O(1). سنرى لاحقًا كيف تعمل، ومتى يتفوّق زمنها الثابت في المتوسط على لوغاريتم map، وما يصاحبها من مزالق التجزئة (أنواع المفاتيح المخصّصة، التصادمات في أسوأ الحالات).

الأسئلة الشائعة

ما هي std::map في C++؟

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

ما الفرق بين operator[] و at() في map بلغة C++؟

map[key] تُرجِع مرجعًا إلى القيمة، وإذا كان المفتاح غير موجود فإنها تدرجه بصمت بقيمة مُنشأة افتراضيًا. أما map.at(key) فتُرجِع مرجعًا أيضًا لكنها ترمي std::out_of_range إذا كان المفتاح غائبًا بدلًا من إدراجه. استخدم at() (أو find()) عندما تنوي القراءة فقط.

كيف أتحقق من وجود مفتاح في map بلغة C++؟

استخدم m.contains(key) (في C++20)، أو m.count(key) التي تُرجِع 0 أو 1، أو m.find(key) != m.end(). تجنّب الاختبار باستخدام m[key] لأنه يدرج المفتاح إن لم يكن موجودًا، وهو نادرًا ما يكون ما تريده.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن