Parcours
/
Python
Python
/
Exercices
/
Billets d'avion
Billets d'avion

Billets d'avion

Exercice d'apprentissage

Introduction

Un generator est une fonction ou une expression qui renvoie un type particulier d'itérateur appelé un generator iterator. Les Generator-iterators sont paresseux : ils ne stockent pas leurs values en mémoire, mais génèrent leurs valeurs au moment où on en a besoin.

Une fonction génératrice ressemble à n'importe quelle autre fonction, mais elle contient une ou plusieurs expressions yield. Chaque yield suspend l'exécution du code et sauvegarde l'état d'exécution courant (y compris toutes les variables locales et les instructions try). Quand le générateur reprend son exécution, il repart de l'état où il s'était interrompu, contrairement aux fonctions classiques qui sont réinitialisées à chaque appel.

Construis un générateur

On construit les générateurs à peu près comme les autres fonctions itératives ou récursives, mais ils ont besoin d'une expression yield, que l'on verra plus en détail un peu plus loin.

Prenons l'exemple d'une fonction qui renvoie les carrés d'une liste donnée de nombres. Telle qu'elle est écrite actuellement, toutes les entrées doivent être traitées avant qu'une seule valeur puisse être renvoyée :

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

Tu peux transformer cette fonction en générateur comme ceci :

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

L'idée derrière tout ça, c'est qu'on utilise un générateur quand on n'a pas besoin de produire toutes les valeurs d'un seul coup. Cela économise de la mémoire et de la puissance de calcul, puisque seule la valeur sur laquelle on travaille à ce moment-là est calculée.

Utilise un générateur

On peut utiliser les générateurs à la place de la plupart des iterables en Python. Cela inclut les fonctions ou les objets qui attendent un iterable/iterator en argument.

Pour utiliser le générateur squares_generator() :

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

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

Les valeurs d'un generator peuvent aussi être produites ou récupérées via la fonction next(). next() appelle la méthode __next__() d'un objet générateur-itérateur, ce qui « fait avancer » le code ou l'évalue jusqu'à son expression yield, laquelle « produit » ou renvoie alors une valeur :

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

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

Quand un generator-iterator est entièrement consommé et n'a plus de valeurs à renvoyer, il lève une erreur StopIteration.

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

Les générateurs-itérateurs sont un sous-ensemble particulier des itérateurs. Les Iterators sont le mécanisme (ou protocole) qui permet de parcourir des iterables. Les générateurs-itérateurs et les itérateurs renvoyés par les iterables courants de Python se comportent de façon très similaire, mais il y a quelques différences importantes à connaître :

  • Ils sont évalués paresseusement ; l'itération est à sens unique et il n'est pas possible de « revenir en arrière » vers une valeur précédente.
  • Ils sont consommés au fur et à mesure que l'on parcourt les valeurs renvoyées ; il n'y a ni réinitialisation ni sauvegarde en mémoire.
  • Ils ne peuvent pas être triés et ne peuvent pas être parcourus à l'envers.
  • Ce ne sont pas des séquences et ils n'ont pas d'indexes. Tu ne peux pas référencer une valeur précédente ou suivante par addition ou soustraction, et tu ne peux pas utiliser la notation entre crochets ([]) ni le découpage.
  • Ils ne peuvent pas être utilisés avec la fonction len(), car ils n'ont pas de longueur.
  • Ils peuvent être finis ou infinis ; fais attention quand tu récupères toutes les valeurs d'un generator-iterator infini !

L'expression yield

L'expression yield est très similaire à l'expression return. Contrairement à l'expression return, yield transmet des valeurs à l'appelant à un moment précis, en suspendant l'évaluation ou le renvoi de toute valeur supplémentaire jusqu'à ce qu'elle soit demandée. Quand yield est évalué, il met en pause l'exécution de la fonction qui le contient et renvoie les valeurs de cette fonction à cet instant précis. La fonction reste alors dans la portée, et quand __next__() est appelé, l'exécution reprend jusqu'à ce que yield soit rencontré à nouveau.

Note

L'utilisation d'expressions yield est interdite en dehors des fonctions.

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

Pourquoi créer un générateur ?

Les générateurs sont utiles dans de nombreuses applications.

Quand on travaille avec une collection de valeurs potentiellement volumineuse, on n'a pas forcément envie de tout garder en mémoire. Un générateur permet de traiter des données plus importantes morceau par morceau, ce qui économise de la mémoire et améliore les performances.

Les générateurs sont aussi très utiles quand un processus ou un calcul est complexe, coûteux ou infini :

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

Maintenant, chaque fois que __next__() est appelé sur l'objet infinite_sequence, il renvoie le nombre précédent + 1.

Instructions

Conda Airlines est la plus grande compagnie aérienne du monde de la programmation, avec plus de 10 000 vols par jour !

Actuellement, ils attribuent tous les sièges aux passagers à la main ; il va falloir automatiser tout ça.

C'est à toi qu'ils ont demandé de créer un logiciel pour automatiser l'attribution des sièges aux passagers. Ils exigent que ton logiciel soit économe en mémoire et performant.

1. Génère les lettres de siège

Conda veut générer les lettres de siège de ses avions. Un avion est composé de rangées de sièges. Chaque rangée compte 4 sièges. Les sièges de chaque rangée portent toujours les noms A, B, C et D. Le premier siège de la rangée est A, le deuxième est B, et ainsi de suite. Après avoir atteint D, on repart à A.

Implémente une fonction generate_seat_letters(<number>) qui accepte un int indiquant le nombre de lettres de siège à générer. La fonction doit ensuite renvoyer un iterable de lettres de siège.

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

2. Génère les sièges

Conda veut un système capable de générer un nombre donné de sièges pour ses avions. Chaque avion a 4 sièges par rangée. Les rangées sont définies par des nombres, en commençant à 1 et en augmentant. Les sièges doivent être ordonnés, comme ceci : 1A, 1B, 1C, 1D, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, ...

Voici un exemple :

x 1 2
Rangée 5 21
Lettre de siège A D
Résultat 5A 21D

Beaucoup de compagnies aériennes n'ont pas de rangée numéro 13 sur leurs vols, par superstition chez les passagers. Conda Airlines suit également cette convention, alors assure-toi de ne pas générer de sièges pour la rangée numéro 13.

Implémente une fonction generate_seats(<number>) qui accepte un int indiquant le nombre de sièges à générer. La fonction doit ensuite renvoyer un iterable des sièges demandés.

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

3. Assigne les sièges aux passagers

Maintenant que tu as une fonction qui génère des sièges, tu peux t'en servir pour attribuer des sièges aux passagers.

Implémente une fonction assign_seats(<passengers>) qui accepte une list de noms de passagers. La fonction doit ensuite renvoyer un dictionnaire avec passenger comme clé et seat_number comme valeur.

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

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

4. Codes de billets

Conda Airlines aimerait avoir un code unique pour chaque billet. Comme c'est une grande compagnie aérienne, elle a beaucoup de vols. Cela signifie qu'il y a plusieurs vols avec le même numéro de siège. Ils veulent que tu crées un système qui génère un billet unique, une string de 12 caractères.

Ce code commence par assigned_seat, suivi de flight_id. Le reste du code est rempli de 0s.

Implémente une fonction generate_codes(<seat_numbers>, <flight_id>) qui accepte une list de seat_numbers et une string contenant le numéro de vol. La fonction doit ensuite renvoyer un generator qui produit un 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'
Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet
Python Exercism

Prêt à commencer Billets d'avion ?

Inscris-toi sur Exercism pour apprendre et maîtriser Python avec 17 concepts146 exercices, et un vrai mentorat humain, le tout gratuitement.