Range و Enumerate و Zip
ولّد متتاليات الأرقام، وتتبّع المؤشرات، واجمع بين الكائنات القابلة للتكرار.
- استخدام range() لتوليد متتاليات الأرقام
- استخدام enumerate() للحصول على المؤشر + القيمة أثناء التكرار
- استخدام zip() للتكرار عبر متتاليات متعددة بالتوازي
- كتابة حلقات بايثونية تتجنب تتبّع المؤشر يدويًا
أدواتٌ ثلاثٌ تُغني عن العدّ اليدوي
منحتك الحلقاتُ التكرارَ؛ وهذه الدرسُ يسلمك الأدواتَ الثلاث التي تُخرج العدّ من يديك. كل أداةٍ تستبدل عادةً عُلّمت كتابتَها يدويًّا، وكلٌّ منها جوابٌ لانزعاجٍ متكرر: توليد الأعداد، والحاجة إلى موضع عنصرٍ، وزدوج قائمتين. وهي معًا الفرق بين حلقةٍ تطبع وحلقةٍ تُقرأ.
Range: المتتالية الحسابية، كسَلًا
جمعت في الدرس السابق بـ range(5). هي تستحق نظرةً عن قرب ، إنها الأداة الكلاسيكية لـ «افعل هذا عددًا معلومًا من المرات»:
for i in range(5):
print(i) # 0 1 2 3 4لـ range ثلاث صيغٍ، تطابقُ المتتاليةَ الحسابية :
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وسيطٌ واحد يعطي ؛ ووسيطان يعطيان الفترة شبه المفتوحة ؛ وثلاثةٌ تضيفان الفرقَ المشترك . والأهم أن range كسولة: تسجّل المعاملات وتحسب كل قيمةٍ فقط حين تطلبها الحلقة. سؤالُ مليون خطوةٍ لا يكلف ذاكرةً أكثر من سؤال خمسٍ ، فالمتتالية لا تُجسَّد أبدًا.
Enumerate: الموضع دون العدّاد
تريد موضع كل عنصر؟ غريزة المبتدئ عدّادٌ يدوي:
fruits = ["apple", "banana", "cherry"]
i = 0
for fruit in fruits:
print(f"{i}: {fruit}")
i += 1إنّ i += 1 إغراءٌ بالخلل: انسَ واحدًا وتختلط تسميات المواضع. تنتج enumerate النصفين في خطوةٍ واحدة ، المؤشر والعنصر ، فلا يبقى شيءٌ تُبقي على اطراده:
for i, fruit in enumerate(fruits):
print(f"{i}: {fruit}")
# مُحصو الكلام الذين يُرقمون الناس من 1:
for i, fruit in enumerate(fruits, start=1):
print(f"{i}: {fruit}")حيث يكتب الرياضي ليلصق الموضع بالقيمة، تُسلّم enumerate الزوجَ لجسم الحلقة مباشرة.
Zip: اصطفافٌ بالموقع
قائمتان متوازيتان ، أسماء ودرجات ، تصرخان أن تُقرآ معًا. تصطفّ zip بينهما عنصرًا بعنصر:
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الاقتران هو الحيلةَ الديكارتية: تركض عبر القائمتين بمشبكٍ واحد مكوّنةً الأزواج . وإذا اختلفت القائمتان طولًا، يتوقف الاقتران عند الأقصر، فلا يُزدوج شيءٌ منقوصًا أبدًا. وإن احتجت الذيل المائل أيضًا، فيملؤه itertools.zip_longest:
import itertools
for pair in itertools.zip_longest([1, 2], [3, 4, 5], fillvalue=0):
print(pair) # (1, 3), (2, 4), (0, 5) — لا تضيع قيمةمثالٌ محلول: سجلُّ الصفِّ
شاهدِ الأدواتِ الثلاثَ تعملُ معًا. تحفظُ معلمةٌ قائمةَ أسماءٍ وقائمةً موازيةً من الدرجاتِ، وتريدُ تقريرًا مرقّمًا:
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 الأزواجَ بدءًا من واحدٍ. أربعةُ إيماءاتٍ كانت ستكلّفك عَدَّادًا مكتوبًا باليدِ تُقرأ الآن كالجملةِ التي تصفها ، يلتصقُ الموضعُ بالقيمةِ زوجًا زوجًا، تمامًا كما يُلصق مؤشرًا بكلِّ حدٍّ.
أخطاء شائعة
rangeمستبعِدةٌ في الأعلى. تنتجrange(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]))؟