PyDA Course
📖 درس 10 مبتدئ ⏱ 12 دقيقة ⚡ +10 XP ColabKagglenbviewerBinderDeepnoteGitHub range · enumerate · zip · iteration

Range و Enumerate و Zip

ولّد متتاليات الأرقام، وتتبّع المؤشرات، واجمع بين الكائنات القابلة للتكرار.

🎯 ماذا ستتعلم:
  • استخدام range() لتوليد متتاليات الأرقام
  • استخدام enumerate() للحصول على المؤشر + القيمة أثناء التكرار
  • استخدام zip() للتكرار عبر متتاليات متعددة بالتوازي
  • كتابة حلقات بايثونية تتجنب تتبّع المؤشر يدويًا

أدواتٌ ثلاثٌ تُغني عن العدّ اليدوي

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

Range: المتتالية الحسابية، كسَلًا

جمعت في الدرس السابق بـ range(5). هي تستحق نظرةً عن قرب ، إنها الأداة الكلاسيكية لـ «افعل هذا عددًا معلومًا من المرات»:

python
for i in range(5):
    print(i)  # 0 1 2 3 4

لـ range ثلاث صيغٍ، تطابقُ المتتاليةَ الحسابية a,a+d,a+2d,a, a+d, a+2d, \ldots:

python
range(5)        # 0, 1, 2, 3, 4
range(2, 8)     # 2, 3, 4, 5, 6, 7
range(0, 20, 3) # 0, 3, 6, 9, 12, 15, 18

وسيطٌ واحد يعطي 0,1,,n10, 1, \ldots, n-1؛ ووسيطان يعطيان الفترة شبه المفتوحة [start,stop)[\text{start}, \text{stop})؛ وثلاثةٌ تضيفان الفرقَ المشترك dd. والأهم أن range كسولة: تسجّل المعاملات وتحسب كل قيمةٍ فقط حين تطلبها الحلقة. سؤالُ مليون خطوةٍ لا يكلف ذاكرةً أكثر من سؤال خمسٍ ، فالمتتالية لا تُجسَّد أبدًا.

Enumerate: الموضع دون العدّاد

تريد موضع كل عنصر؟ غريزة المبتدئ عدّادٌ يدوي:

python
fruits = ["apple", "banana", "cherry"]

i = 0
for fruit in fruits:
    print(f"{i}: {fruit}")
    i += 1

إنّ i += 1 إغراءٌ بالخلل: انسَ واحدًا وتختلط تسميات المواضع. تنتج enumerate النصفين في خطوةٍ واحدة ، المؤشر والعنصر ، فلا يبقى شيءٌ تُبقي على اطراده:

python
for i, fruit in enumerate(fruits):
    print(f"{i}: {fruit}")

# مُحصو الكلام الذين يُرقمون الناس من 1:
for i, fruit in enumerate(fruits, start=1):
    print(f"{i}: {fruit}")

حيث يكتب الرياضي bi=ai+ib_i = a_i + i ليلصق الموضع بالقيمة، تُسلّم enumerate الزوجَ (i,ai)(i, a_i) لجسم الحلقة مباشرة.

Zip: اصطفافٌ بالموقع

قائمتان متوازيتان ، أسماء ودرجات ، تصرخان أن تُقرآ معًا. تصطفّ zip بينهما عنصرًا بعنصر:

python
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]

for name, score in zip(names, scores):
    print(f"{name}: {score}")
# Alice: 85
# Bob: 92
# Charlie: 78

الاقتران هو الحيلةَ الديكارتية: تركض عبر القائمتين بمشبكٍ واحد مكوّنةً الأزواج (n0,s0),(n1,s1),(n_0, s_0), (n_1, s_1), \ldots. وإذا اختلفت القائمتان طولًا، يتوقف الاقتران عند الأقصر، فلا يُزدوج شيءٌ منقوصًا أبدًا. وإن احتجت الذيل المائل أيضًا، فيملؤه itertools.zip_longest:

python
import itertools
for pair in itertools.zip_longest([1, 2], [3, 4, 5], fillvalue=0):
    print(pair)  # (1, 3), (2, 4), (0, 5) — لا تضيع قيمة

مثالٌ محلول: سجلُّ الصفِّ

شاهدِ الأدواتِ الثلاثَ تعملُ معًا. تحفظُ معلمةٌ قائمةَ أسماءٍ وقائمةً موازيةً من الدرجاتِ، وتريدُ تقريرًا مرقّمًا:

python
names = ["Dina", "Omar", "Sara"]
scores = [78, 91, 85]

for i, (name, score) in enumerate(zip(names, scores), start=1):
    print(f"#{i} {name}: {score}")
# #1 Dina: 78
# #2 Omar: 91
# #3 Sara: 85

print(f"Top score: {max(scores)}")   # Top score: 91

اقرأ رأسَ الحلقةِ من الداخلِ إلى الخارج: يزاوجُ zip كلَّ اسمٍ بدرجتِه؛ وتفكّكُ الأقواسُ (name, score) ذلك الزوجَ؛ ويُرقّم enumerate الأزواجَ بدءًا من واحدٍ. أربعةُ إيماءاتٍ كانت ستكلّفك عَدَّادًا مكتوبًا باليدِ تُقرأ الآن كالجملةِ التي تصفها ، يلتصقُ الموضعُ بالقيمةِ زوجًا زوجًا، تمامًا كما يُلصق bi=ai+ib_i = a_i + i مؤشرًا بكلِّ حدٍّ.

أخطاء شائعة

  • range مستبعِدةٌ في الأعلى. تنتج range(5) الأعدادَ 0,1,2,3,40, 1, 2, 3, 4 ، خمسة أعدادٍ لا يساوي أحدُها 55. فكّر كفترةٍ شبه مفتوحة، [0,5)[0, 5).
  • enumerate فوق قاموس. تكرارُ القاموس يعطي مفاتيحه؛ وسترقم enumerate المفاتيح لا الأزواج. استخدم dict.items() حين تريد المفتاح والقيمة.
  • zip بأطوالٍ غير متساوية. العناصرُ بعد المدخل الأقصر تتلاشى بصمت. انتبه للفقدان، أو عبّئ بـ zip_longest.
  • zip مكرِّرٌ ذو استخدامٍ واحد. يسلّمك p = zip(a, b) في بايثونِ 3 مكرِّرًا لا قائمةً: يستهلكه list(p)، فيكونَ list(p) الثاني فارغًا. حوّله مبكرًا بـ list(zip(a, b)) متى ستعودُ لزيارةِ الأزواجِ.

🧩 تحديات

🧩 تحدٍّ ، فكّر أولًا ثم أظهر

استخدم enumerate لتطبع كل لونٍ في colors = ["red", "green", "blue"] مع موضعه بدءًا من 1.

💡 الجواب: for i, color in enumerate(colors, 1): print(f"{i}. {color}") ، يعيد وسيطُ start ترقيمَ الأزواج من واحدٍ.

🧩 تحدٍّ ، فكّر أولًا ثم أظهر

مع keys = ["a", "b"] وvalues = [1, 2]، استخدم zip لبناء قاموسٍ.

💡 الجواب: dict(zip(keys, values)){"a": 1, "b": 2} ، تصير الأزواجُ المصطفّةُ مداخلَ الخريطة.

🤔 أسئلة سقراطية

  • لماذا تفضّل range على كتابة القائمة [0, 1, 2, 3, 4]؟ وماذا يتغير لو حملت القائمة مليونَ عددٍ؟
  • ما دام zip يتوقف عند المدخل الأقصر، فكيف تكشف أيَّ الجانبين كان أقصر؟ ومتى تهمّ تلك التفرقة؟
  • هل يستطيع enumerate أن يتمشى فوق قاموس؟ وما الذي ترقّمه المؤشرات بالضبط؟

✅ فحص سريع

١. ما ناتج list(range(1, 10, 2))؟

٢. ماذا ترجع list(zip([1, 2], [3, 4, 5]))؟

rangeenumeratezipiteration