المجموعات في C#: Dictionary و HashSet و Queue و Stack
في درس المصفوفات تعرّفت على List وعلى القاموس Dictionary وبعض دوال LINQ. لكن مكتبة المجموعات (Collections) في C# أغنى من ذلك، ولكل نوع فيها مهمة يتقنها. في هذا الدرس ستتعمق في القاموس، وتتعرف على HashSet و Queue و Stack، ثم ترتب البيانات وتجمّعها بسطر واحد.
القاموس بعمق
Section titled “القاموس بعمق”القاموس يخزن أزواجاً: مفتاح وقيمته، مثل دليل الهاتف: تبحث بالاسم فتصل للرقم مباشرة.
var stock = new Dictionary<string, int>{ ["قلم"] = 40, ["دفتر"] = 15,};stock["مسطرة"] = 8;stock["قلم"] = 38;stock.Remove("دفتر");
Console.WriteLine(stock["قلم"]);Console.WriteLine(stock.Count);Console.WriteLine(stock.ContainsKey("دفتر"));
if (stock.TryGetValue("مسطرة", out int rulers)){ Console.WriteLine($"المساطر: {rulers}");}Console.WriteLine(stock.GetValueOrDefault("ممحاة", 0));382Falseالمساطر: 80stock[key] = value: تضيف زوجاً جديداً، أو تستبدل القيمة إذا كان المفتاح موجوداً. المفاتيح لا تتكرر.- قراءة مفتاح غير موجود بـ
stock["ممحاة"]ترميKeyNotFoundException. TryGetValue: الطريقة الآمنة. تُرجعtrueوتضع القيمة في المتغير بعدout، أو تُرجعfalseبهدوء.GetValueOrDefault: تُرجع قيمة بديلة إذا لم يوجد المفتاح.
والمرور على القاموس يعطيك كل زوج بمفتاحه وقيمته:
var ages = new Dictionary<string, int> { ["علي"] = 13, ["سارة"] = 14 };foreach (var (name, age) in ages){ Console.WriteLine($"{name} عمره {age}");}Console.WriteLine(string.Join(", ", ages.Keys));Console.WriteLine(ages.Values.Sum());علي عمره 13سارة عمره 14علي, سارة27مثال عملي: عدّ التكرارات
Section titled “مثال عملي: عدّ التكرارات”string[] votes = { "أحمر", "أزرق", "أحمر", "أخضر", "أحمر", "أزرق" };var counts = new Dictionary<string, int>();foreach (string vote in votes){ counts[vote] = counts.GetValueOrDefault(vote) + 1;}foreach (var (color, count) in counts){ Console.WriteLine($"{color}: {count}");}أحمر: 3أزرق: 2أخضر: 1في كل مرة نقرأ العدد الحالي للون (أو صفراً إن لم يظهر من قبل)، ونزيده واحداً.
المجموعة HashSet
Section titled “المجموعة HashSet”HashSet تشبه القائمة لكنها ترفض التكرار، والتحقق من وجود عنصر فيها سريع جداً:
var visitors = new HashSet<string> { "سارة", "عمر" };Console.WriteLine(visitors.Add("ليلى"));Console.WriteLine(visitors.Add("سارة"));Console.WriteLine(visitors.Count);Console.WriteLine(visitors.Contains("عمر"));
int[] numbers = { 3, 1, 3, 2, 1 };var unique = new HashSet<int>(numbers);Console.WriteLine(string.Join(", ", unique.Order()));
var football = new HashSet<string> { "علي", "منى", "زياد" };var swimming = new HashSet<string> { "منى", "هدى", "علي" };football.IntersectWith(swimming);Console.WriteLine(string.Join(", ", football.Order()));TrueFalse3True1, 2, 3علي, منىAddتُرجعfalseإذا كان العنصر موجوداً ولا تضيفه مرة ثانية.IntersectWithتُبقي المشترك فقط، وUnionWithتضيف كل عناصر المجموعة الأخرى، وExceptWithتحذفها.- المجموعة لا تضمن ترتيباً لعناصرها، ولهذا رتبناها بـ
Order()قبل الطباعة.
المجموعة مثل سجل الحضور: الاسم يُكتب مرة واحدة مهما دخل صاحبه وخرج.
الطابور Queue
Section titled “الطابور Queue”الطابور (Queue) يعمل بقاعدة: من يدخل أولاً يخرج أولاً، تماماً مثل طابور المخبز:
var orders = new Queue<string>();orders.Enqueue("طلب علي");orders.Enqueue("طلب منى");orders.Enqueue("طلب زياد");
Console.WriteLine(orders.Peek());Console.WriteLine(orders.Dequeue());Console.WriteLine(orders.Dequeue());Console.WriteLine(orders.Count);طلب عليطلب عليطلب منى1Enqueue تضيف في آخر الطابور، و Dequeue تسحب من أوله، و Peek تنظر إلى الأول دون سحبه. تستخدمه لطلبات تُعالَج بالدور، أو رسائل تنتظر الإرسال.
المكدس Stack
Section titled “المكدس Stack”المكدس (Stack) عكس الطابور: آخر من يدخل أول من يخرج، مثل كومة الأطباق: تضع الطبق فوق الكومة وتأخذ من فوقها.
var history = new Stack<string>();history.Push("الصفحة الرئيسية");history.Push("قائمة الكورسات");history.Push("درس المجموعات");
Console.WriteLine(history.Pop());Console.WriteLine(history.Peek());Console.WriteLine(history.Count);درس المجموعاتقائمة الكورسات2Push تضع فوق الكومة و Pop تسحب من فوقها. زر «رجوع» في المتصفح وأمر «تراجع» في المحرر يعملان بهذه الفكرة بالضبط.
الترتيب والتجميع بـ LINQ
Section titled “الترتيب والتجميع بـ LINQ”عرفت Where و Select من قبل. هذه دوال أخرى ستحتاجها مع أي مجموعة:
var students = new List<Student>{ new("هدى", 88, "القاهرة"), new("كريم", 95, "عمّان"), new("ريم", 72, "القاهرة"), new("زياد", 60, "تونس"),};
var sorted = students.OrderByDescending(s => s.Grade).Select(s => s.Name);Console.WriteLine(string.Join(", ", sorted));
Console.WriteLine(students.Any(s => s.Grade > 90));Console.WriteLine(students.All(s => s.Grade >= 70));Console.WriteLine(students.First(s => s.City == "القاهرة").Name);Console.WriteLine(students.FirstOrDefault(s => s.Grade > 99) == null);
foreach (var group in students.GroupBy(s => s.City)){ Console.WriteLine($"{group.Key}: {group.Count()} - المتوسط {group.Average(s => s.Grade)}");}
record Student(string Name, int Grade, string City);كريم, هدى, ريم, زيادTrueFalseهدىTrueالقاهرة: 2 - المتوسط 80عمّان: 1 - المتوسط 95تونس: 1 - المتوسط 60OrderByوOrderByDescending: ترتيب حسب خاصية تختارها، والنتيجة تسلسل جديد لا يمس القائمة الأصلية.Any: هل يوجد عنصر واحد على الأقل يحقق الشرط؟ وAll: هل كل العناصر تحققه؟Firstترمي خطأ إذا لم تجد شيئاً، وFirstOrDefaultتُرجعnullبدلاً من ذلك.GroupBy: توزّع العناصر على مجموعات حسب مفتاح، ولكل مجموعةKeyوعناصرها.
أيها أختار؟
Section titled “أيها أختار؟”| تحتاج إلى | استخدم |
|---|---|
| عناصر مرتبة تصل إليها برقم الموضع | List<T> |
| البحث عن قيمة بمفتاح | Dictionary<TKey, TValue> |
| عناصر بلا تكرار وفحص وجود سريع | HashSet<T> |
| معالجة بالدور: الأول فالأول | Queue<T> |
| التراجع: الأحدث أولاً | Stack<T> |
جرّب بنفسك
Section titled “جرّب بنفسك”- أنشئ قاموساً يربط اسم الدولة بعاصمتها، واطبع عاصمة دولة غير موجودة بأمان باستخدام
TryGetValue. - خزّن جملة في متغير، وقسّمها بـ
Split(' ')، واستخدمHashSetلمعرفة عدد الكلمات المختلفة فيها. - حاكِ طابور انتظار في عيادة: أضف خمسة مرضى، ثم استدعِهم واحداً واحداً بحلقة
while (queue.Count > 0). - تحدٍّ: استخدم
Stack<char>للتحقق من توازن الأقواس في نص مثل(a[b]c).
أخطاء شائعة
Section titled “أخطاء شائعة”- قراءة مفتاح غير موجود:
dict[key]ترميKeyNotFoundException. استخدمTryGetValueأوContainsKeyأولاً. - Add بمفتاح مكرر:
dict.Add(key, value)ترميArgumentExceptionإذا كان المفتاح موجوداً، بعكسdict[key] = valueالتي تستبدل. - السحب من مجموعة فارغة:
DequeueوPopعلى مجموعة فارغة ترميانInvalidOperationException. افحصCountأو استخدمTryDequeueوTryPop. - التعديل أثناء foreach: إضافة عنصر أو حذفه من مجموعة أثناء المرور عليها يرمي
InvalidOperationException.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
أضفنا إلى Queue العناصر A ثم B ثم C. ماذا تُرجع أول Dequeue؟
الطابور يعمل بقاعدة: من يدخل أولاً يخرج أولاً. أما Stack فكان سيُرجع C.
سؤال
ما الطريقة الآمنة لقراءة قيمة من قاموس قد لا يحتوي المفتاح؟
TryGetValue تُرجع false بدل رمي KeyNotFoundException إذا لم يوجد المفتاح.
الخلاصة
Section titled “الخلاصة”Dictionary: أزواج مفتاح وقيمة، والقراءة الآمنة بـTryGetValueأوGetValueOrDefault.HashSet: عناصر بلا تكرار، معIntersectWithوUnionWith.Queue: الأول يخرج أولاً. وStack: الأخير يخرج أولاً.OrderByوGroupByوAnyوAllوFirstOrDefaultتعمل مع كل المجموعات.- مبروك! أنهيت كورس C#. جرّب الآن بناء برنامج صغير أو أول لعبة لك في Unity.