6.5. Exercices¶
Pour conclure cette introduction à la programmation avec Python et mettre en pratique les principaux concepts étudiés, nous allons réaliser deux programmes complets : un générateur de mots de passes et un grapheur.
6.5.1. Générateur de mots de passe¶
Cet exercice a pour but de mettre en pratique le module random
en réalisant un générateur aléatoire de mots de passe.
Le programme demandera à l’utilisateur de saisir un modèle de génération sur lequel se baser, comme dans l’exemple suivant.
Veuillez saisir le modèle désiré.
. : lettre minuscule
^ : lettre majuscule
@ : lettre
# : chiffre
$ : caractère spécial
- : trait d'union
* : n'importe quel caractère
Modèle : ####-####-####
8650-6490-5811
Le modèle ####-####-####
demande au générateur de produire un mot de passe composé de trois suites de quatre chiffres séparées par des traits d’union.
Dans l’exemple ci-dessus, le mot de passe 8650-6490-5811
conforme au modèle a été généré.
Ensembles de caractères¶
Nous avons besoin pour le générateur de différents ensembles de caractères parmi lesquels tirer au hasard :
Les lettres minuscules
Les lettres majuscules
Toutes les lettres
Les chiffres de 0 à 9
Un assortiment de caractères spéciaux
Le trait d’union
Un ensemble constitué de tous les éléments des groupes ci-dessus
Ces ensembles sont comme des sacs dans lesquels nous tirons au sort des objets, mais contenant chacun une sélection différente d’éléments. Si nous avons besoin d’une lettre minuscule au hasard, il suffit alors de choisir un élément de l’ensemble correspondant.
Concrètement, bien que les ensembles semblent être la structure de donnée la plus indiquée, nous utiliserons des tuples pour représenter les différents groupes de caractères. La principale raison est que les tuples sont immuables, ce qui se prête mieux à l’utilisation qui en sera faite car on ne veut pas qu’ils puissent être modifiés en cours d’exécution. De plus, le choix d’un élément aléatoire est plus efficace avec les tuples qu’avec les collections.
Afin de rendre le code plus concis, les tuples contiendront les valeurs Unicode des caractères plutôt que les caractères eux-même.
Il est possible d’obtenir le caractère correspondant à la valeur v
grâce à la fonction native chr(v)
.
L’inverse se fait au moyen de ord(c)
, qui permet d’obtenir la valeur Unicode du caractère c
.
Commençons par afficher les 128 premiers caractères Unicode et leur valeur. Ces 128 caractères correspondent à la norme ASCII et nous nous limiterons à ceux-ci.
for i in range(128):
print(f"{i}: {chr(i)}")
Note : ci-dessous, la sortie du programme a été mise en forme pour des raisons de lisibilité.
0: 20: 40: ( 60: < 80: P 100: d 120: x
1: 21: 41: ) 61: = 81: Q 101: e 121: y
2: 22: 42: * 62: > 82: R 102: f 122: z
3: 23: 43: + 63: ? 83: S 103: g 123: {
4: 24: 44: , 64: @ 84: T 104: h 124: |
5: 25: 45: - 65: A 85: U 105: i 125: }
6: 26: 46: . 66: B 86: V 106: j 126: ~
7: 27: 47: / 67: C 87: W 107: k 127:
8: 28: 48: 0 68: D 88: X 108: l
9: 29: 49: 1 69: E 89: Y 109: m
10: 30: 50: 2 70: F 90: Z 110: n
11: 31: 51: 3 71: G 91: [ 111: o
12: 32: 52: 4 72: H 92: \ 112: p
13: 33: ! 53: 5 73: I 93: ] 113: q
14: 34: " 54: 6 74: J 94: ^ 114: r
15: 35: # 55: 7 75: K 95: _ 115: s
16: 36: $ 56: 8 76: L 96: ` 116: t
17: 37: % 57: 9 77: M 97: a 117: u
18: 38: & 58: : 78: N 98: b 118: v
19: 39: ' 59: ; 79: O 99: c 119: w
Les valeurs de 0 à 32 ainsi que 127 correspondent à des caractères de contrôle qui ne sont pas destinés à être affichés, tels que le saut de ligne, et nous les ignorerons. Nous pouvons à présent définir les 7 tuples nécessaires à l’aide de compréhensions et les mémoriser dans des constantes.
UPPER_LETTERS = tuple(i for i in range(65, 91)) # entiers dans l'intervalle [65, 91[
LOWER_LETTERS = tuple(i for i in range(97, 123)) # idem pour les autres constantes...
LETTERS = UPPER_LETTERS + LOWER_LETTERS
DIGITS = tuple(i for i in range(48, 58))
SYMBOLS = (tuple(i for i in range(33, 48))
+ tuple(i for i in range(58, 65))
+ tuple(i for i in range(123, 127)))
UNION = (45,)
ANY = LETTERS + DIGITS + SYMBOLS
Structure du programme¶
Le programme final sera composé des trois fonctions suivantes :
La fonction
get_pattern()
demande à l’utilisateur de saisir le modèle de construction du mot de passe et le renvoie sous forme de chaîne de caractères.La fonction
parse_pattern(pattern)
reçoit une chaîne de caractères contenant le modèle et renvoie une liste contenant, dans l’ordre, les groupes des valeurs de caractères correspondants.La fonction
choose_passwd(ranges)
reçoit une liste de groupes de valeurs de caractères et choisit un élément de chacun de ces groupes, dans l’ordre, pour former le mot de passe final.
Voici comment ces fonctions se combinent dans le programme principal.
# programme principal
pattern = get_pattern()
ranges = parse_pattern(pattern)
password = choose_passwd(ranges)
print(password)
get_pattern()
¶
La fonction get_pattern()
est triviale à écrire car il s’agit simplement de renvoyer le résultat d’un appel à input()
.
Le texte d’invitation demande cependant quelques efforts à écrire.
Solution
def get_pattern():
return input("Veuillez saisir le modèle désiré.\n\n"
". : lettre minuscule\n"
"^ : lettre majuscule\n"
"@ : lettre\n"
"# : chiffre\n"
"$ : caractère spécial\n"
"- : trait d'union\n"
"* : n'importe quel caractère\n\n"
"Modèle : ")
parse_pattern(pattern)
¶
Puisque nous avons déjà construit les tuples correspondant aux différents ensembles de caractères, cette fonction ne devrait pas non plus poser beaucoup de problèmes. Il s’agit de créer une liste puis d’y ajouter, pour chaque caractère du modèle reçu en paramètre, le tuple correspondant. Enfin, il faut penser à renvoyer la liste ainsi composée.
Solution
def parse_pattern(pattern):
ranges = []
for symbol in pattern:
if symbol == '.':
ranges.append(LOWER_LETTERS)
elif symbol == '^':
ranges.append(UPPER_LETTERS)
elif symbol == '@':
ranges.append(LETTERS)
elif symbol == '#':
ranges.append(DIGITS)
elif symbol == '$':
ranges.append(SYMBOLS)
elif symbol == '-':
ranges.append(UNION)
elif symbol == '*':
ranges.append(ANY)
else:
print(f"Symbole inconnu dans le modèle : {symbol}")
return ranges
choose_passwd(ranges)
¶
Enfin, la méthode choose_passwd()
tire au sort un élément de chacun des tuples reçus et les assemble en une chaîne de caractères pour former le mot de passe, avant de renvoyer cet dernier.
Il ne faut pas oublier que les éléments des tuples sont les valeurs Unicode des caractères et qu’il faut donc les convertir à l’aide de la fonction chr()
.
Solution
def choose_passwd(ranges):
passwd = ""
for r in ranges:
passwd += chr(random.choice(r))
return passwd
Programme final¶
Toutes les pièces sont maintenant prêtes à être assemblées. Voici le code complet du programme final.
Solution
import random
# constantes
UPPER_LETTERS = tuple(i for i in range(65, 91))
LOWER_LETTERS = tuple(i for i in range(97, 123))
LETTERS = UPPER_LETTERS + LOWER_LETTERS
DIGITS = tuple(i for i in range(48, 58))
SYMBOLS = (tuple(i for i in range(33, 48))
+ tuple(i for i in range(58, 65))
+ tuple(i for i in range(123, 127)))
UNION = (45,)
ANY = LETTERS + DIGITS + SYMBOLS
# fonctions
def get_pattern():
return input("Veuillez saisir le modèle désiré.\n\n"
". : lettre minuscule\n"
"^ : lettre majuscule\n"
"@ : lettre\n"
"# : chiffre\n"
"$ : caractère spécial\n"
"- : trait d'union\n"
"* : n'importe quel caractère\n\n"
"Modèle : ")
def parse_pattern(pattern):
ranges = []
for symbol in pattern:
if symbol == '.':
ranges.append(LOWER_LETTERS)
elif symbol == '^':
ranges.append(UPPER_LETTERS)
elif symbol == '@':
ranges.append(LETTERS)
elif symbol == '#':
ranges.append(DIGITS)
elif symbol == '$':
ranges.append(SYMBOLS)
elif symbol == '-':
ranges.append(UNION)
elif symbol == '*':
ranges.append(ANY)
else:
print(f"Symbole inconnu dans le modèle : {symbol}")
return ranges
def choose_passwd(ranges):
passwd = ""
for r in ranges:
passwd += chr(random.choice(r))
return passwd
# programme principal
pattern = get_pattern()
ranges = parse_pattern(pattern)
password = choose_passwd(ranges)
print(password)
Testons le programme en générant un mot de passe de 10 caractères au hasard…
...
Modèle : **********
ZnXU.6fH{/
…ou un numéro de téléphone…
...
Modèle : ###-###-##-##
033-391-32-22
…ou encore une combinaison pseudo-aléatoire.
...
Modèle : ^....##$
Rwsax63~
6.5.2. Grapheur¶
Cet exercice vise à réaliser, étape par étape, un grapheur basique permettant de visualiser des fonctions mathématiques dans la console.
Première dimension : le curseur¶
Nous allons commencer par écrire une fonction get_slider()
qui construit et renvoie une chaîne de caractères représentant une barre de défilement avec un curseur.
Le curseur est borné entre deux nombres décimaux et permet d’indiquer une valeur donnée via sa position relative aux deux bornes.
La barre de défilement possède une largeur qui correspond au nombre de caractères nécessaires à sa représentation.
Le curseur sera représentée par un astérisque (*
) et le zéro sera affiché par une barre verticale (|
).
Les bornes ne compteront pas dans la largeur et seront représentées par des crochets ([
, ]
).
Voici le résultat attendu pour afficher la valeur de \(\Pi\) dans une barre de défilement dont les bornes sont -2 et 5 et dont la largeur est de 30 caractères (les deux dernières lignes ont été ajoutées à titre indicatif, elles ne sont pas affichées par le programme).
[ | * ]
# -2 0 π 5
# '------------bornes------------'
Pour mettre en œuvre cette fonction, nous allons d’abord définir une autre fonction, get_pos_in_slider(range_from, range_to, width, value)
, en charge de calculer la position d’une valeur value
entre deux bornes range_from
et range_to
espacées de width
caractères.
La complexité de cette fonction est purement mathématiques car, une fois la formule de calcul de la position trouvée, son code s’écrit en une ligne.
La fonction native round()
peut être utilisée pour arrondir un nombre décimal à l’entier le plus proche.
Il ne faut pas oublier de soustraire 1 à la largeur puisque les positions sont indexées à partir de zéro.
Solution
# principe de calcul :
#
# ^ dv: distance valeur
# | db: distance bornes
# |
# | <----------dv---------->
# -------0---[------------------------*-----]----------->
# | <-------------db------------->
# |
# | ratio (position relative) = dv / db
# | position = arrondi(ratio * (largeur - 1))
def get_pos_in_slider(range_from, range_to, width, value):
return round((value - range_from) / (range_to - range_from) * (width - 1))
Nous pouvons maintenant passer à la définition de la fonction get_slider(range_from, range_to, width, value)
, qui possède exactement les mêmes paramètres que get_pos_in_slider()
.
Nous calculons d’abord les positions de value
et du zéro entre les bornes range_from
et range_to
pour une barre de défilement de largeur width
.
Une fois ces deux positions obtenues, nous débutons la construction de la chaîne de caractères représentant la barre de défilement.
Puisque les chaînes sont immuables en Python, il est plus simple de travailler avec une liste de caractères dont nous combinerons les éléments grâce à la méthode str.join()
.
Nous commençons donc par créer une liste représentant une chaîne de longueur width
constituée uniquement d’espaces, en passant cette chaîne à la méthode list()
qui se charge de la conversion.
Nous remplaçons ensuite par une barre verticale (|
) l’espace à la position du zéro, si celle-ci ne se trouve pas en dehors des bornes, puis nous faisons de même à la position de la valeur en remplaçant cette fois l’espace par un astérisque (*
).
Enfin, nous renvoyons le chaîne de caractères composée des éléments de la liste, entourés pas des crochets ([
, ]
).
Solution
def get_slider(range_from, range_to, width, value):
zero_pos = get_pos_in_slider(range_from, range_to, width, 0)
value_pos = get_pos_in_slider(range_from, range_to, width, value)
slider = list(' ' * width)
if 0 <= zero_pos < len(slider):
slider[zero_pos] = '|'
if 0 <= value_pos < len(slider):
slider[value_pos] = '*'
return '[' + ''.join(slider) + ']'
Testons la méthode en affichant la valeur de \(\Pi\) comme dans l’exemple.
print("Pi entre -2 et 5 :")
print(get_slider(-2, 5, 30, math.pi))
print("Pi entre 3 et 4 :")
print(get_slider(3, 4, 30, math.pi))
Pi entre -2 et 5 :
[ | * ]
Pi entre 3 et 4 :
[ * ]
Seconde dimension : le graphique¶
Nous allons nous servir des curseurs pour représenter l’évolution de la valeur de retour d’une fonction f(x)
, en incrémentant le paramètre de la fonction entre chaque impression.
Nous choisissons donc une valeur de départ pour le paramètre, puis nous entrons dans une boucle qui affiche un curseur représentant sa valeur transformée par la fonction mathématique choisie avant de l’incrémenter de la quantité choisie.
Puisque les valeurs à choisir laissent beaucoup de liberté, nous définissons les constantes correspondantes en début de programme afin de pouvoir les ajuster à notre guise.
Voici un exemple du programme final dessinant la courbe de la fonction sinus, définie dans le module math
.
Solution
import math
constantes
LINES = 22
WIDTH = 40
STARTING_VALUE = 0
INCREMENT = 0.3
RANGE_FROM = -1.1
RANGE_TO = 1.1
# fonctions
def f(x):
return math.sin(x)
def get_pos_in_slider(range_from, range_to, width, value):
return round((value - range_from) / (range_to - range_from) * (width - 1))
def get_slider(range_from, range_to, width, value):
zero_pos = get_pos_in_slider(range_from, range_to, width, 0)
value_pos = get_pos_in_slider(range_from, range_to, width, value)
slider = list(' ' * width)
if 0 <= zero_pos < len(slider):
slider[zero_pos] = '|'
if 0 <= value_pos < len(slider):
slider[value_pos] = '*'
return '[' + ''.join(slider) + ']'
# programme principal
x = STARTING_VALUE
for _ in range(LINES):
print(get_slider(RANGE_FROM, RANGE_TO, WIDTH, f(x)))
x += INCREMENT
[ * ]
[ | * ]
[ | * ]
[ | * ]
[ | * ]
[ | * ]
[ | * ]
[ | * ]
[ | * ]
[ | * ]
[ | * ]
[ * | ]
[ * | ]
[ * | ]
[ * | ]
[ * | ]
[ * | ]
[ * | ]
[ * | ]
[ * | ]
[ * | ]
[ * ]
Note
En procédant de la sorte, les axes mathématiques sont bien entendu inversés par rapport à leur sens habituel. La nature de la console à défilement vertical rend la procédure bien plus compliquée si l’on souhaite afficher d’évolution d’une fonction de gauche à droite.
Exemples de fonctions à représenter¶
Voici quelques exemples de fonctions à essayer de représenter avec le grapheur.
Fonction parabolique¶
LINES = 17
WIDTH = 40
STARTING_VALUE = -4
INCREMENT = 0.5
RANGE_FROM = -2.5
RANGE_TO = 15
def f(x):
return x**2 - 2
[ | * ]
[ | * ]
[ | * ]
[ | * ]
[ | * ]
[ * ]
[ * | ]
[ * | ]
[ * | ]
[ * | ]
[ * | ]
[ * ]
[ | * ]
[ | * ]
[ | * ]
[ | * ]
[ | * ]
Fonction cubique¶
LINES = 17
WIDTH = 40
STARTING_VALUE = -4
INCREMENT = 0.5
RANGE_FROM = -8
RANGE_TO = 8
def f(x):
return x**3/2 - 4*x
[ | ]
[ * | ]
[ * | ]
[ | * ]
[ | * ]
[ | * ]
[ | * ]
[ | * ]
[ * ]
[ * | ]
[ * | ]
[ * | ]
[ * | ]
[ * | ]
[ | * ]
[ | * ]
[ | ]
Fonction oscillante¶
LINES = 40
WIDTH = 40
STARTING_VALUE = -0.2
INCREMENT = 0.005
RANGE_FROM = -0.2
RANGE_TO = 0.2
def f(x):
if x == 0:
return 0
return x * math.sin(1/x)
[ * | ]
[ * | ]
[ * | ]
[ * | ]
[ * | ]
[ * | ]
[ * | ]
[ * | ]
[ *| ]
[ | * ]
[ | * ]
[ | * ]
[ | * ]
[ | * ]
[ | * ]
[ | * ]
[ | * ]
[ | * ]
[ | * ]
[ * | ]
[ * | ]
[ * | ]
[ * | ]
[ * | ]
[ *| ]
[ | * ]
[ | * ]
[ | * ]
[ * | ]
[ * | ]
[ | * ]
[ * | ]
[ *| ]
[ *| ]
[ | * ]
[ |* ]
[ *| ]
[ *| ]
[ *| ]
[ *| ]
Note
On observe que les oscillations deviennent si petites lorsque x
approche zéro que la résolution de notre grapheur atteint malheureusement ses limites.
On notera également que l’on doit vérifier si x
est nul pour éviter une division par zéro qui provoquerait une erreur à l’exécution.
Fonction gaussienne¶
LINES = 20
WIDTH = 40
STARTING_VALUE = -4
INCREMENT = 0.4
RANGE_FROM = 0
RANGE_TO = 1
def f(x):
return math.exp(-x**2 / 2)
[* ]
[* ]
[* ]
[|* ]
[| * ]
[| * ]
[| * ]
[| * ]
[| * ]
[| * ]
[| *]
[| * ]
[| * ]
[| * ]
[| * ]
[| * ]
[| * ]
[|* ]
[* ]
[* ]
À vous de jouer¶
Nous vous laissons à présent expérimenter avec d’autres fonction. Bonne exploration !