اذهب إلى المحتوى

الحلقات التكرارية في البرمجة


أسامة دمراني

رأينا في التدريب الأخير من المقال السابق كيف طبعنا جزءًا من جدول ضرب العدد 12، لكن ذلك تطلب منا كثيرًا من الكتابة، وسيستغرق توسيعه وقتًا طويلًا، ولحسن الحظ توجد طريقة أسهل سترينا بداية المزايا القوية التي توفرها لنا لغات البرمجة في تنفيذ المهام وإنجازها.

حلقات for

تتيح لغة البرمجة تكرار تنفيذ أمر أو عدة أوامر لعدد محدَّد من المرات، في ما يسمى حلقة تكرار، حيث يمكن استعمال متغير تزيد قيمته مع كل دورة للحلقة، وستبدو هذه العملية في بايثون كما يلي:

>>> for n in range(1,13):
...    print( "%d x 12 = %d" % (n, n*12) )
...
1 x 12 = 12
2 x 12 = 24
3 x 12 = 36
4 x 12 = 48
5 x 12 = 60
6 x 12 = 72
7 x 12 = 84
8 x 12 = 96
9 x 12 = 108
10 x 12 = 120
11 x 12 = 132
12 x 12 = 144

لندرس الشيفرة السابقة ونستخرج بعض الملاحظات منها:

  • انتهى سطر for بنقطتين رأسيتين (:)، وهذا أمر مهم، إذ يخبر بايثون أن ما سيكتَب بعد هاتين النقطتين هو ما سيُكرَّر.
  • كتبنا مجال الأعداد الذي نريده في دالة range()‎ بالشكل range(1,13)‎، رغم أننا نريد تكرار عملية الضرب إلى العدد 12 فقط، لأن دالة range()‎ تولد الأعداد من العدد الأول فيها إلى العدد الذي يسبق العدد الثاني، 13 في مثالنا، وهذا له أسبابه قطعًا وستعتاده مع الوقت، لكن تجدر الإشارة إلى أن هذه الدالة تستطيع توليد مجموعات معقدة من الأعداد، لكننا اخترنا هذا المثال البسيط لأنه يحقق الغرض.
  • العامل for في بايثون هو عامل foreach في الواقع، إذ يطبق تسلسل الشيفرة التالي على كل عنصر في التجميعة، والتي هي في حالتنا قائمة الأعداد التي تولدها الدالة range()‎، ونستطيع استخدام قائمة أعداد صراحةً عوضًا عن الدالة range()‎، بالشكل التالي:
  >>> for n in [1,2,3,4,5,6,7,8,9,10,11,12]:
  ...     print( "%d x 12 = %d" % (n, n*12) )
  • أُزيح سطر print موازنةً بسطر حلقة for الذي فوقه، وهذا مهم جدًا لأن هذه الإزاحة تخبر بايثون أن عليها تكرار القسم الخاص بـ print، ومن الممكن إزاحة أكثر من سطر، وستكرر بايثون هذه الأسطر المزاحة جميعًا لكل عنصر في التجميعة، ولا يهم مقدار الإزاحة المستخدمة طالما هي نفسها في كل الأسطر المزاحة، وستخبرنا بايثون إذا وجدت اختلافًا في الإزاحة.
  • نحتاج إلى ضغط زر الإدخال Enter مرتين في المفسر التفاعلي لتشغيل البرنامج، لأن مفسر بايثون يضيف سطرًا جديدًا إلى شيفرة الحلقة التكرارية عندما نضغط مرةً واحدةً؛ أما مع الضغطة الثانية فستفترض بايثون أننا أنهينا إدخال الشيفرة، فتشغّل البرنامج.

وبما أننا قد رأينا الآن هيكل حلقة for، لننظر في كيفية عملها خطوةً خطوةً على النحو الآتي:

  • أولًا: تستخدم بايثون دالة range()‎ لتوليد قائمة أعداد من 1 إلى 12، وفقًا لآلية خاصة تسمى بالمولد generator، وتشبه القائمة التي تزيد من عناصرها عنصرًا واحدًا في كل مرة حسب الطلب، مما يوفر الذاكرة عندما تكون القائمة كبيرةً، وهو نفس سبب توليد القائمة صراحةً باستخدام list()‎ عندما طبعنا range()‎ أعلاه، وهذا شبيه بما فعلناه في مثال مفاتيح القاموس keys()‎ في المقال الخامس من هذه السلسلة والمتعلق بالمواد الخام للبرمجة.
  • ثانيًا: تجعل بايثون قيمة n مساويةً للقيمة الأولى في القائمة، وهي 1 في هذه الحالة، ثم تنفذ الشيفرة المزاحة باستخدام القيمة n = 1:
   print( "%d x 12 = %d" % (1, 1*12) )

ثم تعود إلى سطر for وتضبط n على القيمة التالية في القائمة، وهي 2 في هذه الحالة، ثم تنفذ الشيفرة المزاحة مع القيمة n = 2:

   print( "%d x 12 = %d" % (2, 2*12) )

وتستمر في تكرار هذا التسلسل حتى تمر n على جميع القيم في القائمة، ثم تنتقل إلى الأمر التالي غير المزاح، في حالتنا لا توجد أي أوامر، لذا سيتوقف البرنامج.

الحلقة التكرارية في VBScript

تُعَد For...Next أبسط حلقة تكرارية في VBScript، وتُستخدم بالشكل التالي:

<script type="text/vbscript">
For N = 1 To 12
    MsgBox N & " x 12 = " & N*12
Next
</script>

يُعَد أسلوب VBScript أوضح وأسهل في فهم ما تفعله الشيفرة، إذ تتغير قيمة N من 1 إلى 12، وتنفَّذ الشيفرة التي توجد قبل الكلمة المفتاحية Next، وفي حالتنا تطبع الشيفرة النتيجة في صندوق حواري؛ أما إزاحة السطر هنا فاختيارية وليست شرطًا، لكنها تجعل الشيفرة أسهل في القراءة، وقد يحتوي متن الحلقة التكرارية على أكثر من سطر ينفَّذ على عناصر القائمة، كما في حالة بايثون التي رأيناها قبل قليل، ورغم أن VBScript تبدو أوضح للوهلة الأولى؛ إلا أن بايثون أكثر مرونةً كما سنرى بعد قليل.

الحلقة التكرارية في جافاسكربت

تستخدم جافاسكربت البُنية for الشائعة في كثير من لغات البرمجة الشبيهة بلغة C، وستبدو الحلقة كما يلي:

<script type="text/javascript">
for (n=1; n <= 12; n++){
    document.write(n + " x 12 = " + n*12 + "<BR>");
    };
</Script>

تبدو هذه الشيفرة معقدةً للوهلة الأولى، وهي تتكون من ثلاثة أجزاء بين القوسين () هي:

  • جزء البداية: n = 1 الذي يُنفَّذ مرةً واحدةً قبل أي شيء آخر.
  • جزء الاختبار: n <= 12 الذي يُنفَّذ قبل كل تكرار.
  • جزء الزيادة: n++‎ وهو اختصار "زِد n بمقدار 1"، وينفَّذ بعد كل تكرار.

لاحظ أن جافاسكربت تضع الشيفرة المكرَّرة، أي متن الحلقة التكرارية، بين قوسين معقوصين {}، وهذا كافٍ لتكون الحلقة صالحةً للتنفيذ، إلا أنه يفضَّل إزاحة الشيفرة التي داخل الأقواس المعقوصة لتحسين قابلية قراءة الشيفرة.

لن ينفَّذ متن الحلقة loop body إلا إذا تحقق جزء الاختبار، أي كان true. وقد تحتوي هذه الأجزاء على شيفرات عشوائية، إلا أن الجزء الثاني الخاص بالاختبار يجب أن يعطي قيمةً بوليانيةً.

مزيد من المعلومات حول حلقة for في بايثون

تتكرّر حلقة for في بايثون على تسلسل، تذكر أن التسلسل الذي شرحناه في المقالات السابقة يشمل أشياء، مثل السلاسل النصية والقوائم والصفوف tuples، كما تستطيع بايثون أن تكرِّر عدة أنواع أخرى لكننا سنؤجل ذلك إلى وقت لاحق، وبناءً على ذلك نستطيع كتابة حلقات for تتعامل مع أي نوع من التسلسلات، ولنجرب مثلًا طباعة حروف كلمة حرفًا حرفًا باستخدام حلقة for مع سلسلة نصية:

>>> for c in 'word': print( c )
...
w
o
r
d

لاحظ أننا طبعنا كل حرف على سطر منفصل، وأنه يمكننا إضافة متن الحلقة، عندما يكون سطرًا واحدًا، إلى نفس سطر الحلقة بوضعه بعد نقطتين رأسيتين :، إذ تخبران بايثون بوجود كتلة برمجية تليهما.

ويمكن التكرار على صف tuple:

>>> for word in ('one','word', 'after', 'another'): print (word)
...

جعلنا في هذا المثال كل كلمة في سطر منفصل على خلاف المثال السابق، ويمكن وضعها جميعًا في سطر واحد باستخدام ميزة خاصة بدالة print()‎، إذ نستطيع إضافة وسيط argument بعد العنصر المراد طباعته كما يلي:

>>> for word in ('one', 'word', 'after', 'another'): print( word, end='' )
...

لاحظ كيف ستظهر الكلمات الآن في سطر واحد، لأن الوسيط end=''‎ يخبر بايثون أن تستخدم سلسلةً نصيةً فارغةً '' لنهاية السطر عوضًا عن محرف السطر الجديد الذي تستخدمه افتراضيًا، لنجرب حلقة for مرةً أخرى مع القوائم:

>>> for item in ['one', 2, 'three']: print( item )
...

ستظهر هنا مشكلة عند استخدام الحلقات التكرارية التي على نمط foreach، وهي أن الحلقة تعطينا نسخةً مما كان في التجميعة، دون أن نستطيع تعديل محتوياتها مباشرةً، فإذا احتجنا إلى تعديلها؛ فعلينا استخدام برنامج غريب الشكل نستخدم فيه فهرس التجميعة، كما يلي:

myList = [1,2,3,4]
for index in range(len(myList)):
    print( myList[index] )
    myList[index] += 1
print( myList )

سيزيد هذا البرنامج كل فهرس في myList، ولو أننا لم نستخدم طريقة الفهرس تلك، لكنا زدنا العناصر المنسوخة فقط دون تغيير القائمة الأصلية، وقد صار لدينا متن متعدد الأسطر في حلقتنا التكرارية.

اقتباس

دالة التعداد enumerate function

إن المثال الذي أوردناه أعلاه شائع جدًا لدرجة أن بايثون توفر دالةً سهلة الاستخدام لتجنب هذه المشكلة، أو تخفيفها على الأقل، وهي دالة enumerate التي تعيد عنصرين في كل مرة نستخدمها على تجميعة ما، هذان العنصران هما قيمة التجميعة ورقم فهرسها، مما يسمح لنا بالوصول إلى القيمة كما فعلنا في حلقة for الأصلية، وتعديلها بواسطة الفهرس كما فعلنا في المثال أعلاه، وتبدو دالة enumerate كما يلي:

     myList = [1,2,3,4]
     for index, value in enumerate(myList):
         print( value )
         myList[index] += 1
     print( myList )    

لاحظ أننا لم نستخدم محث بايثون التفاعلي ‎>>>‎ في هذا المثال، لذا عليك كتابته في ملف كما شرحنا في مقال المزيد من التسلسلات البرمجية وأمور أخرى؛ أما إذا حاولنا كتابته في محث بايثون، فسنحتاج إلى أسطر فارغة إضافية لنخبر بايثون بانتهاء كتلة برمجية، وذلك بعد السطر myList[index] += 1 مثلًا، وهذه الطريقة مفيدة لتعلّم بداية الكتل البرمجية ونهايتها، بكتابة الشيفرة وتخمين أماكن الحاجة إلى السطر الإضافي، إذ يجب أن يكون عند الموضع الذي تتغير فيه الإزاحة.

لا بد من ملاحظة أمر آخر في حلقات foreach، وهو أننا لا نستطيع حذف العناصر من التجميعة التي نمر عليها، لأن ذلك يربك الحلقة نفسها، فهو يشبه قطع فرع من شجرة أثناء جلوسك عليه، والحل هو استخدام نوع مختلف من الحلقات التكرارية، كما سنرى في جزئية لاحقة من هذه السلسلة.

ربما تجب الإشارة إلى أن كلًا من جافاسكربت وVBScript لديهما بنىً للتكرار على العناصر الموجودة في تجميعة ما، ولن نتحدث عنها بتفصيل هنا، لكن إذا أردت أن تبحث في الأمر بنفسك فانظر صفحات المساعدة لبنية for each...in...‎ في لغة VBScript، وبنية for...in...‎ في جافاسكربت.

حلقات While التكرارية

تتطلب حلقات for أن يعلم المبرمج عدد مرات التكرار التي يريد تنفيذها قبل بدء الحلقة نفسها، أو أن يستطيع حسابها على الأقل، لكن ماذا لو أردنا تنفيذ مهمة ما حتى وقوع حدث معين لا نعرف موعده؟ فقد نرغب في قراءة بيانات من المستخدم ومعالجتها مثلًا إلى أن يخبرنا المستخدم نفسه بالتوقف عن ذلك، كما لن نعرف عدد عناصر البيانات التي سنعالجها إلى أن يكتفي المستخدم ويخبرنا بذلك أيضًا. يُعَد تنفيذ مثل هذه المهام باستخدام حلقة for ممكنًا نظريًا لكنه معقد، لهذا لدينا نوع آخر من الحلقات التي تحل مثل هذه المشاكل، وهي حلقة while، وستبدو في بايثون بالشكل التالي:

>>> j = 1
>>> while j <= 12:
...    print( "%d x 12 = %d" % (j, j*12) )
...    j = j + 1

لنحلل هذه الشيفرة:

  1. نهيئ أولًا j لتأخذ القيمة 1، وهذه الخطوة مهمة للغاية، أي تهيئة متغير التحكم في حلقة while، لأن نسيان تهيئته يتسبب في أخطاء كثيرة.
  2. ننفذ تعليمة while نفسها، التي تختبر تعبيرًا بوليانيًا هو j<=12 في حالتنا.
  3. ننفذ الكتلة المزاحة التي تتبعها إذا كانت النتيجة True، وهو محقق في حالتنا بما أن قيمة j أقل من 12، لذا سننتقل إلى الكتلة.
  4. ننفذ تعليمة الطباعة لإخراج أول سطر من جدولنا.
  5. يزيد السطر التالي في الكتلة متغير التحكم j، وهو السطر الأخير في حالتنا، ليشير إلى نهاية كتلة while.
  6. نعود إلى تعليمة while ونكرر الخطوات من 4 إلى 6 بقيمة j الجديدة.
  7. نكرر هذا التسلسل إلى أن تصل قيمة j إلى 13.
  8. هنا يعيد اختبار while القيمة False، فنتخطى الكتلة المزاحة إلى السطر الجديد الذي يوازي تعليمة while في إزاحتها.
  9. يتوقف البرنامج في مثالنا لأنه لا توجد أسطر أخرى.

تعليمة while واضحة وبسيطة كما رأينا، لكننا نريد الإشارة إلى النقطتين الرأسيتين (:) اللتين في نهاية سطر تعليمة while -وتعليمة for أيضًا-، إذ تخبر هاتان النقطتان بايثون أن ما يليهما كتلة برمجية أو مجموعة مترابطة من التعليمات، وكل لغة لها طريقتها في إخبار المفسر بجمع عدة أسطر معًا كما سنرى، ففي حالة بايثون مثلًا، سنستخدم النقطتين الرأسيتين وإزاحة الأسطر.

حلقة While في VBScript

فيما يلي حلقة while في لغة VBScript:

<script type="text/vbscript">
DIM J
J = 1
Do While J <= 12
    MsgBox J & " x 12 = " & J*12
    J = J + 1
Loop
</script>

يعطينا هذا المثال نفس النتيجة التي حصلنا عليها من مثال بايثون السابق، مع ملاحظة انتهاء كتلة الحلقة التكرارية بالكلمة المفتاحية Loop

اقتباس

هذه البنية التي تستخدمها VBSCript، أي Do...Loop هي بنية متعددة الاستخدامات، ومنها نستطيع تشكيل عدة بنىً مختلفة تعطينا تأثيرًا مختلفًا في كل مرة، ونختار ما يناسبنا وفق حالة التطبيق الذي نعمل عليه، وهذه البنى هي:

  • Do…Loop: وهي البنية الأبسط التي تكرر إلى ما لا نهاية، لكن يمكن الخروج منها بطريقة سنراهافي جزئية لاحقة من هذه السلسة.
  • Do While …Loop: وهي البنية التي استخدمناها في المثال أعلاه.
  • Do Until …Loop: عكس منطق البنية السابقة.
  • Do…Loop While ‎: تشبه حلقة while لكنها تنفَّذ مرةً واحدةً على الأقل، لأنها تختبر الشرط في نهاية متن الحلقة.
  • Do…Loop Until ‎: تشبه البنية السابقة لكن مع عكس منطق الاختبار.

حلقة While في جافاسكربت

<script type="text/javascript">
j = 1;
while (j <= 12){
   document.write(j + " x 12 = "+ j*12 + "<BR>");
   j++;
   }
</script>

تتشابه هذه البنية مع ما سبق، لكن مع بعض الأقواس المعقوصة بدلًا من كلمة Loop التي في VBScript، لاحظ أن ++J تعني زيادة قيمة j بمقدار 1، كما ذكرنا في مقال سابق، وأن جافاسكربت وVBScript لا تشترطان إزاحة الأسطر، على عكس لغة بايثون، لكننا أزحنا الأسطر لتسهيل قراءة الشيفرة فقط.

لنراجع الآن حلقة for في جافاسكربت:

for (j=1; j<=12; j++){....}

نلاحظ أنها تشبه حلقة while تمامًا، لكنها مكتوبة في سطر واحد بحيث يمكن رؤية المهيئ initializer وشرط الاختبار ومعدِّل الحلقة معًا، وبهذا يتبين أن حلقة for في جافاسكربت ما هي إلا حلقة while ولكن بصورة مضغوطة، وسنستنتج أن بعض اللغات قد تستغني عن حلقة for نهائيًا، وهو ما يحدث حقًا.

حلقات تكرارية مرنة

إذا عدنا إلى مثال جدول ضرب العدد 12 الذي ذكرناه في بداية هذا المقال، فسنجد أن الحلقة التي أنشأناها ستطبع جدول هذا العدد بكفاءة، لكن هل يمكننا تعديل الحلقة لجعلها تطبع جدول العدد 7 مثلًا؟ ينبغي أن تبدو الحلقة كما يلي:

>>> for j in range(1,13):
...    print( "%d x 7 = %d" % (j,j*7) )

وهذا يعني أن علينا تغيير 12 إلى 7 مرتين، وإذا أردنا قيمةً أخرى غيرهما فسنغير في موضعين من جديد، ولكن ألا توجد طريقة أفضل لإدخال مضاعف الضرب ذاك؟، يمكن ذلك باستخدام متغير آخر للقيم التي في سلسلة الطباعة، ثم ضبط ذلك المتغير قبل تشغيل الحلقة التكرارية:

>>> multiplier = 12
>>> for j in range(1,13):
...    print( "%d x %d = %d" % (j, multiplier, j*multiplier) )

وهذا جدول العدد 12 السابق، وإذا أردنا تغييره إلى العدد 7 مثلًا، فلا نغير إلا قيمة multiplier، جرب كتابة هذا البرنامج في ملف سكربت بايثون وتشغيله من محث سطر الأوامر، ثم عدِّل قيمة المضاعف multiplier إلى أعداد أخرى، لاحظ أننا جمعنا هنا بين التسلسل والحلقات التكرارية، فقد كتبنا أمرًا وحيدًا في البداية هو multiplier = 12، متبوعًا بحلقة for.

تكرار الحلقة نفسها

لنطور مثالنا السابق قليلًا، ولنفترض أننا نريد طباعة جميع جداول الضرب للأعداد من 2 حتى 12، سيكون كل ما نحتاجه هو ضبط متغير المضاعف ليكون جزءًا من الحلقة التكرارية، ونفعل بذلك بالشكل التالي:

>>> for multiplier in range(2,13):
...    for j in range(1,13):
...       print( "%d x %d = %d" % (j,multiplier,j*multiplier) )

لاحظ أن الجزء المزاح داخل حلقة for الأولى هو نفس الحلقة التي بدأنا بها، وستنفَّذ الحلقة كما يلي:

  1. نضبط multiplier أولًا على القيمة الأولى 2 ثم ننتقل إلى الحلقة الثانية الداخلية.
  2. نعيد ضبط multiplier على القيمة التالية 3 ثم ننتقل إلى الحلقة الداخلية مرةً أخرى.
  3. نكرر هذا حتى نمر على جميع الأعداد.

يُعرف هذا الأسلوب باسم الحلقات المتشعبة nesting loops، لكن من مساوئه أن جميع الجداول ستكون مدمجةً معًا، ونستطيع إصلاح هذه المشكلة بطباعة سطر فاصل في نهاية الحلقة الأولى، كما يلي:

>>> for multiplier in range(2,13):
...    for j in range(1,13):
...       print( "%d x %d = %d" % (j,multiplier,j*multiplier) )
...    print( "------------------- " )

لاحظ أن تعليمة الطباعة الثانية لها نفس إزاحة تعليمة for الثانية، وهي ثاني تعليمة في تسلسل التكرار، فتسلسل الإزاحة مهم جدًا في بايثون كما ذكرنا.

دعنا نرى الآن كيفية تنفيذ هذا التدريب في جافاسكربت، لرؤية الفرق بينهما فقط:

<script type="text/javascript">
for (multiplier=2; multiplier < 13; multiplier++){
    for (j=1; j <= 12 ; j++){
        document.write(j, " x ", multiplier, " = ", j*multiplier, "<BR>");
        }
    document.write("---------------<BR>");
    }
</script>

حاول أن تجعل السطر الفاصل يشير إلى الجدول التابع له، يمكنك استخدام متغير المضاعف وسلسلة التنسيق التي في بايثون لفعل ذلك.

حلقات أخرى

توفر بعض اللغات الأخرى بنىً مختلفةً للتكرار، كما رأينا في مثال VBScript أعلاه، غير أنها لا تخلو من صورة ما لحلقتي for وwhile، ولا تحوي بعض اللغات، مثل Modula 2 وOberon، إلا حلقة while فقط، بما أننا نستطيع تمثيل سلوك for منها كما رأينا قبل قليل.

ومن الحلقات التكرارية الموجودة في اللغات الأخرى:

  • do-while: هذه الحلقة هي نفسها حلقة while لكن مع وجود الاختبار في نهايتها، بحيث تُنفَّذ الحلقة مرةً واحدةً على الأقل.
  • repeat-until: شبيهة بالسابقة أيضًا لكن مع عكس المنطق.
  • GOTO وJUMP وLOOP: هذه الحلقات التكرارية موجودة في اللغات القديمة، وهي تعين علامةً في الشيفرة ثم تقفز إليها مباشرةً.

خاتمة

لقد رأينا أن حلقات for تكرر مجموعةً من الأوامر لعدد محدد وثابت من المرات، وأن while تكرر تلك الأوامر إلى أن يتحقق شرط محدد في الحلقة، ولا تنفذ متن الحلقة أصلًا إذا كان شرط إنهاء الحلقة غير متحقق من البداية، أي أعطى اختباره النتيجة false، وعلمنا أنه توجد أنواع أخرى من الحلقات التكرارية؛ لكن اللغة التي تحتوي عليها، ستحتوي على حلقتي for وwhile أو إحداهما على الأقل، وأن حلقات for في بايثون ما هي إلا حلقات foreach حقيقةً، إذ تعمل على قائمة من العناصر، كما تعلمنا الحلقات التكرارية المتشعبة، بإدخال حلقة فرعية داخل أخرى أكبر منها.

ترجمة -بتصرف- للفصل السابع: Looping - Or the art of repeating oneself من كتاب Learning To Program لصاحبه Alan Gauld.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...