On s'entraîne à coder de petits projets
Quelques exercices pour se mettre le code dans les pattes. Pour chacun des projets vous ferez 2 fichiers :
- le programme principal de nom
main.pyqui sera exécuté - le fichier contenant les différentes fonctions appelées dans le programme principal. Son nom doit être en relation avec son contenu.
Chaque sujet contient son corrigé, mais faites dans l'ordre :
Pour chaque sujet
- faites tous les exercices
- regardez les erreurs courantes et corriger si besoin votre projet
- comparez votre code au corrigé
Syracuse
On essaye d'écrire un programme qui teste la conjecture de Syracuse pour des entiers.
Questions
Écrivez une fonction syracuse telle que :
- entrée : un entier $x$
- sortie :
- $x/2$ si $x$ est pair
- $3x + 1$ si $x$ est impair
En utilisant le fait que le modulo s'écrit % en python.
corrigé
corrigé
def syracuse(x):
if x % 2 == 0:
return x / 2
else:
return 3 * x + 1
Vérifiez que le code fonctionne en demandant à l'utilisateur de taper un nombre et rend la suite de Syracuse de ce nombre.
- vous supposerez que l'utilisateur ne se trompe pas (pas besoin de gérer ses erreurs potentielles)
- vous utiliserez la fonction
input()qui rend une chaîne de caractères tapée par l'utilisateur int(x)est l'entier représenté par la chaîne de caractèrex.
corrigé
corrigé
def syracuse(x):
if x % 2 == 0:
return x / 2
else:
return 3 * x + 1
sortie_utilisateur = input("Donnez un entier : ")
n = int(sortie_utilisateur)
print("suite de Syracuse associée : ", syracuse(n))
Écrivez une fonction qui rend tous les éléments de la suite de Syracuse associée à un nombre
- entrée : un entier $x$
- sortie : les élément de la suite de Syracuse associée à $x$
La suite de Syracuse est définie telle que :
- $u_0 =x$
- $u_{n+1} = \mbox{syracuse}(u_n)$
- on s'arrête lorsque $u_n =1$
corrigé
corrigé
def suite(u_0):
sortie = [u_0]
u_n = u_0
while u_n != 1:
u_n = syracuse(u_n)
sortie.append(u_n)
return sortie
Écrivez le programme qui demande à l'utilisateur de taper un nombre et rend la suite de Syracuse de ce nombre.
- vous supposerez que l'utilisateur ne se trompe pas (pas besoin de gérer ses erreurs potentielles)
- vous utiliserez la fonction
input()qui rend une chaîne de caractères tapée par l'utilisateur int(x)est l'entier représenté par la chaîne de caractèrex.
corrigé
corrigé
def syracuse(x):
if x % 2 == 0:
return x / 2
else:
return 3 * x + 1
def suite(u_0):
sortie = [u_0]
u_n = u_0
while u_n != 1:
u_n = syracuse(u_n)
sortie.append(u_n)
return sortie
print("suite de Syracuse associée : ", suite(int(input("Donnez un entier : "))))
Je me suis amusé pour traiter l'entrée faite par un utilisateur. Comprenez :
- pourquoi ça fonctionne,
- pourquoi on préfère ne pas faire comme ça dans de vrais programmes
Erreurs courantes à éviter
Après un input vous aurez toujours une chaîne de caractère. Il faut la convertir dans ce que va demander vos fonctions, ici des entiers.
Ne faites PAS de conversion de type dans vos fonctions. Si elles demandent des entrées entiers supposez que c'est le cas (par de int(x) % 2 par exemple dans la fonction syracuse). Tôt ou tard ce genre chose va vous sauter à la figure car un jour vous votre programme va planter sans que vous compreniez pourquoi ni où est le soucis.
Jeu du pendu
On essaye d'écrire un programme qui joue au jeu du pendu.
Questions
Écrire la fonction est_une_lettre(lettre, mot) telle que :
- paramètres d'entrée :
lettreun caractèremotune chaîne de caractères
- sortie :
- si au moins une des lettres de
motetlettre, la fonction rendTrue - sinon, la fonction rend
False
- si au moins une des lettres de
- exemples :
est_une_lettre("i", "victoire")doit rendreTrueest_une_lettre("e", "la disparition")doit rendreFalse
corrigé
corrigé
Plusieurs possibilités. Commençons par la plus simple, que tout le monde devrait arriver à faire, c'est une retranscription directe d'un algorithme du cours :
def est_une_lettre(lettre, mot):
for c in mot:
if lettre == c:
return True
return False
On pouvait aussi utiliser le mot clé in de python (supposé connu de tous), pour une solution écrite en 30 secondes chrono :
def est_une_lettre(lettre, mot):
if lettre in mot:
return True
else:
return False
Notez que la version précédente est identique à la version ci-dessous, bien plus élégante :
def est_une_lettre(lettre, mot):
return lettre in mot
Écrire la fonction caractères(lettre, mot) telle que :
- paramètres d'entrée :
lettreun caractèremotune chaîne de caractères
- sortie :
- une liste $L$ contenant tous les indices des caractères de
motqui valentlettre. Cette liste doit être triée par ordre croissant.
- une liste $L$ contenant tous les indices des caractères de
- exemples :
caractères("i", "victoire")doit rendre[1, 5]caractères("e", "la disparition")doit rendre[]
corrigé
corrigé
def caractères(lettre, mot):
position = []
for i in range(len(mot)):
if mot[i] == lettre:
position.append(i)
return position
On utilise ici la fonction range pour itérer sur les indices du tableau plutôt que sur ses valeurs.
Encore une fois, tout le monde devrait arriver à faire cette fonction d'une seule traite, sans réfléchir.
Écrire la fonction découvre(mot_caché, lettre, positions) telle que :
- paramètres d'entrée :
mot_cachéune chaîne de caractèreslettreun caractèrepositionsune liste d'entiers rangés par ordre croissant
- sortie :
- la chaîne de caractères
mot_cachéoù les indices correspondants aux entiers depositionssont remplacés parlettre
- la chaîne de caractères
- exemples :
découvre("......", "r", [1, 2, 5])doit rendre".rr..r"découvre("erre.r", "u", [4])doit rendre"erreur"découvre("erre.r", "u", [])doit rendre"erre.r"
corrigé
corrigé
La fonction que j'attends est :
def découvre(mot_caché, lettre, positions):
mot = ""
for i in range(len(mot_caché)):
if i in positions:
mot += lettre
else:
mot += mot_caché[i]
return mot
En utilisant les caractéristiques de la liste positions trié par ordre croissant, on aurait pu forger la fonction ci-dessous de complexité $\mathcal{O}(n)$ (pourquoi est-ce que ça marche ?):
def découvre(mot_caché, lettre, positions):
mot = ""
if len(positions) == 0:
return mot_caché
pos = 0
for i in range(len(mot_caché)):
if i == positions[pos]:
mot += lettre
pos = min(pos + 1, len(positions) - 1)
else:
mot += mot_caché[i]
return mot
Écrire la fonction caché(mot) telle que :
- paramètres d'entrée :
motune chaîne de caractères
- sortie :
- une chaîne de caractères composée uniquement de
"."et de longueur égale à celle demot
- une chaîne de caractères composée uniquement de
- exemples :
caché("anticonstitutionnellement")doit rendre"........................."caché("")doit rendre""
corrigé
corrigé
En utilisant la multiplications des chaînes de caractères, la fonction est triviale :
def caché(mot):
return "." * len(mot)
Créez le programme permettant de jouer au pendu jusqu'à ce que le mot à trouver ne contienne plus de "."
corrigé
corrigé
Une proposition de programme principal :
def est_une_lettre(lettre, mot):
return lettre in mot
def caractères(lettre, mot):
position = []
for i in range(len(mot)):
if mot[i] == lettre:
position.append(i)
return position
def découvre(mot_caché, lettre, positions):
mot = ""
for i in range(len(mot_caché)):
if i in positions:
mot += lettre
else:
mot += mot_caché[i]
return mot
def caché(mot):
return "." * len(mot)
mot_à_trouver = "table"
mot_caché = caché(mot_à_trouver)
print("mot à trouver :", mot_caché)
nombre_essai = 0
while est_une_lettre(".", mot_caché):
lettre = input("Donnez une lettre : ")
mot_caché = découvre(mot_caché, lettre, caractères(lettre, mot_à_trouver))
print("mot à trouver :", mot_caché)
nombre_essai += 1
print("Victoire !, vous avez gagné en", nombre_essai, "essais.")
Erreurs courantes à éviter
Description d'une fonction
La description d'une fonction (entre """) est inutile. Le code doit se suffire à lui-même pour être lisible et compris. Si ce n'est pas le cas, c'est que vous avez mal codé !
La description de chaque fonction n'est utile que si vous faire une bibliothèque (une suite de fonctions qui devront être utilisées par d'autres sans qu'ils aient à connaître leurs codes). Ici, vous faite du code qui sera exécuté ou utilisé par vous et les autres membres de l'équipe de développement (ou le correcteur, ici moi) : la description ou les commentaires doivent être inutiles : faites du code lisible.
Listes
On préférera toujours utiliser L.append(i) plutôt que L = L + [i] car append est une méthode en $\mathcal{O}(1)$ opérations alors + crée une nouvelle liste et est donc en $\mathcal{O}(n)$ où $n$ est la taille de la liste L.
Comparaison de booléens
On ne teste pas si un booléen est vrai ou faux, on utilise directement sa valeur.
- On écrit :
assert est_une_lettre("i", "victoire") On écrit pasassert est_une_lettre("i", "victoire") == True
En effet, les deux formes sont équivalentes puisque une comparaison avec == rend True ou False mais la seconde est plus compacte et moins redondante.
De même (vu souvent), à la place d'écrire :
Si f() == Vrai alors:
return Vrai
sinon:
return Faux
écrivez :
return f()
Variable différent d'une chaîne de caractère
Ne confondez pas la variable ou le paramètre d'une fonction x avec une chaîne de caractères contenant le caractère x, notée 'x'.
Le compte est bon
Le programme consiste à demander à l'utilisateur une chaîne de caractères et un caractère. On cherchera à savoir combien de fois apparaît le caractère dans la chaîne. On va créer le programme en ajoutant petit à petit des fonctionnalités au programme.
Questions
Créer un programme permettant d'exécuter les 4 étapes de l'algorithme suivant :
- Demander à l'utilisateur une chaîne de caractères que l'on nommera
chaîne_entrée(en utilisant la fonctioninput([prompt: str]) -> str) - Demander à l'utilisateur un caractère que l'on nommera
caractère_entrée(vous ne ferez aucune vérification de type) - Afficher à l'écran le plus petit indice (ou -1) de
chaîne_entréevalantcaractère_entrée(vous pourrez utiliser la méthodefinddes chaînes de caractères) - Retournez à l'étape 1. de cet algorithme à part si
chaîne_entréevalait"sortie"
corrigé
corrigé
Programme :
chaîne_entrée = ""
while chaîne_entrée != "sortie":
chaîne_entrée = input("Entre une chaîne de caractères : ")
caractère_entrée = input("Entre un caractère : ")
index_caractère = chaîne_entrée.find(caractère_entrée)
print("Premier index du caractère :", index_caractère)
On veut savoir si le caractère caractère_entrée apparaît plusieurs fois dans la chaîne chaîne_entrée. Comme on connaît déjà le premier indice où il apparaît, on cherche s'il apparaît aussi plus tard.
Créer la fonction donne_prochain_indice(chaîne:str, indice:int) -> int qui rend :
- Le plus petit indice $j$ strictement plus grand que le paramètre
indicetel quechaîne[j] == chaîne[indice], Nonesi cet indice n'existe pas.
corrigé
corrigé
def donne_prochain_indice(chaîne, indice):
possible_suivant = chaîne.find(chaîne[indice], indice + 1)
if possible_suivant > -1:
return possible_suivant
return None
Dans l'étape 3. de l'algorithme du programme principal, utilisez la fonction que vous venez de coder pour ajouter un affichage qui indique si caractère_entrée apparaît plusieurs fois dans chaîne_entrée ou pas.
L'étape 3. du programme principal sera alors constitué de deux actions :
- Afficher à l'écran le plus petit indice (ou -1) de
chaîne_entréevalantcaractère_entrée(vous pourrez utiliser la méthodefinddes chaînes de caractères) (question 1) - Afficher si
caractère_entréeest présent plus d'une fois danschaîne_entrée(question 2)
corrigé
corrigé
On ajoute à la fin du programme les lignes suivantes, dans le bloc while :
while chaîne_entrée != "sortie":
# ...
if index_caractère == -1:
print("Il n'apparaît pas")
elif donne_prochain_indice(chaîne_entrée, index_caractère) != None:
print("Il apparaît plusieurs fois")
else:
print("Il apparaît une fois")
On veut finalement savoir combien de fois apparaît caractère_entrée dans la chaîne chaîne_entrée. Comme on connaît sa première position grace à la question 1 et qu'on peut connaître la suivante grace à la question 2, on va terminer le boulot et compter combien de fois apparaît caractère_entrée dans chaîne_entrée.
Créer la fonction compte_caractère(chaîne: str, indice: int) -> int qui rend le nombre de fois où le caractère chaîne[indice] est présent dans le paramètre chaîne
corrigé
corrigé
def compte_caractère(chaîne, indice):
compte = 0
while indice != None:
compte += 1
indice = donne_prochain_indice(chaîne, indice)
return compte
Dans l'étape 3. du programme, utilisez la fonction que vous venez de coder pour ajouter un affichage qui indique le nombre de fois où caractère_entrée apparaît dans chaîne_entrée.
L'étape 3. du programme sera alors constitué de trois actions :
- Afficher à l'écran le plus petit indice (ou -1) de
chaîne_entréevalantcaractère_entrée(vous pourrez utiliser la méthodefinddes chaînes de caractères) (question 1) - Afficher à l'écran si
caractère_entréeest présent plus d'une fois danschaîne_entrée(question 2) - Afficher à l'écran le nombre de fois où
caractère_entréeapparaît danschaîne_entrée(question 3)
corrigé
corrigé
On ajoute à la fin du programme les lignes suivantes, dans le bloc while :
while chaîne_entrée != "sortie":
# ...
if index_caractère > -1:
nombre = compte_caractère(chaîne_entrée, index_caractère)
print("Le caractère apparaît", nombre , "fois.")
Cerise sur le gateau, on cherche à savoir si caractère_entrée est le caractère qui apparaît le plus de fois dans chaîne_entrée.
Créer la fonction donne_max_doublon(chaîne: str) -> int qui rend le nombre maximum de fois où apparaît un même caractère dans chaîne.
corrigé
corrigé
def donne_max_doublon(chaîne):
nombre_max = 0
for i in range(len(chaîne)):
nombre_max = max(nombre_max, compte_caractère(chaîne, i))
return nombre_max
Dans l'étape 3. de l'algorithme du programme principal, utilisez la fonction que vous venez de coder pour ajouter un affichage qui le nombre maximum de répétition d'un caractère, et affichez un message de victoire si caractère_entrée réalise ce maximum.
L'étape 3. du programme principal sera alors constitué de quatre actions :
- Afficher à l'écran le plus petit indice (ou -1) de
chaîne_entréevalantcaractère_entrée(vous pourrez utiliser la méthodefinddes chaînes de caractères) (question 1) - Afficher à l'écran si
caractère_entréeest présent plus d'une fois danschaîne_entrée(question 2) - Afficher à l'écran le nombre de fois où
caractère_entréeapparaît danschaîne_entrée(question 3) - Afficher à l'écran le nombre maximum de répétition d'un caractère, et affichez un message de victoire si
caractère_entréeréalise ce maximum. (question 4)
corrigé
corrigé
On ajoute à la fin du programme les lignes suivantes, dans le bloc while :
while chaîne_entrée != "sortie":
# ...
if index_caractère > -1:
nombre = compte_caractère(chaîne_entrée, index_caractère)
print("Le caractère apparaît", nombre, "fois.")
if nombre == donne_max_doublon(chaîne_entrée):
print("c'est le max !")
Jouez au pendu avec votre programme !
corrigé
corrigé
Le programme complet est :
def donne_prochain_indice(chaîne, indice):
possible_suivant = chaîne.find(chaîne[indice], indice + 1)
if possible_suivant > -1:
return possible_suivant
return None
def compte_caractère(chaîne, indice):
compte = 0
while indice != None:
compte += 1
indice = donne_prochain_indice(chaîne, indice)
return compte
def donne_max_doublon(chaîne):
nombre_max = 0
caractère_max = ""
for i in range(len(chaîne)):
compte_i = compte_caractère(chaîne, i)
if compte_i > nombre_max:
nombre_max = compte_i
caractère_max = chaîne[i]
return caractère_max
chaîne_entrée = ""
while chaîne_entrée != "sortie":
chaîne_entrée = input("Entre une chaîne de caractères : ")
caractère_entrée = input("Entre un caractère : ")
index_caractère = chaîne_entrée.find(caractère_entrée)
print("Premier index du caractère :", index_caractère)
if index_caractère == -1:
print("Il n’apparaît pas")
elif donne_prochain_indice(chaîne_entrée, index_caractère) != None:
print("Il apparaît plusieurs fois")
else:
print("Il apparaît une fois")
if index_caractère > -1:
nombre = compte_caractère(chaîne_entrée, index_caractère)
print("Le caractère apparaît", nombre, "fois.")
if nombre == donne_max_doublon(chaîne_entrée):
print("c'est le max !")
Erreurs courantes à éviter
Attention aux erreurs de débutant :
- une mauvaise compréhension des mécanismes : ça se travaille et vient avec l'expérience
- croire que l'on peut produire du code de qualité et qui marche sans l'exécuter : l'humilité s'apprend aussi avec l'expérience
Recoder des fonctions
La majorité d'entre vous n'avez pas lu la documentation de la méthode find. Du coup vous l'avez recodé, le plus souvent avec des erreurs...
Vous ne perdez pas de temps à comprendre et lire de la documentation. Votre code sera plus clair, plus facile à utiliser et avec moins de bug si vous utilisez les fonctions et méthodes que python met à votre disposition. Apprendre à lire de la documentation vous fera gagner un temps fou !
Pour lire une documentation, en particulier savoir quels sont les paramètres d'une fonction lisez cette partie du cours.
Enfin, on utilise les fonctions testées dans le programme principal. On vous demande de coder des fonctions (et de les tester), ce n'est pas pour rien... Utilisez les !
Utiliser le typage des fonctions
Préférez définir vos fonctions sans le typage :
- on préférera écrire ça :
def donne_prochain_indice(chaîne, indice):puis le reste de la définition de la fonction - à ça :
def donne_prochain_indice(chaîne: str, indice: int) -> int:qui est plus lourd et n'apporte pas grand chose à la lisibilité.
Le typage a bien sur son utilité dans la documentation ou lorsque l'on veut définir précisément les paramètres (pour un test ou en algorithmie par exemple), mais dans le code où la fonction va être utilisée tout de suite après c'est inutile et rend la définition plus dure à lire.
Erreurs rares
Mais qu'il faut tout de suite arrêter de faire :
- pas de
from fonctions import *dans le programme principal car on ne sait pas ce qu'on importe - les commentaires ne se font pas avec
""" ...""". Les commentaires en python s'écrivent avec#. Si l'on veut commenter plusieurs lignes à la fois vscode a une commande pour cela :menu édition > afficher/masquer le commentaire de ligne. On l'utilise tellement souvent qu'il y a souvent un raccourci clavier en plus.