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

حلقات for في R: الصياغة وseq_along وbreak وnext ومتى تتجنب الحلقات

كيف تعمل حلقات for في R - التكرار على المتجهات والفهارس، وجمع النتائج دون فخ المتجه المتنامي، وnext وbreak، ومتى يتفوق الاستدعاء المتجهي على الحلقة.

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

حلقة for الأساسية

تنفّذ حلقة for في R كتلة من الشيفرة مرة واحدة لكل عنصر في متجه أو قائمة. ويأخذ متغير الحلقة كل قيمة بالتتابع:

ثلاثة عناصر، وثلاث دورات. ولا عداد تعدّه ولا شرط تصونه - إذ تُقرأ for (fruit in fruits) بمعنى "لكل fruit في fruits"، وهذا تمامًا ما تفعله. والصيغة نفسها تكرر على عناصر قائمة، أو على متتالية أعداد (for (i in 1:10))، أو على أسماء أي شيء عبر names().

الطباعة داخل الحلقة تحتاج إلى print() أو cat()

في الطرفية، تؤدي كتابة x إلى طباعة قيمته. أما داخل جسم حلقة فلا - إذ لا تحدث الطباعة التلقائية إلا في المستوى الأعلى، والتعبير المجرد داخل حلقة يُقيَّم ثم يُرمى:

لذا حين "لا تفعل الحلقة شيئًا"، فهذا أول ما ينبغي فحصه. استخدم print() لنظرة سريعة على أي قيمة، أو cat() حين تؤلف سطر المخرجات بنفسك (وتذكر "\n" - فـ cat() لا تضيفه).

التكرار على الفهارس باستخدام seq_along()

أحيانًا تحتاج إلى الموضع مع القيمة - لتكتب في متجه آخر عند الفهرس نفسه، أو لتطبع قائمة مرقّمة. كرر على الفهارس باستخدام seq_along():

وسترى الصيغة for (i in 1:length(prices)) في الشيفرة الأقدم، وهي تخفي علة حقيقية. فحين يكون المتجه فارغًا، تساوي length() صفرًا، والتعبير 1:0 لا يعني "بلا تكرارات" - إذ تعدّ النقطتان الرأسيتان تنازليًا:

فالحلقة على 1:length(empty) تُنفَّذ مرتين، وتفهرس عناصر غير موجودة (فـ empty[1] تساوي NA، والإسنادات تنشئ مدخلات وهمية). أما seq_along() فتعيد تسلسلًا فارغًا للمتجه الفارغ، فلا يُنفَّذ جسم الحلقة أبدًا. اجعل seq_along() عادتك؛ وتؤدي شقيقتها seq_len(n) المهمة نفسها حين يكون لديك عدد لا متجه.

جمع النتائج: خصّص مسبقًا ولا تنمّ المتجه

الحدس الأول الطبيعي لبناء النتائج يبدو هكذا:

squares <- c()
for (i in 1:10000) {
  squares <- c(squares, i^2)   # copies the ENTIRE vector every pass
}

وهو يعمل، لكن كل استدعاء c(squares, ...) يخصص متجهًا جديدًا تمامًا وينسخ إليه كل العناصر القديمة. وفي النهاية تكون قد نسخت نحو خمسين مليون رقم لإنتاج عشرة آلاف. وهذا النمط المضاد للمتجه المتنامي هو السبب الأكبر منفردًا وراء وصف الناس حلقات R بالبطء.

والحل: أنشئ النتيجة بحجمها الكامل أولًا، ثم أسند بالفهرس:

وللنتائج التي ليست أرقامًا، تنطبق الفكرة نفسها مع character(n) أو logical(n) أو vector("list", n) من أجل قائمة بأشياء متنوعة. والحلقات المخصص لها مسبقًا سريعة تمامًا.

الكلمتان next وbreak

تقود كلمتان مفتاحيتان الحلقة من داخل جسمها: تتخلى next عن الدورة الحالية وتقفز إلى العنصر التالي؛ وتخرج break من الحلقة كليًا.

يطبع هذا 2 4 6 8: إذ تتخطى next القيم الفردية، وحين يصل الرقم 10 تنهي break الحلقة قبل الطباعة. استخدم next لاستبعاد الحالات مبكرًا (فهي تبقي الجسم الرئيسي بلا مسافات بادئة إضافية)، واستخدم break حين تكون الحلقة قد وجدت ما كانت تبحث عنه ولم يعد ثمة سبب للمتابعة.

الحلقات المتداخلة

يمكن لجسم الحلقة أن يحتوي على حلقة أخرى. وتُنفَّذ الحلقة الداخلية حتى نهايتها في كل دورة من الحلقة الخارجية - والعرض الكلاسيكي لذلك هو جدول الضرب:

فلكل صف i، تمشي الحلقة الداخلية على كل الأعمدة j، ويُطبع سطر الصف الجديد بعد انتهاء الحلقة الداخلية. والحلقات المتداخلة لا بأس بها عند مستويين؛ أما عند ثلاثة أو أكثر، فالجسم يطلب عادة أن يصير دالة، أو أن تصير الحسبة كلها استدعاء outer() أو عملية مصفوفية.

ويغطي التداخل أيضًا التكرار على قائمة من المتجهات - حلقة خارجية على القائمة، وحلقة داخلية (أو الأفضل، استدعاء متجهي) على كل عنصر:

لاحظ أنه لا توجد حلقة داخلية أصلًا - إذ تتولى mean() المتجه الداخلي كله في استدعاء واحد. وهذه الملاحظة قابلة للتعميم، وهو ما يقودنا إلى الجزء الصادق.

متى لا تستخدم الحلقة

R لغة متجهية: فعملياتها الأساسية تعمل أصلًا على متجهات كاملة دفعة واحدة. والحلقة التي تحوّل كل عنصر على حدة كثيرًا ما تكون طريقة أطول لكتابة سطر واحد:

فضّل الصيغة المتجهية كلما وُجدت - فهي أقصر، وأصعب في الخطأ، وأسرع. ولتطبيق دالة على كل عنصر في قائمة، تؤدي عائلة apply (‏sapply وlapply وvapply) الدور نفسه.

لكن لا تحوّل هذا إلى عقيدة تقول إن "الحلقات سيئة". فالحلقة هي الأداة الصحيحة حين تعتمد التكرارات على سابقاتها (حالة متراكمة، محاكاة)، وحين تجري آثارًا جانبية مثل كتابة الملفات، أو ببساطة حين تكون الحلقة هي النسخة التي تفهمها أنت وقراؤك بلمحة. فالحلقة الواضحة تتفوق على سطر ذكي لا يستطيع أحد تحليله.

ما تخرج به

  • الصيغة for (x in v) { ... } تنفّذ الجسم مرة لكل عنصر - دون مسك دفاتر للعدادات.
  • داخل الحلقة، اطبع صراحة بـ print() أو cat()؛ فالتعبيرات المجردة تُهمل.
  • كرر على المواضع بـ seq_along(v) لا بـ 1:length(v) أبدًا - فالأخيرة تُنفَّذ مرتين على متجه فارغ.
  • خصّص النتائج مسبقًا بـ numeric(n) أو vector("list", n) وأسند بالفهرس؛ فالتنمية بـ c() تنسخ كل شيء في كل دورة.
  • تتخطى next دورة، وتخرج break من الحلقة؛ وفضّل العمليات المتجهية حين توجد، لكن لا تخف من حلقة مقروءة.

التالي: الحلقات التي تعمل حتى يقع شيء ما بدل مرة لكل عنصر - أي while وrepeat.

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

كيف تكتب حلقة for في R؟

بالصيغة for (x in v) { ... } - إذ يأخذ متغير الحلقة x كل عنصر من المتجه (أو القائمة) v بالتتابع، ويُنفَّذ الجسم مرة لكل عنصر. وللتكرار على المواضع بدل القيم، استخدم for (i in seq_along(v)) وفهرس بالصيغة v[i].

لماذا نستخدم seq_along بدل 1:length(v) في R؟

حين يكون v فارغًا، تساوي length(v) صفرًا، فيصبح 1:length(v) هو 1:0 - أي المتجه ذو العنصرين c(1, 0) - فيُنفَّذ جسم الحلقة مرتين على عناصر غير موجودة. أما seq_along(v) فتعيد تسلسلًا فارغًا للمتجه الفارغ، فتُنفَّذ الحلقة صفر مرة كما ينبغي.

لماذا لا تطبع حلقة for لدي أي شيء في R؟

الطباعة التلقائية في R تحدث للتعبيرات المكتوبة في المستوى الأعلى بالطرفية فقط. وداخل جسم الحلقة، يُقيَّم تعبير مجرد مثل x ثم يُهمل. غلّفه بـ print(x)، أو استخدم cat(...) حين تريد تنسيق المخرجات بنفسك.

هل حلقات for بطيئة في R؟

الحلقة نفسها لا بأس بها - البطيء هو تنمية متجه داخلها بالصيغة result <- c(result, ...)، إذ تنسخ المتجه كاملًا في كل دورة. خصّص مسبقًا بـ numeric(n) أو vector("list", n) وأسند بالفهرس، فتؤدي الحلقة أداءً ممتازًا. ومع ذلك، حين تعبّر عملية متجهية أو استدعاء من عائلة apply عن الفكرة نفسها، فهي عادة أسرع وأوضح.

Coddy programming languages illustration

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

ابدأ الآن