البرمجة بلغة بايثون · الفصل 4 — التنفيذ المتكررביצוע חוזר · יסודות התכנות בשפת Python⌂📚🏠
الفصل 4

التنفيذ المتكرر

ביצוע חוזר
· 30 ساعات تعليمية

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

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

4.1 الحاجة إلى التنفيذ المتكرر – הצורך בביצוע חוזר

نريد متوسط ثلاث قراءات لحسّاس. بما تعلمناه حتى الآن نكتب الأمرين نفسيهما ثلاث مرات:

total = 0
value = float(input("Sample: "))
total += value
value = float(input("Sample: "))
total += value
value = float(input("Sample: "))
total += value
print("Average:", total / 3)

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

الحلقة (לולאה): بنية تعيد تنفيذ مجموعة أوامر مرة بعد مرة، ما دام شرط معيّن يتحقّق. كل مرة تُنفَّذ فيها هذه الأوامر تسمى دورة.

البرنامج نفسه بحلقة أقصر، ويكفي أن نغيّر فيه عددين كي يقرأ مئة قيمة. سنشرح كل سطر فيه في البند التالي:

total = 0
n = 1
while n <= 3:
    value = float(input("Sample: "))
    total += value
    n += 1
print("Average:", total / 3)
المُخرَج
Sample: 20.5
Sample: 21
Sample: 21.5
Average: 21.0

وهناك حالة ثانية لا تحلها أي نسخة، لأن عدد الدورات ينتج عن الحساب نفسه:

مثال 4.1
مكثّف مشحون بجهد 12 V يتفرغ عبر مقاومة، فينخفض جهده إلى النصف كل فترة زمنية ثابتة. نريد أن نعرف كم فترة تمرّ حتى يهبط تحت 1 V. نكتب الخوارزمية بالكلمات أولا.
الحل:

  1. نبدأ بجهد 12 V وبعدد فترات يساوي صفرا.
  2. ما دام الجهد لا يقل عن 1 V: نقسم الجهد على 2، ونزيد عدد الفترات بواحد.
  3. نطبع عدد الفترات والجهد.

لا يظهر في الخوارزمية أي عدد للدورات، وما يحدد نهايتها هو الشرط. وبالشيفرة:

volts = 12.0
steps = 0
while volts >= 1:
    volts = volts / 2
    steps += 1
print("Steps:", steps)
print("Volts:", volts)
المُخرَج
Steps: 4
Volts: 0.75
سؤال 4.1: ما الأوامر التي يكررها البرنامج لو طلبنا قراءة 50 قيمة بلا حلقة، وكم مرة نكتبها؟ ولماذا لا تصلح النسخ إذا كان عدد القراءات يعرفه المستخدم فقط أثناء التشغيل؟

4.2 البنية الأساسية للحلقة while – תחביר בסיסי של לולאת while

الأمر while يعني «ما دام». نكتبه هكذا:

while condition:
    statement
    statement
next_statement
القاعدة: في كل حلقة سليمة ثلاثة أجزاء: تهيئة قبل الحلقة تعطي المتغيّر قيمته الأولى، شرط يقرّر هل نستمر، وتحديث داخل الجسم يغيّر المتغيّر فيقرّبنا من نهاية الحلقة. إذا نسينا واحدا منها فلن تعمل الحلقة كما نريد.
التهيئةالشرطجسم الحلقةالتحديثالأمر التاليTrueFalse
مخطط انسياب الحلقة while: السهم الذي يعود إلى الشرط هو ما يميّزها عن if
مثال 4.2
فحص ذاتي عند تشغيل جهاز: يومض الثنائي الباعث للضوء ثلاث مرات، ثم تظهر رسالة. نكتب البرنامج ونبني له جدول متابعة.
الحل:
blink = 1
while blink <= 3:
    print("LED blink", blink)
    blink += 1
print("Test finished")
المُخرَج
LED blink 1
LED blink 2
LED blink 3
Test finished
متابعة البرنامج
الأمرblinkblink <= 3المُخرَج
blink = 11
while blink <= 3:True
print("LED blink", blink)LED blink 1
blink += 12
while blink <= 3:True
print("LED blink", blink)LED blink 2
blink += 13
while blink <= 3:True
print("LED blink", blink)LED blink 3
blink += 14
while blink <= 3:False
print("Test finished")Test finished

نقرأ الجدول من الأعلى إلى الأسفل. يظهر الشرط أربع مرات والجسم ثلاث مرات: ثلاث مرات تحقّق فيها، وفي المرة الرابعة لم يتحقّق فانتهت الحلقة. وعند الخروج تكون قيمة blink تساوي 4، لا 3.

قد لا يُنفَّذ الجسم ولا مرة: إذا لم يتحقّق الشرط من البداية ننتقل مباشرة إلى ما بعد الحلقة.

volts = 3.0
while volts > 5:
    print("Too high")
    volts -= 1
print("Volts:", volts)
المُخرَج
Volts: 3.0

والتحديث لا يعني دائما زيادة واحد. نستطيع أن نزيد بأي خطوة، مثل volts += 3، وأن ننقص في العدّ التنازلي، مثل left -= 1.

الكتابة الخوارزمية والكتابة بالشيفرة – כתיבה אלגוריתמית ומימוש בקוד:

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

الوصف بالكلماتالشيفرةما أضافته الشيفرة
كرّر خمس مراتn = 1 ، while n <= 5: ، n += 1عدّاد بتهيئته وتحديثه
ما دام الجهد أقل من 12 Vwhile volts < 12:القيمة الأولى لـ volts وتحديثها
حتى يكتب المستخدم -1while x != -1:قلب الشرط، وأمر قراءة داخل الجسم

ننتبه إلى الفرق في السطر الأخير: في الكلام نقول «حتى» ونذكر متى نتوقف، أما while فيسأل متى نستمر. سنعود إلى هذه الترجمة في البند 4.5.

خطأ شائع: ترتيب الأوامر في الجسم يغيّر النتيجة. هنا نحدّث n قبل الطباعة، فنحصل على 2 و3 و4 بدل 1 و2 و3:
n = 1
while n <= 3:
    n += 1
    print("Sample", n)
المُخرَج
Sample 2
Sample 3
Sample 4

وسطر واحد خارج الإزاحة يخرج من الجسم. الأمر print هنا لا يتكرر، ويُنفَّذ مرة واحدة بعد انتهاء الحلقة:

n = 1
while n <= 3:
    n += 1
print("Sample", n)
المُخرَج
Sample 4
سؤال 4.2: ماذا يطبع البرنامج التالي؟
n = 1
total = 0
while n < 4:
    total += n
    n += 1
print(n, total)

4.3 مصفاة المُدخَلات العنيدة – מסננת קלט עקשנית (do while)

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

المصفاة العنيدة (מסננת עקשנית): حلقة لا تترك المستخدم يتقدم حتى يدخل قيمة صالحة. عند الخروج منها نضمن أن القيمة صالحة.

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

الطريقة الأولى: قراءة قبل الحلقة وقراءة داخلها:

نقرأ مرة قبل الحلقة، فيكون عند الشرط ما يفحصه. وشرط الحلقة يصف القيمة غير الصالحة:

adc = int(input("ADC (0-1023): "))
while adc < 0 or adc > 1023:
    print("Out of range, try again")
    adc = int(input("ADC (0-1023): "))
print("Accepted:", adc)
المُخرَج
ADC (0-1023): 1500
Out of range, try again
ADC (0-1023): -4
Out of range, try again
ADC (0-1023): 512
Accepted: 512
متابعة البرنامج للمُدخَلين 1500 و512
الأمرadcadc < 0 or adc > 1023المُخرَج
adc = int(input("ADC (0-1023): "))1500ADC (0-1023): 1500
while adc < 0 or adc > 1023:True
print("Out of range, try again")Out of range, try again
adc = int(input("ADC (0-1023): "))512ADC (0-1023): 512
while adc < 0 or adc > 1023:False
print("Accepted:", adc)Accepted: 512

الطريقة الثانية: حلقة بشرط True وخروج بالأمر break:

الشرط True يتحقّق دائما، فنقرأ مرة واحدة داخل الجسم. والأمر break يخرج من الحلقة فورا، وسنتعرف إليه بالتفصيل في البند 4.7. الشرط هنا يصف القيمة الصالحة، وهو عكس شرط الطريقة الأولى:

while True:
    adc = int(input("ADC (0-1023): "))
    if 0 <= adc <= 1023:
        break
    print("Out of range, try again")
print("Accepted:", adc)
قراءة مزدوجةwhile True
عدد أوامر القراءةأمران متطابقانأمر واحد
الشرط يصفالقيمة المرفوضة، ونستمر ما دام يتحقّقالقيمة المقبولة، ونخرج عندما يتحقّق
متى نفضّلهاحين يكون الشرط قصيرا والقراءة بسيطةحين تطول القراءة أو تسبقها رسالة

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

خطأ شائع: الأمر if بدل while يعطي فرصة واحدة فقط. القيمة الثانية تُقبل بلا فحص، ولو كانت خارج المجال أيضا:
adc = int(input("ADC (0-1023): "))
if adc < 0 or adc > 1023:
    adc = int(input("ADC (0-1023): "))
print("Accepted:", adc)
المُخرَج
ADC (0-1023): 1500
ADC (0-1023): 2000
Accepted: 2000
سؤال 4.3: نكتب مصفاة عنيدة تقرأ نسبة تشغيل duty بين 0 و100، ثم تطبعها.

4.4 قراءة كمية غير معروفة من المعطيات – קליטת כמות לא ידועה של נתונים (תבנית קלט עם זקיף)

الفني يسجّل قدرات الأجهزة المتصلة بالخط واحدا بعد الآخر، ولا يعرف عددها مسبقا. كيف يخبر البرنامج بأنه انتهى؟ يكتب قيمة متفقا عليها لا يمكن أن تكون معطى حقيقيا.

قيمة التوقّف (זקיף): قيمة خاصة، من خارج مجال المعطيات الصحيحة، تعلن أن المعطيات انتهت. هي ليست جزءا من المعطيات، فلا تدخل في الحساب.
القاعدة: نقرأ أول قيمة قبل الحلقة. يفحص الشرط أن القيمة ليست قيمة التوقّف. نعالج القيمة في الجسم، وفي آخر الجسم نقرأ القيمة التالية.
مثال 4.3
نقرأ قدرات أجهزة متصلة بخط واحد، ونطبع مجموعها. قدرة صفر ليست جهازا، ولذلك نجعل الصفر قيمة التوقّف.
الحل:
total = 0
watts = float(input("Load W (0 stops): "))
while watts != 0:
    total += watts
    watts = float(input("Load W (0 stops): "))
print("Total:", total, "W")
المُخرَج
Load W (0 stops): 60
Load W (0 stops): 100
Load W (0 stops): 25
Load W (0 stops): 0
Total: 185.0 W
متابعة البرنامج للمُدخَلات 60 و25 و0
الأمرtotalwattswatts != 0المُخرَج
total = 00
watts = float(input("Load W (0 stops): "))60.0Load W (0 stops): 60
while watts != 0:True
total += watts60.0
watts = float(input("Load W (0 stops): "))25.0Load W (0 stops): 25
while watts != 0:True
total += watts85.0
watts = float(input("Load W (0 stops): "))0.0Load W (0 stops): 0
while watts != 0:False
print("Total:", total, "W")Total: 85.0 W

نلاحظ أن أمر القراءة هو آخر ما في الجسم. بعده مباشرة يأتي فحص الشرط، فلا تصل قيمة التوقّف إلى أمر الجمع. وإذا كانت أول قيمة هي قيمة التوقّف، لا يُنفَّذ الجسم أبدا، ويطبع البرنامج: Total: 0 W

وبالطريقة الثانية من البند السابق نكتب أمر قراءة واحدا: نبدأ الجسم بالقراءة، ثم نفحص قيمة التوقّف ونخرج بالأمر break، ثم نجمع. الفحص يأتي قبل أي حساب.

خطأ شائع: نقرأ في أول الجسم ونجمع بعد ذلك، فتدخل قيمة التوقّف في الحساب. نضطر هنا أيضا إلى قيمة أولى وهمية كي يبدأ البرنامج. المجموع الصحيح هو 30، لكن البرنامج يطبع قيمة أقل بواحد:
total = 0
pulses = 1
while pulses != -1:
    pulses = int(input("Pulses (-1 stops): "))
    total += pulses
print("Total:", total)
المُخرَج
Pulses (-1 stops): 10
Pulses (-1 stops): 20
Pulses (-1 stops): -1
Total: 29

قيمة التوقّف قد تكون جملة نصية أيضا. نقرأ أسماء قطع إلكترونية حتى الكلمة end، ولا نحوّل المُدخَل إلى عدد:

count = 0
part = input("Part (end stops): ")
while part != "end":
    count += 1
    print("Stored:", part)
    part = input("Part (end stops): ")
print("Parts:", count)
المُخرَج
Part (end stops): LM358
Stored: LM358
Part (end stops): 7805
Stored: 7805
Part (end stops): end
Parts: 2
تنبيه: نختار قيمة التوقّف من خارج مجال المعطيات. لا يصلح -1 لقراءة حرارة، لأن -1 درجة قيمة حقيقية في الشتاء، فيتوقف البرنامج قبل الأوان. نختار 999، وهي بعيدة عن كل حرارة يقيسها الحسّاس.
سؤال 4.4: ماذا يطبع البرنامج التالي للمُدخَلات 3 و5 و0؟ وماذا يطبع إذا كان أول مُدخَل 0؟
x = int(input("x: "))
y = 0
while x != 0:
    y += x * 2
    x = int(input("x: "))
print(y)

4.5 ترجمة شرط الإنهاء إلى شرط الحلقة – תרגום תנאי סיום לתנאי הלולאה

السؤال في الامتحان وفي الحياة يصف عادة متى نتوقف: «توقف عندما يصل المجموع إلى كذا، أو بعد كذا قراءة». أما while فيحتاج إلى متى نستمر. لذلك نترجم:

القاعدة: شرط الحلقة هو نفي شرط الإنهاء. نستمر ما دام شرط الإنهاء لا يتحقّق.
نتوقف عندمانستمر ما دامماذا حدث
x == -1x != -1المساواة تصبح عدم مساواة
total >= 100total < 100نقلب المقارنة، ولا ننسى حالة التساوي
v < 0 or v > 5v >= 0 and v <= 5نقلب كل مقارنة، وor تصبح and
a == 0 and b == 0a != 0 or b != 0نقلب كل مقارنة، وand تصبح or

العمود الثالث في السطرين الأخيرين هو ما نعرفه من الأنظمة الرقمية باسم قانوني دي مورغان (חוקי דה-מורגן): عندما ننفي شرطا مركبا، ننفي كل طرف فيه، ونبدّل and بـor والعكس. نفحص ذلك لكل القيم الممكنة:

abnot (a or b)not a and not bnot (a and b)not a or not b
FalseFalseTrueTrueTrueTrue
FalseTrueFalseFalseTrueTrue
TrueFalseFalseFalseTrueTrue
TrueTrueFalseFalseFalseFalse

العمودان الثالث والرابع متطابقان، وكذلك الخامس والسادس.

مثال 4.4
وحدة اتصال تجمع رزما في ذاكرة. تتوقف عن الاستقبال عندما يصل عدد البايتات إلى 256 أو بعد 5 رزم. نترجم الشرط ثم نكتب البرنامج.
الحل:
الشرط
نتوقف عندماtotal >= 256 or packets >= 5
نستمر ما دامtotal < 256 and packets < 5
total = 0
packets = 0
while total < 256 and packets < 5:
    size = int(input("Packet bytes: "))
    total += size
    packets += 1
print("Bytes:", total)
print("Packets:", packets)
المُخرَج
Packet bytes: 100
Packet bytes: 120
Packet bytes: 90
Bytes: 310
Packets: 3

توقف البرنامج هنا بسبب البايتات، بعد ثلاث رزم. ومع خمس رزم صغيرة كل منها 10 بايتات يتوقف بسبب العدد، ويطبع: Bytes: 50 , Packets: 5

وإذا صعبت علينا الترجمة، نستطيع أن نكتب النفي كما هو بالمعامل not حول شرط الإنهاء: while not (total >= 256 or packets >= 5):. الحساب نفسه، والنتيجة نفسها.

خطأ شائع: ننسى أن or تتحول إلى and، فنكتب or بين الشرطين. عندها تستمر الحلقة ما دام أحدهما لم يصل إلى حدّه، أي حتى يتحقّق الشرطان معا. للرزم الثلاث نفسها لا يتوقف البرنامج، بل يطلب رزمتين أخريين، ويطبع في النهاية: Bytes: 330 , Packets: 5

وهذا الخطأ أخطر في صورة أخرى. نريد مصفاة تقبل الوضع 1 أو 2، فنكتب while mode != 1 or mode != 2:. هذه الحلقة لا تنتهي أبدا: كل عدد يختلف عن 1 أو يختلف عن 2، فالشرط يتحقّق دائما حتى عندما يكتب المستخدم قيمة صحيحة. نعود إلى القاعدة: نتوقف عندما mode == 1 or mode == 2، فنكتب while mode != 1 and mode != 2:.

مثال 4.5
قفل إلكتروني يعطي ثلاث محاولات لإدخال الرمز. يتوقف عندما يصل الرمز الصحيح أو بعد ثلاث محاولات. وبعد الحلقة نسأل لماذا توقفنا، فلكل سبب رسالة.
الحل:
code = int(input("Code: "))
tries = 1
while code != 4071 and tries < 3:
    code = int(input("Code: "))
    tries += 1
if code == 4071:
    print("Unlocked")
else:
    print("Locked")
المُخرَج
Code: 1111
Code: 4071
Unlocked

وبعد ثلاث محاولات خاطئة يطبع: Locked

تدريب سريع — نفي الشروط عندما a = 2 وb = 5
1not (a > 3 or b > 3)
2a <= 3 and b <= 3
3not (a > 3 and b > 3)
4a <= 3 or b <= 3
5not a == 2
6not (a == 2 or b == 2)
الإجابات: 1 False 2 False 3 True 4 True 5 False 6 False

4.6 مهام حسابية نموذجية – משימות חישוב טיפוסיות

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

العدّاد – מונה:

العدّاد (מונה): متغيّر يبدأ من قيمة أولى، هي الصفر في الغالب، ويزيد بواحد في كل مرة يحدث فيها شيء نريد أن نعدّه.
مثال 4.6
كم خانة ثنائية نحتاج لتمثيل عدد معيّن؟ كل قسمة صحيحة على 2 تحذف خانة واحدة، فنعدّ القسمات حتى يصل العدد إلى صفر. هذا هو السبب في أن محوّلا أعلى قراءاته 1023 هو محوّل بعشر خانات.
الحل:
value = int(input("Largest value: "))
bits = 0
while value > 0:
    value = value // 2
    bits += 1
print("Bits needed:", bits)
المُخرَج
Largest value: 1023
Bits needed: 10

وللقيمة 1024 يطبع: Bits needed: 11، لأن 1024 يحتاج إلى خانة إضافية.

العدّاد مع شرط – מונה עם תנאי:

العدّاد مع شرط لا يزيد في كل دورة، بل فقط عندما يتحقّق الشرط. مكانه هو if داخل جسم الحلقة:

مثال 4.7
نقرأ قيم تيار في خط حمايته 10 A، ونعدّ كم مرة تجاوزت القيم هذا الحد.
الحل:
limit = 10
over = 0
amps = float(input("Current (-1 stops): "))
while amps != -1:
    if amps > limit:
        over += 1
    amps = float(input("Current (-1 stops): "))
print("Over the limit:", over)
المُخرَج
Current (-1 stops): 8.5
Current (-1 stops): 12
Current (-1 stops): 10.4
Current (-1 stops): -1
Over the limit: 2

المُجمِّع – צובר:

المُجمِّع (צובר): متغيّر يبدأ من الصفر، ونضيف إليه في كل دورة قيمة جديدة، فيحمل في النهاية مجموع القيم كلها.

رأينا المُجمِّع في البند 4.4، حين جمعنا قدرات الأجهزة بالأمر total += watts.

الفرق بين العدّاد والمُجمِّع بسيط: العدّاد يضيف واحدا، والمُجمِّع يضيف القيمة نفسها. وهناك مُجمِّع يضرب بدل أن يجمع، وعندها لا يصح أن يبدأ من الصفر:

مثال 4.8
مكبّر من عدة مراحل موصولة على التوالي. الكسب الكلي هو حاصل ضرب كسب المراحل. نبدأ من 1، لأن حاصل ضرب أي عدد في صفر هو صفر.
الحل:
gain = 1
stage = float(input("Stage gain (0 stops): "))
while stage != 0:
    gain *= stage
    stage = float(input("Stage gain (0 stops): "))
print("Total gain:", gain)
المُخرَج
Stage gain (0 stops): 10
Stage gain (0 stops): 5
Stage gain (0 stops): 0
Total gain: 50.0

المتوسط يحتاج إلى النمطين معا: مُجمِّع يحمل المجموع، وعدّاد يحمل عدد القيم. وبما أن عدد القيم قد يكون صفرا، نفحصه قبل القسمة:

count = 0
total = 0
temp = float(input("Temp (999 stops): "))
while temp != 999:
    count += 1
    total += temp
    temp = float(input("Temp (999 stops): "))
if count > 0:
    print("Average:", round(total / count, 2))
else:
    print("No readings")
المُخرَج
Temp (999 stops): 22.5
Temp (999 stops): 24
Temp (999 stops): 19.5
Temp (999 stops): 999
Average: 22.0

القيم القصوى – ערכי קיצון:

القيمة القصوى (ערך קיצון): أكبر قيمة أو أصغر قيمة في سلسلة قيم. نحفظ أفضل ما وجدناه حتى الآن، ونقارن به كل قيمة جديدة.
مثال 4.9
خرج مصدر قدرة يجب أن يكون مستويا، لكنه يتموج قليلا. مقدار التموّج هو الفرق بين أعلى قراءة وأدناها. نقرأ جهودا حتى الصفر، ونطبع هذا الفرق. نجعل أول قراءة هي الأكبر والأصغر معا، لأنها حتى هذه اللحظة كذلك.
الحل:
v = float(input("Volts (0 stops): "))
high = v
low = v
while v != 0:
    if v > high:
        high = v
    if v < low:
        low = v
    v = float(input("Volts (0 stops): "))
print("Ripple:", round(high - low, 2), "V")
المُخرَج
Volts (0 stops): 11.9
Volts (0 stops): 12.3
Volts (0 stops): 11.7
Volts (0 stops): 0
Ripple: 0.6 V

قيمة التوقّف لا تدخل في المقارنة، لأن الشرط يُفحص مباشرة بعد قراءتها.

خطأ شائع: نبدأ من الصفر في البحث عن الأكبر. ينجح البرنامج ما دامت القيم موجبة، ويخطئ مع الحرارة في الشتاء: أعلى قراءة هي -3، لكنه يطبع قيمة لم يقرأها أصلا:
top = 0
temp = int(input("Temp (999 stops): "))
while temp != 999:
    if temp > top:
        top = temp
    temp = int(input("Temp (999 stops): "))
print("Highest:", top)
المُخرَج
Temp (999 stops): -7
Temp (999 stops): -3
Temp (999 stops): -12
Temp (999 stops): 999
Highest: 0

الحل هو أن تكون القيمة الأولى قراءة حقيقية: نقرأ أولا، ثم نكتب top = temp قبل الحلقة، فيطبع البرنامج Highest: -3.

المزج بين المهام – שילוב בין המשימות:

مثال 4.10
نقرأ قدرة لوح شمسي كل ساعة حتى -1. نطبع المتوسط، أعلى قدرة ورقم الساعة التي ظهرت فيها.
الحل:

نحتاج إلى عدّاد للساعات، مُجمِّع وقيمة قصوى مع موقعها. كل واحد منها سطر أو سطران من الأنماط التي تعلمناها:

hour = 0
total = 0
watts = float(input("Panel W (-1 stops): "))
while watts != -1:
    hour += 1
    total += watts
    if hour == 1 or watts > peak:
        peak = watts
        peak_hour = hour
    watts = float(input("Panel W (-1 stops): "))
if hour > 0:
    print("Average:", round(total / hour, 1), "W")
    print("Peak:", peak, "W at hour", peak_hour)
المُخرَج
Panel W (-1 stops): 60
Panel W (-1 stops): 110
Panel W (-1 stops): 135
Panel W (-1 stops): 90
Panel W (-1 stops): -1
Average: 98.8 W
Peak: 135.0 W at hour 3

نلاحظ الشرط hour == 1 or watts > peak: في الساعة الأولى لا يوجد بعد peak، لكن اختصار الطريق من البند 3.5 يمنع حساب الطرف الأيمن، فلا يقع خطأ.

المهمةقبل الحلقةداخل الحلقة
عدّادcount = 0count += 1
عدّاد مع شرطcount = 0if ok: count += 1
مُجمِّع بالجمعtotal = 0total += x
مُجمِّع بالضربgain = 1gain *= x
الأكبرtop = firstif x > top: top = x
الأصغرlow = firstif x < low: low = x
المتوسطمُجمِّع وعدّادبعد الحلقة: total / count
سؤال 4.5: ماذا يطبع البرنامج التالي للمُدخَلات 3 و7 و5 و9 و0؟ ماذا يمثل كل من a وb؟
a = 0
b = 0
x = int(input("x: "))
while x != 0:
    a += 1
    if x > 5:
        b += x
    x = int(input("x: "))
print(a, b)

4.7 الأمران break وcontinue – הפקודות break ו-continue

الأمرعمله
breakيخرج من الحلقة فورا، وينتقل إلى الأمر الذي بعدها
continueيترك بقية الجسم في هذه الدورة، ويعود إلى فحص الشرط
الشرطشرط داخليبقية الجسمالأمر التاليTrueFalseFalsebreak
الأمر break يقفز إلى ما بعد الحلقة
الشرطشرط داخليبقية الجسمالأمر التاليTrueFalseFalsecontinue
الأمر continue يقفز إلى الشرط

الأمران يظهران دائما تقريبا داخل if، لأننا نقفز عند حالة خاصة فقط.

مثال 4.11
نفحص خلايا بطارية من ست خلايا. نتوقف عند أول خلية ضعيفة جهدها أقل من 3 V: لا حاجة إلى قياس البقية، فهذه البطارية ستدخل الصيانة على كل حال.
الحل:
cell = 1
while cell <= 6:
    v = float(input("Cell V: "))
    if v < 3.0:
        print("Weak cell:", cell)
        break
    cell += 1
print("Scan finished")
المُخرَج
Cell V: 3.7
Cell V: 3.6
Cell V: 2.8
Weak cell: 3
Scan finished

يمكن أن نكتب البرنامج نفسه بلا break، بعلم (דגל) بولياني weak نغيّره إلى True عند الخلية الضعيفة، وبشرط مركب هو ترجمة «نتوقف عند خلية ضعيفة أو بعد ست خلايا»: while cell <= 6 and not weak:

الأمر break يجعل البرنامج أقصر، والشرط المركب يجعل سبب الخروج ظاهرا في رأس الحلقة. وكلاهما مقبول.

مثال 4.12
نقرأ قراءات محوّل حتى -1، ونحسب المتوسط. القراءة الأكبر من 1023 ناتجة عن تشويش، فنتجاهلها ولا ندخلها في الحساب.
الحل:
total = 0
count = 0
while True:
    adc = int(input("ADC (-1 stops): "))
    if adc == -1:
        break
    if adc > 1023:
        print("Ignored:", adc)
        continue
    total += adc
    count += 1
if count > 0:
    print("Average:", total / count)
else:
    print("No readings")
المُخرَج
ADC (-1 stops): 500
ADC (-1 stops): 2000
Ignored: 2000
ADC (-1 stops): 520
ADC (-1 stops): -1
Average: 510.0

في الدورة الثانية نفّذ البرنامج continue، فلم يصل إلى الجمع ولا إلى العدّاد.

خطأ شائع: نضع continue قبل تحديث العدّاد، فلا يتغيّر العدّاد أبدا بعد القفز، ويدور البرنامج في مكانه عند القناة الثالثة:
ch = 1
while ch <= 4:
    if ch == 3:
        continue
    print("Channel", ch)
    ch += 1

نحدّث العدّاد في أول الجسم، قبل أي قفز:

ch = 0
while ch < 4:
    ch += 1
    if ch == 3:
        continue
    print("Channel", ch)
المُخرَج
Channel 1
Channel 2
Channel 4
سؤال 4.6: ماذا يطبع البرنامج التالي؟ نبني جدول متابعة ونفحص.
n = 0
while n < 6:
    n += 1
    if n == 2:
        continue
    if n == 5:
        break
    print(n)
print("end", n)

4.8 التنفيذ اللانهائي – ביצוע אינסופי

الحلقة اللانهائية (ביצוע אינסופי): حلقة لا يصبح شرطها غير متحقّق أبدا، فلا تنتهي من تلقاء نفسها. إذا حدث هذا عن طريق الخطأ بدا البرنامج عالقا. نوقفه بالمفتاحين Ctrl+C، وفي Google Colab بزر الإيقاف بجانب الخلية.

البرامج في هذا البند لا تنتهي، ولذلك نعرضها بلا مُخرَج. لكل منها سبب مختلف، ونتعلم أن نبحث عنه:

n = 1
while n <= 3:
    print("Sample", n)
نسينا التحديث: تبقى قيمة العدّاد كما هي
n = 1
while n <= 3:
    print("Sample", n)
n += 1
التحديث بلا إزاحة، فهو خارج الجسم ولا نصل إليه
volts = 12
while volts > 0:
    print(volts)
    volts += 3
التحديث في الاتجاه الخطأ: الجهد يبتعد عن النهاية
x = 0.0
while x != 1.0:
    x += 0.1
شرط لا يمكن الوصول إليه

البرنامج الأخير يحتاج إلى شرح. بعد عشر دورات نتوقع أن تكون القيمة 1، لكننا تعلمنا في البند 3.3 أن الأعداد العشرية لا تُحفظ بدقة تامة. القيمة تمرّ قريبا جدا من 1 ولا تساويها، ثم تتجاوزها وتبتعد:

x = 0.0
n = 0
while n < 10:
    x += 0.1
    n += 1
print(x)
print(x == 1.0)
المُخرَج
0.9999999999999999
False

لا نكتب != في شرط الحلقة مع عدد عشري ناتج عن حساب. نفحص الاقتراب بسماح، أو نعدّ الدورات بعدد صحيح كما في البرنامج أعلاه. الشرط while abs(x - 1.0) > 0.001: ينتهي بعد عشر دورات بالضبط. أما قيمة التوقّف التي نقرؤها من المُدخَل، كما في البند 4.4، فحالة أخرى: لم نحسبها، ولذلك تكون المقارنة بها دقيقة.

القاعدة: عند كل حلقة نسأل: ما الذي يتغيّر في كل دورة؟ وهل هذا التغيير يقرّبنا فعلا من لحظة لا يتحقّق فيها الشرط؟

حلقة لا نهائية مقصودة:

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

while True:
    adc = read_sensor()
    show_on_display(adc)
    wait_one_second()

وفي الأجهزة نضيف كثيرا ما يسمى حارسا: حدا أعلى لعدد الدورات. السخّان يجب أن يبلغ 60 درجة، لكن إذا لم يبلغها خلال 30 دقيقة فلا ننتظر إلى الأبد، بل نعلن خللا:

temp = 20
minutes = 0
while temp < 60 and minutes < 30:
    temp += 6
    minutes += 1
if temp >= 60:
    print("Ready after", minutes, "min")
else:
    print("Heater fault")
المُخرَج
Ready after 7 min
سؤال 4.7: أي الحلقات التالية تنتهي، وأيها لا تنتهي؟ ولماذا؟
k = 10
while k > 0:
    k = k - 4
(أ)
k = 10
while k != 0:
    k = k - 4
(ب)
k = 1
while k > 0:
    k = k + 1
(ج)

4.9 تحليل صحّة البرنامج بجدول المتابعة – ניתוח נכונות בעזרת טבלת מעקב

صحّة البرنامج (נכונות): أن يعطي البرنامج النتيجة المطلوبة لكل مُدخَل مسموح به، وليس فقط للمثال الذي جرّبناه.

جدول المتابعة يكشف ما يحدث فعلا، لا ما نظن أنه يحدث. وفي الحلقة نقرأه هكذا: ما قيمة كل متغيّر عند أول فحص للشرط؟ وما قيمته عند آخر دورة؟ وما قيمته بعد الخروج؟ أكثر أخطاء الحلقات تقع في البداية أو في النهاية، أي تكون زيادة دورة أو نقصانها.

مثال 4.13
البرنامج التالي يجب أن يحسب متوسط جهود حتى -1، ولكنه يعطي للقيمتين 10 و20 النتيجة 10.0 بدل 15.0. نبحث عن الخطأ.
الحل:
count = 1
total = 0
v = int(input("V (-1 stops): "))
while v != -1:
    total += v
    count += 1
    v = int(input("V (-1 stops): "))
print(total / count)
متابعة البرنامج للمُدخَلات 10 و20 و-1
الأمرcounttotalvv != -1المُخرَج
count = 11
total = 00
v = int(input("V (-1 stops): "))10V (-1 stops): 10
while v != -1:True
total += v10
count += 12
v = int(input("V (-1 stops): "))20V (-1 stops): 20
while v != -1:True
total += v30
count += 13
v = int(input("V (-1 stops): "))-1V (-1 stops): -1
while v != -1:False
print(total / count)10.0

نقرأ العمود count عند الخروج: قيمته 3، مع أننا قرأنا قيمتين فقط. السبب في السطر الأول: العدّاد يبدأ من 1. نصلحه ليبدأ من الصفر.

بعد التصحيح ينجح المثال، لكن هذا لا يكفي. نجرب أيضا الحالات الحدّية (מקרי קצה): لا قيم أبدا، أو قيمة واحدة فقط. وهنا يظهر خطأ جديد. إذا كانت أول قيمة هي قيمة التوقّف، تنهار القسمة:

count = 0
total = 0
v = int(input("V (-1 stops): "))
while v != -1:
    total += v
    count += 1
    v = int(input("V (-1 stops): "))
print(total / count)
المُخرَج
V (-1 stops): -1
ZeroDivisionError: division by zero

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

if count > 0:
    print(total / count)
else:
    print("No readings")
الفحصالمُدخَلالمتوقعالمُخرَج الأخير
حالة عادية10, 20, -115.015.0
قيمة واحدة10, -110.010.0
لا قيم-1رسالةNo readings
قيم متساوية7, 7, 7, -17.07.0
ملاحظة: الفحوص تكشف الأخطاء، لكنها لا تثبت أن البرنامج خالٍ منها. لذلك نختارها بذكاء: حالة عادية، أصغر حالة، وحالة تقع على الحد بالضبط.

وهناك خطأ حدّي شائع جدا، هو خطأ الدورة الواحدة. يجب أن تطبع الحلقة التالية ثلاث قراءات، لكن n < 3 يتوقف قبل الثالثة:

n = 1
while n < 3:
    print("Sample", n)
    n += 1
print("n =", n)
المُخرَج
Sample 1
Sample 2
n = 3

عند الخروج من حلقة ليس فيها break نعرف دائما شيئا مؤكدا: شرط الحلقة لم يعد يتحقّق. هنا يعني ذلك n >= 3، وهذه طريقة سريعة للفحص: نسأل هل الوضع عند الخروج هو ما أردناه.

▶تجربة حية — مختبر الحلقاتنشغّل الحلقة خطوة بعد خطوة، ونرى الشرط يُفحص وقيم المتغيّرات تتغيّر، تماما كما في جدول المتابعة. لا يوجد في المختبر إدخال من المستخدم، ولذلك نكتب القيم مباشرة في المتغيّرات بدل input.
⚠
المختبر غير متاح الآن. المادة في هذا الفصل كاملة من دونه — نواصل القراءة، ونعود إلى التجربة لاحقا أو نفتحها مباشرة من الرابط الذي تحت الإطار.
https://fahed-lab.com/electronics/python_basics#loops
نفتح المختبر في صفحة مستقلة ↗ fahed-lab.com/electronics/python_basics#loops

النجاعة – יעילות:

النجاعة (יעילות): كم عملا يحتاج البرنامج كي يصل إلى النتيجة. برنامجان صحيحان قد يختلفان كثيرا في النجاعة. نقيسها هنا بطريقة بسيطة: عدد الدورات.
مثال 4.14
يبحث محوّل عن عدد بين 0 و1023 يطابق الجهد في دخله. الطريقة الأولى تصعد عددا عددا:
الحل:
target = int(input("Value: "))
guess = 0
steps = 0
while guess < target:
    guess += 1
    steps += 1
print("Found", guess, "in", steps, "steps")
المُخرَج
Value: 731
Found 731 in 731 steps

الطريقة الثانية تبدأ بالمنتصف، وتسأل هل العدد المطلوب أعلى أم لا، وتحذف نصف المجال في كل دورة. هذه هي فكرة المحوّل بالتقريب المتتالي (ממיר קירוב עוקב)، المنتشر في المتحكّمات. يطبع البرنامج حدّي المجال بعد كل دورة:

target = int(input("Value: "))
low = 0
high = 1023
steps = 0
while low < high:
    mid = (low + high) // 2
    if mid < target:
        low = mid + 1
    else:
        high = mid
    steps += 1
    print(low, high)
print("Found", low, "in", steps, "steps")
المُخرَج
Value: 731
512 1023
512 767
640 767
704 767
704 735
720 735
728 735
728 731
730 731
731 731
Found 731 in 10 steps
العدد المطلوبالطريقة الأولىالطريقة الثانية
10Found 10 in 10 stepsFound 10 in 10 steps
731Found 731 in 731 stepsFound 731 in 10 steps
1023Found 1023 in 1023 stepsFound 1023 in 10 steps

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

ملاحظة: طرق بسيطة للنجاعة: نخرج من الحلقة حين نعرف الجواب، بالأمر break أو بشرط مركب. ولا نضع في الجسم حسابا لا يتغيّر من دورة إلى دورة. مثلا نحسب step = 5 / 1023 مرة واحدة قبل الحلقة في المحطة.

4.10 التداخل والمزج بين if وwhile – קינון ושילוב מבני if, while

جسم الحلقة يمكن أن يحتوي أي أمر، وكذلك جسم الشرط. هذا هو التداخل (קינון). وقد استعملنا if داخل while طوال الفصل، ويمكن أيضا أن نضع حلقة في فرع من شرط إذا لم نكن نحتاج إليها إلا في حالة معيّنة. وكل مستوى تداخل يضيف إزاحة.

شروط متسلسلة داخل حلقة:

مثال 4.15
مصدر جهد في المختبر يتحكم فيه المستخدم من قائمة. نكرر السؤال حتى يختار الخروج. هذا نمط كل قائمة خيارات: قيمة توقّف، وسلسلة elif في الجسم.
الحل:
volts = 12.0
choice = int(input("1 up, 2 down, 0 exit: "))
while choice != 0:
    if choice == 1:
        volts += 0.5
    elif choice == 2:
        volts -= 0.5
    else:
        print("Unknown choice")
    print("Output:", volts, "V")
    choice = int(input("1 up, 2 down, 0 exit: "))
print("Bye")
المُخرَج
1 up, 2 down, 0 exit: 1
Output: 12.5 V
1 up, 2 down, 0 exit: 2
Output: 12.0 V
1 up, 2 down, 0 exit: 7
Unknown choice
Output: 12.0 V
1 up, 2 down, 0 exit: 0
Bye

حلقة داخل حلقة:

عندما توضع حلقة داخل حلقة، تُنفَّذ الحلقة الداخلية كلها في كل دورة من دورات الخارجية. تشبه عقربي الساعة: عقرب الدقائق يدور دورة كاملة في كل خطوة من خطوات عقرب الساعات.

مثال 4.16
نكوّن جدول تيارات بالملي أمبير: كل سطر لجهد (3 و6 و9 V)، وكل عمود لمقاومة (100 و200 و300 Ω). الوسيط end يبقينا في السطر نفسه، وprint() الفارغ ينهي السطر.
الحل:
v = 3
while v <= 9:
    print(v, "V:", end=" ")
    r = 100
    while r <= 300:
        print(round(v / r * 1000, 1), end=" ")
        r += 100
    print()
    v += 3
المُخرَج
3 V: 30.0 15.0 10.0 
6 V: 60.0 30.0 20.0 
9 V: 90.0 45.0 30.0 
خطأ شائع: لو نقلنا r = 100 إلى ما قبل الحلقة الخارجية، لبقيت r بعد أول سطر عند 400، فلا تعمل الحلقة الداخلية مرة أخرى، وتبقى بقية السطور فارغة: 3 V: 30.0 15.0 10.0 / 6 V: / 9 V: . التهيئة مكانها داخل الحلقة الخارجية، قبل الداخلية مباشرة.

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

row = 1
while row <= 2:
    col = 1
    while col < row:
        print(row, col)
        col += 1
    row += 1
البرنامج
الأمرrowcolrow <= 2col < rowالمُخرَج
row = 11
while row <= 2:True
col = 11
while col < row:False
row += 12
while row <= 2:True
col = 1
while col < row:True
print(row, col)2 1
col += 12
while col < row:False
row += 13
while row <= 2:False
متابعة حلقتين متداخلتين
سؤال 4.8: ماذا يطبع البرنامج التالي؟
a = 1
while a <= 3:
    b = a
    while b <= 3:
        print(a * b, end=" ")
        b += 1
    print()
    a += 1
تنبيه: الأمر break يخرج من الحلقة الداخلية فقط، وتستمر الخارجية كالمعتاد.
مثال 4.17
لوحة مفاتيح فيها أربعة صفوف وثلاثة أعمدة. يفحص المتحكّم المفاتيح واحدا بعد الآخر، حتى يجد المفتاح المضغوط. للخروج من الحلقتين معا نستعمل علما بوليانيا موجودا في شرطي الحلقتين. قيمتا المفتاح المضغوط مكتوبتان هنا مباشرة في البرنامج.
الحل:
key_row = 2
key_col = 2
found = False
row = 1
while row <= 4 and not found:
    col = 1
    while col <= 3 and not found:
        print("Testing", row, col)
        if row == key_row and col == key_col:
            found = True
        col += 1
    row += 1
print("Key found:", found)
المُخرَج
Testing 1 1
Testing 1 2
Testing 1 3
Testing 2 1
Testing 2 2
Key found: True
مشروع الكتاب: محطة القياس · المرحلة 4 — المحطة تقيس مرة بعد مرة

في الفصلين السابقين قرأت المحطة قيمة واحدة وقررت. الآن تجمع قراءات المحوّل لجهد البطارية حتى قيمة التوقّف -1، ثم تكتب تقريرا: عدد العينات، متوسط الجهد، أدناه وأعلاه، وعدد العينات تحت 11.8 V. الحساب من الفصل 2 كما هو: خطوة المحوّل 5 / 1023، ومقسّم الجهد الذي يقسم على 4.

step = 5 / 1023
limit = 11.8
count = 0
total = 0
below = 0
adc = int(input("ADC battery (-1 stops): "))
while adc != -1:
    volts = adc * step * 4
    count += 1
    total += volts
    if count == 1 or volts > top:
        top = volts
    if count == 1 or volts < bottom:
        bottom = volts
    if volts < limit:
        below += 1
    adc = int(input("ADC battery (-1 stops): "))
if count > 0:
    print("Samples:", count)
    print("Average:", round(total / count, 2), "V")
    print("Min:", round(bottom, 2), "V")
    print("Max:", round(top, 2), "V")
    print("Below limit:", below)
else:
    print("No samples")
المُخرَج
ADC battery (-1 stops): 620
ADC battery (-1 stops): 633
ADC battery (-1 stops): 590
ADC battery (-1 stops): 578
ADC battery (-1 stops): -1
Samples: 4
Average: 11.83 V
Min: 11.3 V
Max: 12.38 V
Below limit: 2

الحلقة تجمع كل ما تعلمناه: قيمة توقّف، عدّاد، مُجمِّع، عدّاد مع شرط وقيم قصوى. ونحسب الخطوة مرة واحدة قبل الحلقة. نجرّب بأنفسنا: ماذا يطبع البرنامج إذا كانت القراءة الأولى -1؟ وكيف نضيف مصفاة ترفض قراءة أكبر من 1023 بالأمر continue؟ ننتبه إلى أن القراءة التالية يجب أن تُقرأ قبل continue، وإلا دارت الحلقة في مكانها.

أهمّ أفكار الفصل:

ملخّص أوامر الفصل:

الأمرعمله
while condition:يكرر الجسم ما دام الشرط يتحقّق
while True:حلقة لا تنتهي إلا بالأمر break
breakخروج فوري من الحلقة الأقرب
continueترك بقية الدورة والعودة إلى الشرط
count += 1تحديث عدّاد
total += xتحديث مُجمِّع
while x != -1:الاستمرار حتى قيمة التوقّف
print(x, end=" ")الطباعة بلا انتقال إلى سطر جديد

قاموس المصطلحات – מילון מונחים:

بالعربيةبالعبرية (كما تظهر في الامتحان)English
الحلقةלולאהLoop
التنفيذ المتكررביצוע חוזרRepeated execution
جسم الحلقةגוף הלולאהLoop body
الدورةסבב (איטרציה)Iteration
العدّادמונהCounter
المُجمِّعצוברAccumulator
قيمة التوقّفזקיףSentinel
مصفاة المُدخَلاتמסננת קלטInput filter
المصفاة العنيدةמסננת עקשניתPersistent input filter
القيمة القصوىערך קיצוןExtreme value
شرط الإنهاءתנאי סיוםStop condition
قانونا دي مورغانחוקי דה-מורגןDe Morgan laws
الحلقة اللانهائيةביצוע אינסופיInfinite loop
التداخلקינוןNesting
صحّة البرنامجנכונותCorrectness
الحالة الحدّيةמקרה קצהEdge case
النجاعةיעילותEfficiency
الإجابات النهائية لأسئلة الفصل
4.1 نكتب أمر القراءة وأمر الجمع 50 مرة لكل منهما. أما عدد لا يُعرف إلا أثناء التشغيل فلا يمكن أن يُكتب مسبقا في عدد الأسطر، بل يحتاج إلى حلقة.
4.2 4 6
4.3
duty = int(input("Duty (0-100): "))
while duty < 0 or duty > 100:
    duty = int(input("Duty (0-100): "))
print("Duty:", duty)
للمُدخَلات 120, 75 يطبع: Duty: 75
4.4 16 ، 0
4.5 4 16. المتغيّر a عدّاد لكل القيم، وb مُجمِّع للقيم التي تزيد على 5.
4.6 1 , 3 , 4 , end 5
4.7 (أ) تنتهي، لأن k تصل إلى قيمة سالبة. (ب) لا تنتهي: القيم هي 10, 6, 2, -2 ثم سالبة، فهي تتخطى الصفر ولا تساويه أبدا. (ج) لا تنتهي: k تكبر، والعدد الصحيح في بايثون بلا حد أقصى.
4.8 1 2 3 / 4 6 / 9

أسئلة للتمرين:

المستوى الأول — أساسي
1.
ماذا يطبع البرنامج؟
k = 2
s = 0
while k < 10:
    s += k
    k = k + 3
print(k, s)
الإجابات: 11 15
2.
نترجم شرط الإنهاء إلى شرط حلقة:
(أ) نتوقف عندما x == 0.
(ب) نتوقف عندما t >= 100 or n == 20.
(ج) نتوقف عندما a < 0 and b < 0.
الإجابات: (أ) x != 0 ، (ب) t < 100 and n != 20 ، (ج) a >= 0 or b >= 0
3.
نكتب مصفاة عنيدة تقرأ رقم قناة من 1 حتى 8، وتطبع رسالة عند كل رقم خاطئ.
الإجابات:
ch = int(input("Channel (1-8): "))
while ch < 1 or ch > 8:
    print("No such channel")
    ch = int(input("Channel (1-8): "))
print("Reading channel", ch)
للمُدخَلين 9, 3 يطبع في النهاية: Reading channel 3
4.
نجد الأخطاء الثلاثة في هذا البرنامج ونصلحها. المطلوب طباعة مضاعفات 5 من 5 حتى 20.
n = 1
while n <= 4
print(n * 5)
n =+ 1
الإجابات: تنقص النقطتان بعد الشرط، والأمران يحتاجان إلى إزاحة، والإشارة الصحيحة هي +=. أما =+ فهي إسناد العدد الموجب 1، وتؤدي إلى حلقة لا نهائية:
n = 1
while n <= 4:
    print(n * 5)
    n += 1
يطبع: 5 , 10 , 15 , 20
المستوى الثاني — متوسط
5.
نبني جدول متابعة للبرنامج التالي عندما يكون المُدخَل 30.
x = int(input("x: "))
n = 0
while x > 1:
    x = x // 3
    n += 1
print(x, n)
الإجابات:
الأمرxnx > 1المُخرَج
x = int(input("x: "))30x: 30
n = 00
while x > 1:True
x = x // 310
n += 11
while x > 1:True
x = x // 33
n += 12
while x > 1:True
x = x // 31
n += 13
while x > 1:False
print(x, n)1 3
6.
نقرأ مقاومات موصولة على التوازي حتى الصفر، ونطبع المقاومة المكافئة.
1 R = 1 R1 + 1 R2 + ...
الإجابات:
inverse = 0
r = float(input("R (0 stops): "))
while r != 0:
    inverse += 1 / r
    r = float(input("R (0 stops): "))
if inverse > 0:
    print("R total:", round(1 / inverse, 2))
للمُدخَلات 100, 300, 0 يطبع: R total: 75.0
7.
نقيس جهد ثنائيات باعثة للضوء حتى الصفر. الثنائي سليم إذا كان جهده بين 1.8 V و2.2 V شاملا الطرفين. نطبع عدد السليمة ونسبتها المئوية.
الإجابات:
good = 0
count = 0
v = float(input("LED V (0 stops): "))
while v != 0:
    count += 1
    if 1.8 <= v <= 2.2:
        good += 1
    v = float(input("LED V (0 stops): "))
if count > 0:
    print("Good:", good)
    print("Percent:", round(good * 100 / count, 1))
للمُدخَلات 2.0, 1.5, 2.2, 2.5, 0 يطبع: Good: 2 , Percent: 50.0
8.
تفقد بطارية 3% من سعتها الحالية كل سنة، وتبدأ من 100%. بعد كم سنة تهبط سعتها تحت 80%؟
الإجابات:
capacity = 100
years = 0
while capacity >= 80:
    capacity = capacity * 0.97
    years += 1
print("Years:", years)
print("Capacity:", round(capacity, 1))
يطبع: Years: 8 , Capacity: 78.4
المستوى الثالث — تحدٍّ
9.
نقرأ درجات حرارة حتى 999. نطبع طول أطول سلسلة متتالية من القراءات فوق 40 درجة.
الإجابات:
run = 0
best = 0
t = int(input("Temp (999 stops): "))
while t != 999:
    if t > 40:
        run += 1
        if run > best:
            best = run
    else:
        run = 0
    t = int(input("Temp (999 stops): "))
print("Longest hot run:", best)
للمُدخَلات 41, 45, 30, 42, 43, 44, 20, 999 يطبع: Longest hot run: 3
10.
ثنائيان يومضان معا في اللحظة صفر، دور الأول a ثانية ودور الثاني b ثانية، وهما عددان صحيحان موجبان. بعد كم ثانية يومضان معا من جديد؟
الإجابات:
a = int(input("LED A period: "))
b = int(input("LED B period: "))
t = a
while t % b != 0:
    t += a
print("Together after:", t)
للدورين 4, 6 يطبع: Together after: 12
11.
نبحث عن مقسّم جهد يعطي من 12 V خرجا قريبا قدر الإمكان من 5 V. نجرّب لكل من المقاومتين R1 وR2 كل القيم من 100 Ω حتى 1000 Ω بخطوة 100 Ω.
Vout = 12 · R2 R1 + R2
الإجابات:
goal = 5
best_err = 12
r1 = 100
while r1 <= 1000:
    r2 = 100
    while r2 <= 1000:
        vout = 12 * r2 / (r1 + r2)
        err = abs(vout - goal)
        if err < best_err:
            best_err = err
            best1 = r1
            best2 = r2
        r2 += 100
    r1 += 100
print("R1 =", best1, "R2 =", best2)
print("Error:", round(best_err, 3), "V")
يطبع: R1 = 700 R2 = 500 , Error: 0.0 V
12.
فحص الزوجية: نقرأ بايتات حتى -1. لكل بايت نعدّ الخانات التي قيمتها 1 بالقسمة المتكررة على 2، ونطبع عددها وباقي قسمته على 2.
الإجابات:
value = int(input("Byte (-1 stops): "))
while value != -1:
    ones = 0
    rest = value
    while rest > 0:
        ones += rest % 2
        rest = rest // 2
    print(value, "ones:", ones, "parity:", ones % 2)
    value = int(input("Byte (-1 stops): "))
للبايتين 5, 7 يطبع: 5 ones: 2 parity: 0 , 7 ones: 3 parity: 1

שאלות בגרות – أسئلة بجروت للفصل 4

السؤالان التاليان يحتاجان إلى ما تعلمناه في هذا الفصل فقط، ولذلك نحلهما كاملين. يظهر كل منهما بنصه العربي الرسمي كما ظهر في الامتحان.
بجروت 2024 السؤال 10 — قمصان وجرزايات
بجروت 2024
البندالمطلوبالإجابة
السؤال كلهنعدّ طلبات القمصان والجرزايات حتى عدد غير قانوني، ثم نطبع العددين والمبلغ الكلي.في الأسفل
الحل الكامل بالعربية: fahed-lab.com/exams/electronics-815381/815381/solutions/solution-2024

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

shirts = 0
sweaters = 0
choice = int(input("1 shirt, 2 sweater: "))
while choice == 1 or choice == 2:
    if choice == 1:
        shirts += 1
    else:
        sweaters += 1
    choice = int(input("1 shirt, 2 sweater: "))
total = shirts * 30 + sweaters * 50
print("Shirts:", shirts)
print("Sweaters:", sweaters)
print("Total:", total)
المُخرَج
1 shirt, 2 sweater: 1
1 shirt, 2 sweater: 2
1 shirt, 2 sweater: 1
1 shirt, 2 sweater: 1
1 shirt, 2 sweater: 2
1 shirt, 2 sweater: 7
Shirts: 3
Sweaters: 2
Total: 190

الأسماء shirts وsweaters بالإنجليزية كما تعوّدنا. ونفحص أيضا الحالة الحدّية: عدد غير قانوني من البداية، فيطبع البرنامج في السطر الأخير Total: 0.

بجروت 2023 السؤال 9 — تبرّعات لحملة انتخابية
بجروت 2023
البندالمطلوبالإجابة
السؤال كلهنقرأ رقم المرشّح ومبلغ التبرّع حتى يتحقّق شرط التوقف، ثم نطبع مجموع كل مرشّح، عدد المتبرّعين وأعلى تبرّع لكل مرشّح.في الأسفل
الحل الكامل بالعربية: fahed-lab.com/exams/electronics-815381/815381/solutions/solution-2023

شرط الإنهاء مركب: يصل المجموع الكلي إلى مليون أو يصل عدد المتبرّعين إلى ألف، فنترجمه كما في البند 4.5:

الشرط
نتوقف عندماsum1 + sum2 >= 1000000 or donors >= 1000
نستمر ما دامsum1 + sum2 < 1000000 and donors < 1000

ونحتاج إلى مُجمِّعين، عدّاد وقيمتين قصويين. يمكن أن تبدأ القيمتان القصويان من الصفر، لأن التبرّع مبلغ موجب، وهذا خلاف ما رأيناه مع الحرارة. ومع ألف تبرّع صغير تتوقف الحلقة بسبب عدد المتبرّعين، ويطبع البرنامج Donors: 1000. ونشغّله الآن على المثال الذي في نص السؤال:

sum1 = 0
sum2 = 0
max1 = 0
max2 = 0
donors = 0
while sum1 + sum2 < 1000000 and donors < 1000:
    candidate = int(input("Candidate: "))
    amount = float(input("Amount: "))
    donors += 1
    if candidate == 1:
        sum1 += amount
        if amount > max1:
            max1 = amount
    else:
        sum2 += amount
        if amount > max2:
            max2 = amount
print("Candidate 1 sum:", sum1)
print("Candidate 2 sum:", sum2)
print("Donors:", donors)
print("Max Donation for Candidate 1:", max1)
print("Max Donation for Candidate 2:", max2)
المُخرَج
Candidate: 1
Amount: 200000
Candidate: 2
Amount: 300000
Candidate: 1
Amount: 100000
Candidate: 1
Amount: 100000
Candidate: 2
Amount: 200000
Candidate: 1
Amount: 150000
Candidate 1 sum: 550000.0
Candidate 2 sum: 500000.0
Donors: 6
Max Donation for Candidate 1: 200000.0
Max Donation for Candidate 2: 300000.0

الأعداد هي نفسها التي في السؤال. الفرق الوحيد هو .0 في نهايتها، لأن المبلغ عدد حقيقي حسب نص السؤال، فقرأناه بالدالة float. وتتوقف الحلقة بعد المتبرّع السادس، حين يصبح المجموع 1050000.