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

‏C++ set: عناصر فريدة ومرتّبة باستخدام std::set

كيف يخزّن std::set قيمًا فريدة ومرتّبة تلقائيًا في C++: الإدراج، التحقق من العضوية باستخدام count و find، التكرار بالترتيب، والفروق بين set و multiset و unordered_set.

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

حقيبة من القيم الفريدة والمرتّبة

رأيت كيف يخزّن unordered_map أزواج المفتاح-القيمة مع بحث سريع قائم على التجزئة. أما std::set فهو ابن العمّ الأبسط: يخزّن القيم فقط دون أي بيانات مرتبطة، ويفرض قاعدتين تلقائيًا. كل عنصر فريد (تُتجاهل التكرارات بصمت)، وتبقى العناصر دائمًا بترتيب مرتّب.

هذا يجعل set الخيار الطبيعي لأسئلة مثل «هل رأيت هذا من قبل؟» أو «أعطني العناصر المتمايزة بترتيب». تُدرج دون قلق بشأن التكرارات، وتُكرّر دون الحاجة إلى الترتيب أولًا.

لاحظ أننا أدرجنا 10 مرتين وبترتيب غير منظّم، ومع ذلك جاء الخرج مرتّبًا وظهر 10 مرة واحدة. لقد تكفّل الـ set بكل التنظيم نيابة عنك.

الإدراج والحذف

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

لحذف قيمة، استدعِ erase مع القيمة نفسها؛ فهو يُرجع عدد العناصر المحذوفة (0 أو 1 في الـ set). حذف شيء غير موجود غير ضارّ ولا يُعدّ خطأً:

التحقق من العضوية

الغرض الأساسي من الـ set هو الاختبارات السريعة لـ «هل هذا موجود هنا؟». وأوضح طريقة هي count التي تُرجع 1 أو 0:

منذ C++20 صار هناك خيار أوضح للقراءة، وهو contains، الذي يُرجع قيمة bool مباشرة:

if (primes.contains(7)) { /* ... */ }   // C++20

من الأخطاء الشائعة اللجوء إلى operator[] كما تفعل مع map. الـ set لا يملك operator[]: لا توجد قيمة لجلبها، بل وجود فقط لاختباره. استخدم count أو contains، لا s[7].

إن احتجت إلى الموضع الفعلي (لحذفه أو للنظر إلى العناصر المجاورة)، استخدم find الذي يُرجع مؤشّرًا تكراريًا أو end():

التكرار المرتّب واستعلامات النطاق

بما أن الـ set مرتّب، فإن التكرار يُنتج العناصر دائمًا من الأصغر إلى الأكبر، وتحصل على حِيَل الحاويات المرتّبة مجانًا. lower_bound(x) يعطي أول عنصر ليس أصغر من x، وupper_bound(x) أول عنصر أكبر تمامًا من x؛ ومعًا يتيحان لك مسح نطاق رقمي دون فحص كل عنصر:

قاعدة دقيقة لكنها مهمة: عناصر الـ set غير قابلة للتعديل. يمنحك المؤشّر التكراري مرجعًا من نوع const، لذا لا يمكنك تغيير عنصر في مكانه؛ فالقيام بذلك قد يكسر ترتيب الفرز الذي تعتمد عليه الحاوية. ولكي «تغيّر» قيمة، احذف القديمة وأدرج الجديدة.

الترتيب افتراضيًا تصاعدي (std::less). وللترتيب التنازلي، مرّر مُقارِنًا مختلفًا كوسيط القالب الثاني:

‏set و multiset و unordered_set

std::set واحد من ثلاثة أقارب متقاربين، واختيار المناسب منها أمر مهمّ:

set<int>            // قيم فريدة، مرتّبة، O(log n)
multiset<int>       // يسمح بالتكرارات، مرتّبة، O(log n)
unordered_set<int>  // قيم فريدة، بلا ترتيب، O(1) في المتوسط

ألجأ إلى unordered_set عندما تحتاج فقط إلى اختبارات عضوية ولا يهمّك الترتيب؛ فعمليات بحثه القائمة على التجزئة أسرع في المتوسط من O(log n) القائمة على الشجرة في الـ set. اختر set عندما تحتاج إلى عناصر بترتيب مرتّب، أو استعلامات نطاق بـ lower_bound/upper_bound، أو سلوك مستقرّ للمؤشّرات التكرارية. استخدم multiset فقط عندما تكون التكرارات ذات معنى (مثل مدرّج تكراري لقيم متكرّرة): ففي الـ multiset قد يُرجع count(x) أكثر من 1، ويحذف erase(x) كل النسخ ما لم تحذف عبر مؤشّر تكراري واحد.

من الاستخدامات الكلاسيكية للـ set: إزالة التكرارات من vector وترتيبه في خطوة واحدة.

بناء الـ set من المؤشّرات التكرارية لـ vector يُسقط كل تكرار ويرتّب الباقي، دون حلقة يدوية، ودون رقصة std::sort مع std::unique.

التالي: pair و tuple

رأيت الآن .first و.second تظهران على الـ pair الذي يُرجعه insert، ورأيت الربط البنيوي (auto [it, inserted]) يفكّكه بأناقة. هذه الأنواع الخفيفة التي «تجمع بضع قيم معًا» منتشرة في كل أنحاء الـ STL. سننظر تاليًا في pair و tuple مباشرة: كيف تبنيها، وتفكّكها، وتُرجع عدة قيم من دالة دون تعريف بنية كاملة.

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

ما هو set في C++؟

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

كيف أتحقق من وجود عنصر في set في C++؟

استخدم s.count(x) التي تُرجع 1 إذا كان x موجودًا و0 إن لم يكن، أو s.contains(x) في C++20 التي تُرجع قيمة bool. تجنّب s.find(x) != s.end() ما لم تكن بحاجة فعلية إلى المؤشّر التكراري؛ فالكلفة نفسها لكنه أكثر إطنابًا.

ما الفرق بين set و unordered_set في C++؟

std::set يبقي العناصر مرتّبة ويوفّر عمليات بكلفة O(log n)؛ بينما std::unordered_set يبقيها دون ترتيب محدّد باستخدام جدول تجزئة بعمليات بكلفة O(1) في المتوسط. استخدم set عندما تحتاج إلى تكرار مرتّب أو استعلامات نطاق، وunordered_set عندما تحتاج فقط إلى اختبارات عضوية سريعة ولا يهمّك الترتيب.

Coddy programming languages illustration

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

ابدأ الآن