كيف تحصل على فهرس العنصر عند اجتياز المصفوفة في Swift؟ نفهم السؤال الشائع
\n\nتخيل: أنت تكتب بلغة Swift وتريد ليس فقط المرور عبر المصفوفة، ولكن أيضًا معرفة موقع كل عنصر. هل تحتاج إلى تعديل العناصر حسب الفهرس، أو مقارنة القيم المجاورة، أو مجرد عرض قائمة مرقمة - المهمة واحدة. سؤال "How to iterate a loop with index and element in Swift" حصد آلاف المشاهدات على Stack Overflow. هذا يعني أن هذا الموضوع يثير اهتمام الكثيرين.
\n\nفي هذه المقالة، لن نقدم إجابة جاهزة فقط. سننظر تحت الغطاء: سنحلل الطرق الموجودة، أيها أسرع، أيها أكثر أمانًا، ولماذا أصبحت إحداها - enumerated() - المعيار الفعلي. لنبدأ!
المشكلة: لماذا لا يكفي for-in البسيط؟
\n\nلنفترض أن لديك مصفوفة من السلاسل النصية:
\n\nlet fruits = ["تفاح", "موز", "كرز", "شمام"]\n\nالطريقة الأكثر وضوحًا هي for-in:
for fruit in fruits {\n print(fruit)\n}\n\nهذا سيطبع أسماء الفواكه. ولكن ماذا لو كنت بحاجة إلى معرفة الفهرس؟ على سبيل المثال: "1. تفاح، 2. موز..."؟ for-in البسيط لا يعطي الفهرس. تضطر لاختراع حلول بديلة:
var index = 0\nfor fruit in fruits {\n print("\\(index). \\(fruit)")\n index += 1\n}\n\nهل يعمل؟ نعم. هل هو جميل؟ لا. وماذا لو كانت المصفوفة فارغة؟ وماذا لو نسيت زيادة index عن طريق الخطأ؟ مرحبًا بك في عالم الأخطاء. تقدم Swift حلولًا أكثر أناقة.
الطريقة رقم 1: الكلاسيكية - طريقة enumerated()
\n\nهذه هي الطريقة الأكثر شيوعًا والموصى بها. طريقة enumerated() تُرجع تسلسلًا من الصفوف (offset: Int, element: Element). لنرى كيف يعمل هذا:
for (index, fruit) in fruits.enumerated() {\n print("\\(index + 1). \\(fruit)")\n}\n\nالإخراج:
\n1. تفاح\n2. موز\n3. كرز\n4. شمام\n\nماذا يحدث تحت الغطاء؟
\n\nenumerated() لا تنشئ مصفوفة جديدة. تُرجع تسلسلًا كسولًا من النوع EnumeratedSequence. هذا يعني أن العناصر لا تُنسخ إلى بنية بيانات جديدة - بل تُغلف أثناء التنقل. في كل مرة يتم فيها الوصول إلى العنصر التالي، يزيد التسلسل العداد ويعيد الصف. هذا فعال جدًا من حيث الذاكرة، خاصة للمجموعات الكبيرة.
ملاحظة مهمة: offset يبدأ دائمًا من 0. إذا كنت بحاجة إلى فهرس يبدأ من 1 (كما في ترقيم القوائم)، فقط أضف واحدًا. Swift لا يفعل ذلك نيابة عنك - هذا قرار متعمد من مطوري اللغة للحفاظ على التوحيد مع فهرسة المصفوفات.
enumerated() مع forEach
\n\nطريقة forEach تدعم أيضًا enumerated():
fruits.enumerated().forEach { (index, fruit) in\n print("\\(index). \\(fruit)")\n}\n\nالفرق بين for-in و forEach مهم: داخل forEach لا يمكن استخدام break أو continue. إذا كنت بحاجة إلى الخروج المبكر من الحلقة - استخدم for-in.
الطريقة رقم 2: الفهرسة عبر النطاق (Range)
\n\nطريقة قديمة ولكنها لا تزال موجودة - التكرار عبر الفهارس:
\n\nfor index in 0..<fruits.count {\n print("\\(index). \\(fruits[index])")\n}\n\nيبدو الأمر بسيطًا. لكن هناك مخاطر خفية:
\n\n- \n
- الأمان: إذا تغيرت المصفوفة أثناء التكرار (مثلًا، في كود متعدد الخيوط)، قد يصبح
fruits.countقديمًا، وستخرج عن حدود المصفوفة. \n - قابلية القراءة: الكود يبدو أكثر "انخفاضًا في المستوى". سيقضي المطورون الآخرون ثانية إضافية لفهم ما تفعله. \n
- الأداء: الوصول عبر الفهرس
fruits[index]هو عملية O(1) للمصفوفات، ولكن بالنسبة للآخرين