Skip to content

Latest commit

 

History

History

plane-tickets

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 

Plane Tickets

Welcome to Plane Tickets on Exercism's Python Track. If you need help running the tests or submitting your code, check out HELP.md. If you get stuck on the exercise, check out HINTS.md, but try and solve it without using those first :)

Introduction

A generator is a function or expression that returns a special type of iterator called generator iterator. Generator-iterators are lazy: they do not store their values in memory, but generate their values when needed.

A generator function looks like any other function, but contains one or more yield expressions. Each yield will suspend code execution, saving the current execution state (including all local variables and try-statements). When the generator resumes, it picks up state from the suspension - unlike regular functions which reset with every call.

Constructing a generator

Generators are constructed much like other looping or recursive functions, but require a yield expression, which we will explore in depth a bit later.

An example is a function that returns the squares from a given list of numbers. As currently written, all input must be processed before any values can be returned:

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

You can convert that function into a generator like this:

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

The rationale behind this is that you use a generator when you do not need to produce all the values at once. This saves memory and processing power, since only the value you are currently working on is calculated.

Using a generator

Generators may be used in place of most iterables in Python. This includes functions or objects that require an iterable/iterator as an argument.

To use the squares_generator() generator:

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

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

Values within a generator can also be produced/accessed via the next() function. next() calls the __next__() method of a generator-iterator object, "advancing" or evaluating the code up to its yield expression, which then "yields" or returns a value:

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

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

When a generator-iterator is fully consumed and has no more values to return, it throws a StopIteration error.

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

Generator-iterators are a special sub-set of [iterators][iterator].
`Iterators` are the mechanism/protocol that enables looping over _iterables_.
Generator-iterators and the iterators returned by common Python [`iterables`][iterables] act very similarly, but there are some important differences to note:

- They are _[lazily evaluated][lazy evaluation]_; iteration is _one-way_ and there is no "backing up" to a previous value.
- They are _consumed_ by iterating over the returned values; there is no resetting or saving in memory.
- They are not sortable and cannot be reversed.
- They are not sequence types, and _do not_ have `indexes`. 
  You cannot reference a previous or future value using addition or subtraction and you cannot use bracket (`[]`) notation or slicing.
- They cannot be used with the `len()` function, as they have no length.
- They can be _finite_ or _infinite_ - be careful when collecting all values from an _infinite_ `generator-iterator`!

[iterator]: https://docs.python.org/3.11/glossary.html#term-iterator
[iterables]: https://wiki.python.org/moin/Iterator
[lazy evaluation]: https://en.wikipedia.org/wiki/Lazy_evaluation

The yield expression

The yield expression is very similar to the return expression. Unlike the return expression, yield gives up values to the caller at a specific point, suspending evaluation/return of any additional values until they are requested. When yield is evaluated, it pauses the execution of the enclosing function and returns any values of the function at that point in time. The function then stays in scope, and when __next__() is called, execution resumes until yield is encountered again.

Using `yield` expressions is prohibited outside of functions.
>>> 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

Why Create a Generator?

Generators are useful in a lot of applications.

When working with a potentially large collection of values, you might not want to put all of them into memory. A generator can be used to work on larger data piece-by-piece, saving memory and improving performance.

Generators are also very helpful when a process or calculation is complex, expensive, or infinite:

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

Now whenever __next__() is called on the infinite_sequence object, it will return the previous number + 1.

Instructions

Conda Airlines is the programming-world's biggest airline, with over 10,000 flights a day!

They are currently assigning all seats to passengers by hand; this will need to be automated.

They have asked you to create software to automate passenger seat assignments. They require your software to be memory efficient and performant.

1. Generate seat letters

Conda wants to generate seat letters for their airplanes. An airplane is made of rows of seats. Each row has 4 seats. The seats in each row are always named A, B, C, and D. The first seat in the row is A, the second seat in the row is B, and so on. After reaching D, it should start again with A.

Implement a function generate_seat_letters(<number>) that accepts an int that holds how many seat letters to be generated. The function should then return an iterable of seat letters.

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

2. Generate seats

Conda wants a system that can generate a given number of seats for their airplanes. Each airplane has 4 seats in each row. The rows are defined using numbers, starting from 1 and going up. The seats should be ordered, like: 1A, 1B, 1C, 1D, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, ...

Here is an example:

x 1 2
Row 5 21
Seat letter A D
Result 5A 21D

Many airlines do not have row number 13 on their flights, due to superstition amongst passengers. Conda Airlines also follows this convention, so make sure you don't generate seats for row number 13.

Implement a function generate_seats(<number>) that accepts an int that holds how many seats to be generated. The function should then return an iterable of seats given.

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

3. Assign seats to passengers

Now that you have a function that generates seats, you can use it to assign seats to passengers.

Implement a function assign_seats(<passengers>) that accepts a list of passenger names. The function should then return a dictionary of passenger as key, and seat_number as value.

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

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

4. Ticket codes

Conda Airlines would like to have a unique code for each ticket. Since they are a big airline, they have a lot of flights. This means that there are multiple flights with the same seat number. They want you to create a system that creates a unique ticket that is 12 characters long string code for identification.

This code begins with the assigned_seat followed by the flight_id. The rest of the code is appended by 0s.

Implement a function generate_codes(<seat_numbers>, <flight_id>) that accepts a list of seat_numbers and a string with the flight number. The function should then return a generator that yields a 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'

Source

Created by

  • @J08K

Contributed to by

  • @BethanyG
  • @kytrinyx
  • @meatball133