مقدمة في علوم الحاسوب والبرمجة باستخدام Python › المحاضرة 3: التكرار (Iteration) 5 / 30 السابق التالي

التفريغ الكامل للمحاضرة 3: التكرار

التفريغ الكامل للمحاضرة 3: التكرار

المصدر الأصلي: التفريغ الإنجليزي الرسمي بصيغة PDF، المرتبط بـصفحة المحاضرة والفيديو الرسمي.

النسبة والترخيص: آنا بيل (Ana Bell)، مقرر 6.100L: مقدمة في علوم الحاسوب والبرمجة باستخدام Python، خريف 2022، معهد ماساتشوستس للتكنولوجيا، MIT OpenCourseWare. هذه ترجمة وتكييف عربي غير رسمي، ولا تعني تأييد MIT أو اعتماده. الأصل المشمول والترجمة تحت رخصة CC BY-NC-SA 4.0: الاستخدام غير التجاري، مع النسبة ومشاركة التكييفات بالمثل. لا تشمل الرخصة تلقائيًا المواد الخارجية أو المستثناة من ترخيص المصدر.

منهج المطابقة: ترجمة التفريغ الرسمي المستخرج من PDF، لا ترجمة الملاحظات ولا إعادة بناء الكلام من الشرائح. تطابق عناوين صفحات المصدر الصفحات الاثنتي عشرة غير الفارغة المفصولة بمحرف فصل الصفحة في الاستخراج. حُفظ ترتيب الفقرات وأدوار المتحدثين والتكرار والتصحيحات والإشارات الصوتية؛ وتبقى المعرّفات والتعبيرات البرمجية كما هي. أي توضيح تحريري موسوم صراحة بأنه ملاحظة مترجم.

صفحة المصدر 1

MITOCW | 6.100L Lecture 3 Multi.mp4

[صرير]

[حفيف]

[نقر]

آنا بيل: لنبدأ محاضرة اليوم. سنتحدث عن فكرة التكرار (Iteration). وهو طريقة أخرى لإضافة تدفّق التحكم (Control Flow) إلى برامجنا. لكن قبل ذلك، لنراجع قليلًا — عفوًا، لنراجع ما فعلناه في المحاضرة الماضية، لأننا قدّمنا فيها بالفعل آلية أخرى لتدفّق التحكم، هي التفرّع (Branching).

وتدفّق التحكم أساسًا طريقة لإخبار Python بألا يمرّ عبر الشيفرة تباعًا. كان التفرّع طريقة لنقول له: بناءً على كون شرط ما صحيحًا أو خاطئًا، قيّم مجموعة من الشيفرة أو مجموعة أخرى. وهذا ليس سيرًا خطيًا؛ كنا نوعًا ما نقفز بين أجزاء الشيفرة.

هذا ما تعلمناه في نهاية المحاضرة. وتعلمنا أيضًا الإدخال والإخراج (Input and Output)، أي طريقة لكتابة برامج تفاعلية. وتعلمنا نوع بيانات جديدًا، هو السلسلة النصية (String): تسلسل من المحارف. آمل أنكم أتيحت لكم فرصة حل بعض تمارين MITx للتدرب على اختبارات اليوم القصيرة في السلاسل النصية والتفرّع.

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

بهذه الكلمات أخبرنا Python أين يضع نقطة التفرّع. وكانت النقطتان الرأسيتان تنهيان شرط التفرّع. وكل ما أُزيح إلى الداخل بوصفه جزءًا من ذلك الشرط كان شيفرة تُنفَّذ عندما يكون الشرط صحيحًا.

سأمرّ سريعًا على كل مربع من هذه المربعات. الأول هنا أبسط طريقة لإضافة شرط إلى برنامجنا. يقول أساسًا: مرّ عبر البرنامج، وعند بلوغ شرط if افحصه؛ إن كان صحيحًا، نفّذ الشيفرة المزاحة داخل تلك الكتلة. وإن لم يكن صحيحًا، فلا تفعل شيئًا؛ تابع بقية البرنامج.

إن أردنا فعل شيء آخر عندما لا يكون الشرط صحيحًا، أضفنا عبارة else هنا. ولها أيضًا شيفرة مزاحة ضمن كتلتها، وتُنفّذ تلك الشيفرة عندما يكون الشرط خاطئًا.

كانت هذه بنية بسيطة جدًا من if أو if/else. لكننا نريد أحيانًا شيفرة تفحص شروطًا كثيرة، لا شرطًا واحدًا. هنا جاءت بنية elif. لدينا شرط if يبدأ كتلة الشيفرة. إن كان صحيحًا، ننفّذ شيفرتها كالمعتاد. وبـ«وإلا إذا»، أي elif، نستطيع إدراج شرط آخر. يقول Python: إن لم يكن الأول صحيحًا، فلأفحص هل هذا التالي صحيح. ثم ننفّذ الشيفرة التابعة لتلك الكتلة.

صفحة المصدر 2

آنا بيل: يمكن ربط ما نشاء من عبارات elif في سلسلة. وسينفّذ Python أول واحدة يجد شرطها صحيحًا في هذه السلسلة، حتى إن كان أكثر من شرط صحيحًا. ومن الممكن ألا يكون أيٌّ منها صحيحًا، فيتجاوزها جميعًا ولا يفعل شيئًا؛ لا يدخل أي كتلة منها.

إن أردتم فعل شيء عندما لا يكون أي شرط صحيحًا، يمكن وضع else في نهاية سلسلة كاملة من if وelif وelif وelif. وتُنفّذ else عندما لا يكون أي من تلك الشروط صحيحًا. آمل أن تكون هذه مجرد مراجعة.

هناك نقطة دقيقة ينبغي تذكرها: if تبدأ كتلة شيفرة. يمكن أن تكون لها else مرتبطة بها، أو سلسلة elif وelif وelif ثم else. لكن إذا جاء شرط if وبعده شرط if آخر، فقد تُنفَّذ الكتلتان؛ فعبارات if مستقلة. ليست حالة «وإلا»، بل بداية كتلة if أخرى.

وأكرر: أخبرتم Python أي شيفرة ينفّذها عندما يكون الشرط صحيحًا باستخدام الإزاحة البادئة (Indentation). وهي شيء عليكم فعله، وليست اختيارية في Python.

لنأخذ ما تعلمناه ونبرمج لعبة بسيطة جدًا. إنها نسخة بسيطة من الغابة المفقودة (Lost Woods) في Zelda، نسختي أنا. لنفترض أنها مرحلة خادعة: تدخل شخصيتكم الغابة المفقودة وتُعرض عليها هذه الشاشة.

والخدعة أنكم تسألون المستخدم إن كان يريد الاتجاه يسارًا أم يمينًا. إن قال يمينًا، تُعرض الشاشة نفسها من جديد. وهذا يمثّل ضياعه في الغابة. وما دام يقول: أريد الاستمرار يمينًا، أريد الاستمرار يمينًا، أريد الاستمرار يمينًا، فسيشاهد الشاشة نفسها مرة بعد مرة. وخدعة الخروج هي أن يقول: أريد الذهاب يسارًا. مهما ابتعد ومهما قال يمينًا مرات متتالية، يكفي أن يكتب يسارًا ليخرج.

لنحاول برمجة ذلك بما نعرفه: الشروط فقط. لدينا if/else. تقول if: إذا خرج المستخدم يمينًا، سنفعل شيئًا. وإلا سنعتبر أنه قال يسارًا، أو شيئًا آخر، أو خروجًا، ثم نخبره بأنه خرج بنجاح.

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

وماذا إن خرج يمينًا؟ نفعل شيئًا مجددًا، أي نعرض الوضع نفسه: نضبط خلفية الغابة مجددًا، ونسأله يمينًا أم يسارًا مرة أخرى. وإلا، إن قال يسارًا، يخرج.

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

صفحة المصدر 3

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

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

وهذا يمهّد لحلقات while. إليكم مثالًا آخر في سياق مشاهدة مسلسل. إن أردنا بدء مسلسل جديد على Netflix ومشاهدة جميع حلقاته دفعة واحدة، نخبر Netflix بأننا نبدأ مسلسلًا جديدًا.

وما دامت هناك حلقات أخرى نشاهدها من هذا المسلسل، نواصل مشاهدة الحلقة التالية. إن لم تبقَ حلقات، انتهينا. سيقترح Python — لا، Netflix — ثلاثة مسلسلات أخرى مشابهة. وما دامت هناك حلقات أخرى — نعم، هناك — نشغّل الحلقة التالية في الترتيب. هذه الفكرة التي نحاول الوصول إليها بحلقات while.

في Python نبرمجها هكذا: نبدأ بالكلمة المفتاحية while. ستصبح زرقاء مجددًا لأنها كلمة خاصة. ثم شرط ما صحيح؛ وهو تعبير أو شيء يُقيَّم إلى قيمة منطقية (Boolean)، كما تحدثنا في المحاضرة الماضية. ثم نقطتان رأسيتان تخبران Python بأننا نبدأ كتلة شيفرة تابعة لكون شرط while صحيحًا. وكالمعتاد، تعني الكتلة أننا نزيح هذه الأسطر. فكل ما نريد تنفيذه عندما يكون الشرط صحيحًا سيكون مزاحًا.

عندما ينتهي تنفيذ العبارات المزاحة، يعود Python تلقائيًا ويفحص الشرط من جديد: هل ما زال صحيحًا أم لا؟ يتم ذلك خلف الكواليس. عندما تبرمجون حلقة while وتكتبون كلمتها المفتاحية، يقوم Python بهذا السلوك تلقائيًا.

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

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

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

صفحة المصدر 4

آنا بيل: لنحاول برمجة الغابة المفقودة بحلقة while فقط. لدينا هنا سؤال المستخدم: أتريد الذهاب يسارًا أم يمينًا؟ نأخذ إدخاله كسلسلة نصية ونحفظه في متغير اسمه where. كل ما يكتبه يُحفَظ في هذا المتغير.

في ذاكرة حاسوبي، إن كتب المستخدم right، فإن تسلسل المحارف هذا يُحفَظ بوصفه المتغير where. ننهي هذا السطر الأول، ثم نتحقق: ما دامت قيمة where مكافئة لـright، ماذا نفعل؟ نسأله مجددًا: أين تريد الذهاب، يسارًا أم يمينًا؟

سأقول right مجددًا، فتبدو الذاكرة كما كانت تمامًا. ما دام المستخدم يواصل كتابة right، أواصل إعادة إسناد القيمة right إلى where. وفي لحظة ما قد يكتب left. عندها نفقد ارتباط المتغير where بتسلسل المحارف right، ونربطه الآن بمحارف left.

إذن بعد التكرار مرات عديدة، سيكتب المستخدم left، وتصبح where مساوية لـleft. وعند فحص الشرط مجددًا يقول Python: لا، هذا غير مكافئ. فلن أدخل هذه الكتلة؛ سأنزل إلى هنا وأطبع: «خرجت من الغابة المفقودة».

وفي الشيفرة يبدو ذلك هكذا، إنه المثال الأول هنا. أنت في الغابة المفقودة، اذهب يسارًا أم يمينًا؟ إن كتبت right، يواصل سؤالي أي اتجاه أريد. وفي لحظة ما أكتب left فأخرج. رائع، أليس كذلك؟ صنعنا مرحلتنا الخاصة في هذه المغامرة النصية.

فكروا في هذا السؤال: ماذا لو كتب المستخدم RIGHT بأحرف كبيرة؟ ماذا تتوقعون؟ هل سنسأله يمينًا أم يسارًا مجددًا، أم نخبره بأنه خرج من الغابة؟

طالب: خرج.

آنا بيل: نعم، خرج. أتريد أن تقول لماذا؟

طالب: لأنها ليست أحرفًا صغيرة.

آنا بيل: بالضبط، لأنها ليست صغيرة. تذكروا أنه عند المقارنة، أي == على السلاسل النصية، يجب أن تتطابق حالة الأحرف. المقارنة حسّاسة لحالة الأحرف (Case Sensitive). لذا RIGHT الكبيرة، أو حتى خليط مثل Right، تعطينا أيضًا أننا خرجنا. هذا يخالف حدسنا كبشر لأننا نرى كلمة right بمعنى يمين مهما كانت حالتها. والحل البديل أن نستخدم أمرًا على السلسلة يحوّلها كلها ربما إلى أحرف صغيرة، لتسهل مقارنتها أو شيء من ذلك.

استخدام آخر لحلقات while هو الأعداد. لننظر إلى هذا المثال. سأطلب عددًا صحيحًا من المستخدم، ثم أفعل شيئًا بسيطًا جدًا: أطبع x على الشاشة بعدد المرات الذي يحدده المستخدم. إن أعطاني 4، أطبع x أربع مرات.

ماذا تفعل هذه الشيفرة في الذاكرة؟ لنقل إن المستخدم أعطاني 4. ماذا يحدث خطوة خطوة؟ أولًا نرى حلقة while؛ أتحقق هل 4، القيمة الحالية لـn، أكبر من 0. نعم. أطبع x ثم أنفّذ السطر التالي من هذه الكتلة المزاحة: آخذ n وأُسند إليها قيمة n ناقص 1. أفقد 4، وآخذ 4 - 1 لتكون 3، وأنشئ كائنًا جديدًا وأربط n بالعدد 3. حسنًا؟

صفحة المصدر 5

آنا بيل: بعد ذلك ينظر Python تلقائيًا إلى الشرط مجددًا، لأن هذا جزء من حلقة while: هل قيمة n ما زالت أكبر من 0؟ نعم، 3 أكبر من 0. إذن نفقد الارتباط مجددًا — عفوًا، نطبع x أولًا، ثم نفقد الارتباط بالقيمة الحالية لـn، وهي 3، ونربطها بـ2. فنُنقِص n بمقدار 1 في كل مرور بالحَلقة.

ويفحص Python الشرط مجددًا: 2 ما زالت أكبر من 0. نطبع x أخرى وننقص n بمقدار 1، فنربطها بـ1. ويفحص الشرط: هل 1 أكبر من 0؟ نعم. فنطبع x أخرى. لقد طبعنا الآن أربعًا منها.

ثم يجعل Python قيمة n هي 0. وعند الفحص التالي يسأل: هل 0 أكبر من 0؟ هذا خاطئ. فلا ينفّذ الكتلة بعد ذلك، وينتهي البرنامج. لا توجد شيفرة أخرى يعود إليها؛ إنه نهاية البرنامج فقط.

وهكذا نكون قد طبعنا أربع x على الشاشة. والمثال موجود في ملف Python الذي أعطيتكم إياه؛ يمكنكم تشغيله للتأكد. سؤالي — وهذا خطأ شائع جدًا — ماذا يحدث إذا نسينا هذا السطر الأخير؟ يمكننا تجربته؛ سأجربه هنا.

طالب: [غير مسموع]

آنا بيل: بالضبط، سيستمر إلى الأبد. سأريكم كيف يبدو. هذه الشيفرة تعمل كالعادة: إن كتبت 3، تطبع ثلاث x. لكن إن نسيت السطر الأخير — سأحوّله إلى تعليق فقط — ثم شغّلت البرنامج، يمكنني إدخال أي عدد وسيواصل الطباعة في وحدة التحكم. يطبع أشياء كثيرة جدًا؛ يمكنكم رؤية كل ما طبعه هنا.

نعم، ليس لدينا برنامج ينتهي، لأن الشرط لا يُغيَّر أبدًا فعليًا — المتغير الذي يدخل في الشرط لا يُغيَّر داخل الحلقة. وهذه مشكلة كبيرة. عندما يحدث هذا يمكننا — وهو ما فعلته هنا — النقر على الصدفة (Shell) والضغط على Control-C، أو Command-C على Mac.

وهذا ينهي البرنامج يدويًا. أو يمكنكم النقر على علامة X الحمراء في الزاوية. هنا مثال آخر يستمر بلا نهاية. وهناك ذلك الصغير — عفوًا، المربع الأحمر في الزاوية؛ يمكن النقر عليه. أو النقر على الخطوط الثلاثة لاختيار مقاطعة النواة (Interrupt Kernel). كل ذلك يوقف البرنامج.

في هذا الصف لن نكتب فعلًا برامج تستغرق ثواني للتشغيل. إن وجدتم أنفسكم تنتظرون البرنامج أكثر من ثانية أو ثانيتين، فمن المرجح أنكم دخلتم حلقة لا نهائية (Infinite Loop). أوقفوه وحاولوا معرفة أين أخطأ البرنامج.

جربوا ذلك إن شئتم، لتعتادوا إيقاف برنامج لا نهائي؛ فمن المرجح جدًا أن تكتبوا برنامجًا لا ينتهي. while True: ما الشرط هنا؟ هو True فقط، أليس كذلك؟ فلا يوجد شرط يُفحَص. سيعمل هذا البرنامج عددًا لا نهائيًا من المرات دائمًا مهما حدث. إنه تمرين «جرّب بنفسك» (You Try It) عند السطر 44. بمجرد تشغيله سيواصل طباعة ذلك على الشاشة. تأكدوا من النقر على الصدفة لتحديد التركيز عليها، ثم اضغطوا Control-C أو X الحمراء.

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

صفحة المصدر 6

آنا بيل: بعدما رأينا حلقة فيها بعض الحساب العددي — كنا نغيّر قيمة n داخلها — أريدكم أن تعملوا على هذا البرنامج الصغير. إنه امتداد للغابة المفقودة، البرنامج نفسه الذي شغّلته قبل بضع شرائح.

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

والطريقة أن تنشئوا متغيرًا جديدًا يعمل كعدّاد (Counter)، يتتبع كم مرة — كم مرة تكررت حلقة while. سأعطيكم بعض الوقت ثم نكتبه معًا.

كالعادة، تمرين «جرّب بنفسك» هنا. يمكنكم إلغاء تعليق الشيفرة؛ في Spyder الاختصار Control-1 أو Command-1 لإلغاء التعليق جماعيًا. ثم انطلقوا من هذه الشيفرة لتحسينها.

هل لدى أحد بداية؟ كيف نفعل هذا؟ لا تحتاجون إلى إعطائي البرنامج كاملًا، يمكن أن نصل إليه تدريجيًا. ما فكرتكم الأولى؟ نعم؟ عفوًا.

طالب: [غير مسموع] متغير مثل n أو شيء يُزاد كلما مرّت حلقة while.

آنا بيل: حسنًا، ننشئ متغيرًا n في بداية برنامجنا. أي قيمة تريد له؟

طالب: 0.

آنا بيل: جيد، 0. ستتتبع 0 — أو n — كم مرة مررنا بالحَلقة. داخل البرنامج، متى نغيّر n؟ عفوًا. كل مرة نمرّ فيها، أليس كذلك؟ في كل مرور نغيّرها إلى قيمة جديدة، ربما بزيادتها 1. إذن n = n + 1.

وهذا يتتبع عدد مرات المرور. ويمكن التأكد بطباعة n: نشغّل ونقول يمينًا، مررنا مرة. يمينًا، مرتين. يمينًا، ثلاث مرات. إذن نحن نزيدها صحيحًا. ماذا يمكن أن أفعل بـn الآن بعدما أعرف أنها تُزاد بصورة صحيحة؟ نعم؟

طالب: نضع عبارة if [غير مسموع].

آنا بيل: نعم، نضع if. نتحقق هل قيمة n أكبر من 2، بحسب المواصفات هنا. ماذا نفعل إن كانت أكبر من 2؟ نطبع شيئًا، أليس كذلك؟ نطبع الوجه الحزين. ولنجرّب التشغيل. إن كتبنا يسارًا مباشرة، ما زال يعمل. يمينًا مرة، لا شيء. مرة أخرى، لا شيء. يمينًا مرة أخيرة، حزين. ومن الآن فصاعدًا سيواصل عرض الوجه الحزين. حسنًا. أسئلة عن الشيفرة؟ نعم؟

طالب: هل يمكن اختبار شيء غير مكافئ؟ هل توجد علامة تختلف عن علامتي المساواة، أو لعدم التكافؤ؟

آنا بيل: هل نستطيع فحص عدم التكافؤ؟ هنا؟

طالب: نعم.

صفحة المصدر 7

آنا بيل: هذا الفحص تحديدًا يفحص ما كتبه المستخدم. يمكن أن نضيف عبارة if تفحص n هنا وشيئًا آخر. لكن قد نحتاج عندئذ إلى أخرى — عليّ التفكير في ذلك. ربما يمكن الجمع بينهما داخل حلقة while.

طالب: إذن هناك رمز يسمح بذلك؟

آنا بيل: آه، لعدم المساواة؟ نعم، علامة عدم المساواة. شيء آخر نستطيع فعله بحلقات while هو المرور على الأعداد في تسلسل. وله نمط شائع جدًا، يقودنا بالفعل إلى نوع الحلقة التالي الذي سنراه في الشريحة التالية.

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

وفي داخل الحلقة تنفّذون ما تشاؤون من الأوامر باستخدام n. لكن عليكم تذكّر تغييرها بطريقة ما؛ وإلا سيبقى شرط while صحيحًا دائمًا. هنا أزيد n بمقدار 1 لأنها تبدأ من 0. أريدها أن تصل إلى شيء، شيء فوق 5، يجعل الشرط خاطئًا.

هذا النمط موجود في برامج كثيرة. إليكم برنامجًا يحسب المضروب (Factorial). هنا أحسب 4!. لست متحمسة بشأن العدد 4؛ هذه علامة المضروب. كيف نفعل ذلك؟ أهيّئ أشياء كثيرة هنا، لكن كلما تعاملتم مع الحلقات اعتدتم تمييز متغير الحلقة.

سيكون i متغير الحلقة. يُضبط إلى قيمة أولية خارجها، وأفحصه في الشرط، ثم أغيّره داخل جسمها بطريقة ما تأخذني إلى نهاية الشرط. أضبط i إلى 0، وأزيده 1 في كل مرور، وأحرز تقدمًا نحو جعله أكبر من x، وعندها يصير الشرط خاطئًا.

أما بقية الشيفرة، x = 4، فتحدد العدد الذي أريد مضروبه. ومتغير factorial هو حاصل الضرب المتراكم (Running Product)، الشيء الذي أواصل ضربه لأحسب المضروب. أهيّئه إلى 1، وفي الحلقة أضربه بمتغير الحلقة في كل مرة.

لن أرسم مخطط ذاكرة لهذا المثال، لكن سأستخدم Python Tutor وأمشي خطوة خطوة لأريكم بالضبط ما يفعل. x هي 4، وi هي 1 في البداية، وfactorial هي 1. x العدد الذي أريد مضروبه، وi متغير الحلقة، وfactorial حاصل الضرب المتراكم.

الخطوة التالية: i، أي 1، أصغر من أو تساوي 4، فأدخل الحلقة. يحسب Python المضروب كقيمته الحالية مضروبة في 1، قيمة i؛ فيبقى 1. ثم أزيد i من قيمته الحالية بمقدار 1. أريد التنبيه إلى أن i += 1 تكافئ i = i + 1.

وهذا صحيح مهما كان المتغير هنا. إذا كان لديكم fact *= 2 أو شيء مشابه، فالمعنى أساسًا factorial تساوي fact مضروبة في 2. هذا هو النمط: هذان متكافئان، وهذان متكافئان. إنها صيغة مختصرة في البرمجة. إذن i += 1 تعني i = i + 1.

ملاحظة مترجم: تقول المحاضِرة في شرح التهيئة أولًا 0 ثم تقول 1 عند التتبع؛ حُفظ القولان، ولم يُستبدل التفريغ بشيفرة مفترضة.

صفحة المصدر 8

آنا بيل: في هذا السطر آخذ قيمة i وأضيف إليها 1، فتصبح 2، ثم أفحص مجددًا. وتذكروا أن Python يفعل ذلك تلقائيًا؛ لأننا نستخدم while، يعود إلى الشرط ويفحصه بالقيم الجديدة للمتغيرات. 2 أصغر من أو تساوي 4، فندخل جسم الحلقة مجددًا. يصبح factorial قيمته الحالية، 1، مضروبة في i، أي 2.

تصبح i هي 2 + 1، أي 3. وأفحص هل 3 أصغر من أو تساوي 4، وما زالت كذلك. يصبح factorial قيمته الحالية، 2، مضروبة في قيمة i الحالية، 3، فيصبح 6. وتصبح i أكبر بواحد من قيمتها الحالية، أي 4. و4 أصغر من أو تساوي 4. ندخل الجسم: factorial قيمته الحالية 6 مضروبة في 4، فتكون 24.

ثم تصبح i قيمتها الحالية زائد 1، أي 5. يسأل Python: هل 5 أصغر من أو تساوي 4؟ لا. فينهي الحلقة وينتقل لطباعة هذه العبارة: مضروب 4 هو، ثم يجلب قيمة factorial. أستخدم هنا صيغة الطباعة بالسلسلة المنسّقة (f-string) التي تعلمناها في المحاضرة الماضية. أشجعكم على تتبعها بأنفسكم خطوة خطوة؛ فهذا ما يفيد فيه Python Tutor جدًا جدًا.

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

وبمثال Netflix، تكافئ حلقة for أن يوقفك Netflix بعد أربع حلقات إن لم تتفاعل معه، لتوفير عرض النطاق. إذن يعرف أنه سيمرّ بتسلسل من أربع حلقات إذا لم تتفاعل.

إذا انتهيت من الحلقات الأربع المسموح بمشاهدتها دون تفاعل، يوقفك ويقول: «هل ما زلت تشاهد؟». لكن إن بقيت حلقات، وإن عرض اثنتين فقط من الأربع، يواصل عرض المزيد حتى يعرض الأربع.

هذا البرنامج الذي رأيناه بـwhile قبل بضع شرائح. كنا نهيّئ متغيرًا، ونفحصه بشرط، ثم نزيده أو نفعل شيئًا يحرز تقدمًا نحو جعل الشرط خاطئًا.

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

صياغة حلقة for تختلف قليلًا عن while. تبدأ بالكلمة المفتاحية for. وهذا متغير تختارون له أي اسم. وتخبر الكلمة المفتاحية in Python بأن هذا المتغير سيأخذ القيم في هذا التسلسل. ولدينا مجددًا نقطتان رأسيتان لبدء الإزاحة. تُنفَّذ الأسطر المزاحة بعدد القيم في التسلسل.

في المرور الأول يجعل Python اسم المتغير يأخذ أول قيمة في التسلسل، ثم ينفّذ الشيفرة. وبعد انتهائها يعود تلقائيًا ويضبط المتغير إلى القيمة التالية وينفّذ الأسطر نفسها. وبعد ذلك يعطيه القيمة التالية وينفّذها. وهكذا تُنفَّذ هذه الأسطر بعدد القيم التي في التسلسل.

صفحة المصدر 9

آنا بيل: عمليًا، هنا متغير، n في هذه الحالة، في تسلسل من القيم. هنا أقول range(5)، ونطبع قيمة n. سأقدّم range الآن؛ إنها طريقة للحصول على تسلسل عددي يتبع نمطًا ما.

إذا قلنا range وعددًا فقط، فالنمط أن نبدأ من 0 ونصل إلى ذلك العدد دون تضمينه. إذن range(5) تعني القيم 0 و1 و2 و3 و4. وrange(10) تعني 0 و1 و2 و3 و4 و5 و6 و7 و8 و9. نصل إلى القيمة المحددة دون تضمينها، بداية من 0. وفي كل مرور يغيّر Python قيمة n تلقائيًا لتأخذ كل قيمة من هذه القيم.

هذان السطران، for n in range(5) ثم print(n)، يعملان خلف الكواليس — وPython يفعل هذا نيابة عنكم — هكذا: أول مرة يواجه الحلقة يجعل n تساوي 0، أول قيمة في تسلسلي، ثم يطبع قيمتها، 0.

في المرور التالي يقول: نفّذت ما طلبتموه داخل الحلقة، أي طباعة n. سأجعلها تأخذ القيمة التالية في التسلسل. يفقد الارتباط بـ0 ويجعلها 1. حسنًا، جعلتها 1؛ ماذا تريدون الآن؟ أنفّذ المزاح، print(n)، فأطبع 1. لقد طبعت 0 ثم 1.

انتهيت من الشيفرة الداخلية. تأخذ n الآن القيمة التالية: تفقد ارتباطها بـ1 وتحصل على 2، وهكذا وهكذا. وفي النهاية يكون البرنامج قد طبع 0 و1 و2 و3 و4، كل قيمة في النطاق.

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

يمكن إعطاؤها بداية ونهاية وخطوة: start وstop وstep. فيولّد Python تلقائيًا تسلسلًا يطابق النمط. ينبغي أن يبدو مألوفًا لأننا رأينا شيئًا مثله مع السلاسل النصية، لكن استخدمنا أقواسًا مربعة بدل العادية، ونقطتين رأسيتين بدل الفواصل. الفكرة نفسها بالضبط.

هنا نولّد أعدادًا، أعدادًا صحيحة فعلية نريد أن يأخذها متغير الحلقة. إن حذفنا البداية والخطوة، تكون البداية افتراضيًا 0 والخطوة 1. وإن حذفنا الخطوة، تكون 1. هنا في range(4) يأخذ i قيم 0 و1 و2 و3. وفي range(3, 5) يأخذ 3 و4. نصل إلى 5 لكن لا نضمّنها.

فكروا في هذه الأسئلة الثلاثة. ما نطاق القيم في الأول، وماذا سنطبع؟ في 1, 4, 1، ما قيم i؟ 1 و2 و3؟

طالب: [غير مسموع]

آنا بيل: نعم، ونتوقف. نصل إلى النهاية دون تضمينها، وهي 4. وماذا نطبع؟

طالب: [غير مسموع]

آنا بيل: نعم، 1 و2 و3. وماذا عن التالي، j؟ ما قيمه؟

طالب: [غير مسموع]

صفحة المصدر 10

آنا بيل: 1 و3 فقط، نعم، لأننا نأخذ قيمة ونترك التالية. وماذا نطبع هنا؟

طالب: [غير مسموع]

آنا بيل: بالضبط، نجري عملية بكل قيمة من قيم j هذه. فنطبع 2 ثم 6. وماذا عن الأخير؟

طالب: [غير مسموع]

آنا بيل: نتحرك إلى الخلف، أليس كذلك؟ الخطوة سالب 1. فما البداية؟

طالب: [غير مسموع]

آنا بيل: 4. ثم؟

طالب: 3.

آنا بيل: 3 و2 و1، فقط. ننزل إلى النهاية دون تضمينها، فلا نضمّن 0. وماذا نطبع؟

طالب: [غير مسموع]

آنا بيل: نعم، أربع علامات دولار أول مرة، ثم ثلاث ثم اثنتين ثم واحدة، بالضبط. يستطيع الجسم إجراء عمليات والتعامل مع متغير الحلقة. يتغير المتغير في كل مرور، ويمكنكم استغلال ذلك.

إليكم مثالًا مفيدًا آخر. يمكننا استخدام حلقات for لتتبع عدد مرات المرور. وفي هذه الحالة نكتب برنامجًا يجمع كل القيم من 0 إلى ما في هذه الأقواس دون تضمينه.

كيف نفعل ذلك؟ لنرسم مخطط الذاكرة. السطر الأول mysum = 0، فيكون 0 في الذاكرة مرتبطًا باسم mysum. وتولّد الحلقة قيمًا من 0 إلى 9 شاملًا.

تكون i في المرور الأول 0. ننفّذ العمليات أو الشيفرة المطلوبة عندما تكون 0. تصبح mysum قيمتها الحالية زائد قيمة i، أي 0، فتبقى 0. انتهى Python من الشيفرة الداخلية، فيغيّر i إلى القيمة التالية، 1.

نعيد ما في الحلقة وi تساوي 1. نأخذ mysum ونضيف 1 فتصبح 1. ثم ننتهي، فيجعل Python قيمة i التالية 2. ونحسب مجددًا mysum: قيمتها الآن 1 زائد قيمة i الآن 2، فتصبح 3.

تُزاد i تلقائيًا إلى 3، القيمة التالية، فتصبح mysum هي 6. ثم تتغير i إلى 4 وهكذا وهكذا حتى تصل إلى 8 قرب النهاية. عند 8 تكون mysum هي 36، أليس كذلك؟ 0 زائد 0 زائد 1 زائد 2 إلى 8 يساوي 36. وعندما تصبح i هي 9، يأخذ Python قيمة mysum، 36، ويضيف 9 فتصبح 45.

وهذه نهاية البرنامج. لا شيء آخر سوى طباعة mysum. في نهاية الحلقة مررنا عشر مرات، نجمع من 0 إلى 9، وسنطبع 45. نعم؟

صفحة المصدر 11

طالب: جرّبت تشغيله. طبع 45 في الأعلى ثم تابع 0 و1 و2 و3 و4 [غير مسموع].

آنا بيل: ربما لديك عبارة طباعة أخرى، أو جزء من برنامج آخر يجري تشغيله قبل هذا ولم تحوّله إلى تعليق. نعم، السؤال التالي؟

طالب: [غير مسموع] صيغة += [غير مسموع]؟

آنا بيل: ما معنى +=؟ تعني مثل mysum = mysum + i. صيغة مختصرة لأن أسماء متغيراتكم قد تكون طويلة جدًا، ومن المزعج إعادة كتابتها؛ لهذا توجد عمومًا. لكن معناها أخذ قيمة mysum وإضافة i إليها، وحفظها مجددًا في المتغير mysum.

جرّبوا هذه الشيفرة سريعًا. موجودة في ملف Python لتبدأوا بها. إنها قريبة جدًا من العمل، لكن فيها مشكلة واحدة. لدينا حلقة for تبدأ عند start وتنتهي عند end، ونحتفظ بمجموع متراكم (Running Sum)، ثم نطبعه في النهاية.

إنها شبيهة جدًا بما رأيناه، لكن أريد جمع البداية والنهاية أيضًا. إذا كانت start هي 3 وend هي 5، أريد جمع 3 + 4 + 5. وهذه الشيفرة لا تفعل ذلك تمامًا؛ أود أن تصلحوها أو تخبروني كيف أصلحها. إنها قرب السطر 140.

أول شيء تفعلونه تشغيلها لتروا الإجابة الفعلية. شغّلتها الآن وأعطتني 7. حين تواجهون مخرجات غير المتوقعة، يمكنكم طبعًا استخدام Python Tutor، لكن من الأشياء الأولى أيضًا وضع عبارات طباعة في أماكن مختلفة. ومن الأماكن المفيدة داخل حلقة for.

يمكن طباعة i هنا، وهو اختيار معقول. ربما نرى بالضبط قيم i التي نجمعها، لأننا نعرف أن الجمع يعمل؛ كتبنا البرنامج في السطر — أو الشريحة — السابقة. حصلنا على 3 و4 و7، وهذا مربك قليلًا. لنحسّن الطباعة: i equals ثم فاصلة ثم قيمة i الفعلية. ماذا تلاحظون؟ i هي 3 ثم 4، وبعدها يطبع المجموع 7. ما المشكلة؟ نعم؟

طالب: [غير مسموع]

آنا بيل: نعم، لا نجمع 5. نجمع 0 أولًا ثم 3 ثم 4، ولم نضف 5 قط. كيف نصلح ذلك؟ نعم؟

طالب: end + 1.

آنا بيل: نعم، يمكن استخدام n + 1، بالضبط. تذكروا أن range تأخذ — آه، زائد 1 — تأخذ هذه القيم أعدادًا تعمل بها. البداية جيدة والنهاية جيدة، لكننا نصل إلى end دون تضمينها. إذا وصلنا إلى end + 1 دون تضمينها، يصبح الأمر أفضل بكثير. نشغّل الآن فنحصل على i هي 3 و4 و5، والمجموع 12. ممتاز.

عبارات الطباعة مفيدة جدًا عند تنقيح الشيفرة (Debugging). أسئلة؟ أم هذا مفهوم؟ آخر شريحة قبل الملخص ستريكم فقط شيفرة المضروب التي رأيناها بحلقة while قبل بضع شرائح. تبدو مطوّلة جدًا، ونحتاج التفكير قليلًا قبل إدراك ما تفعله. كانت تحسب المضروب، وطبعًا أسماء المتغيرات الجيدة ساعدتنا في معرفته.

صفحة المصدر 12

آنا بيل: الشيفرة نفسها بحلقة for تبدو هكذا — باستخدام for تبدو هكذا. ما زلنا نهيّئ x إلى 4، ونهيّئ المضروب، حاصل الضرب المتراكم، إلى 1. لكننا نختزل الأسطر الأربعة التي تكوّن نمط تغيير الأعداد بـwhile إلى سطرين.

هذا السطر i = 1، وحلقة while مع شرطها، وزيادة i، تتحول كلها إلى حلقة for فقط. تتولى for ذلك كله: التهيئة والزيادة والوصول إلى القيمة الأخيرة، x + 1، دون تضمينها. إذن سنضرب المضروب بـ1 ثم 2 ثم 3 ثم 4، إلى x شاملًا.

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

ملخص سريع: رأينا اليوم بعض آليات التكرار. أعرف أن الصياغات كثيرة، لكن تمارين الإصبع (Finger Exercises) لليوم ستساعد بالتأكيد. وفي MITx مساعدة إضافية وتمارين إضافية. من المهم جدًا حلّها لتكوين النموذج الذهني لكيف تعمل الحلقات بالضبط، وكيف تُسنِد المتغيرات وتجري الفحوص خلف الكواليس. وستساعدكم أيضًا على الإسراع في كتابة الشيفرة وحل الاختبارات القصيرة. هذا كل شيء لليوم.