Skip to content

Commit

Permalink
Launch plane tickets concept exercise in beta (#3530)
Browse files Browse the repository at this point in the history
* Tweak plane tickets concept exercise

I did a small pass through to tweak it a bit for
flow and readability.

This exercise is ready to be launched.

* Turn on plane tickets exercise

* (BG) Rather More Edits than Anticipated:

This all started with me re-editing the docstrings to comply with https://peps.python.org/pep-0257/, but then it snowballed.

Turns out that typing.Generator (https://docs.python.org/3/library/typing.html#typing.Generator) has been deprecated as an alias, so the tests needed some love...and that snowballed into reviewing the other docs.

Also renamed the stub and test files to be in line with what we did with other concept exercises.

---------

Co-authored-by: BethanyG <BethanyG@users.noreply.github.com>
kytrinyx and BethanyG authored Oct 31, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 939b0e3 commit a986374
Showing 12 changed files with 272 additions and 172 deletions.
12 changes: 12 additions & 0 deletions concepts/generators/about.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# About

A `generator` is a function or expression that returns a special type of [iterator][iterator] called [generator iterator][generator-iterator].
`Generator-iterators` are [lazy][lazy iterator]: 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][yield expression].
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](#the-yield-expression), which we will explore in depth a bit later.
@@ -131,5 +139,9 @@ Generators are also very helpful when a process or calculation is _complex_, _ex

Now whenever `__next__()` is called on the `infinite_sequence` object, it will return the _previous number_ + 1.


[generator-iterator]: https://docs.python.org/3.11/glossary.html#term-generator-iterator
[iterables]: https://wiki.python.org/moin/Iterator
[iterator]: https://docs.python.org/3.11/glossary.html#term-iterator
[lazy iterator]: https://en.wikipedia.org/wiki/Lazy_evaluation
[yield expression]: https://docs.python.org/3.11/reference/expressions.html#yield-expressions
10 changes: 8 additions & 2 deletions concepts/generators/introduction.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
# Introduction

A generator in Python is a _callable function_ that returns a [lazy iterator][lazy iterator].
A generator in Python is a _callable function_ or expression that returns a special type of [iterator][iterator] called [generator iterator][generator-iterator].
`Generator-iterators` are [lazy][lazy iterator]: they do not store their `values` in memory, but _generate_ their values when needed.

_Lazy iterators_ are similar to `lists`, and other `iterators`, but with one key difference: 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][yield expression].
Each `yield` will suspend code execution, saving the current execution state (_including all local variables and try-statements_).
When the generator function resumes, it picks up state from the suspension - unlike regular functions which reset with every call.

[lazy iterator]: https://en.wikipedia.org/wiki/Lazy_evaluation
[iterator]: https://docs.python.org/3.11/glossary.html#term-iterator
[yield expression]: https://docs.python.org/3.11/reference/expressions.html#yield-expressions
[generator-iterator]: https://docs.python.org/3.11/glossary.html#term-generator-iterator
6 changes: 5 additions & 1 deletion concepts/generators/links.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
[
{
"url": "https://docs.python.org/3.10/reference/expressions.html#yield-expressions",
"url": "https://docs.python.org/3.11/reference/expressions.html#yield-expressions",
"description": "Official Python 3.10 docs for the yield expression."
},
{
"url": "https://en.wikipedia.org/wiki/Lazy_evaluation",
"description": "Wikipedia page about lazy evaluation"
},
{
"url": "https://www.pythonmorsels.com/iterators/",
"description": "Python Morsels: Iterators & Generators"
},
{
"url": "https://realpython.com/introduction-to-python-generators/",
"description": "Real python, introduction to generators and yield"
2 changes: 1 addition & 1 deletion config.json
Original file line number Diff line number Diff line change
@@ -193,7 +193,7 @@
"uuid": "3ba3fc89-3e1b-48a5-aff0-5aeaba8c8810",
"concepts": ["generators"],
"prerequisites": ["conditionals", "dicts", "lists", "loops", "classes"],
"status": "wip"
"status": "beta"
},
{
"slug": "log-levels",
10 changes: 5 additions & 5 deletions exercises/concept/plane-tickets/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -4,19 +4,19 @@

- The returned value should be of _type_ `generator`.
- You can have a sequence of letters from `A` to `D` and cycle through them.
- And use `yield` to return the next letter.
- Use `yield` to return the next letter.

## 2. Generate an amount of seats
## 2. Generate seats

- The returned value should be of _type_ `generator`.
- Row `13` should be skipped, so go from `12` to `14`.
- Keep in mind that the returned values should be ordered from low to high. `1A, 1B, 2A, ...`
- It might be good to reuse or call other functions you have already completed here.
- Here it might be good to reuse or call functions you have already defined.

## 3. Assign seats to passengers

- Make sure your seat numbers do not have any space in them.
- It might be good to reuse or call other functions you have already completed here.
- Make sure your seat numbers do not have any spaces in them.
- Here it might be good to reuse or call functions you have already defined.

## 4. Ticket codes

26 changes: 13 additions & 13 deletions exercises/concept/plane-tickets/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
# Instructions

Conda airlines is the programming-world's biggest airline, with over 10.000 flights a day!
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 are currently assigning all seats to passengers by hand; this will need to be automated.

They have asked _you_ to create software to automate the assigning of seats to passengers.
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 rows seats has the same naming: `A`, `B`, `C`, `D`.
Meaning 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`.
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()` that accepts an `int` that holds how many seat letters to be generated.
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.

```python
@@ -27,9 +27,9 @@ The function should then return an _iterable_ of seat letters.
"B"
```

## 2. Generate an amount of seats
## 2. Generate seats

Conda wants a system that can generate an amount of seats for their airplanes.
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`, ...
@@ -45,7 +45,7 @@ Here is an example:
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()` that accepts an `int` that holds how many seats to be generated.
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.

```python
@@ -60,7 +60,7 @@ The function should then return an _iterable_ of seats given.

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

Implement a function `assign_seats()` that accepts a `list` of passenger names.
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_.

```python
@@ -74,13 +74,13 @@ The function should then return a _dictionary_ of `passenger` as _key_, and `sea

Conda Airlines would like to have a unique code for each ticket.
Since they are a big airline, they have a lot of flights.
Meaning that there are multiple flights with the same seat number.
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()` that accepts a `list` of `seat_numbers` and a `string` with the flight number.
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`.

```python
66 changes: 41 additions & 25 deletions exercises/concept/plane-tickets/.docs/introduction.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
# About
# Generators

A `generator` is a function or expression that returns a special type of [iterator][iterator] called [generator iterator][generator-iterator].
`Generator-iterators` are [lazy][lazy iterator]: 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][yield expression].
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](#the-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.
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:

```python
@@ -23,13 +31,14 @@ You can convert that function into a generator like this:
... yield number ** 2
```

The rationale behind this is that you use a generator when you do not need all the values _at once_.

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.
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:

@@ -45,8 +54,8 @@ To use the `squares_generator()` generator:
16
```

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

```python
>>> squared_numbers = squares_generator([1, 2])
@@ -57,7 +66,7 @@ Values within a generator can also be produced/accessed via the `next()` functio
4
```

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

```python
>>> next(squared_numbers)
@@ -66,34 +75,35 @@ Traceback (most recent call last):
StopIteration
```

### Difference between iterables and generators

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

- Generators are _one-way_; there is no "backing up" to a previous value.

- Iterating over generators consume the returned values; no resetting.
~~~~exercism/note
- Generators (_being lazily evaluated_) are not sortable and can not be reversed.
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:
- Generators do _not_ have `indexes`, so you can't reference a previous or future value using addition or subtraction.
- 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`!
- Generators cannot be used with the `len()` function.
[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
~~~~

- Generators can be _finite_ or _infinite_, be careful when collecting all values from an _infinite_ generator.

## The yield expression

The [yield expression][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.


~~~~exercism/note
Using `yield` expressions is prohibited outside of functions.
~~~~
@@ -112,11 +122,12 @@ Using `yield` expressions is prohibited outside of functions.
1
```

## Why generators?

## Why Create a Generator?

Generators are useful in a lot of applications.

When working with a large collection, you might not want to put all of its values into `memory`.
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_:
@@ -131,5 +142,10 @@ Generators are also very helpful when a process or calculation is _complex_, _ex

Now whenever `__next__()` is called on the `infinite_sequence` object, it will return the _previous number_ + 1.


[generator-iterator]: https://docs.python.org/3.11/glossary.html#term-generator-iterator
[iterables]: https://wiki.python.org/moin/Iterator
[iterator]: https://docs.python.org/3.11/glossary.html#term-iterator
[lazy evaluation]: https://en.wikipedia.org/wiki/Lazy_evaluation
[lazy iterator]: https://en.wikipedia.org/wiki/Lazy_evaluation
[yield expression]: https://docs.python.org/3.11/reference/expressions.html#yield-expressions
10 changes: 6 additions & 4 deletions exercises/concept/plane-tickets/.meta/config.json
Original file line number Diff line number Diff line change
@@ -3,19 +3,21 @@
"J08K"
],
"contributors": [
"BethanyG"
"BethanyG",
"kytrinyx",
"meatball133"
],
"files": {
"solution": [
"plane_tickets.py"
"generators.py"
],
"test": [
"plane_tickets_test.py"
"generators_test.py"
],
"exemplar": [
".meta/exemplar.py"
]
},
"icon": "new-passport",
"blurb": "Learn about generators by assigning seats to passengers."
"blurb": "Learn about generators by assigning seats to passengers on Anaconda Airlines."
}
Loading

0 comments on commit a986374

Please sign in to comment.