TP n°2 PCSI
Introduction
Objectifs :
• Renforcer sa maîtrise des constructions de base : contrôles du flot d’exécution avec des boucles (for
ou while), des instructions de branchement (if, elif, else)
• Spécifier des fonctions : types des paramètres, des valeurs renvoyées, précondition, postcondition.
• Écrire un jeu de tests unitaires pour une fonction.
• Manipulations élémentaires d’un tableau unidimensionnel.
• Implémenter des algorithmes de recherche séquentielle : recherche d’élément, de maximum.
• Annoter un bloc de boucles avec des propriétés : variant (terminaison), invariant (correction)
• Programmer l’algorithme de tri par bulles.
Mode d’emploi : garder les polycopiés de cours à portée de main.
Méthode
Dans un environnement de développement Python, créer un fichier tp2.py et l’enregistrer dans un dos-
sier pertinent de son espace personnel sur le réseau.
On séparera les codes de chaque exercice dans des cellules qui peuvent exécuter de façon indépendante.
• Dans Spyder, on introduit une cellule avec la séquence de caractères #%%.
• Dans Pyzo, on introduit une cellule avec la séquence de caractères ##.
1 Faire le point
Traiter au moins un exercice parmi les exercices 1 à 2, ne pas rester plus de 20 minutes sur la partie Faire le
point.
Exercice 1 Fonction, accumulateur, boucle bornée
1. Écrire une fonction répondant à la spécification suivante :
def somme_puissance(a, b, n):
"""
Description et postcondition:
Renvoie a**n + (a+1) ** n + .... + b ** n
Paramètres:
a, b et b de type int
Préconditions:
Page 1/13 Cours Moodle
TP n°2 PCSI
n >= 0 et a <= b
Retour:
type int
"""
2. Que renvoie somme_puissance(10, 5, 3) ?
3. Compléter le bloc d’instructions de la fonction avec une précondition introduite par assert, qui
renvoie un message d’erreur explicite lors de l’appel somme_puissance(10, 5, 3).
Exercice 2 Fonction, suite, boucle non bornée
A la fin des années 20 du vingtième siècle, Lothar Collatz, mathématicien allemand (1910 - 1990), s’inté-
ressait aux itérations de fonctions prenant des valeurs entières et inventa une suite de nombres définie
de la manière suivante :
• on choisit un entier naturel non nul ;
• si cet entier est pair, on le divise par deux ;
• sinon on le multiplie par 3 et on ajoute 1 ;
• on réitère le procédé avec l’entier obtenu . . .
Cette suite est devenue célèbre à l’université de Syracuse aux États-Unis à partir des années 1950 car
on peut formuler la conjecture qu’elle aboutit à l’entier 1 quel que soit l’entier choisi au départ, mais
cette conjecture n’a toujours pas été démontrée. D’après le mathématicien hongrois Paul Erdos, « Les
mathématiques ne sont pas encore prêtes pour ce type de problème ».
À ce jour, on a vérifié la conjecture avec l’informatique pour tout entier inférieur à 20×258 ≈ 5, 764×1018 .
Si on part d’un entier n, l’ensemble des valeurs prises par la suite de Syracuse jusqu’à l’obtention de 1
s’appelle un vol.
1. On initialise la suite avec l’entier 6, compléter le tableau d’états ci-dessous :
Terme u u est pair ?
6 oui
3 non
10 oui
... ...
2. Écrire une fonction syracuse(n) qui pour un entier n retourne le terme suivant dans une suite de
Syracuse.
3. Écrire une fonction duree_vol(n) qui retourne le nombre d’itérations nécessaires jusqu’à ce que
la suite de Syracuse de départ n atterrisse en 1.
4. Écrire une fonction altitude(n) qui retourne la valeur maximale atteinte par la suite de Syracuse
de départ n avant qu’elle atterrisse en 1.
Page 2/13 Cours Moodle
TP n°2 PCSI
2 Opérations élémentaires sur les tableaux
Exercice 3 Tableau en extension
1. Créer le tableau tab1 = [842,841,833].
2. Remplacer la valeur tab1[2] par 843.
3. Échanger les valeurs tab1[0] et tab1[1] en une seule affectation grace au sequence unpacking.
4. Ajouter l’élément 941 à tab1.
5. Concaténer tab1 et le tableau tab2 = [942, 943]
6. Créer un tableau tab3 contenant cent fois 0.
Exercice 4 Tableau en compréhension
À l’aide du paragraphe du Cours 2 sur les Accès aux éléments d’un tableau et de la documentation Py-
thon https://docs.python.org/3/tutorial/datastructures.html?highlight=comprehension#
tut-listcomps, définir à l’aide d’un tableau en compréhension :
1. Un tableau de dix lancers d’un dé équilibré à 6 faces numérotées de 1 à 6. On utilisera la fonction
randint du module random.
2. Le tableau des carrés des entiers compris entre 1 et 20.
3. Le tableau des entiers impairs compris entre 1 et 20.
4. Le tableau des carrés des entiers impairs compris entre 1 et 20.
5. Le tableau des entiers compris entre 0 et 100 qui sont multiples de 3 ou de 5.
6. Le tableau des entiers pairs compris entre 20 et 0 pris dans l’ordre décroissant. Écrire le même
tableau avec list et range. On consultera la documentation Python https://docs.python.org/
3/library/stdtypes.html#ranges.
Exercice 5 Slicing, découpage en tranches
À l’aide du paragraphe du Cours 2 sur les Accès aux éléments d’un tableau et de la documentation Python
https://docs.python.org/3/library/stdtypes.html?highlight=range#mutable-sequence-types,
compléter la séquence d’instructions qui permet d’obtenir l’état suivant de la mémoire, en accédant uni-
quement à des éléments ou des tranches de t1.
Vérifier avec http://pythontutor.com.
t1 = list(range(10, 1, -1))
Page 3/13 Cours Moodle
TP n°2 PCSI
3 Parcours de tableau et spécification d’une fonction, précondition et
postcondition
Relire les paragraphes spécification et mise au point du Cours 1.
Exercice 6
1. Compléter le code de la fonction Python vsom dont on donne la spécification ci-dessous :
def vsom(tab1, tab2):
"""
Paramètres :
tab1 et tab2 des tableaux de type list contenant des entiers
Page 4/13 Cours Moodle
TP n°2 PCSI
de type int
Préconditions :
tab1 non vide et len(tab1) == len(tab2)
Valeur renvoyée :
un tableau de type list contenant des nombres de type int
Postcondition :
le tableau renvoyé est constitué des sommes
terme à terme des éléments de tab1 et tab2
"""
assert len(tab1) > 0 and len(tab1) == len(tab2), "tab1 et tab2
doivent être de même longueur"
return ................. #à compléter
>>> vsom([1, 2, 3], [4, 5, 6])
[5, 7, 9]
2. Écrire une fonction smul à deux paramètres, un entier et un tableau d’entiers, qui multiplie chaque
élément du tableau par le nombre et renvoie un nouveau tableau.
Spécifier cette fonction dans sa chaîne de documentation (annotations de types, précondition,
postcondition).
>>> smul(2, [1, 2, 3])
[2, 4, 6]
3. Écrire une fonction vdif qui prend en paramètres deux tableaux d’entiers de même longueur et
qui renvoie un nouveau tableau constitué de la différence terme à terme de ces deux tableaux (le
deuxième moins le premier).
Spécifier cette fonction dans sa chaîne de documentation (annotations de types, précondition,
postcondition).
>>> vdif([1, 2, 3], [4, 5, 6])
[3, 3, 3]
4. Écrire une fonction vprod qui prend en paramètres deux tableaux d’entiers de même longueur et
qui renvoie un nouveau tableau constitué des produits terme à terme de ces deux tableaux.
Spécifier cette fonction dans sa chaîne de documentation (annotations de types, précondition,
postcondition).
>>> vprod([1, 2, 3], [4, 5, 6])
[4, 10, 18]
Exercice 7
1. Écrire une fonction somme_tab dont on donne la spécification ci-dessous :
def somme_tab(tab):
"""
Page 5/13 Cours Moodle
TP n°2 PCSI
Paramètres :tab tableau d’entiers de type int
Précondition : tab non vide
Valeur renvoyée : un entier de type int
Postcondition : renvoie la somme des éléments de tab
"""
assert len(tab) > 0, "tab doit être non vide"
#à compléter
2. Écrire une fonction moyenne_tab dont on donne la spécification ci-dessous :
def moyenne_tab(tab):
"""
Paramètres : tab tableau d’entiers de type int
Précondition : tab non vide
Valeur renvoyée : un réel de type float
Postcondition : renvoie la moyenne arithmétique des éléments de
tab
"""
#à compléter
4 Recherche linéaire et terminaison : variant de boucle
Méthode Variant de boucle
Lorsqu’un programme contient une boucle non bornée while, se pose la question de sa terminaison.
Une propriété de boucle appelée variant permet de démontrer sa terminaison.
Une valeur de type numérique définie à partir de variable(s) de la boucle est un variant si elle vérifie les
trois conditions suivantes :
• Condition 1 : le variant est positif avant l’exécution d’une itération la boucle ;
• Condition 2 : le variant diminue strictement à chaque itération de boucle ;
• Condition 3 : la condition d’arrêt de la boucle implique le dépassement d’une valeur particulière
du variant (souvent 0).
On choisit souvent un variant dans l’ensemble des entiers naturels qui ne peut pas contenir de suite
infinie strictement décroissante.
On peut documenter une boucle en précisant un variant sous forme de commentaire, et le vérifier en
phase de test avec une assertion et l’instruction assert.
Dans l’exemple ci-dessous, le variant de boucle est len(tab) - i. Ici, on peut remplacer la boucle
while par une boucle bornée for et la terminaison est immédiate, sans nécessité d’exhiber un variant,
mais ce n’est pas toujours possible sinon la boucle while serait inutile !
def somme_tab2(tab):
"""Renvoie la somme des éléments d’un tableau non vide"""
assert len(tab) > 0
Page 6/13 Cours Moodle
TP n°2 PCSI
s = 0
i = 0
while i < len(tab):
variant_debut = len(tab) - i
# propriété 1 : variant positif en début d’itération
assert variant_debut > 0
s = s + tab[i]
i = i + 1
variant_fin = len(tab) - i
# propriété 2 : variant strictement décroissant dans l’itération
assert variant_fin < variant_debut
#sortie de boucle : i == len(tab) et variant == 0
return s
Exercice 8
On s’intéresse au problème de la recherche d’un élément dans un tableau. On veut écrire deux fonctions
dont voici les spécifications :
def appartient(val,tab):
"""
Paramètres : val valeur de type int et tab tableau d’entiers de type
int
Précondition : tab non vide
Valeur renvoyée : un booléen
Postcondition : renvoie la valeur de val in tab
"""
assert len(tab) > 0
#à compléter
def index(val,tab):
"""
Paramètres : val valeur de type int et tab tableau d’entiers de type
int
Précondition : tab non vide
Valeur renvoyée : un entier
Postcondition : renvoie l’index de la première occurrence de val dans
tab et len(tab) si val not in tab
"""
assert len(tab) > 0
#à compléter
1. a. Écrire une fonction appartient en utilisant une boucle for. Si la valeur est trouvée avant la
fin du parcours du tableau, on sortira de la boucle avec l’instruction break.
b. Écrire une fonction appartient en utilisant une boucle for. Si la valeur est trouvée avant le
parcours intégral du tableau, on sortira prématurément de la fonction avec une instruction
return.
Page 7/13 Cours Moodle
TP n°2 PCSI
c. Écrire une fonction appartient en utilisant une boucle while et une instruction break si la
la valeur est trouvée avant la fin du parcours du tableau. Préciser un variant de boucle sous
forme de commentaire.
d. Écrire une fonction appartient en utilisant une boucle while et une instruction return pla-
cée en dehors de la boucle. Préciser un variant de boucle sous forme de commentaire.
2. Reprendre les questions précédentes pour la fonction index.
5 Recherche linéaire et correction : invariant de boucle
Méthode Invariant de boucle
Pour s’assurer que la postcondition d’un programme est vérifiée après l’exécution d’une boucle, on peut
rechercher une propriété de la boucle qui est :
• Initialisation : vraie avant la première itération de boucle
• Préservation : conservée par chaque itération de boucle
Une propriété vérifiant ces deux conditions se traduit par une valeur booléenne appelée invariant de
boucle.
Par induction, l’invariant de boucle est vrai en sortie de boucle et on recherche un invariant dont la
valeur en sortie de boucle entraîne la vérification de la postcondition. Ainsi la correction partielle du
programme est démontrée. On parle de correction totale lorsque la terminaison de la boucle est aussi
démontrée.
On peut documenter une boucle en précisant un invariant sous forme de commentaire. Dans la phase
de mise au point du programme, on peut vérifier avec un assert, l’initialisation d’un invariant avant la
boucle et sa préservation à la fin du bloc de boucle.
On donne un exemple ci-dessous :
def puissance(x, n):
"""
Paramètres : x un réel et n un entier
Précondition : n >= 0
Valeur renvoyé: un réel
Postcondition: renvoie x ** n sans utiliser l’exponentiation
"""
assert isinstance(n, int) and (n >= 0) # préconditions
p = 1
i = 0
invariant = (p == x ** i) # invariant de type bool
# propriété 1 : invariant vrai avant la boucle
assert invariant
while i < n:
variant_debut = n - i # variant de type int
p = p * x
i = i + 1
variant_fin = n - i
Page 8/13 Cours Moodle
TP n°2 PCSI
assert variant_fin < variant_debut
invariant = (p == x ** i)
# propriété 2 : invariant préservé par une itération
assert invariant
# correction : invariant vrai en sortie de boucle par induction
assert invariant
return p
Exercice 9
On s’intéresse au problème de la recherche du maximum dans un tableau. On veut écrire deux fonctions
dont voici les spécifications :
def valeur_maximum(tab):
"""
Paramètres : tab tableau d’entiers de type int
Précondition : tab non vide
Valeur renvoyée : un entier
Postcondition : renvoie la valeur de max(tab)
"""
#à compléter
def index_maximum(tab):
"""
Paramètres : tab tableau d’entiers de type int
Précondition : tab non vide
Valeur renvoyée : un entier
Postcondition : renvoie l’index de la première occurence du maximum de
tab
"""
#à compléter
1. Écrire les fonctions valeur_maximum et index_maximum. Annoter les codes avec la vérification
d’un invariant de boucle.
2. Relire le paragraphe du Cours 1 sur la Mise au point de programme et proposer un jeu de tests
unitaires pour chaque fonction.
3. Écrire une fonction deux_plus_grands qui renvoie les deux plus grands éléments d’un tableau
d’entiers (de taille ⩾ 2). Choisir une spécification de la fonction dans sa chaîne de documentation
et proposer un jeu de tests unitaires en réfléchissant aux différents cas possibles.
Page 9/13 Cours Moodle
TP n°2 PCSI
Exercice 10
On considère la fonction ci-dessous :
def est_decroissant(tab):
"""
Paramètre : un tableau tab d’entiers
Précondition : tab non vide
Valeur renvoyée : un booléen
Postcondition: détermine si tab dans l’ordre décroissant
"""
i = len(tab) - 2
while i >= 0:
if tab[i] >= tab[i+1]:
return True
else:
return False
i = i - 1
1. Proposer des tests prouvant que cette fonction est incorrecte.
2. Corriger cette fonction en conservant une boucle while. Annoter le codes avec la vérification d’un
invariant de boucle. Soumettre cette fonction aux jeux de tests unitaires écrits par ses deux voisins.
3. Proposer une autre version avec une boucle for.
6 Le tri par bulles
Exercice 11
1. Compléter la fonction ci-dessous en respectant la spécification donnée dans sa docstring. On
commencera par vérifier la précondition avec une instruction assert. Compléter le jeu de tests
unitaires.
def permuter(tab, i, j):
"""Paramètres :tab un tableau d’entiers, i et j des entiers
Précondition : tab non vide, i et j index valides
Valeur renvoyée : aucun (None par défaut)
Postcondition : permute les valeurs tab[i] et tab[j]"""
n = len(tab)
............................. #précondition
.............................
Page 10/13 Cours Moodle
TP n°2 PCSI
#test unitaire
t1 = [11 , 13 , 12]
permuter(t1, 1, 2)
assert t1 == [11, 12, 13]
2. Le tri par bulles consiste à balayer successivement de gauche à droite le tableau à trier en faisant
remonter vers la droite le plus grand élément non trié par permutations d’éléments adjacents.
On donne une première implémentation du tri par bulles sous la forme d’une fonction qui trie en
place le tableau passé en paramètre :
def tri_bulle1(tab):
n = len(tab)
for i in range(n):
for j in range(n - 1):
if tab[j] > tab[j + 1]:
permuter(tab, j, j + 1)
Exécuter tri_bulle1 sur un tableau tab de valeurs [4, 3, 2] en complétant le tableau d’état ci-
dessous avec les valeurs de tab après chaque exécution de la boucle interne pour des valeurs de i
et j fixées :
i 0 0 1 1 2 2
j 0 1 0 1 0 1
tab [3, 4, 2] [3, 2, 4] ... ... ... ...
3. Dans le tableau d’état précédent, on observe certains tours de boucles internes inutiles. Combien
de comparaisons sont effectuées par tri_bulle1(tab) si tab est de taille n ?
n(n − 1)
Comment modifier tri_bulle1(tab) pour que seules n−1+n−2+. . .+1 = comparaisons
2
soient effectuées pour tab de taille n et donc 3 comparaisons si tab de valeur [4, 3, 2] ?
4. Dans l’exemple d’exécution ci-dessous, on voit que l’algorithme peut s’arrêter lorsque aucune
« bulle » n’est remontée lors du dernier balayage.
Page 11/13 Cours Moodle
TP n°2 PCSI
3 5 2 1 8 9 7 3 tableau de départ
3 2 1 5 8 7 3 9 5 et 9 remontent
2 1 3 5 7 3 8 9 3 et 8 remontent
1 2 3 5 3 7 8 9 2 et 7 remontent
1 2 3 3 5 7 8 9 5 remonte
1 2 3 3 5 7 8 9 aucun élément ne remonte, liste triée
Compléter une troisième implémentation de la procédure de tri par bulles pour que le tri s’arrête
si aucune permutation n’a été effectuée lors du dernier parcours du tableau.
def tri_bulle3(tab):
n, i, permutation = len(tab), 0, True
while permutation:
permutation = ...................
for j in range(.............):
if tab[j] > tab[j + 1]:
permuter(tab, ....., .....)
permutation = ...............
i = i + 1
7 Exercices d’entraînement
Exercice 12
On veut écrire une fonction répondant à la spécification ci-dessous :
def nombre_minimal_echanges(tab):
"""
Paramètres : tab un tableau d’entiers
Précondition : tab non vide et contient uniquement des 0 ou des 1
Valeur renvoyée : un entier
Postcondition : la valeur renvoyée est le nombre minimal d’échanges
pour regrouper tous les 1 au début du tableau
"""
1. Écrire une fonction de signature precondition(t:List[int])->bool qui renvoie True si la pré-
condition est vérifiée et False sinon.
Page 12/13 Cours Moodle
TP n°2 PCSI
Dans un style de programmation défensive, on commencera le corps de la fonction par assert
len(tab) > 0 and precondition(tab).
2. Compléter le corps de la fonction nombre_minimal_echanges.
3. Récupérer le script test_nombre_minimal_echanges.py et le placer dans le même dossier que
le script du TP. Il contient un jeu de tests unitaires. Pour exécuter ce jeu de tests il suffit d’im-
porter dans ce script de tests la fonction nombre_minimal_echanges avec par exemple from tp2
import test_nombre_minimal_echanges si le script du TP s’appelle tp2.py.
Mettre au point sa fonction tant que tous les tests ne sont pas réussis. On pourra insérer des ins-
tructions d’affichage dans son code pour tracer certaines variables.
4. Lorsque la fonction passe tous les tests unitaires, annoter son code avec la vérification d’un variant
(terminaison) et d’un invariant de boucle (correction).
Exercice 13
Écrire une fonction prenant un entier n en entrée, et renvoyant le tableau de n + 1 éléments contenant
les termes de la suite de Fibonacci f 0 , f 1 , ..., f n :
>>> fibonacci(10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
Exercice 14
Écrire une fonction
à ligne_binome
! prenant en entrée un entier n ⩾ 0 et renvoyant le tableau des coeffi-
n
cients binomiaux pour k ∈ [0, n].
k
>>> ligne_binome(2)
[1, 2, 1]
Exercice 15
Écrire une fonction prenant en entrée un tableau d’entiers, et renvoyant la plus grande suite croissante
constituée de termes consécutifs du tableau (la première occurrence) :
>>> plus_grand_bloc_croissant([2, 10, 1, 3, 5, 7, 6, 8, 9, 10])
[1, 3, 5, 7]
Page 13/13 Cours Moodle