المسارات
/
Python
Python
/
التمارين
/
تذاكر الطائرة
تذاكر الطائرة

تذاكر الطائرة

تمرين تعلّمي

مقدمة

generator هو دالة أو تعبير يُرجع نوعًا خاصًا من المكرِّرات يُسمى generator iterator. Generator-iterators هي مكرِّرات كسولة: فهي لا تخزّن values في الذاكرة، بل تولّد قيمها عند الحاجة.

دالة المولِّد تبدو كأي دالة أخرى، لكنها تحتوي على تعبير yield واحد أو أكثر. كل yield سيُعلّق تنفيذ الكود، مع حفظ حالة التنفيذ الحالية (بما في ذلك جميع المتغيرات المحلية وعبارات try). وعندما يستأنف المولِّد عمله، يستكمل الحالة من نقطة التعليق، بخلاف الدوال العادية التي تُعاد تهيئتها مع كل استدعاء.

إنشاء مولِّد

تُبنى المولِّدات على نحو يشبه كثيرًا الدوال الأخرى ذات الحلقات أو الدوال التي تستدعي نفسها، لكنها تحتاج إلى تعبير yield، وسنتعمّق في شرحه بعد قليل.

مثال على ذلك دالة تُرجع مربّعات الأعداد الواردة في قائمة معطاة. بالصيغة المكتوبة حاليًا، يجب معالجة كل المدخلات قبل أن يمكن إرجاع أي قيمة:

>>> def squares(list_of_numbers):
...     squares = []
...     for number in list_of_numbers:
...         squares.append(number ** 2)
...     return squares

يمكنك تحويل تلك الدالة إلى مولِّد هكذا:

>>> def squares_generator(list_of_numbers):
...     for number in list_of_numbers:
...         yield number ** 2

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

استخدام المولِّد

يمكن استخدام المولِّدات بدلًا من معظم iterables في Python. ويشمل ذلك الدوال أو الكائنات التي تتطلب iterable/iterator كوسيط.

لاستخدام مولِّد squares_generator():

>>> squared_numbers = squares_generator([1, 2, 3, 4])

>>> for square in squared_numbers:
...     print(square)
...
1
4
9
16

يمكن أيضًا إنتاج القيم داخل generator أو الوصول إليها عبر الدالة next(). تستدعي next() الطريقة __next__() في كائن المولِّد المكرِّر، فتقوم «بتقديم» الكود أو تقييمه حتى تعبير yield الخاص به، الذي «يولّد» أو يُرجع قيمة عندئذ:

>>> squared_numbers = squares_generator([1, 2])

>>> next(squared_numbers)
1
>>> next(squared_numbers)
4

عندما يُستهلك generator-iterator بالكامل ولا تبقى لديه قيم يُرجعها، فإنه يطلق خطأ StopIteration.

>>> next(squared_numbers)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
Note

المولِّدات المكرِّرة مجموعة فرعية خاصة من المكرِّرات. Iterators هي الآلية أو البروتوكول الذي يتيح المرور على العناصر القابلة للتكرار. تتصرّف المولِّدات المكرِّرة والمكرِّرات التي تُرجعها iterables الشائعة في Python بطريقة متشابهة جدًا، لكن هناك بعض الفروق المهمة التي يجب ملاحظتها:

  • إنها تُقيَّم بتكاسل؛ فالتكرار في اتجاه واحد ولا يوجد «رجوع» إلى قيمة سابقة.
  • إنها تُستهلك بالمرور على القيم المُرجَعة؛ فلا إعادة ضبط ولا حفظ في الذاكرة.
  • لا يمكن ترتيبها ولا عكسها.
  • إنها ليست من أنواع المتتاليات، و_ليس_ لها indexes. لا يمكنك الإشارة إلى قيمة سابقة أو لاحقة باستخدام الجمع أو الطرح، ولا يمكنك استخدام صيغة الأقواس المربعة ([]) أو التقطيع.
  • لا يمكن استخدامها مع الدالة len()، لأنه ليس لها طول.
  • يمكن أن تكون محدودة أو لانهائية، فاحذر عند جمع كل القيم من generator-iterator لانهائي!

تعبير yield

يشبه تعبير yield كثيرًا تعبير return. و_بخلاف_ تعبير return، فإن yield يسلّم القيم إلى المستدعي عند نقطة محددة، فيُعلّق تقييم أو إرجاع أي قيم إضافية حتى تُطلب. وعند تقييم yield، فإنه يوقف تنفيذ الدالة الحاوية ويُرجع أية قيم للدالة عند تلك اللحظة. ثم تبقى الدالة في النطاق، وعند استدعاء __next__() يستأنف التنفيذ حتى يُصادف yield مرة أخرى.

Note

يُحظر استخدام تعبيرات yield خارج الدوال.

>>> def infinite_sequence():
...     current_number = 0
...     while True:
...         yield current_number
...         current_number += 1

>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
0
>>> lets_try.__next__()
1

لماذا ننشئ مولِّدًا؟

المولِّدات مفيدة في كثير من التطبيقات.

عند التعامل مع مجموعة قيم قد تكون كبيرة، قد لا ترغب في وضعها كلها في الذاكرة. ويمكن استخدام المولِّد لمعالجة بيانات أكبر قطعة قطعة، مما يوفّر الذاكرة ويحسّن الأداء.

تكون المولِّدات مفيدة أيضًا عندما تكون العملية أو الحساب معقدًا أو مكلفًا أو لانهائيًا:

>>> def infinite_sequence():
...     current_number = 0
...     while True:
...         yield current_number
...         current_number += 1

الآن، كلما استُدعيت __next__() على كائن infinite_sequence، فستُرجع العدد السابق + 1.

التعليمات

Conda Airlines هي أكبر شركة طيران في عالم البرمجة، بأكثر من 10,000 رحلة يوميًا!

وهي تُسند حاليًا جميع المقاعد إلى المسافرين يدويًا؛ وسيلزم أتمتة ذلك.

وقد طلبوا منك أنت إنشاء برمجية لأتمتة إسناد مقاعد المسافرين. وهم يطلبون أن تكون برمجيتك موفّرة للذاكرة وعالية الأداء.

1. توليد أحرف المقاعد

تريد Conda توليد أحرف المقاعد لطائراتها. تتكوّن الطائرة من صفوف مقاعد. يحتوي كل صف على 4 مقاعد. تُسمّى المقاعد في كل صف دائمًا A وB وC وD. المقعد الأول في الصف هو A، والمقعد الثاني في الصف هو B، وهكذا. بعد الوصول إلى D، يجب أن تبدأ من جديد بـ A.

نفّذ دالة generate_seat_letters(<number>) تقبل int يمثّل عدد أحرف المقاعد المطلوب توليدها. ويجب أن تُرجع الدالة بعد ذلك كائنًا قابلًا للتكرار من أحرف المقاعد.

>>> letters = generate_seat_letters(4)
>>> next(letters)
"A"
>>> next(letters)
"B"

2. توليد المقاعد

تريد Conda نظامًا يمكنه توليد عدد محدّد من المقاعد لطائراتها. كل طائرة لديها 4 مقاعد في كل صف. تُعرَّف الصفوف باستخدام الأرقام، بدءًا من 1 وتصاعدًا. يجب ترتيب المقاعد على النحو التالي: 1A، 1B، 1C، 1D، 2A، 2B، 2C، 2D، 3A، 3B، 3C، 3D، ...

وإليك مثال:

x 1 2
الصف 5 21
حرف المقعد A D
النتيجة 5A 21D

لا يوجد لدى كثير من شركات الطيران الصف رقم 13 في رحلاتها، بسبب الخرافات بين المسافرين. وتتبع Conda Airlines أيضًا هذا التقليد، لذا تأكّد من عدم توليد مقاعد للصف رقم 13.

نفّذ دالة generate_seats(<number>) تقبل int يمثّل عدد المقاعد المطلوب توليدها. ويجب أن تُرجع الدالة بعد ذلك كائنًا قابلًا للتكرار من المقاعد المطلوبة.

>>> seats = generate_seats(10)
>>> next(seats)
"1A"
>>> next(seats)
"1B"

3. إسناد المقاعد إلى المسافرين

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

نفّذ دالة assign_seats(<passengers>) تقبل list من أسماء المسافرين. ويجب أن تُرجع الدالة بعد ذلك قاموسًا يكون فيه passenger هو المفتاح، وseat_number هو القيمة.

>>> passengers = ['Jerimiah', 'Eric', 'Bethany', 'Byte', 'SqueekyBoots', 'Bob']

>>> assign_seats(passengers)
{'Jerimiah': '1A', 'Eric': '1B', 'Bethany': '1C', 'Byte': '1D', 'SqueekyBoots': '2A', 'Bob': '2B'}

4. رموز التذاكر

تريد Conda Airlines أن يكون لديها رمز فريد لكل تذكرة. ولأنها شركة طيران كبيرة، فلديها الكثير من الرحلات. وهذا يعني وجود رحلات متعددة تحمل رقم المقعد نفسه. يريدون منك إنشاء نظام يصوغ تذكرة فريدة على شكل سلسلة نصية بطول 12 حرفًا.

يبدأ هذا الكود بـ assigned_seat متبوعًا بـ flight_id. ويُملأ باقي الكود بـ 0s.

نفّذ دالة generate_codes(<seat_numbers>, <flight_id>) تقبل list من seat_numbers وstring يحوي رقم الرحلة. ويجب أن تُرجع الدالة بعد ذلك generator يُنتج ticket_number.

>>> seat_numbers = ['1A', '17D']
>>> flight_id = 'CO1234'
>>> ticket_ids = generate_codes(seat_numbers, flight_id)

>>> next(ticket_ids)
'1ACO12340000'
>>> next(ticket_ids)
'17DCO1234000'
تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة
Python Exercism

مستعد لبدء تذاكر الطائرة؟

سجّل في Exercism لتتعلّم وتتقن Python عبر 17 مفهومًا146 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.