شرح الموضوع
تعرَّف آليَّة تجزئة المشكلة إلى أجزاء صغيرة، وكتابتها على أساس أنها جمل برمجية أو وحدات، وتعلَّم كيفية توثيق البرامج عن طريق التصميم والتطوير. كذلك تعرَّف الدوال البرمجية، وتعلَّم كيف يُمكن استخدام الدوال الجاهزة واستيرادها، وكيف يُمكن تعريف دوال جديدة واستخدامها في البرامج.
نتاجـات التعلُّم :
- أعرِّف المقصود بالوحدات البرمجية (Modules).
- أُجزِّئ المشكلة إلى أجزاء صغيرة، وأعمل على تصميم كل جزء منها وبرمجته.
- أُحدِّد الطريقة الفضلى لتمثيل أجزاء المشكلة في صورة جمل برمجية، أو روتين فرعي، أو وحدات، أو كائنات.
- أستعمل لغة البرمجة بايثون (Python) لاستدعاء روتين فرعي جاهز بناءً على وقوع حدث مُحدَّد.
لماذا نستعمل دوال جاهزة؟
تعرَّفت سابقاً أنَّ الدالة البرمجية (Function) مقطع برمجي له اسم يؤدّي وظيفة ما، ويُمكن استدعاؤه باستخدام اسمه؛ فكيف تُستخدم الدوال البرمجية في لغة البرمجة بايثون (Python)؟
أُجرِّب وأستكشف:
أكتب قائمة تحوي هذه الدوال، ثمَّ أُصنِّفها بناءً على التشابه في كيفية استخدامها.
بعد ذلك أبحث في أهم الجوانب التي تختلف فيها الدوال بعضها عن بعض.
الدوال البرمجية
- إذا افترضت أنَّ الأمر ()print لم يكن موجوداً في لغة البرمجة بايثون (Python)، وأنَّه يتعيَّن عليَّ دائماً طباعة أيَّ شيء على الشاشة بكتابة كامل الكود البرمجي الذي يقوم بالتعامل مع نظام التشغيل وأنواع البيانات المختلفة من أجل إظهارها على الشاشة بالشكل الصحيح،
- فإنَّ ذلك سيكون مُرهقاً لي بلا شك، ويجعل قراءة البرنامج عسيرة.
الدوال البرمجية الجاهزة
- ونظراً إلى كثرة هذه الدوال، فإنَّه يصعب على المبرمجين تذكرها جميعاً، أو حفظ كيفية استخدامها.
- ولهذا يُعَدُّ الموقع الإلكتروني للغة البرمجة بايثون (Python) الملاذ والصديق لكل مُبرمِج مُحترِف
الوحدات البرمجية (Modules)
- فمثلاً، تحتوي وحدة (time) على دوال لها علاقة بالوقت والتاريخ، وتحتوي وحدة (math) على دوال لها علاقة بالعمليات الرياضية، وهكذا.
| الوحدة module | مثال على الدالة البرمجية التي تنتمي إلى الوحدة |
|---|---|
| math | math.factorial(10) (مضروب الرقم 10) |
| math | math.pow(3, 5) (الرقم 3 مرفوع إلى الأس 5) • math.sqrt(2) (الجذر التربيعي للرقم 2) • math.cos(3.14159265) (جيب تمام الزاوية 3.14159265) • math.sin(3.14159265) (جيب الزاوية 3.14159265) |
| random | random.randint(1, 10) (رقم صحيح عشوائي يقع بين 1 و 10) • random.random() (رقم عشري عشوائي يقع بين 0 و 1) • random.choice([1, 5, 7]) (عنصر عشوائي من القائمة [1, 5, 7]) |
| statistics | statistics.mean([1, 1, 10, 8, 8, 1, 2]) (المُتوسِّط الحسابي للقائمة) • statistics.median([1, 1, 10, 8, 8, 1, 2]) (الوسيط الحسابي للقائمة) • statistics.mode([1, 1, 10, 8, 8, 1, 2]) (العنصر الأكثر تكراراً في القائمة) |
هل تستقبل الدالة مدخلات وهل تُرجع شيئاً؟
- أُلاحظ أنَّ بعض الدوال تستقبل مدخلات، وأنَّ بعضها الآخر لا يستقبل أيَّ مدخلات.
- فمثلاً، الدالة (...)sqrt تستقبل رقماً واحداً، والدالة (...)pow تستقبل رقمين اثنين، والدالة (...)median تستقبل قائمة، في حين أنَّ الدالة ()random لا تستقبل أيَّ مدخل.
- أُلاحظ أيضاً أنَّ جميع هذه الدوال تعمل على إرجاع النتائج، بالرغم من أنَّ ذلك ليس شرطاً في الدوال.
- فمثلاً، تطبع الدالة (...)print على الشاشة، ولا تُرجع أيَّ شيء؛ ما يُفسِّر سبب استدعائها من دون تخزين نتيجتها.
- أمَّا الدالة (...)input فتعمل على إرجاع ما أدخله المُستخدم.
نحتاج إلى تخزين النتيجة في مُتغيّر: name = input()
لا نحتاج إلى تخزين أيَّ نتيجة: print(name)
- والشيء نفسه ينطبق على الدالة (...)sorted والدالة ()sort؛ إذ تعمل الأولى على إرجاع إحدى القوائم المُرتَّبة، في حين تعمل الثانية على ترتيب القائمة نفسها، ولا تُرجع أيَّ نتيجة
| نحتاج إلى طباعة القائمة الراجعة من الدالة حتى تظهر النتيجة | print(sorted(mylist)) |
| يُمكننا أيضاً تخزين النتيجة من الدالة لاستخدامها لاحقاً | result = sorted(mylist) |
| هذا الاستدعاء لا أثر له؛ لأنَّ القائمة المُرتَّبة من الدالة لم يتمَّ حفظها أو طباعتها | sorted(mylist) |
| لا داعي لتخزين أيَّ شيء أو طباعته؛ لأنَّه تَمَّ الانتهاء من ترتيب القائمة | mylist.sort() |
| سنجد القائمة مُرتَّبة عند طباعتها | print(mylist) |
استيراد الوحدات (Import)
نستورد الوحدة قبل استدعاء دوالها
``python import math print(math.sqrt(2)) ``
- يعمل المُبرمجون عادةً على وضع جملة الاستيراد في رأس البرنامج حتى يتضح - ابتداءً - لقارئ البرنامج أيَّ الوحدات يراد استخدامها.
- أُلاحظ أنَّه عند استدعاء دالة من وحدة ما، فإنَّ اسم هذه الوحدة يوضع قبل اسم الدالة، مثل (2)math.sqrt.
- ويُمكن التخلُّص من ذلك باستيراد الدالة نفسها، مثل الدالة sqrt التي تُستورَد من وحدة (math) كما يأتي:
``python from math import sqrt print(sqrt(2)) ``
- بعد ذلك تُستخدم الدالة sqrt مباشرةً من دون حاجة إلى اسم الوحدة

تعريف دوال برمجية جديدة
نكتب دالتنا بأنفسنا
قد يحتاج المُستخدم إلى تعريف دوال برمجية خاصة به؛ إمَّا لتنظيم البرامج، وإمَّا لاحتواء هذه الدوال على وظائف تلزمه في البرنامج مُتكرِّرة، وإمَّا لاستدعاء آخرين هذه الدوال للإفادة منها.
- يُبيّن المثال الوارد في الشكل (6-1) كيفية تعريف دالة بسيطة تُسمَّى random_greeting، وتُبادر إلى إلقاء تحية عشوائية كلَّما تَمَّ استدعاؤها:
- يجب تعريف الدالة بكتابة كلمة (def) متبوعةً باسم الدالة، ثمَّ بأقواس ونقطتين رأسيتين.
- ويُمكن للمُفسِّر التمييز بين ما بداخل الدالة وخارجها عن طريق مسافة البدء التي تكون في أوَّل السطر.
- بعد تعريف الدالة، يُمكن استدعاؤها في أيَّ مكان داخل الملف نفسه كما يأتي:
``python random_greeting() ``
- يُبيّن الشكل (6-2) برنامجاً مُتكاملاً يعمل على تعريف دالتين واستدعائهما.
- ولما كانت الدوال لا تُنفَّذ إلا بعد استدعائها
- فإنَّ البرنامج سيبدأ العمل من السطر الذي يُسأل فيه عن الاسم، بالرغم من أنَّ هذا السطر يأتي بعد تعريف الدوال.

أُجرِّب بنفسي: أقوم بطباعة البرنامج الموضح في الشكل 6-2 ثم أقوم بتشغيله عدة مرات وملاحظة اختلاف المخرجات في كل مرة، ثم أقوم بتجربة التالي ومناقشته مع زملائي:
نقل كامل تعريف دالة الوداع randomgreeting لتصبح randomgoodbye قبل دالة الترحيب هل أثر ذلك على صحة عمل البرنامج ولماذا؟
نقل كامل الدالتين ليصبحا في نهاية البرنامج بدلاً من بدايته (أي بعد الجمل التي تسأل عن الاسم والعمر وتقوم باستدعاء الدوال)، هل أثر ذلك على صحة عمل البرنامج ولماذا؟
- يتطلَّب استخدام بعض الدوال توافر مجموعة من البيانات، تعمل الدوال على تحليلها ومعالجتها.
- فمثلاً، لا يُمكن استخدام دالة الجذر التربيعي قبل استقبال رقم يُمكن من حساب جذره التربيعي.


إرجاع النتائج
الدالة تنتهي وتعيد قيمة
إنَّ الدوال التي عَمِلنا على تعريفها في الأمثلة السابقة لا تُرجع أيَّ نتيجة. والمثال الوارد في الشكل (6-4) يُبيّن كيف يُمكن إرجاع نتيجة من دالة، وكيف تُستقبل النتيجة عند استدعاء الدالة.
78.53981633974483 78.53981633974483
- تعمل الدالَّة في المثال السابق على استقبال رقم يُمثِّل نصف قُطر دائرة، وإرجاع مساحة هذه الدائرة، علمًا بأنَّ عملية إرجاع النتيجة تمَّت باستخدام (return)؛ إذ أُنهي تنفيذ الدالَّة عند تلك النقطة، وتمَّ إرجاع النتيجة.
- ولهذا لن يُنفَّذ أيُّ أمر يأتي بعد هذه الجملة في الدالَّة (في حال وجود أيُّ سطر).
- يُمكن استخدام أكثر من جملة (return) في الدالَّة نفسها، بحيث تُنفَّذ كل جملة في حالة مختلفة عن الأخرى، أنظر الشكل (6-5) الذي يُبيِّن مثالاً على دالَّة تعمل على إرجاع حرف مختلف يُمثِّل مُعدَّل الطالب بحسب علامته:


أُدخل البرنامج الوارد في الشكل (6-4)، ثمَّ أُعدِّل الأوامر البرمجية، بحيث تطلب من المُستخدم إدخال نصف القُطر، ثمَّ استدعاء الدالَّة وطباعة النتيجة.
أعمل - بالتعاون مع أفراد مجموعتي - على تعديل البرنامج السابق وتطويره، بحيث يتمكَّن من جمع العلامات وإيجاد المُعدَّل النهائي، ثمَّ أستخدم دالَّة ()letter_grade في تصنيف المُعدَّل.
أُحلِّل - بالتعاون مع أفراد مجموعتي - الأوامر البرمجية الواردة في الشكل (6-6)؛ لتحديد الأخطاء المُحتملة، ثمَّ اقتراح الحلول المناسبة، وتجربتها في بيئة بايثون (Python).
مدى المُتغيِّرات (Scope)
أين يعيش المتغيّر؟
- لكل مُتغيِّر مدى (scope) يُمكن استخدامه في هذا المُتغيِّر.
- فمثلاً، مدى المُتغيِّر (AVOGADRO) في البرنامج الوارد في الشكل (6-7) هو جميع الأسطر التي تلي أوَّل سطر استُخدِم فيه المُتغيِّر؛ لذا يُمكن استخدام هذا المُتغيِّر داخل الدوالَّ التي تأتي بعده، وكذلك استخدامه خارج هذه الدوالَّ.

- يُذكر أنَّ المُتغيِّر (atoms) والمُتغيِّر (moles) المُعرَّفين داخل الدالَّة الأولى هما مُتغيِّران مختلفان عن المُتغيِّر (atoms) والمُتغيِّر (moles) المُعرَّفين داخل الدالَّة الثانية؛
- فكلٌّ من هذه المُتغيِّرات مُعرَّف محليًّا في مدى ما، ولا علاقة له بالمُتغيِّرات المُعرَّفة في المدى الآخر.

- تعريف المُتغيِّر (x) في المدى العام بقيمة (7).
- استدعاء الدالَّة fun1، ثمَّ إرسال القيمة (7) إلى مُتغيِّر محلي داخل الدالَّة، يُسمَّى (x).
- تغيير قيمة المُتغيِّر المحلي (x) إلى (3).
- انتهاء الدالَّة، والتخلُّص من المُتغيِّر المحلي (x) الذي يحمل القيمة (3).
- العودة إلى المدى العام، حيث يوجد مُتغيِّر يُسمَّى (x)، وتبلغ قيمته (7).
- استدعاء الدالَّة fun2، ثمَّ إرسال القيمة (7) إلى مُتغيِّر محلي داخل الدالَّة، يُسمَّى (x).
- تغيير قيمة المُتغيِّر المحلي (x) إلى (5).
- انتهاء الدالَّة، والتخلُّص من المُتغيِّر المحلي (x) الذي يحمل القيمة (5).
- إذن، ناتج تنفيذ البرنامج هو (7).
أُعدِّل على البرنامج الوارد في الشكل (6-7) بتنفيذ الاستدعاء (print(atoms خارج الدالَّتين. ما تأثير ذلك في تنفيذ البرنامج؟
أُعدِّل على البرنامج نفسه بتنفيذ الاستدعاء (print(num_atoms داخل إحدى الدالَّتين. ما تأثير ذلك في تنفيذ البرنامج؟
ما نتيجة البرنامج الوارد فيما يلي؟ كيف أُفسِّر هذه النتيجة؟
تصميم البرامج باستخدام الدوالَّ
الدالة تمنع تكرار الكود
بعد تعرُّف العديد من التفاصيل المُتعلِّقة بتعريف الدوال البرمجية واستخداماتها، لا بُدَّ من استعمالها لتنظيم عملية تصميم البرامج وكتابتها؛ كي تصبح أسهل للقراءة والتصحيح والتعديل وإعادة الاستخدام.
- يطلب البرنامج الآتي إلى طالب في مدرسة إدخال بريده الإلكتروني والبريد الإلكتروني الخاص بأحد والديه، ثمَّ يطلب إليه إعادة إدخال البريد الإلكتروني إن لم يكن البريد المُدخل يتبع نمطًا صحيحًا.

- يُلاحظ عند النظر مباشرة إلى هذا البرنامج وجود تكرار يُمكن التخلُّص منه عن طريق تعريف دالَّة للتحقُّق من صحة البريد الإلكتروني، ثمَّ استدعاء هذه الدالَّة داخل حلقة التكرار كما في الشكل (6-11).

- ونظرًا إلى وجود دالَّة تتحقَّق من نمط البريد الإلكتروني؛ فإنَّه يُمكن الآن استخدامها في أي مكان للتأكُّد من البريد الإلكتروني من دون حاجة إلى إعادة كتابة ذلك الجزء مرَّة أخرى.
أُدخل البرنامج المُبيَّن في الشكل (6-11) في بيئة بايثون (Python)، ثمَّ أستخدمه في التحقُّق من صحة البريد الإلكتروني لثلاثة من زملائي.
المواطنة الرقمية:
نقسّم لعبة النجوم والأقمار إلى دوال
1. بداية اللعبة
- عرض رسالة الترحيب.
- عرض تعليمات اللعبة.
- الخروج من اللعبة.
- توليد الرقم المضمر.
- بدء المحاولات (الحد الأقصى هو (10) محاولات):
- عرض رسالة التهنئة.
- الاستمرار في المحاولات.
- عرض رسالة النهاية.
4. نهاية اللعبة
- والآن سأبدأ تعديل اللعبة وتحسينه بناءً على ما تعلمته في هذه الوحدة من تقنيات برمجية، وأستخدم الدوال البرمجية في تسهيل قراءة البرنامج على النحو الآتي:
- طباعة الرسالة الترحيبية للعبة، وكذلك خيارات القائمة الرئيسة (def welcome()).
- طباعة تعليمات اللعبة (def about())؛ ذلك أن الأجزاء طويلة نسبياً، ووجودها داخل البرنامج قد يجعل قراءته مهمة عسيرة، ومن ثم يمكن عزلها في دوال منفصلة.
- التأكد أن الرقم الذي أدخله اللاعب هو من الأرقام المسموح بها (يتألف من أربعة أعداد).
- عد النجوم في الرقم المدخل.
- عد الأقمار في الرقم المدخل.
- أتجنب كتابة هذه الأجزاء في أكثر من مكان في البرنامج، وذلك بتعريف دوال خاصة بها، واستدعائها عند الحاجة كما يأتي:
| def is_valid(guess): | استقبال سلسلة، والتأكد أنها تتألف من (4) أعداد، وتعيد (True) أو (False). |
| def count_stars(guess, secret): | استقبال الرقم المضمر، وتوقع اللاعب، وإعادة رقم يمثل عدد النجوم بناءً على ذلك. |
| def count_moons(guess, secret): | استقبال الرقم المضمر، وتوقع اللاعب، وإعادة رقم يمثل عدد الأقمار بناءً على ذلك. |
- أقسم البرنامج إلى دوال، مثل:
welcome(), about(), isvalid(), countmoons(), count_stars().
والآن سأعمل - ضمن المجموعة - على كتابة هذه الدوال (يمكن نسخ المقطع البرمجي الذي كتبناه سابقاً) واستخدامها، وأحاول الجمع بينها، بحيث يحاكي منطق البرنامج مخطط سير العمليات الذي اتفق عليه أفراد المجموعة.
أُقَيِّمُ تعلمي
اختبر فهمك للدوال
أُوضِّح المقصود بـ: مُعاملات الدالَّة.
أُوضِّح المقصود بـ: استدعاء الدالَّة.
أُوضِّح المقصود بـ: مدى المُتغيِّر.
أُوضِّح المقصود بـ: استيراد الوحدة.
السؤال الثاني: ما الفرق بين المدى العام والمدى المحلي للمُتغيِّرات؟
السؤال الثالث: ما الفرق بين الدالتين sort و reverse والدالتين sorted و reversed؟
السؤال الأوَّل: أكتب دالَّة تستقبل (3) أرقام، وتُرجع الرقم الوسيط، ثمَّ أكتب برنامجًا بسيطًا يستدعي هذه الدالَّة.
السؤال الثاني: أكتب دالَّة تستقبل رقمين؛ أحدهما يُمثِّل الطول، والآخر يُمثِّل العرض، ثمَّ أطبع مستطيلًا مرسومًا من علامة '#'، ثمَّ أكتب برنامجًا بسيطًا يستدعي هذه الدالَّة.
السؤال الثالث: أكتب دالَّة تستقبل قائمتين، وتتأكد أنَّ كل عنصر في القائمة الأولى يساوي العنصر المُقابل له في القائمة الثانية (يجب أن تُرجع الدالَّة (True) أو (False))، ثمَّ أكتب برنامجًا بسيطًا يختبر هذه الدالَّة.
السؤال الرابع: أكتب برنامجًا يحتوي على دالَّة من تصميمي، ويطبع جدول الضرب لجميع الأرقام من (0) إلى (9)، بدءًا بطباعة الجدول صفر كاملًا، ثمَّ طباعة الجدول الرقم (1) كاملًا، وهكذا. (تلميح: يُمكن استخدام دالتين؛ واحدة لطباعة النصف العلوي، وأخرى لطباعة النصف السفلي).
السؤال العاشر: أكتب برنامجًا بلغة البرمجة بايثون (Python)، تُستخدم فيه الدوال (Functions) لتنفيذ مجموعة من العمليات الحسابية (الجمع، الطرح، الضرب، القسمة) بناءً على مدخلات المُستخدم.
مُلخَّص الوحدة
سلاسل الحروف (Strings)
السلسلة تشبه القائمة لكنها لا تتغيّر
- يوجد تشابه بين سلاسل الحروف والقوائم، يتمثَّل في أنَّ كلتيهما تتألَّف من عناصر متتابعة.
- غير أنَّ جميع العناصر في السلسلة هي حروف (أي رموز تُمثِّل حروفًا أبجدية، أو أرقامًا، أو علامات ترقيم، أو غير ذلك)؛ لذا يُمكنني تطبيق معظم ما تعلَّمتُه عن القوائم على سلاسل الحروف.
- يُمكن الوصول إلى أيِّ حرف في السلسلة باستخدام الموقع (index)،
- ويُمكن أيضًا استخدام الدوال، مثل:
()sortedو()maxو()lenو()count. - وكذلك استخدام العوامل، مثل:
+و*كما تعلَّمتُ سابقًا، فضلًا عن إمكانية اختبار وجود عنصر في السلسلة باستخدام العامل (in) والعامل (not in)، - إذ تتحقَّق العوامل في القوائم من وجود عنصر ما في إحدى القوائم، في حين تتحقَّق العوامل في السلاسل من وجود سلسلة أخرى داخل السلسلة نفسها.
``python url = input("ما موقعك الإلكتروني؟") if '.gov.jo' in url: print("يبدو أنك أدخلت موقعاً حكومياً أردنياً") else: print("شكراً جزيلاً") ``
أيُّ هذه الجمل لم أتمكَّن من تنفيذها؟
ما العامل المشترك بين الجمل التي لم أتمكَّن من تنفيذها؟
الدوال الجاهزة بسلاسل الحروف
- توجد دوال جاهزة تختصُّ بسلاسل الحروف، أنظر الجدول الآتي الذي يعرض أمثلة على ذلك
| الشرح | المثال |
|---|---|
| تعمل ()upper على إنشاء نسخة من السلسلة، تحوي الحروف نفسها، ولكن بعد تحويل الأحرف الصغيرة إلى أحرف كبيرة. | >>> text = 'Hello there' • >>> text.upper() • 'HELLO THERE' |
| تعمل ()lower على إنشاء نسخة من السلسلة، تحوي الحروف نفسها، ولكن بعد تحويل الأحرف الكبيرة إلى أحرف صغيرة. | >>> text.lower() • 'hello there' |
| تعمل (replace(a, b على إنشاء نسخة من السلسلة بعد استبدال كل ظهور لـ a بـ b. | >>> text.replace('Hello', 'Hi') • 'Hi there' |
| تعمل ()isalpha على التأكُّد أنَّ جميع حروف السلسلة هي حروف أبجدية | >>> text.isalpha() • False |
- تتمثَّل أهمُّ الفروق بين السلاسل والقوائم في أنَّ السلاسل في لغة البرمجة بايثون (Python) غير قابلة للتغيير (Immutable)، وأنَّ القوائم قابلة للتغيير (Mutable).
- ولهذا لا يُمكن -مثلًا- تغيير أحد الحروف في السلسلة، في حين يُمكن تغيير عنصر ما في القائمة، أنظر الشكل (7-1).

``python Traceback (most recent call last): File "<pyshell#1>", line 1, in <module> name[0] = 'D' TypeError: 'str' object does not support item assignment ``
- أُلاحظ أنَّ جميع العمليات في الشكل السابق لا تُعنى بتعديل السلسلة، وإنَّما تُعنى بإنشاء نسخة جديدة مُعدَّلة منها.
``python >>> text = 'Hello there' >>> text.replace('Hello', 'Hi') 'Hi there' >>> text.upper() 'HELLO THERE' >>> print(text) Hello there ``
- ونظرًا إلى عدم حفظ هذه النسخ أو طباعتها؛ فإنَّ السلسلة لم تتأثَّر بهذه العمليات.
- وبالرغم من ذلك، يُمكن حفظ النسخ المُعدَّلة للسلسلة كما في الجمل الآتية:
``python >>> text = 'Hello there' >>> text = text.replace('Hello', 'Hi') >>> text = text.upper() >>> print(text) HI THERE ``
برنامجان على السلاسل: كلمة السر والتشفير
- عند إنشاء حساب جديد في موقع إلكتروني، فإنَّ هذا الموقع يتحقَّق من (جودة) كلمة السّر؛ سعيًا لتقليل خطر اكتشافها من طرف المُخترقين.
- اشتمال كلمة السّر على (10) أحرف فأكثر.
- احتواء كلمة السّر على أحرف إنجليزية صغيرة، وأحرف إنجليزية كبيرة، وأرقام، ورموز غير الأحرف والأرقام.
- يبدأ البرنامج المرور على أحرف السّر، وعدَّ مرَّات تكرار كلٍّ من الأحرف الأبجدية (الصغيرة والكبيرة) والأرقام والرموز، ثمَّ يتأكَّد أنَّ عدد مرَّات التكرار لكل ما سبق لا يساوي صفرًا، أنظر الشكل (7-2).
```python psw = input("Enter Password: ") small = 0 capital = 0 number = 0 special = 0
for c in psw: if c.islower(): small += 1 elif c.isupper(): capital += 1 elif c.isdigit(): number += 1 else: special += 1
if len(psw) < 10: print("Password must be >= 10 characters long.")
if small == 0: print("Password must contain small letters.")
if capital == 0: print("Password must contain capital letters.")
if number == 0: print("Password must contain numbers.")
if special == 0: print("Password must contain special characters.")
if small != 0 and capital != 0 and number != 0 and special != 0 and len(psw) >= 10: print("Strong password!") ```
أُنفِّذ البرنامج في المثال السابق في بيئة بايثون (Python)، وأُلاحظ الناتج، ثمَّ أستكشف الأخطاء التي قد تحدث أثناء تنفيذي للبرنامج، وأعمل على تصحيحها.
أتتبَّع الأوامر البرمجية في المثال السابق، وأستكشف إمكانية كتابتها بطريقة أُخرى، ثمَّ أُناقش زملائي/زميلاتي في أهمية كل أمر، وتأثير حذفه في تنفيذ البرنامج بصورة صحيحة.
- يُمكن كتابة برنامج لتشفير الرسائل بناءً على فكرة قديمة جدًّا، تُنسب إلى القيصر الروماني يوليوس، الذي يقال إنَّه وظَّف هذه الفكرة في مراسلاته.
- تقوم الفكرة على تمثيل كل حرف من الحروف الأبجدية بحرف آخر يبعد عنه مسافة مُحدَّدة، مثل استبدال كل حرف من الحروف الأبجدية بالحرف الذي يبعد عنه (13) خطوة؛
- إذ يُستبدل حرف الـ'a' بحرف الـ'n'، وحرف الـ'w' بحرف الـ'j'، وهكذا كما هو مُوضَّح في الشكل الآتي:

- بناءً على ذلك، فإنَّ تشفير كلمة 'abu' هو 'noh'، وتشفير كلمة 'bad' هو 'onq'.
- أُلاحظ أنَّه لحساب موقع الحرف البديل يجب إضافة (13) خطوة، لكنَّ ذلك قد يجعل الموقع أكبر من (25) كما هو الحال بالنسبة إلى الحرف (w)؛ إذ سيكون الناتج هو (35) إن أُضيف (13) إلى موقع الحرف (22)،
- في حين يجب استبدال الحرف (w) بالحرف (j) الموجود في الموقع (9).
- ومن ثَمَّ يُمكن طرح (13) بدلًا من إضافة (13)؛ ما يفي بالغرض (22 - 13 = 9).
``python 1 chars = 'abcdefghijklmnopqrstuvwxyz' 2 result = '' 3 4 text = input("Enter the text to encrypt/decrypt: ") 5 6 for c in text: 7 if c.isalpha(): # هل الحرف أبجدي؟ 8 c = c.lower() # حول الحرف الكبير لحرف صغير 9 i = chars.index(c) # احسب موقع الحرف الأصلي 10 11 new_i = i+13 # احسب موقع الحرف الجديد 12 if new_i >= len(chars): 13 new_i = i - 13 14 15 c = chars[new_i] # استبدل الحرف 16 17 result += c # أضف الحرف للرسالة المشفرة 18 19 print(result) ``
الأحرف الخاصة
الشرطة المائلة تُغيّر معنى الحرف
- تحمل بعض الأحرف معاني خاصة في لغة البرمجة بايثون (Python)؛ لذا يجب الانتباه عند استخدامها في السلاسل.
- فمثلاً، تُستعمَل علامة التنصيص " " لتحديد بداية سلسلة الحروف ونهايتها؛ ما يجعل استخدام هذه العلامة حرفًا داخل السلسلة مشكلة.
- ولهذا تسمح لغة بايثون (Python) بالتفرقة بين علامة التنصيص التي هي جزء من السلسلة وعلامة التنصيص التي تُحدِّد بداية السلسلة ونهايتها، وذلك عن طريق استخدام الرمز \ قبل علامة التنصيص كما يأتي:
``python >>> print('What is the difference between \' \' and " " ? ') What is the difference between ' ' and " " ? ``
- أُلاحظ أنَّ الرمز \ لم يُضَف قبل علامة التنصيص "؛ لأنَّ العلامة التي استُخدِمت في تحديد بداية السلسلة ونهايتها هي علامة '.
- كذلك يُستخدَم الرمز \ قبل بعض الحروف لإعطائها معنىً خاصًّا، مثل: '\n' التي تعني سطرًا جديدًا، و'\t' التي تعني مسافة مُطوَّلة:
``python >>> print('Name:\tJamilah\nAge:\t16 years old') Name: Jamilah Age: 16 years old ``
- ولكن، كيف يُستخدَم الرمز \ حرفًا داخل السلسلة إن كان يحمل معنىً خاصًّا؟ يُمكن فعل ذلك عن طريق إضافة رمز \ آخر قبله كما يأتي:
>>> print('I can print \\') I can print \
أكتب أوامر برمجية، أستخدم فيها الأحرف الخاصة، ثمَّ أُلاحظ ناتج التنفيذ كل مرَّة.
القوائم المُركَّبة
قائمة داخل قائمة
- تَعَرَّفتُ سابقًا أنَّ عناصر القائمة قد تحوي جملة من القوائم؛ ما يعني إمكانية إنشاء قائمة من مجموعة قوائم.
- يُمكن - مثلًا - في الرياضيات تمثيل المصفوفة ثنائية الأبعاد (2D Matrix) في صورة قائمة مُركَّبة تحوي أرقامًا، وكذلك تمثيل رقعة الشطرنج في صورة مصفوفة ثنائية الأبعاد تحوي أحجارًا، وغير ذلك كثير.
- يُمكن إنشاء قائمة مُركَّبة كما في الجملة الآتية:
إنشاء القوائم المُركَّبة
a = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
- أُلاحظ أنَّ هذه القائمة تتألَّف من (3) قوائم، وأنَّ كل قائمة منها تتألَّف من (3) عناصر؛ أي إنَّ هذه القائمة المُركَّبة تحوي (3) صفوف و(3) أعمدة.
- يُمكن الوصول إلى الصف الأوَّل باستخدام
a[0]، والوصول إلى الصف الثاني باستخدامa[1]، وهكذا. - كذلك يُمكن الوصول إلى أي عنصر من عناصر القائمة المُركَّبة عن طريق تحديد موقع الصف، ثمَّ تحديد موقع العنصر داخل هذا الصف (أي رقم العمود).
- فمثلاً، العنصر الأوَّل في القائمة المُركَّبة موجود في المكان
a[0][0]، والعنصر الأخير في هذه القائمة موجود في المكانa[2][2]، وهكذا. - يعمل البرنامج الآتي على تعيين قيمة (99) للعنصر
a[1][2]، ثمَّ طباعة عناصر القائمة:
مثال:
``python >>> a[1][2] = 99 >>> print(a) [[0, 0, 0], [0, 0, 99], [0, 0, 0]] ``
مثال:
- يطبع البرنامج الآتي عناصر القائمة المُركَّبة، ويضع كل صف على سطر مُنفصل:
``python >>> for row in a: ... print(row) [0, 0, 0] [0, 0, 99] [0, 0, 0] ``
- إنَّ هذه الطريقة اليدوية في تعيين قِيَم العناصر تُستخدَم فقط في إنشاء القوائم المُركَّبة الصغيرة.
- أمَّا القوائم المُركَّبة الكبيرة (مثل مصفوفة تتألَّف من (1000) صف و(2000) عمود) فيتطلَّب إنشاؤها:
- استخدام حلقة تكرار، بدءًا بإعداد قائمة فارغة، وانتهاءً بإضافة كل صف إلى القائمة داخل حلقة التكرار كما يأتي:
a = [] for i in range(1000): row = [0] * 2000 a.append(row)
المرور على القوائم المُركَّبة
- لا يختلف المرور على القائمة المُركَّبة عن المرور على أي قائمة أُخرى، لكنَّنا نحتاج كثيرًا إلى استخدام حلقات تكرار مُركَّبة (حلقة للمرور على كل صف داخلها، وحلقة للمرور على كل عنصر في الصف)؛ لأنَّ عناصر القائمة تتألَّف أساسًا من قوائم.
- يستخدم البرنامج الآتي حلقة التكرار في المرور على كل عنصر في رقعة (اسمها board) مُخصَّصة للعبة (XO)؛ بُغيةَ التَّحقُّق من عدم وجود أحرف في الرقعة، ما عدا 'X' أو 'O' أو '-':
مثال:
``python board = [['-', 'X', 'P'], ['-', 'X', '-'], ['K', 'O', '-']] for row in board: for c in row: if c != 'X' and c != 'O' and c != '-': print('Invalid character ' + c + ' in the board') print('Finished checking the board') ``

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

- يُمكن تبسيط المسألة بتقسيمها إلى مسألتين؛ الأولى: رسم مُثلَّث مقلوب، والثانية: رسم عدد من المُثلَّثات المقلوبة التي يتناقص حجم كل منها.
- التفكير في كيفية رسم مُثلَّث مقلوب، وكتابة دالَّة تتولَّى عملية الرسم.
خطوات العمل:
| عدد النجمات | عدد الفراغات |
|---|---|
| 9 | 0 |
| 7 | 1 |
| 5 | 2 |
| 3 | 3 |
| 1 | 4 |

- يُبيِّن الشكل (7-3) أنَّ كل مُثلَّث مقلوب يتألَّف من أسطر، وأنَّ كل سطر يحوي عددًا من الفراغات والنجوم، وأنَّ عدد الفراغات يزداد في كل سطر بمقدار (1)، وعدد النجوم يقل.
- تعريف دالَّة تستقبل حجم المُثلَّث، مُمثَّلًا بعدد النجوم في السطر العلوي، وتطبع هذه النجوم وفقًا للعلاقة السابقة.
``python def draw_triangle(n): spaces = 0 while n > 0: print(' ' * spaces + '*' * n) spaces += 1 n -= 2 ``
- يتبيَّن أنَّ حجم المُثلَّث الأصغر هو (3)، وأنَّ حجوم المُثلَّثات تزداد بمقدار (2) وصولًا إلى عدد المُثلَّثات المطلوب؛ لذا يُمكن حساب حجم أكبر مُثلَّث باستخدام المعادلة الآتية:
size = 1 + 2*n، حيث n عدد المُثلَّثات المطلوب.
- استدعاء دالَّة لرسم مُثلَّث كبير الحجم، ثمَّ تقليل الحجم بمقدار (2)، ثمَّ استدعاء الدالَّة مرَّة أُخرى لرسم مُثلَّث آخر، وهكذا حتَّى يتمَّ الوصول إلى الحجم (1).
``python n = int(input("How many triangles? ")) size = 1 + 2*n while size > 1: draw_triangle(size) size -= 2 ``
- عند تشغيل البرنامج، سيظهر المُثلَّث الأوَّل على النحو الذي خُطِّط له، خلافًا لبقية المُثلَّثات؛ إذ ستظهر مائلة بصورة غير صحيحة كما في الشكل الآتي:

تشغيل البرنامج للحصول على النتيجة وارفاق صورة لها
- يُعزى سبب ذلك إلى خطأ في تصميم دالَّة رسم المُثلَّث؛ إذ لا تبدأ جميع المُثلَّثات من أوَّل السطر، ويجب إزاحة المُثلَّث إلى اليمين كلَّما كان أصغر؛
- لذا يجب أوَّلًا تعديل الدالَّة على نحوٍ يُمكنها من تسلُّم مقدار الإزاحة (إلى جانب حجم المُثلَّث)، ثمَّ إرسال هذا المقدار إلى الدالَّة،
- فيبدأ المُثلَّث الأوَّل من دون أي إزاحة، ثمَّ يأخذ كمَّ الإزاحة يتزايد بمقدار (1) كلَّما قلَّ حجم المُثلَّث.
```python def draw_triangle(n, shift): spaces = 0 while n > 0: print(' ' shift + ' ' spaces + '' n) spaces += 1 n -= 2
n = int(input("How many triangles? ")) size = 1 + 2*n shift = 0 while size > 1: draw_triangle(size, shift) size -= 2 shift += 1 ```