Треки
/
Python
Python
/
Вправи
/
Авіаквитки
Авіаквитки

Авіаквитки

Навчальна вправа

Вступ

generator - це функція або вираз, що повертає особливий тип ітератора, який називається generator iterator. Generator-iterator-и ліниві: вони не зберігають свої 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

Сенс у тому, що генератор використовують, коли не потрібно отримувати всі значення одразу. Це заощаджує памʼять і обчислювальні ресурси, адже обчислюється лише те значення, з яким ми працюємо в цей момент.

Використання генератора

У Python генератори можна використовувати замість більшості iterables. Це стосується функцій та обʼєктів, які приймають 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-iterables, поводяться дуже схоже, але варто звернути увагу на кілька важливих відмінностей:

  • Вони обчислюються ліниво; ітерація є односторонньою, і «відкату» до попереднього значення немає.
  • Вони вичерпуються, коли ми перебираємо повернуті значення; скинути їх чи зберегти в памʼяті не можна.
  • Їх не можна сортувати й не можна розвертати.
  • Вони не належать до послідовностей і не мають 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

Тепер, коли для обʼєкта infinite_sequence викликають __next__(), він повертатиме попереднє число + 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 хотіла б мати унікальний код для кожного квитка. Оскільки це велика авіакомпанія, у неї багато рейсів. Це означає, що є багато рейсів з однаковим номером місця. Вони хочуть, щоб ми створили систему, яка формує унікальний квиток - рядок тексту (англ. string) завдовжки 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 вправ та справжнє наставництво від людей, і все це безкоштовно.