Livre Correction Nsi
Livre Correction Nsi
Livre Correction Nsi
• PUISSANCE (cours)
Le programme de l’exercice 5 du QCM est disponible dans le fichier qcm5.json. Les programmes des
autres exercices sont également disponibles dans des fichiers de nom exoNN.json où NN est le numéro de
l’exercice. Ces programmes peuvent être chargés dans le simulateur par simple cliquer-tirer du fichier
vers la fenêtre du simulateur, ou bien par la commande Ouvrir du menu Projet du simulateur. Le
chargement d’un programme prend parfois un peu de temps.
À titre d’information, il existe de nombreux autres simulateurs (la grande majorité en anglais), par
exemple :
• Little Man Computer - https://www.101computing.net/LMC/#
2. Le Registre d’Instruction (RI) contient l’instruction en cours d’exécution. Son contenu vient du
contenu de la mémoire à l’adresse du Compteur de Programme (CP). Le CP valait 0 au début, le
contenu de la mémoire à l’adresse 0 est l’instruction LOD X qui est chargée dans le RI.
L’exécution de cette instruction va chercher le contenu de la mémoire pour la cellule X, qui vaut 2.
Celle-ci est envoyée dans l’entrée de l’Unité Arithmétique et Logique (UAL), qui la stocke dans
l’accumulateur (ACC).
À l’issue de l’étape, le CP vaut 1 (adresse de la prochaine instruction) et l’accumulateur vaut 2 (valeur
chargée par l’instruction LOD X).
3. Le chiffre 2 dans l’entrée gauche de l’UAL vient de l’accumulateur ACC.
L’instruction qui a été exécutée à cette étape est ADD Y, qui ajoute à cette valeur 2 la valeur de la cellule
mémoire Y.
Cette instruction a chargé dans l’autre entrée de l’UAL la valeur de Y (5), et effectué l’addition avec
2, d’où le résultat 5 dans l’accumulateur.
4. L’instruction exécutée à cette étape est STO Z, qui enregistre la valeur de l’accumulateur (5) dans la
cellule mémoire Z.
Le compteur de programme vaut 3 à l’issue de cette étape. L’exécution de cette instruction va arrêter
le programme.
Les sauts
1. L’instruction JMP 1 met la valeur 1 dans le compteur de programme CP, de telle sorte que
QCM (CHECKPOINT)
1 a et d ; 2 b ; 3 b et c ; 4 b et c ; 5 d ; 6 b ; 7 a ; 8 b ; 9 a et d.
Exercices Newbie
1 • Le contenu du compteur de programme est envoyé à la mémoire.
• L’instruction est lue de la mémoire.
2
RI ACC RI CP Donnée Adresse
LOAD X X X X
LOAD #1 X
ADD Y X X X
MUL #2 X
STORE Z X X
soit
ranger la valeur de (X - Y) * 100 / (X - Z) dans W
4
0 JMP 0
Le programme exécute l’instruction JMP 0, qui est à l’adresse 0. Il exécute donc cette instruction
indéfiniment. Si une boucle de ce genre se retrouve dans un programme, on n’a en général pas d’autre
choix que de redémarrer l’ordinateur.
5 a.
LOAD X
STORE Z ; mettre la valeur de X dans Z
LOAD Y
STORE X ; mettre la valeur de Y dans X
LOAD Z
STORE Y ; mettre la valeur de Z dans X
HALT
b. Le programme est disponible dans le fichier exo5.json et peut être chargé dans le simulateur par
cliquer-tirer.
6 a.
LOAD X
DIV #2
MUL #2
CMP X
JMZ a ; Saut à l'adresse a si X est nul
LOAD #1 ; Charger 1
JMP b
a: LOAD #0 ; Charger 0
b: STORE Y
b. Le programme est disponible dans le fichier exo6.json et peut être chargé dans le simulateur par
cliquer-tirer.
7 a.
LOAD X
CMP Y ; comparer X et Y
JMPN ymax ; aller à ymax si X < Y
LOAD X
STORE W ; ranger X dans W
JMP suite
ymax: LOAD Y
STORE W ; ranger Y dans W
suite: CMP Z ; comparer le max de X et Y à Z
JMPP fin ; si max(X, Y) > Z on a fini
LOAD Z
STORE W ; ranger Z dans W
fin: HALT
b. Le programme est disponible dans le fichier exo7.json et peut être chargé dans le simulateur par
cliquer-tirer.
9
Pour traiter 1 million de données il faut 100 millions d’instructions. Le processeur exécute 1 milliard
d’instructions par seconde (1 Kilo = 1000, 1 Mega = 1 million, 1 Giga = 1 milliard). Il faut donc
1/10ème de seconde pour traiter les données.
10 a.
• livre : 1500 x 300 = 450 Ko
b.
Exercices Initié
11 a. L’Unité Arithmétique et Logique est en rouge au milieu (ALU pour Arithmetic and Logic Unit).
Ses entrées sont deux registres : l’accumulateur en haut à gauche (« Accumulator ») et un registre
temporaire juste à sa droite (« Temp Register »). Le registre « Accumulator Latch » permet de stocker
temporairement le contenu de l’accumulateur.
Le registre d’instruction (« Instruction Register ») est un peu plus loin à droite du registre temporaire.
Comme les deux autres registres, il est connecté au bus de données en haut (« 8 Bit internal Data Bus
»). Il est également connecté à l’unité de contrôle en bas.
Le module « Multiplexer » à droite contient d’autres registres, notamment le compteur de programme
(« Program Counter »). Ce module est connecté au bus de données en haut (« D0-D7 bidirectional
Data Bus ») et au bus d’adresses en bas à droite (« A0-A15 Address Bus »). On note que le bus de
données a 8 bits et le bus d’adresse a 16 bits. On peut donc adresser 216 octets, soit 64 kilo-octets.
L’unité de contrôle est en bas (« Timing and Control ») : elle reçoit le décodage de l’instruction
(flèche grise) et elle est connectée aux différents organes du processeur (flèches noires).
13 a.
LOAD #0
STORE Z ; mettre 0 dans Z
b: LOAD Z
ADD X
STORE Z ; ajouter X à Z
LOAD X
SUB #1
STORE X ; enlever 1 de X
JMPP b ; boucler si X > 0
HALT
Ce programme est disponible dans le fichier exo13.json et peut être chargé dans le simulateur par
cliquer-tirer.
b. 3 + 7 × (𝑛 + 1) instructions.
14 a.
• X = 10 et Y = 2 : Z vaut 0
15 a.
LOAD X
STORE Z ; copier X dans Z
b: LOAD Z
CMP Y
JMPN a ; si Z < Y, on a fini
LOAD Z
Ce programme est disponible dans le fichier exo15a.json et peut être chargé dans le simulateur par
cliquer-tirer.
b.
LOAD X
STORE Z ; copier X dans Z
LOAD #0
STORE W ; mettre 0 dans W (quotient)
b: LOAD Z
CMP Y
JMPN a ; si Z < Y, on a fini
LOAD Z
SUB Y
STORE Z ; retirer Y de Z
LOAD W
ADD #1
STORE W ; ajouter 1 au quotient
JMP b ; boucler
a: HALT
Ce programme est disponible dans le fichier exo15b.json et peut être chargé dans le simulateur par
cliquer-tirer.
16 a.
b.
• X = 2 et Y = 8 : Z vaut 256
• X = 3 et Y = 4 : Z vaut 81
c. 𝑍 = 𝑋 𝑌
Chapitre 1 – Au cœur de l’ordinateur
7
d. Ce programme est disponible dans le fichier exo16.json et peut être chargé dans le simulateur par
cliquer-tirer.
17 a. Le processeur exécute 1 MHz / 5 = 200 000 instructions par seconde. La boucle d’attente
nécessite deux instructions (une pour compter, une pour le saut), il faut donc l’exécuter 100 000 fois.
def LED 1000
go: LOAD #100
STORE LED ; allumer la LED
LOAD #100000 ; commencer le compte à rebours
on: SUB #1 UN
JMPP on ; boucle du compte à rebours
LOAD #0
STORE LED ; éteindre la LED
LOAD #100000 ; commencer le compte à rebours
off:SUB #1
JMPP off ; boucle du compte à rebours
JMP go ; recommencer clignotement
Une version de ce programme adaptée au simulateur est dans le fichier exo17a.json. Elle utilise la
variable N au lieu de LED, et le compte à rebours commence à 10 au lieu de 100 000 car le simulateur
est très lent par rapport à un vrai ordinateur (même avec le délai à 0 ms).
b. Il faut remplacer la ligne 4 par LOAD X et la ligne 9 par LOAD Y. Si les valeurs sont exprimées en
millisecondes, il faut les multiplier par 100 (MUL #100) avant chaque boucle du compte à rebours.
c. L’intensité de la LED doit passer de 0 à 100 puis de 100 à 0. On crée deux boucles imbriquées : la
boucle interne compte de 1 000 à 0, la boucle externe fait varier l’intensité de la LED.
def LED 1000
LOAD #0
STORE LED ; initialiser la LED
on: LOAD #1000 ; démarrer compte à rebours
Une version de ce programme adaptée au simulateur est dans le fichier exo17c.json. Elle utilise la
variable N au lieu de LED, et le compte à rebours commence à 2 au lieu de 1 000 car le simulateur est
très lent par rapport à un vrai ordinateur (même avec le délai à 0ms).
18
• 50.10−6 /10.10−9 = 5.103 , soit 5 000 accès mémoire pour un accès disque SSD ;
• 10.10−3 /10.10−9 = 1.106 , soit 1 million d’accès mémoire pour un accès disque mécanique.
b. Pour X = 8, Y vaut 3.
Pour X = 15, Y vaut 3.
Pour X = 16, Y vaut 4.
Chapitre 1 – Au cœur de l’ordinateur
9
c. Le programme calcule le nombre de fois que l’on peut diviser X par 2 jusqu’à ce que le résultat soit
0. Cela correspond à la partie entière du logarithme en base 2, qui est aussi le nombre de bits
nécessaires pour représenter X en base 2.
d. Le programme est disponible dans le fichier exo20.json et peut être chargé dans le simulateur par
cliquer-tirer.
21
LOAD Z ; charger l'operateur
JMZ add
SUB #1
JMZ sub
SUB #1
JMZ mul
SUB #1
JMZ div
HALT
add: LOAD X
ADD Y
JMP fin
sub: LOAD X
SUB Y
JMP fin
mul: LOAD X
MUL Y
JMP fin
div: LOAD X
DIV Y
JMP fin
Le programme commence par charger Z et selon sa valeur, branche sur l’une des quatre sections
étiquetées add (addition), sub (soustraction), mul (multiplication) ou div (division). Chaque section
effectue l’opération entre X et Y et va à l’étiquette fin où le résultat est stocké dans R et le programme
s’arrête.
Le programme est disponible dans le fichier exo21.json et peut être chargé dans le simulateur par
cliquer-tirer.
22
LOAD N
STORE R ; R <- N
DIV #2
STORE Z ; Z <- N/2
a: LOAD Z
JMZ fin ; fin si Z=0
SUB #1
STORE Z ; Z <- Z-1
LOAD N
DIV R
ADD R
DIV #2
STORE R ; R <- (R + N/R) / 2
JMP a ; boucle
fin: HALT
Chapitre 1 – Au cœur de l’ordinateur
10
On utilise l’adresse mémoire Z pour compter le nombre de fois que l’on doit faire la boucle.
Le programme est disponible dans le fichier exo22.json et peut être chargé dans le simulateur par
cliquer-tirer.
23 a. L’écran contient 1 920 × 1 080 = 2 073 600 pixesl, soit 2 073 600 × 3 = 6 220 800 octets.
Un mega-octet (Mo) est égal à un million d’octets, le contenu de l’écran nécessite donc un peu plus de
6,2 Mo.
b. 3 840 × 2 160 × 3 = 24 883 200 octets, soit près de 25 Mo (4 fois plus que pour la question a.).
c. 1 GHz représente un milliard d’opérations par seconde. Dans le premier cas, une image est affichée
est 1 000 000 000/2 073 600 = 482,253, soit plus de 480 images par seconde.
Dans le second cas 1 000 000 000/(3 840 × 2 160) = 120,563, soit 120 images par seconde.
La fréquence de réaffichage habituelle d’un écran de bonne qualité est 60 Hz (60 images par seconde).
On voit que dans le second cas le processeur passerait la moitié de son temps à afficher l’écran. C’est
la raison pour laquelle on utilise des processeurs graphiques (GPUs) qui ont des capacités d’affichage
beaucoup plus élevées, et qui déchargent le processeur central (CPU).
24 a. Si chaque processeur accède la mémoire une instruction sur deux en moyenne, la probabilité
que les deux accèdent la mémoire en même temps est de 25 %.
En effet il y a quatre cas de figure qui ont chacun la même probabilité : les deux processeurs veulent
accéder à la mémoire en même temps, ou bien seul le processeur A accède à la mémoire, ou bien seul
le processeur B, ou bien aucun des deux.
b. Si le programme a n instructions, 25 % de celles-ci (en moyenne) vont créer un conflit d’accès, soit
n/4. A chaque conflit, l’un des processeurs « gagne » l’accès à la mémoire, et l’autre doit réessayer à
l’instruction suivante. Chaque processeur devra donc attendre pour accéder à la mémoire la moitié du
A B C D Conflit d’accès
0 0 0 0 non
0 0 0 1 non
0 0 1 0 non
0 0 1 1 oui (2 accès)
0 1 0 0 non
0 1 0 1 oui (2 accès)
0 1 1 0 oui (2 accès)
1 0 0 0 non
1 0 0 1 oui (2 accès)
1 0 1 0 oui (2 accès)
1 0 1 1 oui (3 accès)
1 1 0 0 oui (2 accès)
1 1 0 1 oui (3 accès)
1 1 1 0 oui (3 accès)
1 1 1 1 oui (4 accès)
On compte 6 combinaisons sur 16 avec deux accès simultanés, 4 combinaisons avec 3 accès
simultanés, et 1 combinaison avec 4 accès simultanés. Le risque de conflit est donc 6 ∗ 0,2 ∗ 0,2 + 4 ∗
0,2 ∗ 0,2 ∗ 0,2 + 1 ∗ 0,2 ∗ 0,2 ∗ 0,2 ∗ 0,2 = 0,2736, soit un peu plus de 27 %.
Certains de ces Notebooks sont assez longs à charger, merci d’être patients ! En principe, les énoncés
s’affichent sur fond bleu pour les distinguer des corrections. Si ce n’est pas le cas, il suffit d’exécuter
la première cellule (cliquer dans la cellule et taper Majuscule-Entrée).
La plus grande partie des corrigés sont directement exécutables dans Jupyter (en tapant Majuscule-
Entrée dans les cellules de code). Pour les exercices qui ne sont pas exécutables dans Jupyter, les
fichiers nécessaires (Python ou autres) sont fournis séparément.
Une version Python pure de chacun des ces fichiers est également fournie (même nom mais suffixe
.py). Ces fichiers Python ne sont pas destinés à être exécutés tels quels, mais à servir de source pour
ou
from random import random
2.
for i in range(36):
polygone(3, 100)
right(10)
carre(8)
TP B : D’autres formes
def carre_croix(cote):
""" Affiche un carré avec un croix au milieu """
mediane = cote // 2 # Important à définir quand `cote` n'est pas impair.
carre_croix(9)
print()
def carre_diagonale(cote):
""" Affiche un carré avec une diagonale """
# Les lignes de notre dessin.
for i in range(cote):
if i == 0 or i == cote-1:
# Première ou dernière ligne.
print("X " * cote)
else:
# Lignes intermédiaires : on affiche le premier et dernier 'X ',
# ainsi que le 'X ' correspondant au numéro de ligne, avec des
# espaces entre eux.
print("X " + " " * (i-1) + "X " + " " * (cote-i-2) + "X ")
carre_diagonale(9)
print()
def origami(cote):
""" Affiche un carré avec une diagonale et une demi-diagonale"""
mediane = cote // 2 # Important à définir quand `cote` n'est pas impair.
# Diagonale montante :
elif j == cote - i - 1:
ligne += "X "
else:
ligne += " "
print(ligne)
origami(9)
def origami2(cote):
""" Affiche un carré avec une diagonale et une demi-diagonale"""
origami2(9)
def triangle(base):
""" Affiche un triangle pointant vers le haut """
mediane = base // 2
for i in range(mediane):
# On construit chaque ligne caractère par caractère.
ligne = ""
for j in range(base):
# Dernière ligne.
print("X " * base)
triangle(11)
def triangle_inverse(base):
""" Affiche un triangle pointant vers le haut en négatif """
mediane = base // 2
for i in range(mediane+1):
# On construit chaque ligne caractère par caractère.
ligne = "X " # On commence toujours par un 'X '.
for j in range(base):
# Note : Ici on teste l'appartenance à un intervalle
if mediane - i <= j <= mediane + i:
ligne += " "
else:
ligne += "X "
ligne += "X " # On termine toujours par un 'X '
print(ligne)
triangle_inverse(11)
TP C : Le sapin de Noël
def sapin(hauteur_feuillage, hauteur_tronc, rayon_tronc):
""" Affiche la forme générale d'un sapin """
# Première ligne
print(" " * hauteur_feuillage + "^")
for i in range(hauteur_feuillage):
# On construit chaque ligne caractère par caractère.
ligne = ""
for j in range(2 * hauteur_feuillage + 1):
if j == hauteur_feuillage - i - 1:
# Côté gauche
ligne += "/"
elif j == hauteur_feuillage + i + 1:
# Côté droit
ligne += "\\"
else:
ligne += " "
print(ligne)
# Maintenant le tronc
nb_espaces_avant_tronc = hauteur_feuillage - rayon_tronc
ligne = " " * nb_espaces_avant_tronc + "|" * (2 * rayon_tronc + 1)
for i in range(hauteur_tronc):
print(ligne)
# Deuxième ligne
print(" " * hauteur_feuillage + "^")
for i in range(hauteur_feuillage):
# On construit chaque ligne caractère par caractère.
ligne = ""
for j in range(2 * hauteur_feuillage + 1):
if j == hauteur_feuillage - i - 1:
# Côté gauche
ligne += "/"
elif j == hauteur_feuillage + i + 1:
# Côté droit
ligne += "\\"
else:
ligne += " "
print(ligne)
# Maintenant le tronc
nb_espaces_avant_tronc = hauteur_feuillage - rayon_tronc
ligne = " " * nb_espaces_avant_tronc + "|" * (2 * rayon_tronc + 1)
for i in range(hauteur_tronc):
print(ligne)
# Deuxième ligne
print(" " * hauteur_feuillage + "^")
for i in range(hauteur_feuillage):
# On construit chaque ligne caractère par caractère.
ligne = ""
for j in range(2 * hauteur_feuillage + 1):
if j == hauteur_feuillage - i - 1:
# Côté gauche
ligne += "/"
elif j == hauteur_feuillage + i + 1:
# Côté droit
ligne += "\\"
elif hauteur_feuillage - i <= j <= hauteur_feuillage + i:
# Feuillage : on alterne ' et "
if (i + j) % 2 == 1:
ligne += "'"
else:
ligne += '"'
else:
ligne += " "
print(ligne)
# Maintenant le tronc
nb_espaces_avant_tronc = hauteur_feuillage - rayon_tronc
ligne = " " * nb_espaces_avant_tronc + "|" * (2 * rayon_tronc + 1)
sapin(9, 4, 2)
# Première ligne
print(" " * hauteur_feuillage + "*")
# Deuxième ligne
print(" " * hauteur_feuillage + "^")
for i in range(hauteur_feuillage):
# On construit chaque ligne caractère par caractère.
ligne = ""
for j in range(2 * hauteur_feuillage + 1):
if j == hauteur_feuillage - i - 1:
# Côté gauche
ligne += "/"
elif j == hauteur_feuillage + i + 1:
# Côté droit
ligne += "\\"
elif hauteur_feuillage - i <= j <= hauteur_feuillage + i:
# Feuillage ou boule selon le résultat de `random`
if random() < proba_boule:
# Maintenant le tronc
nb_espaces_avant_tronc = hauteur_feuillage - rayon_tronc
ligne = " " * nb_espaces_avant_tronc + "|" * (2 * rayon_tronc + 1)
for i in range(hauteur_tronc):
print(ligne)
sapin(9, 3, 2, .2)
Exercices
Exercices Newbie
1 Chaîne de caractères, nombre à virgule flottante, Booléen, entier, Chaîne de caractères, Chaîne de
caractères.
3
a = 10
b = 20
(a+b)/2
print()
x = 10
print('x = 0')
print(x < 10 and x > -10)
print(x < -10 or x > 10)
print(x <= 10 and x * x >= 100)
print(x > -25 and x < -5 or x > 5 and x < 25)
5 1: Logique.
2: (rien)
3: Logique.
Ou pas.
3: Ou pas.
6 10 fois (s = 45);
5 fois (s = 120);
4 fois (s = 20);
boucle infinie
# Note : l'exercice est à faire "à la main" !
n=0
s=0
for i in range(10):
n += 1
s=s+i
print('n =', n,', s =', s)
n=0
s=1
for i in range(1,6):
n += 1
n=0
s=0
while s < 20:
n += 1
s=s+5
print('n =', n,', s =', s)
7
for i in range(1,11):
print('7 x', i, '=', i*7)
8
factorielle = 1
for i in range(1, 11):
b. n compte le nombre de fois que l’on peut retirer b de a. C’est la définition du quotient. (Et a vaut le
reste de la division à la fin du programme.)
c.
n=0
a = 27
a0 = a # mémoriser la valeur initiale de a
b=5
while a >= b:
n=n+1
a=a-b
# tester que le résultat est correct
if n != a0 // b:
print('incorrect !')
10
def quotient(a, b):
n=0
while a >= b:
n=n+1
a=a-b
return n
print(quotient(16, 4))
print(quotient(16, 5))
print(pair(10))
print(pair(11))
12 a. La fonction double ne retourne pas de valeur, ce qui en Python se note None. a vaut donc None.
13
pi = 3.14 # avec un point et pas une virgule
def aire_cercle(rayon): # _ et pas -, : à la fin
return pi * rayon **2 # rayon et pas r, ** et pas *
print(aire_cercle(10)) # indentation, parenthèse fermante
Exercices Initié
14 a.
d = 149597870700 # mètres (aussi appelé UA = Unité Astronomique)
c = 299792458 # mètres / seconde
t = 40075017 # mètres (à l'équateur)
p = 7874966000 # environ, en 2021
b.
temps_lumiere_terre_soleil = d / c
vitesse_terre = (2*pi*d) / (365.25*24*60*60)
nb_signal_electrique = c/t
rayon_terre = t / (2*pi)
surface_terre = 4 * pi * rayon_terre*rayon_terre
surface_terre_km2 = surface_terre / 1000000
surface_emergee = surface_terre_km2 * (1-0.707)
densite_moyenne_population = p / surface_emergee
print(temps_lumiere_terre_soleil, 'secondes')
print(vitesse_terre, 'm/s, soit', vitesse_terre/1000*60*60, 'km/h')
print(nb_signal_electrique, 'tours')
print(surface_terre, 'm2, soit', surface_terre_km2/1000000, 'millions de km2')
print(densite_moyenne_population, 'hab/km2')
15
# Note : il existe d'autres solutions.
# Note 2 : les résultats sont des nombres en virgule flottante
# lorsqu'on utilise la division.
# On peut utiliser la divition entière pour avoir des résultats entiers
# (mais alors on ne sait pas si le résultat est réellement entier)
print((3 + 1 - 4) / 2)
print(2*3 - (4 + 1))
print(2 * (3 + 1)/4)
print((4 + 2) / (3 - 1))
print((4 * (3 - 1))/2)
print((3 - 1)/2 + 4)
print(4*2 - 3 + 1)
print(4 + 3 * (2 - 1))
print(4 * (3+1) / 2)
print(4*3 - (2 + 1))
print((4*3 - 2)/1)
17
for i in range(2):
a = i == 1
for j in range(2):
b = j == 1
print('a =', a, ' b =', b)
print(' not (a and b) =', not (a and b))
print(' not a or not b =', not a or not b)
print(' not (a or b) =', not (a or b))
print(' not a and not b =', not a and not b)
19 a.
# On définit une fonction pour faciliter le test
def et(a, b):
return not (not a or not b)
b.
# On définit une fonction pour faciliter le test
def ou(a, b):
return not (not a and not b)
20
t = 25
21 a.
a = 2021 # changer l'année pour tester d'autres valeurs
bissextile = False
if a % 4 == 0 and (a % 100 != 0 or a % 400 == 0):
bissextile = True
print(bissextile)
b.
m = 2 # changer le mois pour tester d'autres valeurs
print(jours)
22 a.
def est_bissextile(annee):
""" retourne True si l'annee est bissextile """
bissextile = False
if annee % 4 == 0 and (annee % 100 != 0 or annee % 400 == 0):
bissextile = True
return bissextile
b.
print(nb_jours(1, 2, 2022))
print(nb_jours(14, 7, 2022))
print(nb_jours(31, 12, 2022))
print(nb_jours(31, 12, 2024))
c.
def jour_semaine(jour, mois, annee, jour0):
""" retourne le jour de la semaine
de la date donnee. `jour0` est le
code du premier jour de l'annee """
return (jour0 + nb_jours(jour, mois, annee) -1) % 7
# Le dernier jour d'une année non bissextile est le même que le premier
print(jour_semaine(31, 12, 2021, 4))
d.
23
for i in range(10):
print((i+1)*2)
i=2
while i <= 20:
print(i)
i=i+2
24
from random import random
print("Ha" + "ha"*round(random()*9))
25 a. Note : L’exercice est à faire (au moins dans un premier temps) à la main.
Valeurs après chaque « tour » de boucle, pour a = 10, b = 3
• r = 1, a = 3, b = 1
• r = 0, a = 1, b = 0
• r = 5, a = 10, b = 5
• r = 0, a = 5, b = 0
26 a.
x=1
while x < 1000000:
print(x)
x=x*2
b.
n=0
x=1
while x < 1000000:
x=x*2
n=n+1
print(n)
while a != 0:
print(a % 10)
a = a // 10
28 a.
for i in range(0,16):
print(i, sin(i*pi/15) * 30)
b.
x = 0.1
print('=' * round(x * 30))
x = 0.5
print('=' * round(x * 30))
x=1
print('=' * round(x * 30))
c.
for i in range(0,16):
print('=' * round(sin(i*pi/15) * 30))
d.
for i in range(0,16):
v = sin(i*3.14*2/15)
if v > 0:
print(' '*20 + '='*round(v*20))
else:
l = round(-v*20)
print(' '*(20-l) + '='*l)
29
# En dehors de Jupyter on peut importer turtle à la place de nsi_turtle
from nsi_turtle import *
dedans = True
while dedans:
# tourner d'un angle aléatoire et avancer de 10 à 50 pas
left(360.0*random())
forward(10.0 + 40.0*random())
# tester si on est dans la fenêtre
dedans = abs(xcor()) < 400 and abs(ycor()) < 400
b.
n1 = 0
n2 = 1
print(1, n1)
print(2, n2)
for i in range(98):
print(i+3, n1 + n2, (n1 + n2) / n2)
n = n1 # se souvenir de n1 avant de changer sa valeur !
n1 = n2
n2 = n + n2
31 a.
def Syracuse(n):
""" Affiche les valeurs successives de la suite de Syracuse """
while n != 1:
print(n)
if n % 2 == 0:
n = n // 2
else:
n = n*3 + 1
print('1 - fin!')
b.
def Syracuse_vol(n):
""" Retourne la durée de vol de la suite de Syracuse """
vol = 0
while n != 1:
vol = vol + 1
if n % 2 == 0:
n = n // 2
else:
n = n*3 + 1
return vol
print(Syracuse_vol(120))
print(Syracuse_vol(123))
print(Syracuse_vol(130))
print(Syracuse_altitude(120))
print(Syracuse_altitude(123))
print(Syracuse_altitude(130))
b.
for i in range(10):
print('*' * i)
c.
for i in range(10):
print('*' * (9 -i))
33 a.
total = 5
for i in range(1, 7): # valeur du premier dé
for j in range(1, 7): # valeur du second dé
if i+j == total: # vérifier si on a le total voulu
print(i, j)
Note : Ce programme n’est pas efficace car il teste beaucoup de possibilités inutiles.
Une fois connue la valeur du premier dé (i), on peut calculer la valeur du second. On évite ainsi la
deuxième boucle imbriquée :
Note : On pourrait encore améliorer l’algorithme lorsque total est inférieur à 6 : la première boucle for
peut alors s’arrêter à total.
b.
for total in range(2, 13): # totaux possibles entre 2 et 12
print('total =', total)
for i in range(1, 7):
j = total - i # valeur du second dé
if j > 0 and j < 7: # s'assurer que c'est une valeur possible
print(i, j)
c.
for total in range(3, 19):
print('total =', total)
for i in range(1, 7):
for j in range(1, 7):
k = total - i - j
if k > 0 and k < 7:
print(i, j, k)
d.
print('Avec 2 dés')
for total in range(2, 13): # totaux possibles entre 2 et 12
n=0
for i in range(1, 7):
print()
print('Avec 3 dés')
for total in range(3, 19):
n=0
for i in range(1, 7):
for j in range(1, 7):
k = total - i - j
if k > 0 and k < 7:
n=n+1
print('total = ', total, ',', n, 'possibilités')
34 a.
prix = 127
if prix // 50 > 0:
print(prix // 50, 'billets de 50€')
prix = prix % 50
if prix // 20 > 0:
print(prix // 20, 'billets de 20€')
prix = prix % 20
if prix // 10 > 0:
print(prix // 10, 'billets de 10€')
prix = prix % 10
if prix > 0:
print(prix, 'pièces de 1€')
b.
a_rendre = paiement - prix
# nombre de billets de 50
n50 = a_rendre // 50
if n50 > nb_50euros:
# ne pas utiliser plus de billets que disponible
n50 = nb_50euros
if n50 > 0:
print(n50, 'billets de 50€')
a_rendre = a_rendre - n50*50
nb_50euros = nb_50euros - n50
# nombre de billets de 20
n20 = a_rendre // 20
if n20 > nb_20euros:
# ne pas utiliser plus de billets que disponible
n20 = nb_20euros
if n20 > 0:
print(n20, 'billets de 20€')
a_rendre = a_rendre - n20*20
nb_20euros = nb_20euros - n20
# nombre de billets de 10
n10 = a_rendre // 10
# nombre de billets de 5
n5 = a_rendre // 5
if n5 > nb_5euros:
# ne pas utiliser plus de billets que disponible
n5 = nb_5euros
if n5 > 0:
print(n5, 'billets de 5€')
a_rendre = a_rendre - n5*5
nb_5euros = nb_5euros - n5
# nombres de pièces de 1€
n1 = a_rendre
if n1 > nb_1euro:
# ne pas utiliser plus de pièces que disponible
n1 = nb_1euro
if n1 > 0:
print(n1, 'pièces de 1€')
a_rendre = a_rendre - n1
nb_1euro = nb_1euro - n1
c. S’il n’y a pas assez de monnaie, on s’en rend compte seulement à la fin. Il faut donc remettre
l’argent dans la caisse. Les variables n50, n20, etc. permettent de mettre à jour la caisses :
if a_rendre > 0: # on n'a pas pu rendre la monnaie
print('Pas suffisamment de monnaie !')
print('Il manque', a_rendre, '€')
# on remet la monnaie rendue dans la caisse
nb_50euros = nb_50euros + n50
nb_20euros = nb_20euros + n20
nb_10euros = nb_10euros + n10
nb_5euros = nb_5euros + n5
nb_1euro = nb_1euro + a_rendre
d.
a_rendre = paiement - prix
n5 = rendre(a_rendre, 5, nb_5euros)
a_rendre = a_rendre - n5 * 5
nb_5euros = nb_5euros - n5
n1 = rendre(a_rendre, 1, nb_1euro)
a_rendre = a_rendre - n1
nb_1euro = nb_1euro - n1
b.
def epargne(montant, taux, duree):
for i in range(duree):
montant = montant + interets_annuels(montant, taux)
return montant
c.
print(epargne(10000, 1.5, 10))
36 L’instruction print(a) dans le premier programme est incorrecte car a n’est pas définie.
Le second programme est correct et affiche 10, 100, 200.
comparer(100)
comparer(10000)
comparer(1000000)
38 a.
compteur = 0
def f(n):
global compteur # ne pas oublier !!
compteur = compteur + 1
print(n)
for i in range(100):
if random() < 0.5:
f(i)
def f(n):
global compteur, total # ne pas oublier !!
compteur = compteur + 1
total = total + n
print(n)
for i in range(100):
if random() < 0.5:
f(i)
c.
def raz():
global compteur, total
compteur = 0
total = 0
raz()
Exercices Titan
39 a. Contrairement à ce à quoi on pouvait s’attendre, la boucle while ne s’arrête pas après la 11-ième
Étant donné que x n’est pas exactement égal à 1.0 au début de 11-ième itération, celle-ci s’exécute, et
l’instruction x = x + 0.1 fait prendre à x une valeur strictement supérieure à 1.0. À partir de la 12-ième
itération, la valeur de x étant strictement supérieure à 1.0, puisque celle-ci ne peut que croître, on peut
en déduire que la condition d’arrêt de la boucle while ne sera jamais vraie lors des itérations suivantes
: la boucle est donc infinie.
b.
x = 0.0
while x <= 1.0:
print(x)
x = x + 0.1
c.
x = 0.0
while x <= 3.0:
d. Afin de contourner ce problème causé par la représentation des nombres à virgule flottante dans un
ordinateur, on peut faire le choix de définir une nouvelle façon de tester si deux nombres à virgule
flottants sont égaux, de façon à ce que deux nombres suffisament proches l’un de l’autre soient
considérés comme égaux.
# Le seuil de différence entre deux nombres en dessous duquel
# les deux nombres sont considérés comme égaux est souvent noté epsilon :
EPSILON = 1e-9
Avec cette nouvelle façon de tester si deux nombres sont égaux et cette valeur de la constante epsilon,
les nombres 3.0 et 3.0000000000000013 (la valeur de x dans l’itération manquante de la question
précédente) sont désormais considérés égaux, et le programme fonctionne dans les deux cas.
x = 0.0
while est_inferieur_ou_egal(x, 1.0):
print(x)
x = x + 0.1
x = 0.0
while est_inferieur_ou_egal(x, 3.0):
print(x)
x = x + 0.1
40 a.
def est_premier(n):
"""Retourne True si n est premier, et False dans le cas contraire."""
# Sinon, on teste tous les diviseurs potentiels de n dans l'intervale [2, n-1].
n_est_premier = True
b.
def est_premier(n):
"""Retourne True si n est premier, et False dans le cas contraire."""
# Sinon, on teste tous les diviseurs potentiels de n dans l'intervale [2, n-1].
diviseur_potentiel = 2
return True
c.
def est_premier(n):
return True
d.
def affiche_nombres_premiers(m):
"""Affiche les m premiers nombres premiers."""
n=1
nb_premiers_affiches = 0
41 a.
# On suppose que les signes sont représentés par les chaînes de caractères
# "pierre", "papier", et "ciseaux".
# Si on atteint cette étape, cela signifie que le joueur A n'a pas gagné
# Quelques tests :
b.
from random import random
def tire_signe_au_hasard():
"""Retourne un des trois signes possibles du jeu
en le choissisant au hasard.
"""
def simule_une_partie_avec_gagnant():
"""
Simule une partie de jeu et retourne un nombre entier dont le signe indique
qui a gagné (positif pour le joueur A, négatif pour le joueur B)
et dont la valeur absolue indique le nombre de points obtenus.
En cas d'égalité, le nombre de points en jeu augmente de 1, et la simulation
continue jusqu'à ce que l'un des joueurs perde.
"""
nb_points_en_jeu = 1
# Tant que les deux signes tirés au hasard produisent une égalité,
# on recommence l'opération en incrémentant le nombre de points en jeu de 1.
comparaison_des_signes = compare_deux_signes(tire_signe_au_hasard(), tire_signe_au_hasard())
while comparaison_des_signes == 0:
nb_points_en_jeu = nb_points_en_jeu + 1
comparaison_des_signes = compare_deux_signes(tire_signe_au_hasard(), tire_signe_au_hasard())
nb_parties_a_simuler = 50
42 a.
# On importe la fonction sinus et la constante PI (utiles pour cet exercice).
from math import sin, pi
def f(x):
"""Fonction dont on souhaite estimer l'antécédent x
étant donné une image f(x)."""
return sin(x)
V = 0.5
min = 0
max = 1
pas = 0.1
x = min
estimation_antecedent_trouvee = False
b.
# On commence ici avec x = 0.5, tel qu'on l'a trouvé dans la question précédente.
x = 0.5
min = x
max = x + 0.1
pas = 0.01
estimation_antecedent_trouvee = False
if estimation_antecedent_trouvee:
print(x)
c.
# Les valeurs des variables min, max et pas sont amenées à être modifiées
# dans la boucle externe du code ci-dessous.
min = 0
max = 1
pas = 0.1
x = min
estimation_antecedent_trouvee = False
estimation_precedente = x
x = min
if estimation_antecedent_trouvee:
print(x)
else:
print("Aucun antécédent de V n'a été trouvé dans l'intervale [", min, max, "]")
d.
# Le code ci-dessus nous a permis de trouver la valeur x = 0.5235979999999998.
# On vérifie donc que 0.5235979999999998 est proche de pi/6 :
43 Oui, les deux programmes sont équivalents : les deux contiennent une boucle de 10 itérations
opérant sur une variable i, affichée à chaque tour, débutant à 0, et augmentant de 1 à chaque itération.
for i in range(10):
i=i*2
print(i)
i=0
while i < 10:
i=i*2
print(i)
i=i+1
# Affiche 0, 2, 6, 14.
Lorsqu’on insère i = i * 2 avant print(i), les deux programmes ne sont plus équivalents : on peut
l’observer en évaluant les deux morceaux de code ci-dessus, qui affichent deux suites de nombres
différentes.
button("Effacer", clear)
def creer_polygone():
polygone(get_int(tirette_ncotes), get_int(tirette_taille))
main_loop()
def droite():
right(10)
onkey(droite, "Right")
onkey(gauche, "Left")
main_loop()
QCM (CHECKPOINT)
1 c ; 2 c ; 3 c ; 4 a, c et e ; 5 b ; 6 b et c ; 7 c ; 8 b.
Exercices Newbie
1
def input_bool(message):
""" Demande une saisie booléenne """
while True:
r = input(message)
if r == 'o' or r == 'oui':
return True
if r == 'n' or r == 'non':
return False
print('Répondre oui ou non !')
2 a.
temp = input('Température à convertir : ')
print(float(temp)*9/5 + 32)
b.
3
entree = input('Entrer un chiffre entre 1 et 9 : ')
n = int(entree)
if n > 0 and n < 10:
for i in range(1, 11):
print(f'{i:2} x {n} = {i*n:2}')
else:
print('Entrée invalide')
4
def input_defaut(message, defaut):
""" Demande un saisie et retourne `defaut`
si l'utilisateur tape simplement Entree
"""
reponse = input(f'{message}({defaut}) ')
if reponse == '':
return defaut
return reponse
5
from nsi_ui import *
def convert():
""" Convertit le montant saisi dans les deux monnaies """
m = get_float(montant)
r1 = m * taux
r2 = m / taux
set_text(conv1, str(m) + str(monnaie1) + " = " + str(r1) + str(monnaie2))
set_text(conv2, str(m) + str(monnaie2) + " = " + str(r2) + str(monnaie1))
# Construire l'interface
montant = entry("Montant")
button("Convertir", convert)
begin_vertical()
conv1 = label('conversion')
conv2 = label('conversion')
end_vertical()
main_loop()
def ajouter():
""" Afficher la somme des valeurs des champs A et B """
a = get_float(champA) # valeur entrée dans le champ A
b = get_float(champB) # valeur entrée dans le champ B
set_text(resultat, a+b) # afficher le resultat
def soustraire():
""" Afficher la difference des valeurs des champs """
a = get_float(champA)
b = get_float(champB)
set_text(resultat, a-b)
button("Différence", soustraire)
button("Somme", ajouter) # bouton pour lancer le calcul
resultat = label("") # zone d'affichage du résultat
b.
# Note : nécessaire seulement dans Jupyter
clear_ui()
def ajouter():
def soustraire():
""" Afficher la difference des valeurs des champs """
a = get_float(champA)
b = get_float(champB)
set_text(resultat, f'Différence = {a-b}')
# Contruction de l'interface
begin_vertical()
begin_horizontal()
champA = slider("Valeur de a", 0, 100)
champB = slider("Valeur de b", 0, 100)
end_horizontal()
begin_horizontal()
button("Somme", ajouter)
button("Différence", soustraire)
end_horizontal()
resultat = label("")
end_vertical()
main_loop()
# L'heure courante
H = 12
M=0
def avance_heure():
""" Ajouter 1 à l'heure H """
global H
H=H+1
if H > 23:
H=0
set_value(heures, H)
def recule_heure():
""" Retirer 1 de l'heure H """
global H
H=H-1
if H < 0:
H = 23
set_value(heures, H)
def avance_minute():
""" Ajouter 1 aux minutes M """
global M, H
M=M+1
if M >= 59:
M=0
avance_heure()
set_value(minutes, M)
# Construire l'interface
button("-", recule_heure)
heures = entry("")
set_width(heures, 4)
button("+", avance_heure)
label("heure")
button("-", recule_minute)
minutes = entry("")
set_width(minutes, 4)
button("+", avance_minute)
label("minutes")
set_value(heures, H)
set_value(minutes, M)
main_loop()
def avance():
forward(10)
def recule():
backward(10)
def gauche():
left(10)
def droite():
right(10)
onkey(avance, 'Up')
onkey(recule, 'Down')
onkey(gauche, 'Left')
onkey(droite, 'Right')
b.
ecriture = True # etat du stylet
def stylet():
""" Inverser l'état du stylet """
global ecriture
if ecriture:
penup()
else:
pendown()
ecriture = not ecriture
onkey(stylet, '.')
onkey(pentagone, 'p')
mainloop()
Exercices Initié
9 a.
def input_int(message):
""" Demande un nombre entier à l'utilisateur """
while True:
try:
return int(input(message))
except ValueError:
print('erreur de saisie - recommencer')
b.
c.
def input_int_interval(message, min, max):
""" Demande un nombre entier dans l'intervalle donné """
v = input_int(message)
while v < min or v > max:
if v < min:
print('Valeur trop petite')
else:
print('Valeur trop grande')
v = input_int(message)
return v
10 a.
from random import randint
b.
from random import randint
c.
from random import randint
else:
if nessais <= 6:
print(f'Bravo ! trouvé en {nessais} essais')
else:
print(f'Trouvé en {nessais} essais')
trouve = True
11
from math import log10
n = input_int('entrer n : ') # exercice 9
n1 = 1 + int(log10(n))
n2 = 1 + int(log10(n*n))
for i in range(n):
carre = (i+1)*(i+1)
print(f'le carré de {i+1:{n1}} est {carre:{n2}}')
12 a.
insectes = 10
maximum = input_int("Nombre maximal d'insectes ? ") # exercice 9
njours = 1
def simulation():
njours = 1
# obtenir les paramètres
facteur = get_int(entree_facteur)
insectes = get_int(entree_initial)
maximum = get_int(entree_maximum)
# lancer la simulation
while insectes * facteur < maximum:
insectes = insectes * 3
njours = njours + 1
# afficher le résultat
print(f'{insectes} insectes en {njours} jours')
main_loop()
13
from nsi_turtle import * # Dans Python on peut importer turtle
from nsi_ui import *
def demarre():
""" Lance le chronomètre """
clear()
compteur = get_int(compteur_texte)
animate(decompte, 1000)
main_loop()
14
from nsi_ui import *
def get_chrono():
""" Retourne la valeur du chronomètre """
return get_float(chrono_texte)
def compte():
""" Mettre à jour le chronomètre """
chrono = get_chrono()
chrono += .1
set_text(chrono_texte, round(chrono, 1))
def demarre():
""" Lancer le chronomètre """
global dernier_tour
dernier_tour = 0
set_text(chrono_texte, 0.0)
set_text(tour_texte, 0.0)
animate(compte, 100)
def arrete():
""" Arrêter le chronomètre """
stop_animate()
def tour():
""" Afficher le temps au tour """
global dernier_tour
chrono = get_chrono()
set_text(tour_texte, round(chrono - dernier_tour, 1))
dernier_tour = chrono
# Créer l'interface
main_loop()
15 a.
from turtle import *
def affiche_nombre(heure):
write(heure)
def dessine_cadran():
""" Dessine le cadran de l'horloge analogique """
penup()
goto(0, 0)
setheading(90)
# affiche les 12 nombres des heures
for i in range(1, 13):
right(30)
forward(50)
affiche_nombre(i)
backward(50)
dessine_cadran()
b.
def dessine_aiguille(angle, length):
""" Affiche une aiguille à l'angle donné
et de la longueure donnée
"""
penup()
goto(0, 0)
setheading(90)
pendown()
right(angle)
forward(length)
affiche_heure(10, 10)
c.
# Note : nécessaire seulement dans Jupyter
clear_ui()
# L'heure courante
H = 16
M = 40
H=H+1
if H > 23:
H=0
set_value(heures, H)
affiche_heure(H, M)
def recule_heure():
""" Retirer 1 de l'heure H """
H=H-1
if H < 0:
H = 23
set_value(heures, H)
affiche_heure(H, M)
def avance_minute():
""" Ajouter 1 aux minutes M """
global H, M
efface_heure(H, M)
M=M+1
if M >= 59:
M=0
avance_heure()
set_value(minutes, M)
affiche_heure(H, M)
def recule_minute():
""" Retirer 1 des minutes M """
global H, M
efface_heure(H, M)
M -= 1
if M < 0:
M = 59
recule_heure()
set_value(minutes, M)
affiche_heure(H, M)
# Construire l'interface
button("-", recule_heure)
heures = entry("")
set_width(heures, 4)
button("+", avance_heure)
label("heure")
button("-", recule_minute)
minutes = entry("")
set_width(minutes, 4)
button("+", avance_minute)
label("minutes")
set_value(heures, H)
set_value(minutes, M)
main_loop()
16
from nsi_ui import *
def FtoC():
""" Convertit la température du slider en degrés centigrades
et affiche le résultat
"""
t = get_int(temperature)
set_text(result, (t - 32.) * 5/9)
# Construire l'interface
temperature = slider("Temperature", -100, 100)
convert = button("C to F", CtoF)
convert = button("F to C", FtoC)
result = entry("Resultat")
set_width(result, 10)
main_loop()
17
from nsi_ui import *
def convertirF(v):
# convertir de centigrade -> Fahrenheit
set_value(fahrenheit, v * 9/5 + 32.)
def convertirC(v):
# convertir de Fahrenheit -> centigrade
set_value(celsius, (v - 32.) * 5/9)
# Créer l'interface
celsius = slider("centigrade", -100, 100, convertirF)
fahrenheit = slider("Farenheit", -148, 212, convertirC)
18
from turtle import *
def afficher():
""" Reafficher le texte avec les attributs courants """
clear()
write(get_string(text))
def aligne(a):
""" Changer l'alignement du texte """
set_align(a)
afficher()
def taille(t):
""" Changer la taille du texte """
set_size(t)
afficher()
def style(s):
""" Changer le style d'affichage """
set_style(s)
afficher()
# Construire l'interface
end_vertical()
main_loop()
Exercices Titan
19 a. Une première stratégie pourrait être de proposer des nombres aléatoires entre 0 et 100. Mais cela
ne prend pas en compte l’information fournie par l’utilisateur.
Note : Une meilleure stratégie est de prendre le milieu de l’intervalle (il s’agit alors de la recherche
dichotomique qui sera vue au chapitre 6) :
def choix(min, max):
return (min + max) // 2
b. Le joueur triche s’il repond < ou = pour un nombre inférieur ou égal au minimum, ou > ou = pour un
nombre supérireur au maximum.
Avec le code ci-dessus ce cas ne peut pas se produire puisque l’on choisit un nombre entre minimum+1
et maximum-1. Cependant, si cet intervalle est vide, cela signifie que l’utilisateur a triché. Pour détecter
ce cas il suffit d’ajouter un test à la fin de la boucle while :
if minimum == maximum or minimum+1 == maximum:
print('Vous avez triché !')
trouve = True
20 a.
from nsi_curses import *
init_curses()
add_str(20, 10, 'Choisissez un nombre dans votre tête.')
add_str(20, 11, 'À chacune de mes propositions, répondre')
add_str(20, 12, ' < si votre nombre est plus petit que celui que je propose')
add_str(20, 13, ' > si votre nombre est plus grand que celui que je propose')
add_str(20, 14, " = si j'ai deviné le nombre")
add_str(20, 15, "Taper une touche pour commencer")
# donner le temps au joueur de lire les règles et de choisir un nombre
c = wait_key()
reponse_ok = False
while not reponse_ok:
b. Pour gérer le fait que get_key retourne une chaîne vide lorsque l’on n’a rien saisi dans l’intervalle
donné, on ajoute un cas dans le corps de la boucle while :
while not reponse_ok:
reponse = get_key(10) # attente au plus 1 seconde
reponse_ok = True
if reponse == '': # pas d'entrée
trouve = True
21 a.
from nsi_ui import *
def rien():
""" Fonction de rappel qui ne fait rien """
pass
# Construire l'interface
begin_vertical()
# La zone résultat
res = entry("")
set_width(res, 16)
# La rangée du haut
begin_horizontal()
button('7', rien)
button('8', rien)
button('9', rien)
button('/', rien)
end_horizontal()
# La troisième rangée
begin_horizontal()
button('1', rien)
button('2', rien)
button('3', rien)
button('+', rien)
end_horizontal()
# La rangée du bas
begin_horizontal()
button('C', rien)
button('0', rien)
button('=', rien)
button('-', rien)
end_horizontal()
end_vertical()
main_loop()
b.
from nsi_ui import *
def chiffre(d):
""" Entrer le chiffre d """
global a
# mettre à jour et afficher le nouveau nombre
a = a*10 + d
set_text(res, a)
def effacer():
""" Remettre à zéro la calculette """
global a
a=0
set_text(res, a)
# Construire l'interface
# Pour chaque chiffre, on utilise la fonction de rappel
# `chiffre` en lui passant le chiffre correspondant à la touche
begin_vertical()
# La zone résultat
res = entry("")
set_width(res, 16)
# La rangée du haut
# La deuxième rangée
begin_horizontal()
button('4', chiffre, 4)
button('5', chiffre, 5)
button('6', chiffre, 6)
button('*', rien)
end_horizontal()
# La troisième rangée
begin_horizontal()
button('1', chiffre, 1)
button('2', chiffre, 2)
button('3', chiffre, 3)
button('+', rien)
end_horizontal()
# La rangée du bas
begin_horizontal()
button('C', effacer)
button('0', chiffre, 0)
button('=', rien)
button('-', rien)
end_horizontal()
end_vertical()
main_loop()
def chiffre(d):
""" Entrer le chiffre d """
global a, nb_en_attente, debut_nombre
if debut_nombre:
# Si on vient de lancer la calculette ou
# si on vient de taper une opération,
# on est au début d'un nombre :
def effacer():
""" Remettre à zéro la calculette """
global a, debut_nombre, nb_en_attente, op_en_attente
a=0
debut_nombre = True
nb_en_attente = 0
op_en_attente = ""
set_text(res, a)
def operation(op):
""" Mémoriser l'opération op et exécuter l'opération
en attente, s'il y en a une.
"""
global a, debut_nombre, nb_en_attente, op_en_attente
# Le résultat de l'opération est mis dans nb_en_attente
# de manière à pouvoir enchaîner les opérations 8 + 10 - 2
div_zero = False
if op_en_attente == '+':
a = nb_en_attente + a
elif op_en_attente == '-':
a = nb_en_attente - a
elif op_en_attente == '*':
a = nb_en_attente * a
# afficher le résultat
if div_zero:
set_text(res, "Division par 0 !")
else:
set_text(res, a)
# Construire l'interface
# Pour chaque chiffre, on utilise la fonction de rappel
# `chiffre` en lui passant le chiffre correspondant à la touche
# Pour chaque opération, on utilise la fonction de rappel
# `operation` en lui passant l'opération correspondant à la touche
begin_vertical()
# La zone résultat
res = entry("")
set_width(res, 16)
# La deuxième rangée
begin_horizontal()
button('4', chiffre, 4)
button('5', chiffre, 5)
button('6', chiffre, 6)
button('*', operation, '*')
end_horizontal()
# La troisième rangée
begin_horizontal()
button('1', chiffre, 1)
button('2', chiffre, 2)
button('3', chiffre, 3)
button('+', operation, '+')
end_horizontal()
# La rangée du bas
begin_horizontal()
button('C', effacer)
button('0', chiffre, 0)
button('=', operation, '=')
button('-', operation, '-')
end_horizontal()
end_vertical()
Bonus : En remplaçant les appels à set_text(res, a) ci-dessus par l’appel à la fonction affichage(res) ci-
dessous, on obtient un affichage de l’opération en cours, au lieu d’afficher seulement le nombre en
cours de saisie.
def affichage(res):
""" Affiche l'opération en cours dans res """
if op_en_attente == "" or op_en_attente == "=":
set_text(res, a)
else:
if debut_nombre:
set_text(res, f'{a} {op_en_attente}')
else:
set_text(res, f'{nb_en_attente} {op_en_attente} {a}')
22 a.
from turtle import *
from nsi_ui import *
def avance():
forward(2)
animate(avance)
onkey(stop_animate, '.')
b.
def gauche():
left(10)
def droite():
right(10)
bouge = False
def start_stop():
global bouge
if bouge:
stop_animate()
else:
animate(avance)
bouge = not bouge
onkey(gauche, 'Left')
onkey(droite, 'Right')
onkey(start_stop, 'Space')
left(90)
main_loop()
c.
vitesse = 2 # vitesse de la tortue
def avance():
forward(vitesse)
def gauche():
left(10)
def accelere():
global vitesse
vitesse = vitesse + 1
def ralentit():
global vitesse
# Note : on pourrait autoriser les vitesses négatives,
# ce qui aurait pour effet de faire reculer la tortue
if vitesse > 1:
vitesse = vitesse -1
bouge = False
def start_stop():
global bouge
if bouge:
stop_animate()
else:
# Note : on pourrait remettre la vitesse
# à sa valeur par défaut
animate(avance)
bouge = not bouge
onkey(gauche, 'Left')
onkey(droite, 'Right')
onkey(accelere, 'Up')
onkey(ralentit, 'Down')
left(90)
main_loop()
23 a. Note : Le code de cet exercice ne peut être exécuté dans Jupyter car ipyturtle ne reconnait pas les
événements d’entrée. Le corrigé est disponible dans le fichier exo23.py pour exécution directe dans
Python.
# importer nsi_turtle de préférence à turtle
from nsi_turtle import *
main_loop()
b.
# importer nsi_turtle de préférence à turtle
from nsi_turtle import *
main_loop()
Les tuples
Singleton - La note de la page 64 indique qu’il faut noter un singleton (10,) et non pas (10) qui est
simplement l’entier 10. Elle ne dit pas qu’il faut également une virgule pour extraire l’élément d’un
singleton :
n = (10,) # n vaut le singleton (10,)
n, = (10,) # n vaut l'entier 10
plutôt que :
t = []
for i in range(n):
t = t + [i*i]
Cependant, lorsque l’on connait d’avance la taille du tableau, on peut suivre l’esprit du programme en
créant un tableau de la bonne taille (ici n), initialisé avec une valeur « neutre » (0 ou None par
exemple), puis en afffectant ses éléments :
Les dictionnaires
p-uplets nommés - Le programme mentionne les p-uplets nommés, qui devraient être présentés
comme des tuples, tout en indiquant d’utiliser les dictionnaires Python pour les réaliser. Il nous a
semblé qu’introduire la notion de p-uplet nommé, immuable, pour la réaliser avec un dictionnaire,
muable, allait créer plus de confusion qu’autre chose.
Les tuples nommés de Python (« named tuples ») sont plus proches conceptuellement de la notion de
p-uplet nommé, mais leur réalisation nécessite d’introduire les paramètres mots-clés, et la syntaxe
n’est pas très heureuse. Nous avons donc choisi de faire l’impasse sur cette notion, qui au demeurant
n’apparait pas dans les sujets de bac que nous avons pu consulter.
Au besoin, on peut, après avoir introduit les dictionnaires, indiquer que certains langages permettent
de manipuler des dictionnaires immuables, appelés p-uplets nommés.
Valeurs et références
Cette partie est importante et sera certainement source de difficultés. Une ressource qui peut s’avérer
utile est le Python Tutor (http://pythontutor.com), qui représente l’état des variables et des références
de manière graphique, semblable aux schémas qui sont dans le manuel. Malheureusement, l’interface
est uniquement en anglais, mais elle est facile à comprendre.
def moyenne(t):
tmin, tmax = t
return (tmin + tmax) / 2
print(moyenne(releve[0]))
def ajouter_moyennes(releve):
""" retourne un tableau des moyennes des températures de releve """
avec_moyennes = [0] * len(releve) # créer un tableau rempli de 0
for i in range(len(releve)):
tmin, tmax = releve[i]
avec_moyennes[i] = (tmin, tmax, moyenne(releve[i]))
return avec_moyennes
TP A : Le jeu du pendu
1. Proposer une lettre
def demander_lettre():
""" Demande une lettre (en minuscule), un tiret ou une apostrophe à
l'utilisateur. Redemande tant que l'entrée n'est pas correcte. """
# Test
demander_lettre()
2. Mot à trou
def remplace(reference, actuel, lettre):
""" "Révèle" chaque occurrence de `lettre` dans `actuel` à partir
du mot recherché `occurrence`.
if lettre in actuel:
# La lettre proposée a déjà été identifiée dans le mot
# recherché : on garde le mot à trous actuel.
return actuel
return mot_resultat
# Test
print(remplace("boom", "b---", "o"))
3. Structure du programme
def pendu(mot, nb_erreurs):
""" Joue au pendu avec le mot cible `mot`
et le nombre maximal d'erreur `nb_erreurs`
"""
if remplacement is None:
erreurs += 1
print(f"Non, '{lettre}' n'est pas dans le mot recherché.")
if nb_erreurs > erreurs:
print(f"Plus que {nb_erreurs-erreurs} essais !")
else:
mot_actuel = remplacement
print(f"Bravo, le mot actuel est '{mot_actuel}'.")
# Tester si on a gagné
if mot == mot_actuel:
print("Bien joué !")
return
# Test
pendu("test", 4)
def allera(p):
""" Aller à la position p en prenant en compte
un décalage pour que l'affichage soit centré
"""
x, y = p
goto(x-50, y-70)
def polyligne(points):
""" Afficher une suite de segments reliant
les points du tableau `points`
"""
penup()
allera(points[0])
pendown()
for p in points:
allera(p)
def cercle(c):
""" Afficher un cercle de centre (x, y) et de diamètre d,
passés sous forme de tuple `(x, y, d)`
"""
x, y, d = c
penup()
allera((x, y))
pendown()
for a in range(0, 100):
forward(1.0)
right(4.0)
def dessiner(segment):
""" Afficher un segment décrit sous forme d'un tuple
(fonction à appeler, paramètre)
t = 27 # diametre tete
v = 60 # hauteur corps
e = 20 # tete -> epaule
b = 20 # longueur verticale bras
m = 20 # longueur horizontale bras
j = 40 # longueur verticale jambe
p = 30 # longueur horizontale jambe
Cette version du jeu est la même que précédemment, sinon qu’elle appelle dessiner_potence au fur et à
mesure de l’avanceée du jeu. Par conséquent, le nombre maximal d’erreurs est 9.
def pendu_potence(mot):
""" Joue au pendu avec le mot cible `mot`
et le nombre maximal d'erreur `nb_erreurs`
"""
mot_actuel = "-" * len(mot) # Le mot à trous, vide au début.
nb_erreurs = 9 # le nombre maximal d'erreurs est fixé
erreurs = 0 # Le nombre d'erreurs effectuées.
dessiner_potence(0)
if remplacement is None:
erreurs += 1
print(f"Non, '{lettre}' n'est pas dans le mot recherché.")
if nb_erreurs > erreurs:
print(f"Plus que {nb_erreurs-erreurs} essais !")
dessiner_potence(erreurs)
else:
pendu_potence("test")
def dessine(dessin):
""" Dessine des traits reliant les points du tableau `dessin` """
# lever le crayon pour aller au premier point
penup()
x, y = dessin[0]
goto(x, y)
pendown()
# dessiner les points
for p in dessin:
x, y = p
dessine(maison)
b.
# Note : ces dessins proviennent de la correction de l'exercice 16
# après avoir ajusté leurs coordonnées pour commencer à (0, 0)
avion = [(0, 0), (-66, -41), (-78, 18), (-124, 34), (134, 86), (-73, 17),
(-63, -36), (-52, 10), (29, -3), (140, 86), (-52, 12)]
ordi = [(0, 0), (-79, 9), (-11, 28), (-48, 32), (-88, 23), (-45, 18),
(29, 37), (-15, 41), (-145, 12), (-4, -2), (-5, -12), (102, 144),
(226, 159), (134, 20), (-6, -13), (-143, 10)]
c.
def dessine(dessin):
""" Dessine des traits reliant les points du tableau `dessin`,
à partir de la position courante de la tortue
"""
# calculer l'écart entre le début du dessin
# et la position de la souris
x, y = dessin[0]
x0 = xcor() - x
y0 = ycor() - y
# dessiner les points en ajoutant l'écart
for p in dessin:
x, y = p
goto(x+x0, y+y0)
clear()
dessine(avion)
ondrag(tracer)
b.
dessin = []
ondrag(tracer)
c.
from nsi_ui import *
def redessiner():
""" Dessine le dessin enregistre """
clear()
dessine(dessin)
button("Dessiner", redessiner)
def enregistrer():
""" enregistre un dessin sous le nom de la zone d'entrée """
nom = get_string(entree_nom)
if nom != "":
dessins[nom] = dessin
clear_ui()
# créer l'interface
entree_nom = entry("Nom du dessin")
button("Enregistrer", enregistrer)
button("Dessiner", dessiner)
button("Effacer", clear)
start_ui()
Exercices
Exercices Newbie
1
def milieu(p1, p2):
""" Retourne le point milieu de p1 et p2 """
x1, y1 = p1
x2, y2 = p2
return ((x1+x2)/2, (y1+y2)/2)
2 a.
def anterieur(d1, d2):
""" Retourne True si d1 est strictement avant d2 """
j1, m1, a1 = d1
j2, m2, a2 = d2
# comparer l'année
if a1 < a2:
return True
if a1 > a2:
return False
b.
# Notes : on suppose que d1 est antérieur à d2
def age(d1, d2):
""" Retourne le nombre d'années pleines entre d1 et d2 """
3
def nom_date(personne):
""" Retourne le nom de famille et la date de naissance """
(prenom, nom), (jour, mois, an) = personne
return (nom, an)
4
def moyenne(t):
""" Retourne la moyenne de nombres de t """
# L'énoncé ne le précise pas, mais si le tableau est vide,
# on veut éviter une division par zéro.
# Par convention on retourne 0
if len(t) == 0:
print(moyenne([1, 2, 3, 4, 5, 6]))
5
def est_ordonne(t):
""" Retourne True si le tableau t est ordonné """
# ne pas oublier le cas où le tableau a moins de 2 éléments !
if len(t) <= 1:
return True
print(est_ordonne([1, 2, 3, 4, 5, 6]))
print(est_ordonne([1, 2, 3, 5, 4 ,6]))
6 a.
def unzip(t):
""" Sépare le tableau de tuples t en deux tableaux """
tx = []
ty = []
La version suivante n’utilise pas append et suit donc l’esprit du programme qui évite les tableaux
dynamiques :
def unzip(t):
""" Sépare le tableau de tuples t en deux tableaux """
tx = [0] * len(t)
ty = [0] * len(t)
for i in range(len(t)):
x, y = t[i]
tx[i] = x
ty[i] = y
return (tx, ty)
b.
Comme pour unzip, on peut aisément écrire une version sans append :
def zip(tx, ty):
""" Crée un tableau de tuples à partir de tx et ty"""
n = min(len(tx), len(ty))
t = [0] * n
for i in range(n):
t[i] = (tx[i], ty[i])
return t
7
divisible_7 = [False] * 1000
# Mettre à True les nombres divisibles par 7
for i in range(1000):
if i % 7 == 0:
divisible_7[i] = True
8
def palindrome(ch):
""" Retour True si ch est un palindrome """
for i in range(len(ch) // 2):
# Comparer chaque élément à son symétrique
if ch[i] != ch[len(ch)-i-1]:
return False
return True
print(palindrome('ressasser'))
print(palindrome('rester'))
print(palindrome('123321'))
9
chiffres = '0123456789'
def entier(nombre):
""" Convertit la chaîne nombre en entier """
print(entier('12345'))
print(entier('2022'))
print(entier('2xyz'))
La version suivante est plus élégante : elle utilise une fonction auxiliaire.
def chiffre(car):
""" Retourne la valeur numérique d'un caractère représentant un chiffre """
# Note : la methode `index` des chaînes et des tableaux fait la même chose
# mais elle n'a pas été introduite dans le cours.
chiffres = '0123456789'
for i in range(10):
if car == chiffres[i]:
return i
# un caractère inconnu sera pris comme 0
return 0
def entier(nombre):
""" Convertit la chaîne nombre en entier """
valeur = 0
# on prend les chiffres de gauche à droite
for car in nombre:
print(entier('12345'))
print(entier('2022'))
print(entier('2xyz'))
Cette version de la fonction chiffre utilise un dictionnaire et évite donc de parcourir la chaîne chiffres :
def chiffre(car):
""" Retourne la valeur numérique d'un caractère représentant un chiffre """
chiffres = {
'0': 0, '1': 1, '2': 2, '3': 3, '4': 4,
'5': 5, '6': 6, '7': 7, '8': 8, '9': 9
}
if car in chiffres:
return chiffres[car]
return 0
print(entier('12345'))
print(entier('2022'))
print(entier('2xyz'))
10
minuscules = 'abcdefghijklmnopqrstuvwxyz'
majuscules = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
chiffres = '0123456789'
def bonmotdepasse(mot):
print(bonmotdepasse('C0ur!'))
print(bonmotdepasse('Trop-Facile'))
print(bonmotdepasse('Tr0p-dur!'))
Note : les méthodes isupper(), islower(), isalpha() et isdigit() des chaînes de caractères peuvent être utilisées
à la place des tests if lettre in … ci-dessus. Elles ont l’avantage de prendre en compte les caractères
accentués :
print('ABC'.isupper())
11
# Avec la méthode split :
def nb_mots(phrase):
""" Retourne le nombre de mots dans phrase """
return len(phrase.split(' '))
13
capitales = {'France': 'Paris', 'Italie': 'Rome',
'Espagne': 'Madrid', 'Allemagne': 'Berlin',
def pays_de(ville):
""" Retourne le pays dont `ville` est la capitale, ou None si inconnu """
for pays in capitales:
if capitales[pays] == ville:
return pays
return None
print(pays_de('Madrid'))
print(pays_de('Bruxelles'))
La solution suivante est plus efficace : elle crée d’abord un dictionnaire qui associe chaque ville à son
pays. La fonction pays_de obtient alors directement la réponse grâce à ce dictionnaire :
# Construire le dictionnaire capitale -> pays
pays = {}
for (p, c) in capitales.items():
pays[c] = p
print(pays_de('Madrid'))
print(pays_de('Bruxelles'))
print(age("Jean Aymar"))
print(age("J Suipa"))
b.
def taille_moyenne(personnes):
""" Retourne la taille moyenne des personnes """
moyenne = 0
for personne in personnes.values():
moyenne = moyenne + personne['taille']
return moyenne / len(personnes.values())
print(taille_moyenne(personnes))
Exercices Initié
15 a.
couleurs = ['Pique', 'Coeur', 'Carreau', 'Trèfle']
def carte_valide(carte):
""" Retourne True si la carte est valide """
valeur, couleur = carte
return valeur >= 2 and valeur <= 14 and couleur in couleurs
print(carte_valide((12, 'Pique')))
b.
def nom_carte(carte):
""" Retourne le nom de la carte comme chaîne de caractères """
# dictionnaire qui donne les noms des cartes
noms_cartes = {
11: 'Valet', 12: 'Dame', 13: 'Roi', 14: 'As'
}
valeur, couleur = carte
if valeur < 11:
nom = str(valeur) # convertit le nombre en chaîne (par ex. "8")
else:
nom = noms_cartes[valeur] # prend le nom dans le dictionnaire (par ex. "Roi")
return nom + ' de '+ couleur
print(nom_carte((12, 'Pique')))
print(nom_carte((8, 'Coeur')))
c.
jeu = []
# ajouter les cartes par couleur
for couleur in couleurs:
for valeur in range(2, 15):
jeu.append((valeur, couleur))
# Note : on pourrait aussi écrire
# jeu = jeu + [(valeur, couleur)]
print(noms_cartes(jeu))
Note : Si l’on veut rester dans l’esprit du programme et éviter l’utilisation de la méthodes append ou la
concaténation de tableaux, on peut créer le tableau de 52 entrées et le remplir :
jeu = [None] * 52
i=0
# ajouter les cartes par couleur
for couleur in couleurs:
for valeur in range(2, 15):
jeu[i] = (valeur, couleur)
i=i+1
print(noms_cartes(jeu))
d.
from random import random
# tirer un numéro de carte au hasard
rang = round(random()*len(jeu))
print(nom_carte(jeu[rang]))
16 a.
from math import sqrt
b. Les dessin est constitué d’un ensemble de points (x, y) reliés par des segments. On calcule le
rectangle englobant en calculant les valeurs minimales et maximales des x et desy. On retourne la
largeur et la hauteur de ce rectangle.
def dimensions(dessin):
""" Retourne une tuple (largeur, hauteur) = taille du dessin """
xmin, ymin = dessin[0]
xmax, ymax = dessin[0]
for x, y in dessin:
if x < xmin:
xmin = x
if x > xmax:
xmax = x
if y < ymin:
ymin = y
if y > ymax:
ymax = y
return (xmax - xmin, ymax - ymin)
print(dimensions(dessin1))
c. Les points du dessin sont reliés par des traits. La longueur du dessin est la somme des longueurs de
ces traits.
def longueur(dessin):
""" Retourne la longueur total du dessin """
long = 0
for i in range(len(dessin) - 1): # attention à ne pas dépasser !
long = long + distance(dessin[i], dessin[i+1])
return l
print(longueur(dessin1))
print(longueur(dessin2))
print(longueur(dessin3))
17 a.
def inverse_tableau(t):
""" Retourne le tableau t dans l'ordre inverse """
inv = [None] * len(t)
for i in range(len(t)):
inv[i] = t[len(t)-1-i]
return inv
print(inverse_tableau([1, 2, 3, 4, 5]))
La solution suivante utilise la méthode insert mais n’est pas recommandée. D’une part, le programme
insiste sur l’utilisation de tableaux non dynamiques. D’autre part, l’insertion dans un tableau est
coûteuse (il faut décaler tous les éléments qui suivent l’élément inséré).
def inverse_tableau2(t):
""" Retourne le tableau t dans l'ordre inverse """
print(inverse_tableau2([1, 2, 3, 4, 5]))
b. On ne peut pas utiliser la fonction ci-dessus car une chaîne est immuable. Voici une première
solution où l’on construit progressivement le résultat avec l’opérateur + de concaténation des chaînes :
def inverse_chaine(ch):
""" Retourne la chaine ch en ordre inverse """
inverse = ''
for i in range(len(ch)):
inverse = ch[i] + inverse
return inverse
print(inverse_chaine('abcdef'))
Cette méthode peut également s’utiliser avec les tableaux, mais elle est moins efficace que la première
méthode ci-dessus (question a.), car il faut à chaque étape créer un nouveau tableau.
def inverse_tableau3(t):
""" Retourne le tableau t dans l'ordre inverse """
inverse = []
for i in range(len(t)):
inverse = [t[i]] + inverse
return inverse
Comme cette méthode ne repose pas sur la muabilité des tableaux, on peut l’utiliser pour inverser une
chaîne. Comme le résultat est un tableau, il faut le convertir en chaîne avec la méthode join.
def inverse_chaine3(ch):
""" Retourne la chaine ch en ordre inverse """
return ''.join(inverse_tableau3(ch))
print(inverse_chaine3('abcdef'))
18 a.
from matplotlib.pyplot import bar, show
from random import random
def test_random(n):
""" Affiche les fréquences de n tirages d'une nombre entre 0 et 1 """
freq = [0] * 10 # tableau des fréquences
for _ in range(n):
# calculer dans quelle case tombe le nombre tiré au hasard
i = int(random()*10)
# mettre à jour cette case
freq[i] += 1
b.
test_random(100000)
test_random(1000000) # 1 million
test_random(10000000) # 10 million
On observe qu’avec 1000 tirages, la distribution n’est pas très uniforme. Avec 100 000 tirages, c’est
mieux, mais encore irrégulier. Avec 1 million de tirages on approche d’une distribution uniforme.
19 a.
from random import randint
from matplotlib.pyplot import bar, show
def des():
""" Retourne le résultat du tirage de deux dés """
return randint(1, 6) + randint(1, 6)
test_des(1000)
test_des(100000)
b.
def ndes(n):
""" Retourne la somme du tirage de n dés """
d=0
for i in range(n):
d += randint(1, 6)
return d
test_ndes(10, 1000)
test_ndes(10, 100000)
c.
test_loi_normale(100, 1000)
test_loi_normale(100, 100000)
20 a. Chaque tranche est un tuple (revenu minimal, revenu maximal, taux d’imposition). La dernière
tranche n’a pas de taux maximal, on l’indique par la valeur -1 (on appelle cela une sentinelle). Les
tranches sont placées dans un tableau par ordre croissant de revenu minimal.
b.
def montant_impot(revenu):
""" Retourne le montant de l'impot pour le revenu donné """
impot = 0
for i in range(len(tranches)):
# extraire les éléments de la tranche i
tmin, tmax, taux = tranches[i]
# si le revenu tombe à l'intérieur de la tranche
# on bien si on atteint la sentinelle :
if tmax == -1 or revenu < tmax:
# ajouter la part de cette tranche et terminer
impot += (revenu - tmin) * taux/100
return impot
# sinon ajouter l'ensemble de la tranche
impot += (tmax - tmin) * taux/100
# on n'arrivera jamais ici en principe
# grâce à la sentinelle en fin de tableau
return impot
print(montant_impot(5000))
print(montant_impot(20000))
print(montant_impot(50000))
print(montant_impot(100000))
print(montant_impot(500000))
c.
tx = []
ty = []
# Compléter les séries des revenus et de l'impôt correspondant
for revenu in range(0, 200000, 5000):
tx.append(revenu)
# Afficher la courbe
from matplotlib.pyplot import plot, show
plot(tx, ty)
show()
d.
tx = []
ty = []
# Compléter les séries des revenus et du taux d'imposition correspondant
for revenu in range(5000, 200000, 5000):
tx.append(revenu)
ty.append(montant_impot(revenu)*100/revenu)
# Afficher la courbe
from matplotlib.pyplot import plot, show
plot(tx, ty)
show()
e.
def taux_tranche(revenu):
"""Retourne le taux d'imposition de la tranche correspondant au revenu"""
for i in range(len(tranches)):
tmin, tmax, taux = tranches[i]
if tmax == -1 or revenu < tmax:
return taux
# on n'arrivera jamais ici en principe
# grâce à la sentinelle en fin de tableau
def taux(revenu):
""" Affiche la tranche et le taux marginal pour le revenu donné """
tranche = taux_tranche(revenu)
marginal = montant_impot(revenu)*100/revenu
print(f'revenu = {revenu}, tranche {tranche}%, taux marginal = {marginal}%')
taux(15000)
taux(50000)
taux(200000)
21 a.
def retire_element(t, e):
""" Retire l'élément e de t et retourne True s'il était présent """
for i in range(len(t)):
if e == t[i]:
# on a trouvé l'élément
t.pop(i)
return True
# l'élément n'est pas dans le tableau
return False
t = [1, 2, 3, 4]
print(t)
print(retire_element(t, 3), t)
print(retire_element(t, 5), t)
Note : Cette fonction retire seulement la première occurrence de e dans t. La version suivante retire
toutes les occurrences. Mais attention au piège : comme on modifie le tableau t avec la méthode pop,
qui réduit la taille du tableau, on va provoquer une erreur de dépassement du tableau car len(t) est
calculé une seule fois au début de la boucle. De plus, on va manquer des occurrences si deux éléments
identiques se suivent, car pop a décalé tous les éléments qui suivent.
t = [1, 2, 2, 3, 4]
print(t)
print(retire_element_bug(t, 2), t)
Pour éviter ces problems, il faut parcourir le tableau « à la main » avec une boucle while :
def retire_element2(t, e):
""" Retire toutes les occurrences de l'élément e de t
et retourne True s'il était présent au moins une fois
"""
present = False
i=0
while i < len(t):
if e == t[i]:
t = [1, 2, 2, 3, 4]
print(t)
print(retire_element2(t, 2), t)
Une autre solution consiste à se passer de pop et à construire le résultats progressivement avec la
concaténation de tableaux, comme dans l’exercice 17. Cependant, dans ce cas, il faut retourner le
tableau résultat puisque l’on ne modifie pas le contenu de t.
def retire_element3(t, e):
""" Retourne une copie de `t` dans laquelle
on a retiré toutes les occurrences de l'élément `e`,
ou bien `t` lui-même s'il ne contient pas `e`.
"""
present = False
res = [] # le résultat
for i in range(len(t)):
if e == t[i]:
# l'élément est présent
present = True
else:
# l'élément n'est pas présent, on l'ajoute au résultat
res = res + [t[i]]
if present:
return res
return t
b.
def meme_contenu(t1, t2):
""" Retourne True si t1 et t2 ont les mêmes éléments
(même s'ils sont dans un ordre différent)
"""
# Note : ajout par rapport au corrigé du manuel :
# il faut que les tableaux soient de même longueur
if len(t1) != len(t2):
return False
# copier t2 car on va le modifier
t2 = t2.copy()
# retirer de t2 les éléments de t1
for e1 in t1:
if not retire_element(t2, e1):
return False
# contenu identique si t2 est vide
return len(t2) == 0
BONUS (Titan) : La méthode suivante utilise un dictionnaire qui associe à chaque élément du tableau
son nombre d’occurrences. Elle est moins coûteuse que la méthode ci-dessus, qui retire les éléments
c.
def anagramme(mot1, mot2):
""" Retourne True si mot1 et mot2 ont les mêmes lettres """
# transformer mot1 en tableau
t1 = []
for c in mot1:
t1.append(c)
# transformer mot2 en tableau
t2 = []
for c in mot2:
t2.append(c)
return meme_contenu(t1, t2)
print(anagramme('imaginer', 'migraine'))
print(anagramme('pablo picasso', 'pascal obispo'))
La version ci-dessous utilise la fonction list qui convertit une chaîne en tableau, mais celle-ci n’a pas
été vue en cours :
def anagramme(mot1, mot2):
""" Retourne True si mot1 et mot2 ont les mêmes lettres """
return meme_contenu(list(mot1), list(mot2))
print(anagramme('imaginer', 'migraine'))
print(anagramme('pablo picasso', 'pascal obispo'))
def melanger(t):
""" Retourne une copie de t avec les mêmes éléments dans un ordre aléatoire """
# copier t car on va le modifier
t = t.copy()
melange = []
# retirer les éléments de t tant qu'il y en a
while len(t) > 0:
i = randint(0, len(t)-1) # tirage d'un indice
melange.append(t[i]) # ajout au résultat
t.pop(i) # retrait de t
return melange
print(melanger([1, 2, 3, 4, 5]))
b.
# on suppose que le code de l'exercice 15 a été exécuté
print(noms_cartes(melanger(jeu)))
23 a.
def duel(c1, c2):
""" Retourne 1 si c1 gagne, 2 si c2 gagne,
0 si les deux cartes ont même valeur
"""
val1, coul1 = c1
val2, coul2 = c2
if val1 > val2: # c1 gagne
return 1
if val1 < val2: # c2 gagne
return 2
b.
# on suppose que le code de l'exercice 15 a été exécuté
def tirer(paquet):
""" Retourne une carte au hasard """
return paquet[randint(0, 51)]
def jouer(paquet):
""" Jouer un tour de bataille et retourner un tuple (duels, gagnant)"""
duels = 0
gagnant = 0
while gagnant == 0:
# tirer deux cartes
c1 = tirer(paquet)
c2 = tirer(paquet)
# jouer
gagnant = duel(c1, c2)
duels += 1
# afficher le résultat
if gagnant == 1:
print(noms_cartes([c1, c2]), "- joueur 1 l'emporte")
c.
# Mélanger le jeu
jeu_melange = melanger(jeu)
j1, j2 = partie()
if j1 > j2:
print(f'Joueur 1 gagne {j1} points à {j2}')
elif j1 < j2:
print(f'Joueur 2 gagne {j2} points à {j1}')
else:
print(f'Égalité {j1}-{j2} points !')
24 a.
def inverse_dict(d):
""" Retourne un dictionnaire dont les clés sont les valeurs de d
et vice versa """
inverses = {}
for cle, valeur in d.items():
inverses[valeur] = cle
return inverses
print(inverse_dict(capitales))
b. Non. Les clés d’un dictionnaire doivent être de type immuable. Donc si les valeurs du dictionnaire à
inverser sont muables (par exemple des tableaux), on ne peut pas l’inverser.
releves = {
"Paris" : [15, 16, 12.5, 12, 11, 12, 14.5],
"Montreal" : [2.5, 1, -3, 2, 3, 5, 5.5],
}
inverse_dict(releves)
25 a.
def valeur_lettre(lettre):
""" Retourne la valeur de `lettre` """
# on cherche la lettre dans les entrées du dictionnaire
for score, lettres in valeurs_Scrabble.items():
if lettre in lettres:
return score
# On arrive ici si la lettre n'a pas été trouvée dans valeurs_Scrabble
return 1
def valeur_mot(mot):
""" Retourne la valeur de `mot` """
valeur = 0 # Score total du mot
for lettre in mot:
valeur += valeur_lettre(lettre)
return valeur
b.
lettres_Scrabble = {}
# créer le dictionnaire lettre -> valeur
for lettre in "abcdefghijklmnopqrstuvwxyz":
lettres_Scrabble[lettre] = valeur_lettre(lettre)
print(lettres_Scrabble)
c.
def valeur_mot(mot):
""" Calcule la valeur de `mot` au Scrabble. """
valeur = 0
for lettre in mot:
valeur += lettres_Scrabble[lettre]
return valeur
d.
def lettre_compte_triple(mot, indice):
""" Retourne la valeur de `mot` au Scrabble, quand la lettre
numéro `indice` compte triple. """
return valeur_mot(mot) + 2 * lettres_Scrabble[mot[indice]]
26 a. Note : Le fichier geographie.py fourni aux élèves contient les 35 villes du monde qui ont plus de 4
millions d’habitants.
print(nvilles)
b.
population_max = 0
ville_max = ''
for ville in villes_population:
# on extrait la population de chaque ville
population = villes_population[ville]
if population > population_max:
population_max = population
ville_max = ville
print(ville_max, population_max)
c.
nvilles = 0
print(nvilles)
27 a.
todo_list = {
"Faire les courses": True,
"Ranger le garage": False,
"Compléter l'exercice 4": False
}
b.
def nombre_taches_incompletes():
""" Calcule le nombre de tâches non complétées dans `todo_list`. """
nombre = 0
# Ne liste que les valeurs (les booléens) dans le dictionnaire.
for etat in todo_list.values():
if not etat:
nombre += 1
return nombre
c. Note 1 : L’énoncé ne le mentionne pas, mais il faut ajouter une option `q' pour permettre à
l’utilisateur de sortir de la fonction.
Note 2 : On veut pouvoir accéder aux éléments de la liste par numéros, ce que ne permet pas
directement un dictionnaire (l’ordre des clés d’un dictionnaire est garanti ou non selon les versions de
Python). Plusieurs options sont envisagables :
• Changer explicitement le type d’une todo-liste en une liste de couples (nom, effectue_ou_non)
qui sont alors accessibles via des indices.
• Compter sur l’invariance à court terme de l’ordre des éléments dans todo_list.keys(), quitte à ce que
cet ordre change lorsqu’on ajoute ou supprime une tâche.
def gestion_liste():
""" Permet d'effectuer des commandes simples sur todo_list. """
fini = False
while not fini:
commande = input("Quelle commande voulez-vous effectuer ? [+-v?q]")
else:
print(f"La commande {commande} n'est pas reconnue, veuillez recommencer ou quitter ('q').")
gestion_liste()
28 a.
def entree_annuaire(chaine):
""" Retourne un tuple (nom, prenom, telephone) à partir de chaine """
mots = chaine.split(' ')
if len(mots) != 3: # vérifier qu'on a 3 éléments
b.
def ajoute_annuaire(annuaire, entree):
""" Ajoute une entrée à l'annuaire """
prenom, nom, telephone = entree
# la clé du dictionnaire est le tuple (prénom, nom)
annuaire[(prenom, nom)] = telephone
annuaire = {}
ajoute_annuaire(annuaire, entree_annuaire("Jean Dupont 0987654321"))
print(annuaire)
c.
fini = False
while not fini:
cmd = input('a/jout, r/echerche ou f/in ? ')
if cmd == 'a': # ajouter une entrée
entree = entree_annuaire(input('prénom nom téléphone : '))
if entree is not None:
ajoute_annuaire(annuaire, entree)
d.
def recherche_annuaire(annuaire, entree):
""" Recherche un nom correspondant à l'entrée de l'utilisateur.
Affiche le ou les noms trouvés.
"""
nom_recherche = entree.split(' ')
fini = False
while not fini:
cmd = input('a/jout, r/echerche ou f/in ? ')
if cmd == 'a': # ajout d'une entrée
entree = entree_annuaire(input('prénom nom téléphone : '))
if entree is not None:
ajoute_annuaire(annuaire, entree)
29 a.
index = {}
b.
# On utilise la fonction retire_element de l'exercice 21
retire(index, 'abri')
retire(index, 'martien')
retire(index, 'absent')
retire(index, 'xyz')
print(index)
30 a. Le type construit est un dictionnaire dont les clés sont les mots et les valeurs le nombre
d’occurrence.
c.
occurrences = compte_mots(mots)
uniques = []
# construire le tableau des mots qui apparaissent une seule fois
for mot, nb in occurrences.items():
if nb == 1:
# ajouter seulement les mots qui ont 1 occurrence
uniques.append(mot)
print(', '.join(uniques))
31 a.
def entier(ch):
""" Convertir la chaine ch en entier (0 en cas d'erreur) """
try:
return int(ch)
except ValueError:
def operation(entree):
""" Exécute l'opération de la chaîne `entree` et affiche son résultat.
Retourne True si OK, False sinon.
"""
expr = entree.split(' ') # séparer '2 + 3' en ['2', '+', '3']
if len(expr) != 3 or expr[1] not in ['+', '-', '*', '/']:
return False
def calculette():
""" Interface de la calculette """
fini = False
while not fini:
entree = input('opération (. pour terminer) : ')
if entree == '.':
fini = True
else:
if not operation(entree):
print('Opération non reconnue')
calculette()
b.
# dictionnaire qui associe à chaque variable sa valeur
variables = {}
def affectation(entree):
""" Traite des entrées de la forme a = 10.
Retourne True si OK, False sinon.
"""
expr = entree.split(' ')
# on attend une expression de la forme ['a', '=', '10']
if len(expr) != 3 or expr[1] != '=':
return False
var = expr[0]
val = expr[2]
def calculette():
calculette()
c.
def affectation(entree):
""" Traite des entrées de la forme a = 10 ou a = x + y.
Retourne True si OK, False sinon.
"""
# analyser l'entrée
expr = entree.split(' ')
# on attend une expression de la forme ['a', '=', ...]
if len(expr) < 3 or expr[1] != '=':
return False
var = expr[0]
calculette()
32 Une première solution est d’utiliser un tableau de tuples de la forme (épreuve, année, champion).
Une seconde solution est d’utiliser un dictionnaire dont les clés sont les noms des épreuves et les
valeurs sont également des dictionnaires dont les clés sont l’année et les valeurs le nom du champion.
La première solution est moins efficace car il faut parcourir le tableau pour trouver la réponse. Elle
nécessite également de répéter le nom de l´épreuve.
olympique2 = {
"Natation 400m Femmes": {
2004: "Laure Manaudou",
2012: "Camille Muffat"
},
}
# Exemple :
# conversions['cm']['in'] est le facteur de la conversion cm -> inch
# conversions['in']['cm'] est le facteur de la conversion inverse inch -> cm
conversions = {
'cm': {
'm': 0.01, # 1 cm = 0.01 m
'in': 1/2.54, # 2.54 cm = 1 in
},
'in': {
'cm': 2.54, # 1 in = 2.54 cm
'ft': 1/12 # 12 in = 1 ft
},
'm': {
'cm': 100 # 1 m = 100 cm
},
'ft': {
'in': 12 # 1 ft = 12 in
}
}
b.
conversions = {}
def liste_unites():
""" Affiche la liste des unités connues """
def nombre(ch):
""" Retourne la valeur numérique du nombre contenu dans la chaîne ch"""
try:
i = float(ch)
return True
except ValueError:
return False
def convertisseur():
""" Interface textuelle du convertisseur """
fini = False
while not fini:
cmd = input('? ').split(' ')
else:
print('Commande non reconnue')
convertisseur()
c. La solution ci-dessous résoud cette question en calculant ce que l’on appelle la fermeture transitive
de la relation de conversion. Tant que l’on peut ajouter des conversions, on regarde tous les triplets (A,
B, C) tels qu’il existe une conversion A → B et B → C, et on ajoute la conversion A → C si elle
n’existe pas déjà.
def ajouter_1conversion(unite_depart, unite_arrivee, facteur):
""" Ajouter la conversion depart -> arrivee et retourner True
si elle n'est pas déjà dans le dictionnaire, False sinon
"""
if unite_depart not in conversions:
conversions[unite_depart] = {}
if unite_arrivee in conversions[unite_depart]:
def completer_conversions():
""" Compléter toutes les conversions par transitivité """
encore = True
while encore: # tant qu'on a ajouté de nouvelles conversions
# parcourir tous les triplets (A, B, C) tels qu'il existe
# une conversion A -> B et B -> C
ajouts = []
for A in conversions:
for B in conversions[A]:
for C in conversions[B]:
if A != C and C not in conversions[A]:
# ajouter la conversions A -> C
f = conversions[A][B] * conversions[B][C]
# Python n'autorise pas la modification d'un
# dictionnaire qu'on est en train de l'énumérer.
# On range donc les conversions à ajouter
# dans le tableau `ajouts` :
ajouts.append((A, C, f))
encore = False
# Test
conversions = {}
print('Métrique')
ajouter_conversion('m', 'cm', 100)
ajouter_conversion('km', 'm', 1000)
print('Impérial')
ajouter_conversion('ft', 'in', 12)
ajouter_conversion('mi', 'ft', 5280)
print('Métrique <-> Impérial')
ajouter_conversion('in', 'cm', 2.54)
# Test interactif
convertisseur()
print(conversions)
34 a. Pour trouver les ingrédients à partir des étapes de la recette, on utilise un dictionnaire qui
associe à chaque ingrédient une paire formée de la quantité et l’unité.
etapes = ["mélanger le lait et la farine", "ajouter les oeufs"]
ingredients = {
"farine": (150, 'g'),
b.
def afficher_ingredient(ingredient, quantite, unite):
""" Afficher un ingrédient et sa quantité """
if unite == 'unités':
print(f' {quantite} {ingredient}')
else:
print(f' {quantite} {unite} de {ingredient}')
c.
def liste_ingredients(ingredients):
""" Afficher la liste des ingrédients """
print('Ingrédients :')
for ingredient in ingredients:
quantite, unite = ingredients[ingredient]
def liste_etapes(etapes):
""" Afficher la liste des étapes """
print('Étapes :')
for i in range(len(etapes)):
print(f' {i+1}. {etapes[i]}')
recette(etapes, ingredients)
d.
def convertir_quantite(quantite, unite):
""" Retourner la première conversion de quantité, si elle existe """
# s'il n'existe pas de conversion, retourner la quantité non convertie
if unite not in conversions:
return (quantite, unite)
# sinon retourner la première conversion
for unite_cible in conversions[unite]:
facteur = conversions[unite][unite_cible]
return (quantite * facteur, unite_cible)
# au cas (qui ne devrait pas se produire) où il n'y aurait pas de conversions
return (quantite, unite)
35 a. On peut représenter l’emploi du temps comme une liste de créneaux, chacun représenté par un
tuple (jour, heure, matière) : [('lundi', '9h', 'histoire'), ('lundi', '10h', 'NSI'), ('mardi', …) …].
On peut aussi le représenter de manière plus structurée, sous forme d’un dictionnaire dont les clés sont
les jours de la fenêtre, et les valeurs sont également des dictionnaires dont les clés sont les heures de
début et les valeurs les matières : {'lundi': {'9h': 'histoire', '10h': 'NSI', …}}, 'mardi': {…} …}.
b. Solution avec la première représentation :
edt = [('lundi', '9h', 'histoire')]
# ajouter le créneau
edt.append((jour, heure, matiere))
return True
Exercices Titan
36 a.
# nombre de jours par mois pour une année non bissextile
# on met -1 à l'index 0 pour pouvoir indexer par le numéro de mois
jours_par_mois = [-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
def date_valide(date):
""" Retourne True si la date est valide """
if len(date) != 3:
print(date, ": la date doit être un tuple de trois éléments")
return False
j, m, a = date
# tester le mois
if m < 1 or m > 12:
print(date, ": le mois doit étre compris entre 1 et 12")
return False
# tester le jour
return True
print(date_valide((31, 3, 2020)))
print(date_valide((31, 4, 2020)))
print(date_valide((31, 9, 2020)))
print(date_valide((31, 10, 2020)))
print(date_valide((29, 2, 2000)))
print(date_valide((29, 2, 2001)))
print(date_valide((29, 2, 2004)))
print(date_valide((29, 2, 2100)))
b.
def jours_depuis_1jan(d):
""" Retourne le nombre de jours depuis le 1er de l'an"""
j, m, a = d
njours = 0
# ajouter les jours des mois complets
for mois in range(1, m): # on s'arrête à m-1, mois précédent !
njours += jours_mois(mois, a)
# ajouter les jours du mois m
return njours + j
def jours_jusqua_31dec(d):
""" Retourne le nombre de jours jusqu'au 31 décembre"""
_, _, a = d
jours_an = 365
if bissextile(a):
jours_an += 1
return jours_an - jours_depuis_1jan(d)
j1, m1, a1 = d1
j2, m2, a2 = d2
if a1 == a2:
# dates la même année
njours = jours_depuis_1jan(d2) - jours_depuis_1jan(d1)
else:
# années différents
njours = jours_jusqua_31dec(d1) # fin de l'année a1
if negatif:
return -njours
return njours
37
def cles(dict):
""" Retourne l'équivalent de dict.keys() """
les_cles = []
for cle in dict:
les_cles.append(cle)
return les_cles
def valeurs(dict):
""" Retourne l'équivalent de dict.values() """
les_valeurs = []
for cle in dict:
les_valeurs.append(dict[cle])
return les_valeurs
def elements(dict):
""" Retourne l'équivalent de dict.items() """
les_elements = []
38 a. Une trajectoire est un tableau de commandes, et chaque commande est un tuple formé d’une
fonction et de ses paramètres éventuels.
b.
trajectoire = [(forward, 100), (left, 45), (pencolor, 'red'), (forward, 50)]
def trace(trajectoire):
""" Exécute les commandes du tablea trajectoire """
# On suppose que toutes les commandes ont exactement un paramètre.
for commande in trajectoire:
cmd, param = commande # extraire la commande et le paramètre
cmd(param) # appeler la commande avec le paramètre
trace(trajectoire)
def trace(trajectoire):
""" Exécute les commandes du tablea trajectoire """
for commande in trajectoire:
# selon le nombre d'éléments dans le tuple, on extrait la commande et
# les paramètres éventuels, et on appelle la commande avec les paramètres
if len(commande) == 1: # commande sans paramètre
cmd, = commande # attention à la virgule pour extraire la valeur
cmd()
elif len(commande) == 2: # commande avec un paramètre
cmd, param = commande
cmd(param)
elif len(commande) == 3: # commande avec deux paramètres
cmd, param1, param2 = commande
cmd(param1, param2)
trace(trajectoire)
39 a.
todo_list = {}
b.
ajouter_tache("Coiffeur", (25, 5, 2022), 2)
print(todo_list)
ajouter_tache("Test1", (5, 25, 2022), 2)
print(todo_list)
ajouter_tache("Test2", (10, 10, 20, 25), 6)
print(todo_list)
ajouter_tache("Test3", (10, 10, 2025), 6)
print(todo_list)
c.
def decrire_tache(intitule):
""" Décrit la tâche et son statut, si elle existe dans todo_list. """
if intitule in todo_list:
effectuee, (jour, mois, an), priorite = todo_list[intitule]
message = f"La tâche '{intitule}' de priorité {priorite} "
message += f"et à compléter au plus tard le {jour}/{mois}/{an} "
if effectuee:
message += "a été effectuée."
else:
message += "n'a pas été effectuée."
return message
decrire_tache("Coiffeur")
d.
# Note : on utilise la fonction `anterieur` de l'exercice 2
def trop_tard(date):
""" Affiche l'ensemble des tâches de todo_list non effectuées,
et dont la date limite est postérieure à cette date.
"""
taches = {}
if date_valide(date):
# parcourir toutes les tâches
for intitule, description in todo_list.items():
effectuee, date_tache, _ = description
e.
def important(priorite_min):
""" Affiche l'ensemble des tâches de todo_list non effectuées et dont
le score d'importance est au moins priorite_min.
"""
for intitule, description in todo_list.items():
effectuee, _, priorite = description
if not effectuee and priorite >= priorite_min:
print(decrire_tache(intitule))
important(4)
40 a.
# Note : on utilise la variable `couleurs` et les fonctions
# `nom_carte` et `carte_valide` de l'exercice 15
# Test
print(belote_simple( "Trèfle", {"J1": (4, "Trèfle"), "J2": (13, "Pique"), "J3": (2, "Trèfle")}))
print(belote_simple("Carreau", {"J1": (4, "Trèfle"), "J2": (13, "Pique"), "J3": (2, "Carreau")}))
print(belote_simple( "Coeur", {"J1": (4, "Trèfle"), "J2": (13, "Pique"), "J3": (2, "Carreau")}))
print(belote_simple( "Coeur", {"J1": (4, "Trèfle"), "J2": (18, "Pique"), "J3": (2, "Carreau")}))
b.
# Constante décrivant l'ordre particulier des valeurs au sein de la couleur atout.
# Ainsi valeurs_atout[11] (Valet) > Valeurs_Atout[9] > Valeurs_Atout[14] (As) > ...
valeurs_atout = [None, None, 2, 3, 4, 5, 6, 7, 8, 15, 10, 16, 12, 13, 14]
if couleur == atout:
if not meilleure_carte_atout:
# La carte à l'atout gagne si la meilleure carte ne l'était pas.
gagnant = True
elif valeurs_atout[meilleure_carte] < valeurs_atout[valeur]:
# Comparaison entre deux atouts : on applique les règles spéciales.
gagnant = True
else:
# Carte non atout :
# gagne seulement si la meilleure carte n'était pas un atout
# ET si cette meilleure carte avait une valeur inférieure.
if not meilleure_carte_atout and meilleure_carte < valeur:
gagnant = True
return meilleur_joueur
# Test
print(belote( "Trèfle", {"J1": (14, "Trèfle"), "J2": (13, "Pique"), "J3": (11, "Trèfle")}))
print(belote("Carreau", {"J1": (14, "Carreau"), "J2": (9, "Carreau"), "J3": (11, "Carreau")}))
Le chapitre au vu du programme
Au vu du programme paru dans le B.O., et au vu des exemples donnés sur le site Eduscol, seules les
sections 1 à 4 du cours sont exigibles des élèves, les sections 5 et 6 étant nettement "hors programme".
De plus, le programme parle d’itérer sur les éléments d’un tableau et les construire par compréhension.
Toutefois, nous n’avons pas observé d’exemples utilisant la fonction Python "enumerate" pour
énumérer les éléments d’un tableau, les exemples observés privilégiant plutôt le parcours par indice.
Nous avons respecté l’indication du programme de ne pas utiliser les "slices" python. En revanche, un
certain nombre d’exercices utilisent la fonction Python "append", ce qui signifie que nous exploitons
implicitement le fait que les "list" de Python sont des tableaux dynamiques. En fait, un certain nombre
de sujets types proposés sur Eduscol pour les épreuves pratiques de terminale (sujets 7,8,9,10,18,27)
utilisent aussi la fonction append, sans doute parce-que beaucoup de problèmes "rencontrés en
pratique" se résolvent naturellement en construisant un tableau ou liste "à la volée" par ajouts
successifs.
• en déduire un test de palindrome par égalité (on peut d’ailleurs observer que ce n’est pas très
efficace comme méthode pour tester si une chaine est un palindrome) ;
• et enfin l’exercice propose de réduire (ramener) le problème du test de palindrome d’un nombre
à celui des chaines en convertissant le nombre en chaine.
• [f(i,j) for i in range(a) for j in range(b)] qui produit un tableau avec a*b éléments. Dans ce code, la
boucle externe est for i in range(a) : pour chaque i on calcule pour chaque j l’expression f(i,j); on
calcule donc a*b fois l’expression; dans ce cas l’ordre des deux boucles for est le même que si
Nous n’avons pas observé cette seconde forme de compréhension dans les annales (ce qui ne garantit
rien). Une définition expliquant ces compréhensions imbriquées est donnée dans le tutoriel officiel :
https://docs.python.org/fr/3/tutorial/datastructures.html "Une compréhension de liste consiste à placer
entre crochets une expression suivie par une clause for puis par zéro ou plus clauses for ou if. Le
résultat est une nouvelle liste résultat de l’évaluation de l’expression dans le contexte des clauses for et
if qui la suivent".
Pour clarifier la sémantique, on donne ci-dessous les codes équivalents, écrits sans compréhension :
a=3
b=2
def f(i,j):
return (i,j)
print(res) #[[(0, 0), (1, 0), (2, 0)], [(0, 1), (1, 1), (2, 1)]]
print(res) #[[(0, 0), (1, 0), (2, 0)], [(0, 1), (1, 1), (2, 1)]]
print(res) #[(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]
Accessoirement, on peut aussi construire les dictionnaires par compréhension avec une syntaxe assez
proche de celle des tableaux : ex: {cle:'v' for cle in range(4)} mais ce chapitre utilise peu les dictionnaires, et
ces compréhensions sont beaucoup plus rares que celles de listes donc il ne nous a pas paru utile de les
mentionner.
Les matrices
Pour afficher élégamment une matrice, le plus simple est sans doute d’utiliser numpy :
import numpy
np.array([[2,4],[1,3]])
# ou np.matrix # on peut ajouter print, l'alignement reste respecté
ou pour une petite matrice, écrire soi-même une boucle affichant ligne par ligne :
L’orientation des indexs de matrice est un point potentiellement perturbant pour l’élève dans ce
chapitre. Lorsque l’on accède à t[i][j] dans un tableau de tableau, i est l’indice de la ligne, j de la
colonne (comme dans la représentation traditionnelle en mathématiques). Les programmes afficheront
traditionnellement une matrice ligne par ligne de haut en bas, c’est ce que fait numpy. C’est ce que fait
matplotlib.pyplot.imshow(), et donc le TP (compression d’image). Le cours et les exercices traitant de
matrices en tant que telles (ex 2, ex 22) suivent cette organisation.
En revanche, dans certains problèmes géométriques, la case [x][y] d’une matrice représente la
coordonnée (x,y) du plan (xOy). Si l’on affiche la matrice en Python, ses valeurs seront organisées
dans un ordre qui ne sera pas celui du plan (x0y). C’est le cas pour l’activité (snake) et quelques
exercices (puissance 4).
Curiosités : références
On pourra faire remarquer en particulier aux plus curieux que, lorsque r et r2 référencent le même
tableau, les deux instructions ci-dessous se comportent différemment :
r.append(valeur)
# et
r = r+[valeur]
La première modifie r2, la seconde ne modifie que r et laisse r2 inchangé (r et r2 référençant dès lors 2
tableaux distincts). A priori, aucun exercice de ce chapitre ne présente ce cas de figure, donc on pourra
utiliser indifféremment l’un ou l’autre des syntaxes pour ajouter un élément en fin de tableau.
Curiosités : boucles
On s’interdit généralement de modifier un objet sur lequel on est en train d’itérer par une boucle for ().
Si on le fait, alors la terminaison n’est plus garantie, et on peut avoir d’autres problèmes : le
comportement est assez imprévisible si on supprime des éléments de la liste pendant qu’on itère
dessus.
t= ['a','b']
for x in t:
t.append(x)
• Il est normal que sur une image de grande taille (comme la photo du martin-pêcheur), l’on ne
voie pas l’effet de l’agrandissement, parce que l’image est automatiquement redimensionnée.
Exercices observés dans les sujets-types de l’épreuve de QCM de première sur Eduscol
On trouve surtout des exercices correspondant à ce chapitre dans le thème B : types construits, et aussi
le thème C : traitement de données en table.
• construire un tableau par compréhension (1C1),
Exercices observés dans les sujets-types de l’épreuve pratique de terminale sur Eduscol
On trouve à plusieurs reprises dans les sujets-types sur eduscol :
• la recherche de l’indice du minimum (ou maximum) dans tableau. (sujets 1,7,10,12,15,17,20)
• sujet 19: Code césar (qui fait aussi le sujet d’un exercice dans ce manuel) ;
• sujet 25: Parcours du tableau par indice pour renvoyer les paires (elt, elt suivant) ;
• sujet 26: Calcul de la lettre la plus fréquente dans une chaîne en utilisant un tableau de 26
compteurs (voir aussi chap 6 de ce manuel) ;
• sujet 27: Manipulation d’images noir et blanc vues comme matrices de 0/1. Essentiellement
compléter du code à trou pour "zoomer" en remplacant chaque pixel par k*2 pixels.
• étant données 2 chaines de caractères de même longueur, compter le nombre d’indices où elles
diffèrent (distance de Hamming).
• vérifier si un tableau contient un nombre et son inverse (une solution est de parcourir le tableau
en stockant les nombres rencontrés dans un dictionnaire)
• manipulations de chaînes de caractères; le chapitre en comporte assez peu, en partie car le sujet
sera traité de façon approfondie en terminale. On peut demander de vérifier si une chaîne de
caractère s1 est préfixe/suffixe d’une autre s2. Un peu plus dur; vérifier si s1 est facteur de s2 : cf
ex 53). Beaucoup plus dur (attendre plutôt d’avoir traité le chapitre sur les algorithmes gloutons)
: si s1 est une suite extraite de s2 (la différence avec le facteur étant qu’on peut sélectionner des
caractères non consécutifs). Des manipulations plus spécifiques aux chaînes de caractères sont
envisageables, par exemple vérifier qu’une chaîne ne comporte que des majuscules à l’aide de la
• un certain nombre d’exercices ci-dessus et dans le manuel visent à faire recoder certaines des
méthodes natives de Python (min, max, sum, startswith, endswith). On peut bien sûr en faire
recoder d’autres, comme all, any…
Solution : [x for x in p if x[0]==x[1]]. On notera que la solution reste valide si les coordonnées sont des
flottants (tant que ces flottants sont donnés directement et non comme le résultat d’un calcul).
filename = '/chemin_du_fichier/Kroaz_Du.png'
m = np.array(Image.open(filename))/255).tolist()
# m = charge_img(filename) # avec le code du TP
[(label, sum(1 for _ in group)) for label, group in groupby([x[0] for x in m])]
# indique :
[([1.0, 1.0, 1.0, 1.0], 125),
([0.0, 0.0, 0.0, 1.0], 50),
([1.0, 1.0, 1.0, 1.0], 125)]
c) Écrire une fonction d_hamming(p1,p2) qui calcule la distance de Hamming entre 2 points. Il s’agit du
nombre d’indices sur lesquels p1 et p2 sont différents.
d) Écrire une fonction d_t(p1,p2) qui calcule la distance de Tchebychev entre 2 points. Il s’agit de la
différence maximale entre leurs coordonnées sur une dimension (on renvoie donc la plus grande des
valeurs |𝑝1 (𝑖) − 𝑝2 (𝑖)|).
(Il peut être utile d’ajouter une illustration, en particulier pour Manhattan)
Solution :
from math import sqrt,abs
def d_euclid(p1,p2):
return sqrt(sum([(p2[i]-p1[i])**2 for i in range(len(p1))]))
def d_taxi(p1,p2):
return sum([abs(p2[i]-p1[i]) for i in range(len(p1))])
p1=(0,1)
p2=(2,0)
d_euclid(p1,p2) # 2.23606797749979
d_taxi(p1,p2) # 3
d_hamming(p1,p2) # 2
Écrire les fonctions (prenant en entrée une chaine de caractère et un entier k) permettant de
coder/décoder un message avec ce code.
Prérequis – TEST
1 a : tuple, b : list (tableau), c : dict (dictionnaire) ; 2 3 [1, 4] [1, 4] ; 3 5 [0, 1, 2, 3, 4] [0, 0, 0, 0] ; 4
'n' ; 5 La moyenne est (1 + 19 + 0 + 4) / 4 = 6. N'importe quelle valeur entre 1 et 4 est une médiane
(par exemple 2.5 ou 3) ; 6 True True False True.
# _step == 40
# _position_initiale == (0,0)
# ======== Liste des actions: ========
def avance():
forward(_step)
def gauche():
left(90)
def droite():
right(90)
def accelerer():
modifier_delai(-1)
def ralentir():
modifier_delai(1)
# Accelerer/decelerer
onkey(accelerer, 'Up')
onkey(ralentir, 'Down')
# Go!
main_loop()
Représenter le serpent
1.
"""
Ce fichier presente un serpent qui se deplace.
A chaque pas, le tableau snake enregistre les 10 positions du serpent.
Le jeu est interrompu lorsque la tete du serpent serpent quitte la fenetre.
"""
def avance_serpent1(step):
""" Avance le serpent d'un pas """
# effacer la queue
erase(snake[0], snake[1])
# avancer dans la direction courante
forward(step)
# recuperer la position de la tete et mettre a jour le serpent
head = position()
if not in_window(head):
print('Fin du jeu !')
animate(None)
bouge(snake, head)
# ======== LA SUITE REPREND tel quel snake0, sauf pour avance() ========
# ======== Liste des actions: ========
# ...
2. Si l’on bouge d’abord le serpent, on perd la position de la queue et l’appel à erase effacerait alors la
nouvelle queue et non l’ancienne. L’affichage serait donc incorrect.
3. En décalant d’abord le serpent, on crée un « espace » pour la nouvelle position de la tête.
Si l’on affectait d’abord la nouvelle tête (snake[-1] = head) on écraserait la position antérieure qu’il
faudrait d’abord sauvegarder dans une variable temporaire pour l’affecter à la position précédente,
qu’il faudrait aussi sauvegarder, etc. Le code est plus simple en effectuant d’abord le décalage.
Une autre possibilité serait d’ajouter la tête à la fin du tableau (avec snake.append(head) par exemple), et
de retirer la queue du serpent (avec snake.pop(0)). Mais ces deux opérations sont coûteuses.
def avance_serpent2(step):
""" Avance le serpent d'un pas """
# effacer la queue
erase(snake[0], snake[1])
# avancer dans la direction courante
forward(step)
# ======== LA SUITE REPREND tel quel snake0, sauf pour avance() ========
# ======== Liste des actions: ========
def avance():
avance_serpent2(_step)
# ...
from snake_ui import animate, main_loop, reinitialiser_delai, modifier_delai, _step, _position_initiale, erase, position, in_w
indow, position_aleatoire, dessiner_obstacle
def creer_obstacle():
""" Cree un obstacle aleatoire, met un 1 dans la matrice """
x,y = position_aleatoire()
while m[x][y] != 0: # continuer jusqu'à trouver un emplacement libre
x,y = position_aleatoire()
m[x][y] = 1
dessiner_obstacle(x,y)
for i in range(7):
creer_obstacle()
def avance_serpent3(step):
""" Avance le serpent d'un pas """
erase(snake[0], snake[1])
forward(step)
head = position()
bouge(snake, head)
# voir si le serpent se marche dessus
x,y = head
if (not in_window(head)) or m[x][y] == 1 or auto_intersecte(snake):
print('Fin du jeu !')
# ======== LA SUITE REPREND tel quel snake0, sauf pour avance() ========
# ======== Liste des actions: ========
def avance():
avance_serpent3(_step)
# ...
3. La boucle while m[x][y] != 0 risque d’être une boucle infinie si toutes les positions du tableau sont
occupées, ce qui n’est possible que si l’on a plus d’obstacles que de cases dans le tableau de jeu moins
la longueur du serpent, ou bien si l’on jouait de malchance et que le générateur aléatoire retournait
systématiquement des positions qui s’avèrent occupées.
On élimine le premier risque en contrôlant le nombre d’obstacles (7 ici), et le second en utilisant un
bon générateur aléatoire (celui de Python) et en ayant une grande proportion de cases libres (la moitié,
voire même 10% de cases libres suffit).
4. Si le serpent sort de la fenêtre, l’instruction m[x][y] va provoquer une erreur d’accès en dehors des
bornes d’un tableau. On utilise ici l’évaluation dite « court-circuit » des expressions booléennes
(Chapitre 9) qui veut que l’on n’évalue pas le deuxième membre d’un or si le premier est vrai.
Nourriture
"""
- On cree aussi des bonus dont la position est enregistree par des 2 dans la matrice m.
"""
def creer_obstacle():
""" Cree un obstacle aleatoire, met un 1 dans la matrice """
x,y = position_aleatoire()
while m[x][y] != 0: # continuer jusqu'à trouver un emplacement libre
x,y = position_aleatoire()
m[x][y] = 1
dessiner_obstacle(x,y)
for i in range(7):
creer_obstacle()
def avance_serpent4(step):
""" Avance le serpent d'un pas """
global snake
forward(step)
head = position()
x,y = head
# commencer par verifier qu'on reste dans la fenetre
if (not in_window(head) or m[x][y] == 1):
print('Fin du jeu !')
animate(None)
# effacer la queue SAUF SI on est un bonus
if m[x][y] != 2:
erase(snake[0], snake[1])
bouge(snake, head)
else: # le cas ou est sur un bonus
snake = snake + [head]
m[x][y] = 0 # Un bonus disparait si on le consomme
# ======== LA SUITE REPREND tel quel snake0, sauf pour avance() ========
# ======== Liste des actions: ========
def avance():
avance_serpent4(_step)
# ...
"""
- Chaque fois que le serpent consomme un bonus, on en cree un autre
"""
def creer_bonus():
""" Cree un bonus aleatoire, met un 2 dans la matrice """
x,y = position_aleatoire()
found = False
while m[x][y] != 0 or found: # continuer jusqu'à trouver un emplacement libre
x,y = position_aleatoire()
found = False
for pos in snake:
if (x,y)== pos:
QCM (CHECKPOINT)
marche = creer_une_marche()
import matplotlib.pyplot as plt
plt.plot(marche)
plt.show()
# Test:
print(creer_marche(2,4))
# Test:
print(calcul_stats_v1(2,4))
# Ce test a peu d'intérêt; on peut juste vérifier:
# - que la fonction renvoie une liste d'entiers distincts
# - que chaqun de ces entiers est compris entre 0 et nb_joueurs-1
calcul_stats_v2(150,6)
calcul_stats_v2(150,80)
b.
def dimensions_image(matrice):
"""
Entrée: matrice: list of list of float
Renvoie une paire d'entier (hauteur, largeur) de la matrice.
"""
return len(matrice), len(matrice[0])
# Test:
print(dimensions_image(img))
affiche([calcule_palette_uniforme(20)],'Palette')
# Test:
print(f"Test palette uniforme de taille 3: {calcule_palette_uniforme(3)==[0.0, 0.5, 1.0]}")
print(f"Test palette uniforme de taille 20: {sum(abs(calcule_palette_uniforme(20)[i])-[0.0, 0.05263157894736842, 0.105263
15789473684, 0.15789473684210525, 0.21052631578947367, 0.2631578947368421, 0.3157894736842105, 0.3684210526
315789, 0.42105263157894735, 0.47368421052631576, 0.5263157894736842, 0.5789473684210527, 0.631578947368421
, 0.6842105263157895, 0.7368421052631579, 0.7894736842105263, 0.8421052631578947, 0.8947368421052632, 0.9473
684210526315, 1.0][i] for i in range(20))<.01}")
d.
def couleur_approchee(pixel, palette):
"""
Entrée: pixel: float, palette: list of float.
# Test:
print(f"Test couleur approchee 1: {couleur_approchee(.3,[0.0, 0.5, 1.0])==.5}")
print(couleur_approchee(.75,[0.0, 0.5, 1.0]))
print(f"Test couleur approchee 2: {couleur_approchee(.25,[0.0, 0.5, 1.0])==0.}")
e.
def quantifie_image_palette(img, palette):
"""
Entrée: img: list of list of float, palette: list of float
Renvoie: la nouvelle image, quantifiee en utilisant la palette.
"""
h, w = dimensions_image(img)
return [[couleur_approchee(img[i][j], palette) for j in range(w)] for i in range(h)]
2.a. Statistiquement, la diffusion ne sous-corrige pas ou ne sur-corrige pas, car le poids total des
corrections (7/16+3/16+5/16+1/16)*dxy=dxy compense exactement l’erreur.
b.
Exercices Newbie
1 Ces instructions renvoient respectivement : b', une erreur (`IndexError: list index out of range), `z', `a', `a'.
2 a.
m[1][0]
b.
print(m[1][0], m[0][0], m[0][1], m[1][1])
c.
def affiche_dimensions(mat):
print(f'{len(mat)} lignes et {len(mat[0])} colonnes')
#Test:
affiche_dimensions(m)
d. t[len(t)][0]
3
def m_l(m,i):
if len(m)>i and i>=0:
return m[i]
else:
return None
# Test :
print(m_l([['a','b'], ['d','z']],1) == ['d','z'])
# Code complété:
m=[[0]*4 for i in range(4)]
for i in range(4):
for j in range(4):
if i<j :
m[i][j] = 1
m
c.
# Réponse : i+j == 3
# Code complété :
m=[[0]*4 for i in range(4)]
d.
# Pour b:
m=[[0]*4 for i in range(4)]
for i in range(4):
for j in range(i+1,4):
m[i][j] = 1
# Pour c:
m=[[0]*4 for i in range(4)]
for i in range(4):
m[i][3-i] = 1
5
def f(t):
"""
Entrée : un tableau
Renvoie: la somme des éléments du tableau.
"""
tot = 0
for x in t:
tot = tot + x
return tot
# Test :
print(abs(moyenne([3,5,1])-3.)<.00001)
6
def f(m):
"""
Entrée: une matrice
Renvoie: la somme des éléments de la matrice
"""
tot = 0
for t in m:
for x in t:
tot = tot + x
return tot
7
def min_tableau(t):
"""
Entrée: un tableau
Renvoie: le plus petit élément du tableau
"""
min_temporaire = t[0] # on suppose len(t)>0
for y in t:
if y <= min_temporaire:
min_temporaire = y
return min_temporaire
# Test :
m = [5,4,1,3]
print(min_tableau(m)==1)
8 a.
def trouve(v,t):
"""
Entrée: un tableau t et une valeur v
Renvoie: la liste des indices des éléments de t égaux à v"""
r = []
for i in range(len(t)): # ou: for i,x in enumerate(t):
if t[i] == v:
r = r + [i]
return r
# Test :
b.
def compte(v,t):
"""
Entrée: un tableau t et une valeur v
Renvoie: le nombre des éléments de t égaux à v"""
nb = 0
for x in t:
if x == v:
nb = nb + 1
return nb
# Test :
print(compte(4, [5,4,1,3,4])==2)
c.
def trouve_matrice(v,t):
r = []
for i in range(len(t)):
for j in range(len(t[i])):
if t[i][j] == v:
r = r + [(i,j)]
return r
# Test
print(trouve_matrice(4, [[5,4],[1,4]])==[(0, 1), (1, 1)])
10 a.
[ 2*i for i in range(50) ]
# [ 2*i for i in range(100) if 2*i < 100 ] donnerait le bon résultat mais serait un peu stupide
b.
[ 2*i for i in range(100) ]
Exercices Initié
11
couleur = ["trèfle", "carreau", "coeur", "pique"]
[(i,c) for i in range(1,11) for c in couleur]
12
#[ x for x in t if x>5 ]
Remarque : la première expression n’est même pas valide, elle ressemble à une expression ternaire e1
if condition else e2. Mais si on essayait de la compléter en une expression ternaire, comme par exemple x
if x>5 else None for x in t alors on obtiendrait un élément pour chaque élément de t, ce qui n’est pas le
résultat voulu.
13
t = [2,100,54.2,10]
res = []
for x in t :
if x >= 10 and x <= 100:
res.append(x)
14 a.
t_pair = [x for x in t if x%2 == 0]
print(t_pair)
b.
t_pair_idx = [i for i in range(len(t)) if t[i]%2 == 0]
#t_pair_idx = [i for (i,x) in enumerate(t) if x%2 == 0]
print(t_pair_idx)
t = [x for x in t0]
# ou: t = t0.copy()
# ou: t = list(t0)
# Par contre t = t0 est incorrect.
16
def mini(t,f):
temp = f(t[0])
res = 0
for i in range(1,len(t)):
if f(t[i])<temp:
res = i
temp = f(t[i])
return res
# Test:
def f(x):
return x%3
print(mini([1,4,2,6,5,7,6],f)==3)
17 a.
def maxi(t):
""" renvoie la liste des indices du max du tableau. Suppose len(t)>0. """
temp = t[0]
res = 0
for i in range(len(t)):
if t[i] > temp:
res = i
temp = t[i]
# Test:
print(maxi([3,5,2,5])==1)
b. Presque pareil, mais il faut mettre à jour res dès que la valeur est supérieure ou égale (ce qui change
c’est qu’on met à jour res quand la valeur est égale).
def maxi(t):
""" renvoie l'indice du max du tableau. On suppose len(t)>0.
Le dernier indice si la valeur max se retrouve a plusieurs indices.
"""
temp = t[0]
res = 0
for i in range(len(t)):
if t[i] >= temp:
res = i
temp = t[i]
return res
# Test:
print(maxi([3,5,2,5])==3)
Une solution alternative serait de garder le code de a. avec inégalité stricte, mais en parcourant le
tableau dans le sens inverse (depuis la fin) :
def maxi(t):
""" renvoie l'indice du max du tableau. On suppose len(t)>0.
Le premier indice si la valeur max se retrouve a plusieurs indices.
# Test:
print(maxi([3,5,2,5])==3)
c.
def maxi(t):
""" renvoie la liste des indices du max du tableau. On suppose len(t)>0.
"""
temp = t[0]
res = []
for i in range(len(t)):
if t[i] > temp:
res = [i]
temp = t[i]
elif t[i] == temp:
res.append(i)
return res
# Test:
print(sorted(maxi([3,5,2,5]))==[1,3])
18
t0 = [[1,4],[2,3]]
19 La fonction inconnu(x, m) renvoie les indices des cases égales à x dans la matrice m.
20
def echange(t,i,j):
l = len(t)
if max(i,j) >= l or min(i,j) < 0:
print("impossible")
else:
t[i], t[j] = t[j], t[i]
#Test:
t=[1,4,2,3]
echange(t,2,3)
print(t==[1,4,3,2])
21 a.
def m_c(m,i):
if i < len(m[0]):
return [ x[i] for x in m ]
else:
return None
b.
def cherche_m(m,s):
for (i,x) in enumerate(m):
for (j,s1) in enumerate(x):
if s == s1:
print(i,j,m_c(m,j))
# Test:
cherche_m([['aa','b'], ['d','z'], ['aab','aa']],'aa')
22
[[i*5+j+1 for j in range(5)] for i in range(3)]
23 a.
def alignement1(g,i,j):
"""
Entrée:
- une matrice g représentant les 7 colonnes * 6 lignes, à valeurs parmi : None, 0, 1
- indices de colonne i et de rangée j.
Renvoie True ssi la matrice g admet un alignement horizontal vers la droite
en partant de i,j
Déplacement dans la direction dx,dy = (1,0)
"""
couleur = g[i][j]
if couleur is None:
return False
if i+3 <= 6:
k=1
while k < 4 and g[i+k][j] == g[i][j]:
# Test:
g1 = [[1, None, None, None, None, None], [0, 0, None, None, None, None], [0, 1, 1, 0, None, None], [0, 1, 0, None, None, No
ne], [1, 0, 1, None, None, None], [1, None, None, None, None, None], [None, None, None, None, None, None]]
g2 = [[1, None, None, None, None, None], [0, 0, None, None, None, None], [0, 1, 1, 0, None, None], [0, 1, 0, None, None, No
ne], [0, 0, 1, None, None, None], [1, None, None, None, None, None], [None, None, None, None, None, None]]
print(alignement1(g1,1,0)==False)
print(alignement1(g2,1,0))
b. Indication : on va s’inspirer de la fonction précédente pour vérifier depuis tous les points (𝑖, 𝑗) s’il
existe un alignement partant de $$g[i$[j]$]. Pour limiter les directions de recherche on observe que
puisque on va tester toutes les cases (𝑖, 𝑗) il suffit de vérifier - les alignements partant vers la droite -
ceux partant vers le haut - ceux partant en diagonale descendante vers la droite - ceux partant en
diagonale ascendante vers la droite
Le plus simple si l’on ne souhaite pas beaucoup réfléchir est donc sans doute de définir 4 fonctions
traitant chacune un de ces cas. Mais une approche plus élégante consiste à essayer de factoriser le code
comme ci-dessous en observant que ces 4 fonctions effectuent essentiellement le même calcul, mais en
se déplaçant dans des directions différentes sur la grille :
def alignements_suffisants(g,i,j):
"""
Entrée:
- une matrice g représentant les 7 colonnes * 6 lignes,
# Test:
#g2 = [[1, None, None, None, None, None], [0, 0, None, None, None, None], [0, 1, 1, 0, None, None], [0, 1, 0, None, None, No
ne], [0, 0, 1, None, None, None], [1, None, None, None, None, None], [None, None, None, None, None, None]]
#print(alignements_suffisants(g2,1,0))#True
def verifie_jeu(g):
for i in range(len(g)):
for j in range(len(g[0])):
if alignements_suffisants(g,i,j):
return True
return False
print(verifie_jeu(g2)) #True
def alignement2(g,i,j):
"""
Renvoie True ssi la matrice g admet un alignement vertical ascendant
en partant de i,j
dx,dy = (0,1)
"""
couleur = g[i][j]
if couleur is None:
return False
if j+3 <= 5:
k=1
while k < 4 and g[i][j+k] == g[i][j]:
k = k+1
if k==4:
return True
return False
def alignement3(g,i,j):
"""
Renvoie True ssi la matrice g admet un alignement diagonal ascendant vers la droite
en partant de i,j
dx,dy = (1,1)
"""
couleur = g[i][j]
if couleur is None:
return False
if i+3 <= 6 and j+3 <= 5:
def alignement4(g,i,j):
"""
Renvoie True ssi la matrice g admet un alignement diagonal descendant vers la droite
en partant de i,j
dx,dy = (1,-1)
"""
couleur = g[i][j]
if couleur is None:
return False
if i+3 <= 6 and j-3 >= 0:
k=1
while k < 4 and g[i+k][j-k] == g[i][j]:
k = k+1
if k==4:
return True
return False
def alignements_suffisants2(g,i,j):
return alignement1(g,i,j) or alignement2(g,i,j) \
or alignement3(g,i,j) or alignement4(g,i,j)
def verifie_jeu2(g):
for i in range(len(g)):
for j in range(len(g[0])):
if alignements_suffisants2(g,i,j):
return True
return False
c. On pourrait penser qu’il faut compléter la fonction précédente pour indiquer de quelle couleur est
l’alignement, mais ce n’est pas le cas: puisqu’il n’y a dans une partie normale pas d’alignement avant
que jaune joue, s’il parvient à réaliser un alignement en posant une pièce jaune, c’est que cet
alignement est jaune.
L’idée est donc simplement de tester pour chaque colonne si on crée un alignement en ajoutant une
pièce. Si on a utilisé l’approche ci-dessus se contentant de tester les alignements vers la droite, il faut
vérifier la présence d’alignement globalement et pas seulement ceux commençant par la pièce ajoutée.
def solution_jeu(g):
"""
Entrée:
- une matrice g représentant le jeu
Renvoie le (premier) numéro de colonne permettant au joueur jaune de gagner,
et renvoie -1 s'il n'y en a pas.
"""
for i in range(len(g)):
j=0
while j < len(g[0]) and g[i][j] is not None:
j += 1
if j < len(g[0]):
g[i][j] = 0 # poser une piece jaune
if verifie_jeu(g):
g[i][j] = None
return i
g[i][j] = None
g3 = [[1, 0, 1, None, None, None], [0, 0, None, None, None, None], [0, 1, 1, 0, None, None], [0, 1, 0, 1, 0, None], [1, 0, 1, Non
e, None, None], [1, None, None, None, None, None], [None, None, None, None, None, None]]
g4 = [[1, 1, None, None, None, None], [0, 0, 1, 0, None, None], [0, 1, 1, 0, 0, None], [0, 1, 0, None, None, None], [1, 0, 1, Non
e, None, None], [1, None, None, None, None, None], [None, None, None, None, None, None]]
g5 = [[1, 1, None, None, None, None], [0, 0, 1, 0, 1, None], [0, 1, 1, 0, 0, None], [0, 1, 0, None, None, None], [1, 0, 1, None, N
one, None], [1, None, None, None, None, None], [None, None, None, None, None, None]]
print(solution_jeu(g1)==-1)
print(solution_jeu(g3)==1)
print(solution_jeu(g4)==1)
print(solution_jeu(g5)==-1)
Pour aller plus loin, on peut s’amuser à écrire une fonction qui visualise une grille de jeu à l’aide de
matplotlib. On peut aussi envisager de donner directement la fonction telle quelle aux élèves pour les
aider à visualiser les tests ci-dessus.
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle, Circle
def visualise(jeu):
plt.clf()
#define Matplotlib figure and axis
fig, ax = plt.subplots()
ax.set_xlim(-.5,6.5)
ax.set_ylim(-.5,5.5)
#create simple line plot
ax.scatter([x for x in range(7) for y in range(6)],[y for x in range(7) for y in range(6)],s=1000,c='white',zorder=2)
ax.scatter([i for i,t in enumerate(jeu) for j,v in enumerate(t) if v==0],[j for i,t in enumerate(jeu) for j,v in enumerate(t) if v
==0],s=1000,c='yellow',zorder=2)
ax.scatter([i for i,t in enumerate(jeu) for j,v in enumerate(t) if v==1],[j for i,t in enumerate(jeu) for j,v in enumerate(t) if v
==1],s=1000,c='red',zorder=2)
#add rectangle to plot
visualise(g3)
24
def list_to_string(l):
s = ''
for c in l:
s += c
return s
# bien sûr, un programmeur python expérimenté préférera écrire: ''.join(s)
# Test:
print(list_to_string(['é','p','h','é','m','è','r','e'])=='éphémère')
25
from random import randint
def f(t):
i = randint(0,len(t)-1)
return t[i]
# bien, entendu il est plus idiomatique d'utiliser choice:
# from random import choice
# choice(t) == f(t)
# Test:
print(inverse([2,3.1,7.5,8.5,4])==[4,8.5,7.5,3.1,2])
27 L’énoncé étant un peu ambigu, en particulier en ce qui concerne les premiers termes, il pourra être
utile de donner les premiers termes de la suite en même temps que l’énoncé.
def nara(n=20):
t = [1 for i in range(n)]
t[0] = 0
for i in range(4,n):
t[i] = t[i-1] + t[i-3]
return t
t = nara()
# Test:
print(t==[0,1,1,1,2,3,4,6,9,13,19,28,41,60,88,129,189,277,406,595])
28
def f(u0):
t = [u0]*100000
for i in range(1,len(t)):
if t[i-1]%2 == 0:
t[i] = t[i-1]//2
else:
# Test :
print(len(f(2))==100000)
print((f(2)[0],f(2)[1],f(2)[2],f(2)[3])==(2, 1, 4, 2)) # slice
29
def f(a,b):
res = [ None ] * len(a)
for i in range(len(a)):
res[i] = [ None ] * len(a[i])
for j in range(len(a[i])):
res[i][j] = a[i][j] + b[i][j]
return res
# Test:
print(f([[1,3,7],[2,4,5]],[[0,0,3],[6,4,20]])==[[1, 3, 10], [8,8,25]])
30 Ces codes renvoient 0.0 (la valeur de sin(0)) et [0.0, 1.0, 0.0, 1.0] respectivement.
31
def tri_casier(t):
"""
Entrée: tableau t d'entiers.
Suppose que t ne contient que des entiers entre 0 et 100.
Sortie: tableau nb de taille 101,
# Test:
t = tri_casier([1,4,2,9,8,4])
print((t[0],t[1],t[2],t[3],t[4])==(0, 1, 1, 0, 2))
Remarque : en parcourant alors le tableau renvoyé, on peut renvoyer une copie triée du tableau
initial. Cela n’était toutefois pas demandé dans l’énoncé, les tris étant discutés au chapitre 6.
Remarque : la solution suivante n’a pas une complexité linéaire dans le pire cas (les dictionnaires ont
un temps d’accès constant en moyenne, mais pas dans le pire cas), mais reste efficace et peut servir
lorsque t contient peu d’éléments différents, mais pas forcément des entiers :
def tri_casier(t):
nb = {}
for x in t:
if x in nb:
nb[x] = nb[x] + 1
else:
nb[x] = 1
return nb
32
n = 100
premier = [False, False]
premier = premier + [True]*(n-1)
# Test:
for x in range(2,n):
y = 2*x
while y<=n:
premier[y] = False
y=y+x
# Test:
print(premier[2]==True)
print(premier[3]==True)
print(premier[4]==False)
print(premier[15]==False)
print(premier[25]==False)
print(premier[30]==False)
print(premier[97]==True)
print(premier[99]==False)
for x in range(2,n):
if x:
y = x*x
while y<=n:
premier[y] = False
y=y+x
# Test:
33
def montee(t):
"""
Entrée: un tableau t de flottants, représentant l'altitudes du randonneur au fil du temps:
une mesure par minute.
Sortie: un triplet (longueur_max, z_tot, z_maxl)
- longueur_max indique la longueur maximale (en minutes) d'une montée.
- z_maxl indique le dénivelé de cette plus longue montée
- z_tot indique le dénivelé total.
On définit une montée comme une suite d'au moins 2 altitude consécutives croissantes.
On notera que la plus longue montée n'est pas forcément celle qui a le plus grand dénivelé.
"""
longueur_max = 0
z_tot = 0
z_maxl = 0
nbtemp = 0
ztemp = 0
# Parcourir le tableau, en maintenant les invariants suivants:
# nbtemp = nombre d'étapes de la montée courante, 0 si on n'est pas sur une montée, au moins 1 si l'altitude courante est
supérieure à la précédente...
# ztemp = dénivelé depuis le début de la montée courante, 0 si on n'est pas sur une montée.
# Test:
print(montee([2.4,1.1,.5,1.2,1.7,.4,7.2])==(2, 8.0, 1.2))
34
def rle(t):
"""
Entrée: tableau t de valeurs quelconques.
Renvoie: le codage RLE de t comme définit dans l'énoncé.
"""
if t == []:
return []
nb = 1
res = []
for i in range(1,len(t)):
# Test:
print(rle(['aa','aa','b','c','c','c','aa'])==[(2,'aa'),(1,'b'),(3,'c'),(1,'aa')])
35 a.
def vcreux(t):
"""
Entrée: tableau t d'entiers.
Renvoie: le codage de m sous forme d'un vecteur creux comme indiqué dans l'énoncé.
"""
return [(i,v) for (i,v) in enumerate(t) if v != 0]
# Test:
print(vcreux([0,0,4,0,0,0,5,6])==[(2, 4), (6, 5), (7, 6)])
b.
def mcreuse(m):
"""
Entrée: matrice m d'entiers.
Renvoie: le codage de m sous forme d'un dictionnaire comme indiqué dans l'énoncé
"""
return {(i,j):v for (i,t) in enumerate(m) for (j,v) in enumerate(t) if v != 0}
# Test:
print(mcreuse([[0,4],[5,6]])=={(0, 1): 4, (1, 0): 5, (1, 1): 6})
# Test:
print(vplage([1000,800,802,1000,1003])==[1000, -200, 2, 198, 3])
37 a.
def est_dans_echiquier(x,y):
""" Renvoie True ssi la case est bien dans l'échiquier """
return (0 <= x <= 7) and (0 <= y <= 7)
# Test:
print(est_dans_echiquier(2,4)==True)
print(est_dans_echiquier(0,8)==False)
print(est_dans_echiquier(3,-1)==False)
b.
# Test:
m = [[None]*8 for i in range(8)]
#m[5][2]=('noir','tour')
m[5][5]=('blanc','roi')
m[1][2]=('noir','roi')
print(sorted(mouvements_tour(m,'noir',5,2))==[(2,2),(3,2),(4,2),(5,0),(5,1),(5,3),(5,4),(5,5),(6,2),(7,2)])
print(sorted(mouvements_tour(m,'blanc',5,2))==[(1,2),(2,2),(3,2),(4,2),(5,0),(5,1),(5,3),(5,4),(6,2),(7,2)])
print(sorted(mouvements_tour(m,'blanc',3,3))==[(0,3),(1,3),(2,3),(3,0),(3,1),(3,2),(3,4),(3,5),(3,6),(3,7),(4,3),(5,3),(6,3),(7,3)])
c.
def mouvements_roi(echiquier,couleur,x,y):
tx = [-1,0,1]
ty = [-1,0,1]
return [(x+dx,y+dy) for dx in tx for dy in ty
if est_dans_echiquier(x+dx,y+dy)
and (dx != 0 or dy != 0)
and (echiquier[x+dx][y+dy] is None or echiquier[x+dx][y+dy][0] != couleur)]
# Test:
m = [[None]*8 for i in range(8)]
m[5][5]=('blanc','roi')
d.
def trouve_roi(echiquier, couleur_roi):
""" Renvoie la position du roi couleur_roi """
found = False
for x in range(8):
for y in range(8):
if echiquier[x][y] is not None:
couleur, nom = echiquier[x][y]
if couleur == couleur_roi and nom == 'roi':
return x,y
return None # ne devait pas arriver de toute maniere
# Test:
m = [[None]*8 for i in range(8)]
#m[5][2]=('noir','tour')
m[5][5]=('blanc','roi')
m[1][2]=('noir','roi')
m[3][2]=('noir','tour')
print(trouve_roi(m,'noir')==(1,2))
e.
def cases_menacees(echiquier, x, y):
""" Renvoie les positions accessibles, y compris celles en prises pour le roi """
piece = echiquier[x][y]
# Test:
m = [[None]*8 for i in range(8)]
m[5][0]=('blanc','roi')
m[7][2]=('noir','roi')
m[7][0]=('noir','tour')
print(sorted(cases_menacees(m,7,0))==[(5, 0), (6, 0), (7, 1)])
m = [[None]*8 for i in range(8)]
m[5][0]=('blanc','roi')
m[7][2]=('noir','tour')
m[7][0]=('noir','roi')
print(sorted(cases_menacees(m,7,0))==[(6, 0), (6, 1), (7, 1)])
f.
def echec(echiquier, couleur_roi):
"""
Renvoie True ssi le roi de couleur couleur_roi est en echec
"""
x_roi, y_roi = trouve_roi(echiquier, couleur_roi)
for x in range(8):
for y in range(8):
if echiquier[x][y] is not None and echiquier[x][y][0] != couleur_roi:
t = cases_menacees(echiquier,x,y)
for x,y in t:
if x_roi == x and y_roi == y:
return True
return False
g.
def mouvements_valides(echiquier, x, y):
"""Renvoie les cases où la pièce peut aller sans mettre ou laisser le roi en échec"""
couleur, nom = echiquier[x][y]
res = []
for nx,ny in cases_menacees(echiquier, x, y):
piece_prise_eventuelle = echiquier[nx][ny]
echiquier[nx][ny] = (couleur,nom)
echiquier[x][y] = None
if not echec(echiquier, couleur):
res = res + [(nx,ny)]
echiquier[nx][ny] = piece_prise_eventuelle
echiquier[x][y] = (couleur,nom)
return res
# Test
m = [[None]*8 for i in range(8)]
m[5][4]=('noir','tour')
m[4][2]=('blanc','roi')
m[2][2]=('noir','roi')
print(mouvements_valides(m,4,2)==[(4,1),(4,3)])
def board_to_string(echiquier):
"""
Renvoie une chaine de caractere qui represente l'echiquier.
Le format est celui de la bibliothèque chess
P.S. "from itertools import groupby" permettrait un code un peu plus elegant
"""
d = {'roi':'k', 'reine':'q', 'tour':'r', 'fou':'b', 'cavalier':'n', 'pion':'p'}
board_string = ''
none_counter = 0
for y in range(7,-1,-1):
s = ''
for x in range(8):
if echiquier[x][y] is None:
none_counter += 1
else:
couleur, piece = echiquier[x][y]
if none_counter > 0:
s += str(none_counter)
none_counter = 0
s += d[piece] if couleur == 'noir' else d[piece].upper()
if none_counter > 0:
def board_to_svg(echiquier):
"""
Cree un fichier svg qui rempresente l'echiquier.
"""
board = chess.Board(board_to_string(echiquier))
with open("/Users/benoitgroz/Documents/temp.svg","w") as f:
f.write(chess.svg.board(board, size=350))
def mouvements_reine(echiquier,couleur,x,y):
res = []
for direction in [(dx,dy) for dx in [-1,0,1] for dy in [-1,0,1] if dx != 0 or dy != 0]:
dx, dy = direction
i=1
while est_dans_echiquier(x+i*dx,y+i*dy) and echiquier[x+i*dx][y+i*dy] is None:
res = res + [(x+i*dx,y+i*dy)]
i += 1
if est_dans_echiquier(x+i*dx,y+i*dy) and echiquier[x+i*dx][y+i*dy][0] != couleur:
res = res + [(x+i*dx,y+i*dy)]
def mouvements_fou(echiquier,couleur,x,y):
res = []
for direction in [(dx,dy) for dx in [-1,1] for dy in [-1,1]]:
dx, dy = direction
i=1
while est_dans_echiquier(x+i*dx,y+i*dy) and echiquier[x+i*dx][y+i*dy] is None:
res = res + [(x+i*dx,y+i*dy)]
i += 1
if est_dans_echiquier(x+i*dx,y+i*dy) and echiquier[x+i*dx][y+i*dy][0] != couleur:
res = res + [(x+i*dx,y+i*dy)]
return res
def mouvements_cavalier(echiquier,couleur,x,y):
res = []
for direction in [(dx,dy) for dx in [-2,-1,1,2] for dy in [-2,-1,1,2] if abs(dx)+abs(dy)==3]:
dx, dy = direction
if est_dans_echiquier(x+dx,y+dy) and (echiquier[x+dx][y+dy] is None or echiquier[x+i*dx][y+i*dy][0] != couleur):
res = res + [(x+dx,y+dy)]
return res
38 a.
def vitesses(i):
"""
Renvoie une paire (vx, vy), où
- vx est la vitesse horizontale de la voiture i
(positive si la voiture va vers l'Est, négative sinon),
- vy est la vitesse verticale de la voiture i (positive si la voiture va vers le nord, négative sinon)
"""
v = None
if to[i] == 'Nord':
v = (0,tv[i])
elif to[i] == 'Sud':
v = (0,-tv[i])
elif to[i] == 'Est':
v = (tv[i],0)
elif to[i] == 'Ouest':
v = (-tv[i],0)
# Test
tx = [0,0]
ty = [0,4]
tv = [3,2]
to = ['Nord','Nord']
print(vitesses(1)==(0,2))
# Test
tx = [4,1,6]
ty = [1,4,5]
tv = [1,2,4]
to = ['Nord','Est','Sud']
print(vitesses(2)==(0,-4))
b.
def croisement(i,j):
""" Renvoie True ssi les voitures i et j se croisent """
return (tx[i]-tx[j])*(vitesses(j)[1]-vitesses(i)[1]) == (ty[i]-ty[j])*(vitesses(j)[0]-vitesses(i)[0]) \
and (tx[i]-tx[j])*(vitesses(j)[0]-vitesses(i)[0]) + (ty[i]-ty[j])*(vitesses(j)[1]-vitesses(i)[1])>0
# Test
tx = [0,0]
ty = [0,4]
print(croisement(0,1)==True)
Comme les opérations mises en oeuvres n’effectuent que des additions, soustractions et
multiplications, la supposition que les données en entrée sont des entiers suffit effectivement à garantir
qu’on va effectuer ici tous les calculs sur des entiers et non des flottants. Donc on peut se contenter de
tester l’égalité sans ajouter de ``tolérance''
c.
# Test
tx = [0,0]
ty = [0,4]
tv = [3,2]
to = ['Nord','Nord']
#collision car 0 rattrape 1
39 a.
import string
# Solution naive : on construit un tableau de toutes les lettres minuscules (dans l'ordre) avec string.ascii_lowercase
def rang_naif(lettre):
"""
Entrée: un caractère minuscule
Renvoie: le rang de la lettre: 0 pour 'a', 25 pour 'z'...
Utilise string.ascii_lowercase.
"""
for i, x in enumerate(string.ascii_lowercase):
if x == lettre:
return i
# Test:
print(encode_naif('letempsestpluvieux',1)=='mfufnqtftuqmvwjfvy')
print(decode_naif('mfufnqtftuqmvwjfvy',1)=='letempsestpluvieux')
def rang(lettre):
"""
Entrée: un caractère minuscule
Renvoie: le rang de la lettre: 0 pour 'a', 25 pour 'z'...
Utilise ord ou chr (on ne vous indique pas lequel).
Il n’est pas nécessaire de définir explicitement une fonction pour le décodage, car la fonction
d’encodage peut être utilisée pour décoder: il suffit d’utiliser le décalage opposé.
40
carre_vigenere = [[chr((i+j)%26+65) for j in range(26)] for i in range(26)]
# Test:
print(carre_vigenere[0][0] == 'A')
print(carre_vigenere[0][1] == 'B')
print(carre_vigenere[1][0] == 'B')
print(carre_vigenere[25][25] == 'Y')
def rang(c):
"""
Entrée: un caractère c
Renvoie le rang du caractère """
return ord(c)-65
# Test:
print(rang('A') == 0)
# Test:
print(code_caractere('L',0,'SECRET',carre_vigenere) == 'D')
# Test:
print(code("LETRESOR", "SECRET") == 'DIVIILGV')
def decode(m,c):
"""
Entrée:
- un message m à déchiffrer
- une clé de chiffrement cle
Renvoie: le texte t déchiffré.
"""
#Test:
print(decode('DIVIILGV','SECRET') == 'LETRESOR')
On peut tout à fait se passer de calculer le carré de Vigenère. Il est surtout pratique pour un utilisateur
humain qui peut rapidement visualiser le caractère chiffré sans risquer de faire une erreur de calcul.
Mais sur un ordinateur il est au moins aussi simple d’écrire une fonction qui calcule à la volée la
valeur voulue d’une case du carré de Vigenère sans calculer tout le carré. En plus, le procédé de
déchiffrement ci-dessus n’est pas optimal, on peut directement calculer la valeur déchiffrée sans
parcourir une ligne du carré, comme ci-dessous :
def code_bis(m,c):
return ''.join([chr((rang(m[i])+rang(c[i%len(c)]))%26+65) for i in range(len(m))])
def decode_bis(m,c):
return ''.join([chr((rang(m[i])-rang(c[i%len(c)]))%26+65) for i in range(len(m))])
#Test
print(code_bis('LETRESOR','SECRET') == 'DIVIILGV')
print(decode_bis('DIVIILGV','SECRET') == 'LETRESOR')
Si on connaît la longueur k de la clé, déchiffrer le message même sans connaître la clé est relativement
simple, car le chiffrement se compose alors de k chiffrements de César (décalages alphabétiques) :
• un même chiffrement de César est appliqué pour les lettres aux positions 0,k,2k,3k…
Donc on peut déchiffrer indépendemment (par la méthode des fréquences des lettres) chacun de ces
chiffrements de César.
41 a.
drapeau = [[None for x in range(450)] for y in range(300)]
for y in range(300):
if 125 <= y <= 175:
drapeau[y] = [[0.0, 0.0, 0.0] for x in range(450)]
else:
t = [[1.0, 1.0, 1.0] for x in range(200)] + [[0.0, 0.0, 0.0] for x in range(50)] + [[1.0, 1.0, 1.0] for x in range(200)]
drapeau[y] = t
# affiche(drapeau)
# ou bien :
import matplotlib.pyplot as plt
plt.axis('off')
plt.imshow(drapeau)
b.
for i in range(len(drapeau)):
drapeau[i][0] = [.5,.5,.5]
drapeau[i][len(drapeau[0])-1] = [.5,.5,.5]
for i in range(len(drapeau[0])):
drapeau[0][i] = [.5,.5,.5]
# affiche(drapeau)
# ou bien :
import matplotlib.pyplot as plt
plt.axis('off')
plt.imshow(drapeau)
42 a.
# pour Charger directement depuis une url en ligne, c'est un peu plus compliqué:
from matplotlib import image
from PIL import Image
import urllib
import numpy as np
#img = image.imread(fichierlocal).tolist()
url = 'https://upload.wikimedia.org/wikipedia/commons/thumb/c/c9/Kingfisher-2046453.jpg/640px-Kingfisher-2046453.jp
g'
img = np.array(Image.open(urllib.request.urlopen(url))).tolist()
m=img
print(m[0][0])
b.
def ajoute_cadre(img):
"""
Entrée: une matrice img de triplets RGB (3 entiers) représentant une image.
Ajoute un cadre à l'image, de couleur rgb (255,223,0)
"""
taille_cadre = min(len(img),len(img[0]))//10
img2 = [[[255,223,0]]*(len(img[0])+2*taille_cadre) for i in range(len(img)+2*taille_cadre)]
for i in range(len(img)):
for j in range(len(img[0])):
img2[i+taille_cadre][j+taille_cadre] = img[i][j]
return img2
affiche_rgb(img2)
c.
img3 = [[x[len(x)-i-1] for i in range(len(x))] for x in img]
affiche_rgb(img3)
d.
img4 = [[img[j][i] for j in range(len(img))] for i in range(len(img[0]))]
affiche_rgb(img4)
e.
img5 = [[[round(0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2])]*3 for c in x] for x in img]
affiche_rgb(img5)
43 a.
def reduit(m,k):
return [ [m[k*i][k*j] for j in range(len(m[0])//k) ] for i in range(len(m)//k) ]
b. Il vaut mieux essayer avec une petite valeur de 𝑘 : 2, 3 ou 4 pour que le résultat ressemble à l’image
de départ.
print(reduit(m,2))
img = charge_img(urllib.request.urlopen(url))
affiche(reduit(img,4))
c. Le nombre de pixels est divisé par 𝑘 2 lorsque les 2 dimensions sont un multiple de 𝑘, et un peu
moins sinon.
44
# Test:
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
import urllib
def charge_img(filename):
"""
Entrée: filename: text (chemin d'accès au fichier)
Enregistre l'image sous forme d'une matrice de flottants dans [0,1.].
Renvoie cette matrice.
"""
Attention, les valeurs sont rééchelonnées de sorte que le min est noir, le max est blanc.
Exemples: affiche([[1.,.9],[.5,0]])
affiche([[(.1,.8,.1),(.2,.2,.2)]])
"""
plt.figure(1)
plt.clf()
plt.axis('off')
plt.title(tit)
plt.imshow(matrice,cmap='Greys_r')
plt.show()
# Test:
m=[[ .1, .2, .3, .4],
[ .5, .6, .4, .3],
[ .9, .8, .3, .5],
[ .9, .9, .9, .9]]
mc=[[[x,x,x] for x in v] for v in m] # une matrice qui représente une image en nuances de gris.
def moyenne_ponderee(distances,valeurs):
"""
# Test:
# On s'autorise à utiliser numpy pour le test :
import numpy as np
# Cas où une distance est nulle:
print(np.allclose(moyenne_ponderee([7,0], [[.7,.6,.2],[.3,.4,.5]]),[.3,.4,.5]))
# Cas où aucune distance n'est nulle:
print(np.allclose(moyenne_ponderee([1,4], [[.7,.6,.2],[.3,.4,.5]]),[0.6199999999999999, 0.5599999999999999, 0.26]))
# Si on ne veut pas utiliser numpy, voilà une expression à peu près équivalente avec sum:
print(sum([abs(moyenne_ponderee([1,4], [[.7,.6,.2],[.3,.4,.5]])[i]
-[0.6199999999999999, 0.5599999999999999, 0.26][i]) for i in range(3)])< .0001)
L'ordre importe peu pour l'utilisation dans agrandit2, mais pour que le test fournit ci-dessous fonctionne correctement, il f
aut renvoyer les voisins dans l'ordre suivant:
pixel en bas à gauche, pixel en haut à gauche, , pixel en haut à droite, pixel en bas à droite
"""
return [i%k+j%k,k-i%k+j%k,k+i%k-j%k,2*k-i%k-j%k],[m[i//k][j//k],m[(i-1)//k+1][j//k],m[i//k][(j-1)//k+1],m[(i-1)//k+1][(j-1)/
/k+1]]
def agrandit2(m,k):
dimx = 1+k*(len(m[0])-1)
dimy = 1+k*(len(m)-1)
m2 = [ [None for i in range(dimx)] for j in range(dimy)]
for i in range(dimy):
for j in range(dimx):
distances, valeurs = calcule_distances(m,i,j,k)
m2[i][j] = moyenne_ponderee(distances, valeurs)
return m2
# Test:
mat = [[(.3,.8,.4),(.3,.8,.4)],[(.9,.6,.2),(.4,.4,.4)],[(.9,.2,.2),(0,0,.9)]]
print(calcule_distances(mat,4,2,3) == ([3, 4, 2, 3], [(0.9, 0.6, 0.2), (0.9, 0.2, 0.2), (0.4, 0.4, 0.4), (0, 0, 0.9)])
)
affiche(mat,'image de départ')
affiche(agrandit2(mat,3),'image agrandie par interpolation')
# print(agrandit2(mat,3))
url = 'https://upload.wikimedia.org/wikipedia/commons/thumb/c/c9/Kingfisher-2046453.jpg/320px-Kingfisher-2046453.jp
g'
45
s='abflazl'
''.join([x for x in s if x>'h'])
c. L’astuce ne se généralise pas car il n’y a que deux indices qui correspondent à des booléens : 0 et 1.
47 a.
def verifie_tableau(t):
"""
Entrée: un tableau t de taille 9
Renvoie: True ssi chaque chiffre entre 1 et 9 apparaît une et unique fois dans t.
# Test:
print(verifie_tableau([3,1,4,8,2,5,9,6,7])==True)
print(verifie_tableau([3,1,4,8,6,5,9,6,7])==False)
print(verifie_tableau([3.2,1,4,8,2,5,9,6,7])==False)
b.
def verifie_horizontal(g):
"""
Entrée: une grille g de dimension 9x9 représentée par un tableau de tableaux.
Renvoie: True ssi la grille satisfait P0 et P1.
"""
for x in g:
if not verifie_tableau(x):
return False
return True
# Test:
print(verifie_horizontal([[3,1,4,8,2,5,9,6,7] for i in range(9)])==True)
print(verifie_horizontal([[3,1,4,8,2,5,9,6,7] for i in range(8)]+[[3,1,4,8,6,5,9,6,7]])==False)
c.
def verifie_vertical(g):
"""
Entrée: une grille g de dimension 9x9 représentée par un tableau de tableaux.
Renvoie: True ssi la grille satisfait P0 et P2.
"""
for i in range(9):
# Test:
print(verifie_vertical([[3,1,4,8,2,5,9,6,7] for i in range(9)])==False)
print(verifie_vertical([[3,1,4,8,2,5,9,6,7] for i in range(8)]+[[3,1,4,8,6,5,9,6,7]])==False)
d.
for i in range(9):
print((i//3,i%3))
def verifie_carre(g):
"""
Entrée: une grille g de dimension 9x9 représentée par un tableau de tableaux.
Renvoie: True ssi la grille satisfait P0 et P3.
"""
for i in range(9):
if not verifie_tableau([g[3*(i//3)+(j//3)][3*(i%3)+(j%3)] for j in range(9)]):
return False
return True
# Test:
print(verifie_carre([[3,6,7,5,1,2,8,4,9]
,[9,8,1,7,6,4,2,3,5]
,[5,2,4,8,3,9,7,6,1]
,[1,9,6,4,8,5,3,7,2]
,[2,4,8,1,7,3,9,5,6]
,[7,5,3,2,9,6,1,8,4]
e.
def verifie_solution(g):
"""
Entrée: une grille g de dimension 9x9 représentée par un tableau de tableaux.
Renvoie: True ssi g est une solution valide (satisfaisant P0,p1,P2,et P3).
"""
return verifie_horizontal(g) and verifie_vertical(g) and verifie_carre(g)
# Test:
print(verifie_solution([[3,6,7,5,1,2,8,4,9]
,[9,8,1,7,6,4,2,3,5]
,[5,2,4,8,3,9,7,6,1]
,[1,9,6,4,8,5,3,7,2]
,[2,4,8,1,7,3,9,5,6]
,[7,5,3,2,9,6,1,8,4]
,[4,1,5,3,2,7,6,9,8]
,[8,3,9,6,5,1,4,2,7]
,[6,7,2,9,4,8,5,1,3]])==True)
print(verifie_solution([[3,1,4,8,2,5,9,6,7] for i in range(9)])==False)
print(verifie_solution([[3,1,4,8,2,5,9,6,7] for i in range(8)]+[[3,1,4,8,6,5,9,6,7]])==False)
48
# Les solutions sont : v1.append(v2) et v1 = v1+[v2]
# Test:
v1=[[2,3,4]]
v2=[1,7]
v1=[[2,3,4]]
v1 = v1+[v2]
print(v1)
49
def f(t):
v=1
for e in t:
v = v*e
return v
# Test:
print(f([1,4,2,4])) # 32
50 Il faut : - inverser tour gauche en tour droit. Les angles sont alors les mêmes et on ne change rien
pour `forward'. - trouver la bonne orientation au départ (faire demi-tour de 180 degré).
Lorsque ch est une chaine de caractères comportant une unique parenthèse ouvrante, ch.split('(') renvoie
une liste [before, after] où before est le préfixe de la chaîne avant la parenthèse, et after le suffixe de la
chaîne après la parenthèse.
def chemin_retour(t):
inverses = {'forward': 'forward', 'right':'left', 'left':'right'}
res = ['right(180)']
for i in range(len(t)):
# Test:
ch1 = ['forward(10)','right(30)','forward(15)','forward(12)','left(30)','right(20)']
ch2 = ['forward(10)','right(3)','forward(15)','left(30)','right(2)']
print(chemin_retour(ch1)) #['right(180)', 'left(20)', 'right(30)', 'forward(12)', 'forward(15)', 'left(30)', 'forward(10)']
print(chemin_retour(ch2)) #['right(180)', 'left(2)', 'right(30)', 'forward(15)', 'left(3)', 'forward(10)']
51
def f(x,t):
v=0
for i,c in enumerate(t):
v += c*x**i
return v
f(2,[1,3,2]) # 15
Exercices Titan
52 Piège : on écrase la définition de i donc peu importe la valeur du paramètre i en entrée : on affiche
les éléments du tableau aux indices 5%3=2, puis, 0, 1, et 2 du tableau, c’est-à-dire
vert'',bleu'',rouge'',vert'' chacun sur une ligne.
C’est une très mauvaise idée de réutiliser le nom d’un argument de la fonction comme variable locale:
ça rend le code peu lisible.
53
# Test:
print(f([1,2,4,0],[1,2,3,5,1,2,4,0,6])==True)
print(f([1,2,4,0],[1,2,3,5,2,4,0,6])==False)
# accessoirement le code ci-dessus est aussi valide pour un type 'string' même si l'énoncé ne l'impose pas:
print(f('rill','emerillonne')==True)
54 a.
# Exemple de données:
t1 = [0.24497866312686414, 0.46364760900080615, 0.7853981633974483, 1.0303768265243125, 0.7086262721276703]
t2 = [2.976443976175166, 2.819842099193151, 2.7367008673047097, 2.5213431676069717, 2.0344439357957027]
# Code de la solution:
b. Parce que les calculs sont effectués avec des nombres flottants, donc on calcule des valeurs
approchées et non des valeurs exactes. Par conséquent, le test devrait plutôt vérifier que les valeurs
renvoyées sont proches de 4 et 1, ce qui peut s’écrire sous la forme print(abs(position(x1,x2)[0]-
4)+abs(position(x1,x2)[1]-1)<.0000001).
55 a.
def voisins(max_x,max_y,max_z,case):
"""
Entrée:
- 3 entiers: la dimension sur x, y, z moins 1, c'est à dire les plus grand entiers
tels que m[max_x][max_y][max_z] soit définit.
- un triplet d'entiers (ou liste de 3 entiers) x,y,z
tels que (0 <= x <= max_x, etc.), indiquant les coordonnées d'une case.
Retourne:
la liste des voisins (bouchés ou non) de la case
"""
x,y,z = case
l = []
if x > 0:
# Test:
print(voisins(9,9,9,(0,1,2))==[[1, 1, 2], [0, 0, 2], [0, 2, 2], [0, 1, 1], [0, 1, 3]])
print(voisins(9,9,9,[0,0,0])==[[1, 0, 0], [0, 1, 0], [0, 0, 1]])
b.
def voisins_dans_tuyau(m,case):
"""
Entrée: une matrice 3D m à valeur dans 0 et 1. Une case (triplet de coordonnées) dans cette matrice.
Retourne la liste des voisins de la case qui font partie du tuyau.
"""
return [c for c in voisins(len(m)-1,len(m[0])-1,len(m[0][0])-1,case) if m[c[0]][c[1]][c[2]] == 1]
c.
# Test:
def gener_cube(n = 10):
return [[[0]*n for j in range(n)] for i in range(n)]
a = gener_cube()
for c in [[0,0,0],[0,0,1],[0,0,2],[0,1,2],[0,1,3],[0,2,3],[0,3,3],[0,4,3],[0,4,2],[1,4,2],[1,5,2],[2,5,2],[3,5,2],[3,4,2],[4,4,2],[5,4,2],[6
,4,2],[7,4,2],[7,4,3],[7,4,4],[8,4,4],[8,5,4],[9,5,4],[9,5,5],[9,5,6],[9,5,7],[9,5,8],[9,5,9],[9,6,9],[9,7,9],[9,8,9],[9,9,9]]:
56 a.
def flatten(t):
res = []
for elem in t:
res = res+elem
return res
# Test:
print(flatten([['a','ca'],['d']]) == ['a', 'ca', 'd'])
b.
def flatten_b(t):
return [s for vec in t for s in vec]
c.
def flatten_c(t):
return [s for vec in t for s in vec if len(s)>=2]
d.
def flatten_d(t):
return [s if len(s)>=2 else 'trop_court' for vec in t for s in vec]
Description de l'algorithme:
On parcourt les éléments de t l'un après l'autre.
Tant que la "valeur courante" a un compteur supérieur à 0:
- chaque fois qu'on rencontre cette valeur on incrémente le compteur de 1
- chaque fois qu'on rencontre une autre valeur on décrémente le compteur de 1
Lorsque le compteur tombe à 0, la prochaine valeur lue devient la valeur courante.
"""
val = t[0]
nb = 1
for i in range(1,len(t)):
if t[i] == val:
nb += 1
elif nb > 0:
nb -= 1
else:
val = t[i]
nb = 1
return val
#Test:
print(majorite_boyer_moore(['a','b','b','c','b'])=='b') # 'b' qui est bien majoritaire.
print(majorite_boyer_moore(['a','b','a','c','a','d','a','d'])=='a') # 'a' qui est bien majoritaire.
print(majorite_boyer_moore(['a','b','b','c','d'])) # 'd', il n'y a en fait pas d'element majoritaire.
La complexité est linéaire. En fait c’est même un exemple célèbre d’algorithme de “streaming'', un
algorithme capable de traiter une séquence de valeurs données au fur à mesure sans les stocker, et en
# Test
print(verifie(['a','b','b','c','b'],'b'))
print(verifie(['a','b','b','c','d'],'b')==False)
58 a.
def decale(m,dx,dy):
"""
Entrée : une matrice de 0-1
Renvoie une nouvelle matrice obtenue de la précédente en
décalant tous les "1" de `dx` cases vers la droite et `dy` vers le bas.
Lorsqu'une valeur recherchée dépasse du bord, on obtient la valeur en "bouclant" à partir du bord opposé.
Dit autrement: les accès à la matrice m sont effectués "modulo" la longueur correspondante.
"""
# Test:
m=[[0,1,0,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]]
print(decale(m,0,1)==[[0, 0, 0, 0], [0, 1, 0, 0], [0, 1, 1, 0], [0, 0, 0, 0]])
b.
def convolution_px(m,masque,i,j):
"""
Entrée :
- une matrice m de triplets,
- une seconde matrice 3x3 formant le masque de convolution.
- deux indices i<len(m),j<len(m[0])
Renvoie: la valeur lorsqu'on lui applique le produit de convolution entre les 3x3 pixels entourant (i,j) et le masque.
Les accès à la matrice m sont effectués "modulo" dans le cas où l'on accède à un pixel hors de la matrice
"""
dy = len(m) # remarque; d pour dimension, pas pour décalage ici.
dx = len(m[0])
# Test:
m = [[0,1,0,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]]
masque = [[0,0,0],[0,0,0],[0,1,0]]
print(convolution_px(m,masque,0,0)==0)
print(convolution_px(m,masque,1,1)==1)
print(convolution_px(m,masque,0,1)==0)
m = [[0,1,2,3],[0,4,5,6],[0,7,8,9],[0,10,11,12]]
masque = [[1,2,3],[40,50,60],[700,800,900]]
print(convolution_px(m,masque,2,2)==13044)
# 7*60+8*50+9*40+4*900+5*800+6*700+10*3+11*2+12*1 == 13044
def convolution(m,masque):
"""
Entrée:
- une matrice m
- une seconde matrice 3x3 formant le masque de convolution.
Renvoie une matrice,
dont la valeur (i,j) est le résultat obtenu en appliquant la fonction précédente à i,j.
applique à chaque pixel l
"""
dy = len(m)
dx = len(m[0])
c.
m = [[0,1,0,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]]
masque = [[0,0,0],[0,0,0],[0,1,0]]
print('décalage_bas:',convolution(m,masque))
masque = [[0,0,0],[0,1,0],[0,0,0]]
print('identité:',convolution(m,masque))
masque = [[0,0,0],[1,0,0],[0,0,0]]
print('décalage_gauche:',convolution(m,masque))
masque_nettete = [[0,-1,0],[-1,5,-1],[0,-1,0]]
print('netteté:',convolution(m,masque_nettete))
masque_contour = [[0,1,0],[1,-4,1],[0,1,0]]
print('contour:',convolution(m,masque_contour))
# Test:
print(f([(1,2),(2,2),(0,0),(2,2)])==[1.25, 1.5])
print(f([[1,2,4],[2,2,6],[0,0,0],[2,2,6]])==[1.25, 1.5, 4.0])
Le chapitre au vu du programme
Certains éléments du chapitre ne sont pas mentionnés explicitement dans le B.O. En particulier, le TP,
et les exercices portant sur la méthode dichotomique (ou la recherche exponentielle) en dehors des
tableaux (33, 34,46,48,50). Le tri fusion sera vu en détail en terminale pour illustrer la récursivité (les
exercices 26,27 en donnent un premier aperçu simplifié). Les notions de tri stable et d’inversions ne
sont pas au programme, c’est pourquoi elles sont définies dans les exercices y faisant appel. Pour le
reste, le chapitre nous semble présenter une approche et des exercices plutôt "classique" des éléments
au programme.
Les premiers paragraphes ci-dessous précisent un certain nombre de points laissés un peu flous ou
ignorés dans le manuel.
• savoir identifier si la complexité est logarithmique, linéaire ou quadratique à partir de codes très
simples. La complexité asymptotique, mais aussi la complexité exacte (un énoncé bien formulé
devrait alors préciser ce que l’on compte, par ex. les comparaisons) dans le cas d’algorithmes
ressemblant aux algorithmes du cours ;
• retenir que sur des données de "suffisamment" grande taille un algo logarithmique sera beaucoup
plus rapide qu’un algo linéaire, lui-même beaucoup plus rapide qu’un algo quadratique ;
• comprendre que lorsque la taille des données est multipliée par 𝑥, le temps d’exécution est
multiplié par 𝑥 2 pour un algorithme quadratique, est multiplié par 𝑥 pour un algo linéaire, et
augmente à peine (on ajoute une petite valeur, au lieu de multiplier) pour un algo logarithmique.
• en supposant qu’on a prouvé que la recherche linéaire fait au plus 𝑛 − 1 comparaisons sur un
tableau de taille 𝑛 − 1, comme sur un tableau de taille n, on ajoute au plus une comparaison
(pour vérifier la dernière valeur t[n]==val dans le cas où on n’a pas renvoyé un résultat avant),
alors la recherche linéaire fera au plus 𝑛 comparaisons sur un tableau de taille 𝑛.
• La recherche dichotomique sur un conteneur n’a d’intérêt que si l’on peut accéder directement
(en temps constant) à n’importe quel élément du conteneur, par son rang. Seul le tableau garantit
cette fonctionnalité parmi les conteneurs "de base" (dictionnaire, liste chaînée, tableaux, piles).
• En revanche, à défaut d’utilise l’algorithme tel quel, il reste bien entendu possible d’appliquer la
méthode dichotomique à autre chose que des conteneurs, par exemple pour trouver de façon
approchée le minimum d’une fonction… C’est ce que montre le TP, même si le programme de
NSI ne mentionne pas cette application.
• presque certainement les nombres et leurs inverses avaient été calculés d’abord, puis triés ensuite
• la tache de trier 700 "grands" nombres à la main prend des heures (N.B. en 1972, les
informaticiens écrivaient leurs programmes sur des cartes perforées, et les programmes
pouvaient effectivement faire des centaines de cartes, qu’il fallait bien sûr insérer dans le bon
ordre sur la machine faute de quoi le programme n’aurait pas eu de sens). La tâche de trier 700
nombres avait du être très difficile pour les babyloniens avec la technologie dont ils disposaient :
tablettes, système de numération moins "concis" que le notre…
• utiliser la variante de la recherche dichotomique bisect_left qui n’apparaît dans le cours que sous
forme de note anecdotique.
Remarque : bisect_left est le nom Python, pas un nom standard. La même fonction est par exemple
nommée upper/lower_bound en C++, tandis que Java implémente Array.BinarySearch d’une façon un peu
hybride : en cas d’absence, renvoie un nombre négatif à partir duquel on peut déduire l’indice de la
position suivante.
N.B. : * La méthode dichotomique n’est pas mentionnée explicitement au programme d’informatique.
C’est néanmoins un exercice courant au Lycée, généralement en mathématique (puisqu’il s’agit
souvent juste de calcul numérique), donc il nous a semblé utile de faire le lien. * Le programme ne
précise pas quelle variante de la recherche dichotomique doit être étudiée lorsque l’élément est absent
: renvoyer -1, renvoyer l’élément suivant comme bisect_left? Nous avons choisi de présenter en cours
la variante renvoyant -1, qui est la plus simple, et la plus courante dans les documents pédagogiques.
En revanche les variantes bisect_left, bisect_right sont les plus utilisées en pratique dans les langages
de programmation car elles sont plus générales. On peut en effet avec ces variantes bisect_left/right
• en utilisant une suite de valeurs croissant de façon exponentielle pour trouver assez rapidement
une valeur sur laquelle la fonction prend un signe opposé à son signe sur la borne inférieure.
Exercice 13 : inversions
Une anecdote/application ludique sur le nombre d’inversions est le jeu du Taquin. On peut faire
vérifier expérimentalement à l’élève que tout mouvement du Taquin fait changer la parité du nombre
d’inversions dans le tableau formé en parcourant le taquin ligne à ligne (haut-bas/droite-gauche).
En déduire que le jeu du taquin proposé par Sam Loyd en 1878 (dans lequel il avait inversé 14 et 15 et
pour la résolution duquel il avait proposé 1 000 dollars) n’a pas de solution !
http://images.math.cnrs.fr/Le-jeu-de-taquin-du-cote-de-chez-Galois?lang=fr
Activité supplémentaire : illustrer les tris dans une activité débranchée 'simple'
• Faire exécuter les algorithmes de tri par insertion et sélection sur un jeu de carte (en se limitant
par exemple à une couleur).
• Ensuite, une fois les cartes triées, faire exécuter l’algorithme de recherche dichotomique.
• On peut cliquer sur un numéro de page (on suppose que tous les numéros sont visibles) pour
accéder directement à la page numéro X.
Domitille souhaite savoir si Hanifa est déjà dans sa liste d’amis. Domitille a exactement 300 amis dans
sa liste. Combien de pages devra-t-elle consulter dans le pire des cas avant de trouver Hanifa si elle s’y
prend bien ?
Solution : l’exercice peut se traduire comme suit: on a un liste triée de 300/20=15 pages. On peut donc
utiliser la recherche dichotomique, ce qui garantit que Domitille trouvera la page contenant Hanifa (ou
saura qu’il n’est pas dans la liste) en au plus (⌊log 2 (15) + 1⌋ = 4 recherches (La question est bien sûr
plus simple (niveau "Newbie") si on utilise une liste à 140 amis = 7 pages au total donc 3 pages à
consulter).
Solution a.
def argmin(t):
""" renvoie l'indice du (premier) minimum du tableau t.
Complexite: O(n)
"""
i=0
for j in range(len(t)):
if t[i] > t[j]:
i=j
return i
argmin([3,1,5,2,0,8])
c.
def argmin(t):
""" renvoie l'indice du (premier) minimum du tableau t.
Complexite: O(n)
"""
return min(enumerate(t),key=inverse_paire)[0]
argmin([3,1,5,2,0,8])
Noter que sorted(enumerate(t),key=inverse_paire)[0][0] n’est pas aussi satisfaisant car on trierait tout le
tableau (complexité O(n log n) pour simplement récupérer le min).
b. La complexité est quadratique : sur un tableau de n éléments on va faire (n-2)/2 parcours du tableau,
donc au total 𝑛(𝑛 − 2)/2 + 1 accès au tableau.
Ce second algorithme est donc très inefficace, beaucoup moins satisfaisant que l’algorithme en 𝑛log𝑛
donné en a).
Exercices observés dans les sujets-types de l’épreuve de QCM de première sur Eduscol
On trouve surtout des exercices correspondant à ce chapitre dans le thème G : algorithmique.
• Condition pour pouvoir appliquer la recherche dichotomique (sujet type 0 question 42)
• Complexité = nombre d’opérations (d’un type donné) nécessaires soit pour un programme
simple, par exemple une boucle imbriquée (1G2, 1G4, 1G5), soit pour un des algorithmes du
cours, par ex: trouver le maximum d’un tableau à n éléments (4G1), ou pour effectuer une
recherche dichotomique sur une instance donnée (nombre approximatif dans 18G2, nombre exact
dans sujet-type 0 question 41), ou un algorithme de tri du cours (sujet type 0 question 39)…
• Exercices du style "si on multiplie la taille de l’entrée par 100 pour le tri par insertion, par
combien multiplie-t-on le temps de calcul"? (20G2) …
Exercices observés dans les sujets-types de l’épreuve pratique de terminale sur eduscol :
On trouve à plusieurs reprises dans les sujets-types sur eduscol:
• compléter ou écrire le code d’un des algorithmes du chapitre (recherche dichotomique, tri par
insertion et tri par sélection. Plusieurs variantes apparaissent dans les sujets pour la recherche
dichotomique (booléenne, version avec indice et -1 si absent du tableau…)
• dans le sujet 6 et 17: tri d’un tableau ne comportant que 2 valeurs. Dans le sujet 17 le code force
à utiliser 2 indices i croissant et j décroissant pour maintenir l’invariant "le tableau contient des 0
jusque i-1 et des 1 à partir de j+1". On peut le voir comme une variante un peu originale du tri
insertion où on trie par les 2 bouts à la fois (et il n’y a pas besoin de "décaler" des valeurs après
une insertion grâce au fait qu’il n’y a que 2 valeurs 0 et 1 et qu’on "insère" toujours un 0 à
gauche et un 1 à droite ;
• dans le sujet 18: compléter un code pour insérer un élément dans un tableau trié. Comme le code
commence par ajouter l’élément en fin de tableau par append avant de le déplacer, on observe
qu’il s’agit d’implémenter d’une étape du tri par insertion.
• Animations de tris :
http://sortbenchmark.org/
Prérequis – TEST
1 a ; 2 a ; 3 a : 4, ..., 9 - b : 10, ..., 5 – c (en colonne) 0 0 / 0 1 / 1 0 / 1 1 / 2 0 / 2 1 - d : [False, True,
False] - e : (0, 'c'), (1, 'a') - f : ['c','ab'] ; 4 b.
# On observe que la recherche dichotomique est un peu plus lente pour trouver le premier element du tableau,
# mais beaucoup plus rapide pour trouver le dernier, comme on peut s'y attendre.
2.
affiche_pire_cas1()
• La recherche linéaire (pire cas) augmente bien à peu près proportionnellement à la taille du
tableau. La durée de la recherche dichotomique est minime quelle que soit la taille du tableau, et
très faible par rapport à la recherche linéaire. Par contre, si la courbe est cohérente avec une
courbe logarithmique, on ne peut pas le distinguer de manière aussi nette que la linéarité de la
recherche linéaire :
– d’une part car les valeurs sont négligeables par rapport aux durées de la recherche
linéaire qui impose donc une échelle inadaptée sur l’axe vertical ;
• Sur un ordinateur portable (CPU : i5 à 2,9 GHz, RAM : 16GB DDR3) : la recherche linéaire
demande approximativement (dans le pire cas) .7s sur un tableau de taille 1.000.000. Donc le
temps d’exécution de la recherche linéaire sur un tableau de taille n dans le pire cas est à peu près
de n*7*10**-7 secondes sur cet ordinateur. Les valeurs obtenues sur un autre ordinateur peuvent
varier, mais sont probablement du même ordre de grandeur.
r = 2.1
d = .7
def f(h):
return - h**3 + 3*r*h**2 - 4*d*r**3
plt.clf()
nb_pas = 100
liste_x = [0.05*i for i in range(0,int(4.2/.05)+1)]
liste_y = [f(h) for h in liste_x]
plt.plot(liste_x,liste_y)
plt.grid()
plt.show()
2.
import bisect
id = bisect.bisect_left([f(0.01*i) for i in range(0,int(4.2/.01)+1)],0)
print(f"L'indice de la première valeur positive du tableau est {id} donc h0 vaut à peu près {0.01*id}")
3.
def bisection(f, a, b, eps):
""" Entree: fonction f continue sur [a,b], a < b, eps > 0
tels que f(a)*f(b) < 0
Renvoie h tel qu'il existe x dans [a,b] avec f(x)=0 et |x-h|<eps
On peut noter qu’il faut à peu près une itération de plus chaque fois qu’on augmente la précision
voulue en divisant step par 2. Ce qui est normal puisque le nombre d’itérations est à peu près
log 2 ((𝑏 − 𝑎)/𝑠𝑡𝑒𝑝).
Remarque : La convergence parait donc raisonnablement rapide, mais est en réalité lente par rapport
à d’autre méthodes : le nombre de chiffres significatifs n’augmente `que'' linéairement avec le nombre
d’itérations. En outre il n’est pas toujours facile de trouver des valeurs de départ `a,b satisfaisantes pour l’intervalle.
Enfin, la technique se généralise assez mal lorsqu’on cherche à résoudre des systèmes à plusieurs
inconnues. D’autres méthodes (comme par exemple la méthode de Newton ou méthode des sécants)
convergent plus rapidement, mais ces méthodes sont moins robustes que la recherche binaire au sens
où elles peuvent ne pas converger.
4.
def bisection_valeur(f, a, b, eps):
""" Entree: fonction f continue sur [a,b], eps>0, a<b tels que f(a)*f(b)<0
Renvoie h tel que |f(h)| < eps
"""
nb_iterations = 0
while True:
Sur la fonction f du problème, la convergence est relativement rapide. Mais pour certaines fonctions
on peut ne jamais terminer (penser à une fonction du type "2*(partie entière de x-.3)-1").
5.
[rac(x) for x in [2,3.2,8,.25]]
Cette fonction rac(x) calcule une approximation de la racine carrée du nombre x lorsque x>=1.
Par contre, le comportement est dangereux lorsque x<1: en ce cas on est en train d’appeler bisection
avec une fonction f=smaller_sk de même signe sur les deux bornes de l’intervalle, ce qui n’est pas
`permis''. Pour `0<x<1, on a en effet smaller_sq(0)<0 et smaller_sq(x)<0. Le comportement de bisection n’est
pas spécifié dans ce cas. Il se trouve que la fonction bisection termine quand même (pour la même
raison que la recherche dichotomique termine: la boucle maintient l’invariant a<b et l’intervalle décroît
rapidement (divisé par 2 ç chaque étape), donc finit par devenir plus petit que eps. On aurait pu (et
On observe expérimentalement que le résultat est exact lorsque l’entrée est une puissance paire de 2
(positive ou négative, avec la modification suggérée), sauf 1. Il n’est pas question de le prouver, mais
deux raisons expliquent pourquoi ce n’est pas si surprenant :
• les nombres flottants représentés de manière exacte sont des sommes de puissances de 2 ;
6. Si le polynôme p a un minimum local c unique sur [a,b], cela signifie qu’il décroît à partir de a
jusqu’à c, puis croît ensuite. La pente de la fonction est donc négative de a à c, puis positive de c à b.
Le minimum est donc un zéro de la fonction qui à x associe la pente de p en x''; cette fonction pente'' est la
dérivée de p et peut être calculée facilement à partir de la formule de p.
7.
• Dans la recherche dichotomique, l’espace de recherche est l’ensemble des indices, qui sont des
entiers, donc dès qu’on obtient un intervalle de taille 1, le prochain intervalle est de taille 0 et la
recherche se termine.
• Le fait qu’on ne se contente en fait pas de diviser strictement l’intervalle en 2 mais diminue
• Zénon lance une pierre vers un arbre distant de 8 mètres. La pierre parcourt d’abord la moitié de
la distance (4m), puis la moitié de la distance restante (2m), puis 1m, etc. Chaque étape demande
un temps non nul, et il faut une infinité d’étapes avant d’atteindre l’arbre, donc, pense Zénon, un
temps infini. Le paradoxe peut se résoudre en notant que (pour une pierre à vitesse constante) le
temps nécessaire à chaque étape décroît : 𝑥 secondes pour l’étape 1, 𝑥/2 pour la 2, etc. Donc le
total de cette suite infinie fait 2 ∗ 𝑥 secondes seulement, et c’est bien au bout de 2 ∗ 𝑥 secondes
que la pierre atteint l’arbre.
– Une interprétation qui résout l’apparente contradiction consiste donc à se rendre compte
que lorsqu’on divise l’espace, on divise aussi proportionnellement le temps de parcours
et que, finalement, la vitesse reste inchangée contrairement à ce que sous-entend le
paradoxe.
– Une autre interprétation (reposant sur le même principe) consiste à observer que même si
chaque temps est strictement positifs, comme ils décroissent très vite, la somme des
temps ``converge'' vers une durée totale finie: 2secondes, et non pas une durée totale
infinie comme le sous-entend le paradoxe.
https://fr.wikipedia.org/wiki/Paradoxes_de_Z%C3%A9non
• Pour se convaincre que la continuité de f n’est pas nécessaire pour la méthode dichotomique on
peut observer que bisection n’observe jamais des points à distance inférieure à eps. Donc on peut
toujours prolonger la fonction f entre les points observés pour en faire une fonction continue.
• Par contre, il est nécessaire que la fonction soit continue pour la variante (traitée en Question 3)
de la méthode dichotomique dans laquelle on définit la tolérance eps par rapport à la valeur f(x) et
non par rapport à x (et la preuve consistant à prolonger la fonction entre les points observés n’est
plus valide dans ce cas là car la fonction ne termine même pas toujours sur une entrée
discontinue).
Mais, dans le cas présenté ici, la fonction g a un zéro entre a=0 et b=10**8, est de signes opposés entre a
et b, et croissante. Donc elle satisfait aussi la condition et on peut l’utiliser dans la fonction bisection.
x=2021
def w(y):
""" fonction mystere """
if y>x:
return True
elif y<x:
return False
else:
return 4
def g(z):
""" transforme le resultat de w d'une facon exploitable tel quel par bisection """
round(bisection(g,0,10**8,.25)) #2021
Noter qu’on n’a même pas besoin de connaître la borne supérieure 10**8 pour construire une fonction
efficace. On peut deviner une borne supérieure par… recherche exponentielle comme suit:
x=2021
borne_sup = 1
while f(borne_sup)==False:
borne_sup = 2*borne_sup
round(bisection(g,0,borne_sup,.25))
9. La recherche dichotomique applique la méthode dichotomique aux indices. La subtilité est que les
indices sont des entiers. Il va donc falloir arrondir les extrémités de l’intervalle à des positions
entières.
Il est difficile avec la fonction bisection de gérer la fin de la recherche, lorsque l’intervalle ne contient
plus que quelques indices (par exemple, si a=0 et b=1.5, alors floor((a+b)/2)=0 donc on risque de
boucler). Une solution consiste à utiliser la méthode dichotomique pour se rapprocher de l’indice
cherché tant que l’intervalle est de taille au moins 2 pour que mid reste différent de a et b, et ensuite
recherche_dicho([2,4,5,7,9],3)
Exercices
Exercices Newbie
1
print(t) # t == ['15','2','7']
print(t1) # t1 == ['1515','22','77']
Le « piège » est ici que l’on trie sur des chaînes de caractères et non des entiers. Donc la chaîne de
caractère `15' est plus petite que `7' puisqu’on compare son premier caractère, `1', avec `7'.
2 j est un variant de boucle : c’est un entier positif qui décroît strictement à chaque itération. Comme
ce variant est évident, on ne s’est même pas donné la peine de discuter la terminaison de tri_insertion
dans le cours.
3 a. Un seul accès pour chercher 15 dans $$[1,2,15,17,30$$]: le premier accès se faisant sur l’indice
du milieu, on trouve directement la valeur cherchée.
b. Deux accès pour chercher 15 dans $$[-4,-2,0,2,10,15,18$$]: le premier accès se fait sur l’indice
6//2=3, dont la valeur 2 est plus petite que la valeur cherchée. On effectue donc l’accès suivant sur le
milieu de la partie droite $$[10,15,18$$] (donc à l’indice 5 dans le tableau d’origine) qui contient
valeur cherchée.
c. Le nombre d’itérations des pires recherches le pire cas augmente de 1 (noter par contre que la pire
recherche dépendant du tableau, il n’est pas certain que la pire recherche sur un tableau de taille n
donné reste la pire recherche lorsque l’on rajoute n nouveaux éléments à ce même tableau. En fait une
recherche donnée peut même dans certains cas devenir plus rapide en ajoutant de nouveaux éléments
au tableau, si les éléments ajoutés ont pour résultat que la valeur cherchée se trouve désormais juste au
milieu du nouveau tableau).
5 a. 2 solutions sont acceptables : créer un dictionnaire à partir des produits et trier t puis utiliser la
recherche dichotomique pour répondre aux requêtes.
Le chapitre et l’énoncé suggèrent plutôt la seconde solution : trier t et utiliser la recherche
dichotomique. Les deux ont leurs avantages et inconvénients (*). En pratique les 2 seraient
certainement assez rapides. La recherche linéaire, elle, serait peut-être trop lente, et gaspillerait en tout
cas du temps de calcul.
(*) Par exemple, pour les plus curieux : s’il faut très souvent ajouter de nouveaux produits ou en
retirer, ces opérations sont plutôt efficaces sur un dictionnaire, alors que retrier le tableau après qu’il
ait été modifié, ou insérer directement les valeurs entre les éléments appropriés dans le tableau, est
relativement coûteux. A l’inverse, un simple tableau trié occupe moins d’espace qu’un dictionnaire.
On ne peut toutefois pas attendre de l’élève qu’il sache comparer les 2 approches.
b. ⌊log 2 (𝑛 + 1)⌋ avec 𝑛 = 100 000, donc 16.
6 a.
# ex pour tester cette solution, avec juste 20 villes:
p=[284677, 290053, 868277, 156854, 154508, 150659, 257068, 233098, 217728, 2175601, 518635, 518635, 486828, 34103
# bien sûr le test echoue si la solution utilise la taille 1000 du tableau, ce qui resterait correct vu l'énoncé,
# par ex:
# [sorted(p)[999-i] for i in range(1000)]
b. Il suffit tout simplement d’adapter le tri par sélection pour n’en effectuer que les k premières
itérations.
def topk(t, k):
""" Trie t sur place. """
for i in range(k):
j_min = i
for j in range(i+1, len(t)):
# j_min = indice du minimum parmi t[i],...,t[j]
if t[j] < t[j_min]:
j_min = j
if j_min != i: # on echange les cases i et j_min
t[i], t[j_min] = t[j_min], t[i]
# t[0] <= ... <= t[i] sont les plus petits elements de t
topk(p,10)
print(p)
7
print(t) # t=[1,3,2,5]
8
t1=[4,1,5,2]
t2=[2,1,4,5]
sorted(t1)==sorted(t2)
La condition testée revient à vérifier que t1 trié est égal à t2 trié. Le cours mentionne cette utilisation
du tri pour obtenir une « représentation canonique » d’un (multi-)ensemble d’éléments.
9 a.
algorithme opérations échanges comparaisons
b.
Taille 4 :
10 Pour trouver 10 dans un tableau de 3 éléments triés :vles 2 sont possibles puisque le tableau est
trié, mais la recherche linéaire est préférable. Elle sera même plus rapide sur un tout petit tableau (3
éléments, et même sans doute 10 éléments) car elle est plus simple.
Pour trouver `c' dans un tableau de 100 éléments triés : les 2 sont possibles puisque le tableau est trié,
mais on préférera probablement la recherche dichotomique car elle sera probablement beaucoup plus
rapide.
Pour trouver 5 dans un tableau de 1000 éléments non triés : on n’a pas le choix, comme le tableau
n’est pas trié la recherche dichotomique ne peut pas être appliquée et il faut donc adopter la recherche
linéaire.
Exercices Initié
11 a. Une fois le tableau trié il suffit de parcourir le tableau dans l’ordre. On compare le numéro à la
case x du tableau avec le dernier numéro lu pour voir s’il s’agit d’un nouveau numéro.
Remarque : Cet usage rentre dans le cadre de ce qu’on a appelé dans le cours « trier pour regrouper ».
Compter le nombre de groupes ou le nombre d’élément dans un groupe (ou n’importe quel agrégat) est
plus facile lorsque le tri garantit que les éléments du même groupe sont consécutifs.
b.
def compte_distinct(t):
"""
Affiche le nombre d'elements distincts dans le tableau t.
"""
t_trie = sorted(t)
nb = 0
x_prev = None
if len(t) > 0:
nb = 1 # ne pas oublier le dernier
x_prev = t_trie[0]
for x in t_trie:
if x != x_prev:
nb = nb+1
x_prev = x
print(nb)
c.
def compte_par_numero(t):
"""
Affiche pour chaque valeur du tableau
le nombre de fois qu'elle apparait dans t.
On affiche donc des paires (valeur, nombre d'occurrences)
"""
t_trie = sorted(t)
if len(t) > 0:
x_prev = t_trie[0]
nb = 0
for x in t_trie:
if x != x_prev:
print(x_prev, nb)
x_prev = x
nb = 1
else:
nb = nb+1
# afficher le dernier numero
print(x_prev, nb)
12 a. Sur cet exemple, il n’y a même pas de choix car il n’y a à chaque étape qu’un indice i qu’on peut
échanger : on commence nécessairement par échanger 10 ↔ 2 suivi de 4 ↔ 2 (puisque 2 se retrouve
entre 4 et 10 après le premier échange).
b. 3 échanges. Qu’on échange d’abord le 3 avec 1 ou 8, il suffit ensuite de 2 échanges (on peut
dessiner un arbre pour s’en convaincre si nécessaire).
c. On aurait tort de penser qu’on a diminué le nombre d’indices tels que t[i] > t[i+1]; sur t1 ou t2 ce
nombre ne change pas à chaque étape. En fait il peut même augmenter, par exemple si on commence
par échanger 8 et 3 sur [7, 8, 3, 4]. Donc ce n’est pas un variant de boucle.
Une intuition est qu’une étape de l’algorithme déplace un grand nombre vers la droite et un petit vers
la gauche, jusqu’à ce que le tableau soit trié.
Donc, on peut définir comme variant la fonction 𝑓(𝑡) qui renvoie $$n*t[0$ + (n-1)*t[1] + (n-
2)*t[2]+…+1*t[n-1] + n*abs(max(t))$]. N’importe quelle séquence de coefficients entiers strictement
décroissants convient d’ailleurs aussi bien, en ajustant le dernier terme qui permet de s’assurer que le
variant soit positif. Montrons que 𝑓 est bien un variant. D’une part, 𝑓(𝑡) est bien un entier positif. Soit
𝑡 un tableau et 𝑖 tel que $$t[i$>t[i+1]$]. Soit 𝑡′ le tableau definit à partir de 𝑡 en échangeant $$t[i$$] et
$$t[i+1$$]. Alors $$f(t')-f(t) = t[i+1$-[i] < 0$].
Une autre fonction qui pourrait faire office de variant la fonction 𝑓(𝑡) qui renvoie le nombre
def compte_inversions(t):
nb = 0
for i in range(len(t)-1):
for j in range(i+1,len(t)):
if t[i]>t[j]:
nb += 1
return nb
affiche_inversions([4,7,2,5])
print(compte_inversions([4,7,2,5]))
c. Les tableaux sans inversions sont des tableaux triés (par ordre croissant), et inversement les tableaux
triés (par ordre croissant) n’ont pas d’inversions. Un tableau trié par ordre décroissant peut avoir
𝑛(𝑛 − 1)/2 inversions. C’est le nombre de paires i<j dans un tableau de taille n donc c’est le nombre
maximal d’inversions que peut avoir un tableau non trié.
Pour le prouver, il suffit de noter qu’un échange de voisins i et i+1 n’annule ou ne crée aucune
inversion avec les autres indices j<i-1 ou j>i+2.
e. Le tri par insertion élimine une inversion à chaque échange. On ne peut pas faire mieux d’après la
question précédente. Donc le tri par insertion est optimal parmi les algorithmes qui n’échangent que
des voisins. Attention, on a limité les échanges aux voisins. Si on s’autorise à échanger des éléments
qui ne sont pas voisins, il existe des algorithmes plus efficaces (le tri par sélection, par exemple, ne fait
que n échanges. Et les méthodes de tri de Python en font au plus de l’ordre de 𝑛log𝑛).
14 Oui, le tri est correct, lors de l’itération i (i-ème itération ligne 2), le minimum du « tableau restant
» est ramené à gauche jusqu’à la position i. On est donc exactement sur le même principe que le tri par
sélection, sauf qu’au lieu de commencer par identifier le minimum et ensuite l’échanger avec la case i,
ici on va parcourir le tableau en décalant un élément vers la gauche tant qu’il est plus petit que son
voisin de gauche (c’est à dire tant qu’il est le plus petit élément rencontré en traversant le tableau
depuis la droite), etc. Lorsqu’il n’est pas plus petit, on continue avec l’élément de gauche que l’on
décale à son tour jusqu’à ce qu’il rencontre plus petit que lui.
N.B. C’est une forme de tri bulle (bubble sort, pas au programme), dans sa variante « sink sort » où on
inversé le sens de parcours. Dans le tri bulle, c’est le maximum qu’on déplace vers la droite du
tableau, alors qu’ici on déplace le minimum vers la gauche. Le tri bulle est ainsi nommé parce que les
bulles d’air les plus plus grosses sont les premières à remonter dans un liquide (ceci correspond à une
variante du tri où les gros éléments sont déplacés vers la droite, mais on a préféré étudier ici la version
• la recherche dichotomique, elle, fait des sauts dans la mémoire, ce qui n’est pas très efficace et
ne se justifie que si cela épargne beaucoup d’opérations ;
Avec l’ « optimisation » on perdrait donc plusieurs propriétés intéressante du tri par insertion tel que
donné en cours, à savoir :
• qu’il est efficace sur un petit tableau (parce qu’il est simple et se contente de parcourir le
tableau).
Ceci explique que les implémentations habituelles n’utilisent pas la variante dichotomique (de toute
façon on utilise rarement le tri par insertion sur des grandes données).
16 a. La condition est superflue : sans la condition le tri reste correct (il n’a aucune influence sur la
preuve dans le cours.)
b. Cette condition sert à éviter d’échanger un élément avec lui-même.
En pratique on trouve aussi bien des versions du tri avec la condition que des versions sans la
condition qui effectuent l’échange systématiquement.
c. On peut rendre le tri stable en utilisant l’indice pour départager les ex-aequo.
def stabiliser_le_tri(tableau):
t = [(x,i) for i,x in enumerate(p,t)]
return [ pair[0] for pair in p(t) ]
18 a. Oui, il suffit de remplacer la ligne 5 par : for j in range(len(t)-1,i,-1):. La valeur sélectionnée est la
même que l’on parcoure le tableau de gauche à droite ou de droite à gauche. Par contre si on ne
change que la ligne 5, le tri ne sera plus stable : cette variante ne va pas conserver l’ordre des éléments
ex-aequo. Mais on peut même rendre cette variante stable en utilisant une inégalité large à la ligne 6 :
if t[j] ⇐ t[j_min]:
b. Oui, il faut alors inverser à la fois le sens de parcours et les comparaisons. Par exemple :
def tri_selection(t):
""" Trie t sur place. """
for i in range(len(t)-1,0,-1):
j_m = i
19 a.
def tri_partiel(t):
""" Trie partiellement t:
à la fin de l'exécution de la fonction,
les cases qui valent 0 sont situées à gauche des cases qui ont une valeur non nulle.
Ce programme est un tri sur place qui se contente d'échanger les éléments du tableau.
On peut le voir comme une variante un peu originale du tri insertion,
dans laquelle on va trier par les 2 bouts à la fois.
Et dans cette variante, il n'y a pas besoin de "décaler" des valeurs après une insertion
parce que:
- il n'y a que 2 sortes de valeurs : 0 et 'non-zero'
- et qu'on "insère" toujours un 0 à gauche et un 'non-zero' à droite.
Ce tri partiel modifie l'ordre des éléments 0 (et des non-zero) entre eux.
Ce n'est donc pas un tri stable.
"""
i=0
j = len(t)-1
while i < j:
# invariant:
# les cases à gauche de i valent 0
# les cases à droite de j ont une valeur != 0
while j > 0 and t[j] != 0:
j = j-1
while i < j and t[i] == 0:
i = i+1
#Test:
t=[0,3,5,1,0,4,0]
print(tri_partiel(t) == [0, 0, 0, 1, 5, 4, 3])
Remarque : un programmeur python chevronné préférerait sans doute utiliser la méthode sort.
t.sort(key=lambda x: not x). Mais ce n’est pas l’esprit du programme et ce n’était pas la question
demandée.
b. On observe que i ne fait qu’augmenter. j ne fait que décroître. Donc j-i est un variant de boucle qui
décroît à chaque passage dans une des trois boucles while. Ce qui garantit qu’on ne peut exécuter
chaque instruction qu’au plus len(t) fois.
c. Remplacer les comparaisons avec 0 par des comparaisons avec « bleu ». On n’a pas besoin de
comparer avec 'rouge'.
def creation_triee(t):
""" Entree: un tableau t ne comportant que 2 valeurs distinctes rouges et bleu.
Sortie: renvoie un tableau trie comportant autant de bleu que t et autant de rouge
"""
nb_b = 0
for x in t:
if x=='bleu':
nb_b +=1
creation_triee(['rouge','bleu','rouge','rouge'])
creation_trieene « copie » pas les elements du tableau mais crée de nouvelles valeurs. Si on triait des
valeurs générales en fonction d’une partie de la valeur, par exemple des paires (clé, valeur) et que c’est
la clé qui est bleu ou rouge mais la valeur est quelconque, alors on ne peut pas utiliser creation_triee
alors qu’on peut utiliser tri_partiel.
Noter qu’on pourrait quand même modifier creation_triee pour trier de telles valeurs mais l’adaptation
serait lourde et ne serait pas un tri sur place: on pourrait enregistrer en plus des compteurs: - une liste
liste_bleue contenant toutes les valeurs du tableau ayant une clé bleue et - une liste liste_rouge toutes les
valeurs ayant une clé rouge.
Ensuite, on génère le résultat en parcourant la liste bleue puis la rouge.
20
def topk(t, k):
""" Trie t sur place.
Quel que soit le nombre de valeurs différentes dans t.
Complexité: au plus de l'ordre de k*n opérations s'il y a k valeurs distinctes dans t.
"""
i=0
while i<len(t):
j_min = i
for j in range(i+1, len(t)):
# j_min = indice du minimum parmi t[i],...,t[j]
21 a.
def f_2val(t):
""" reorganise t de maniere a placer les impairs avant pairs """
x = -1
for i in range(0,len(t)):
# Invariants:
# toutes les cases à gauche de x sont impaires.
# x>0 s'il existe un pair à gauche de i.
# si x>0, toutes les cases entre x et i (exclu) sont paires.
if t[i]%2 == 1 and x >= 0:
t[x], t[i] = t[i], t[x]
x += 1
elif t[i]%2 == 0 and x <0:
x=i
return t
# Test:
t = [2,5,3,9,4,2,3,7]
print(f_2val(t).count(3)==2)
print(set(f_2val(t)[0:5])=={5, 3, 9, 7} and set(f_2val(t)[5:])== {2, 4})
b.
# Test:
print(est_impair(4) == 1 and est_impair(3) == 0)
t = [2,5,3,9,4,2,3,7]
t.sort(key=est_impair)
print(t==[5, 3, 9, 3, 7, 2, 4, 2])
22 a. Ce tri est appelé tri casier ou tri comptage (counting sort). L’idée est de construire un
histogramme des données dans un tableau de taille n pré-déterminée. Puis parcourir le tableau n.
On donne directement la solution générale. Pour le cas particulier, il suffit d’appeler
tri_entiers_non_distinct(t,1999)
def tri_entiers_distincts(t,n):
"""
Trie le tableau d'entiers t.
Suppose que t ne contient que des entiers distincts entre 0 et n
"""
casiers = [0]*n
for x in t:
casiers[x] = 1
return [val for val, compteur in enumerate(casiers) if compteur == 1]
b.
import random
print(tri_entiers_distincts(random.sample(range(0, 1999),1000),1999))
c.
import random
print(tri_entiers_non_distincts(random.choices(range(0, 1999),k=1000),1999))
d. La complexité est O(n+k). Donc quand k est suffisamment grand par rapport à n ce tri est beaucoup
plus efficace que les tris par insertion et selection (par exemple : pour k=n/2 comme ici, ou k=n, ou
k>n). En fait il est même plus efficace que tous les tris par comparaison. Par contre la technique ne
s’applique que pour trier des tableaux d’entiers et n’a d’intérêt que lorsque ces entiers ne sont pas très
grand (n petit).
Il en existe une généralisation un peu plus compliquée appelée tri par base (radix sort), qui permet de
trier efficacement des entiers un peu plus grands et qui est parfois utilisée dans des cas très particuliers
- elle était utilisée dans les machines qui triaient les cartes perforées dans les années 1920.
# Test:
print(produit_scalaire([2,3],[2,1])==7)
b. Il suffit d’une astuce : remarquer que (-t3) est un tableau de nombres croissant. Notons t4 une
permutation de t2 différente de t3; la permutation n’est pas triée dans l’ordre contraire. Par l’inégalité
de réarrangement, on a donc t1·(-t4) < t1·(-t3). En multipliant des 2 côtés par (-1), on en déduit :
t1·t4>t1·t3.
c.
print(f"La meilleure moyenne est {produit_scalaire([7,15,19],[2,4,5])/(2+4+5)}")
print(f"La pire moyenne est {produit_scalaire([7,15,19],[5,4,2])/(2+4+5)}")
24 a. Oui il suffit d’effectuer 2 recherches dichotomiques identifiant chacune l’indice d’un élément à
val, la première renvoie le premier tel indice dans le tableau, et la seconde renvoie le dernier tel indice.
b. On peut utiliser une recherche dichotomique pour trouver le premier élément idx_left ayant la valeur,
puis itérativement parcourir les éléments à partir de idx_left tant qu’ils ont la valeur x. La recherche
dichotomique préserve donc son efficacité tant que les éléments cherchés ne sont qu’une petite
fraction du total. Noter que la recherche_lineaire, elle, va systématiquement devoir parcourir
l’ensemble du tableau pour cette version du problème de recherche donc est beaucoup moins efficace
si le résultat ne comprend qu’une petite fraction des éléments.
Autre solution un peu moins efficace en général : effectuer, comme ci-dessus, deux recherches
dichotomiques pour trouver idx_left, idx_right. On sait alors les éléments cherchés sont ceux entre idx_left
25 a. Il s’agit de parcourir les bâtiments d’Ouest en Est en retenant la hauteur maximale des bâtiments
plus à l’Ouest.
def est_ensoleille(h):
n = len(h)
v = [False]*n
h_max = -1
for i in range(n):
if h[i] > h_max:
v[i] = True
h_max = h[i]
return v
# Test :
print(est_ensoleille([1.5,3,3,5,4,7])==[True, True, False, True, False, True])
b. Il s’agit de parcourir les bâtiments d’Ouest en Est en retenant l’angle maximal auquel s’élève un
bâtiment plus à l’Ouest du point de vue du chihuahua. Il n’est pas nécessaire d’utiliser une mesure
particulière pour cet angle, donc on va se contenter du ratio y/x sans le convertir en radian ou degré,
puisque l’angle est une fonction strictement croissante de ce ratio.
def est_visible(h,d):
n = len(h)
v = [False]*n
angle_max = -1
for i in range(n):
if h[i]/d[i] > angle_max:
# Test :
print(est_visible([1.5,3,3,5,4,7],[2,3.5,5,6,7,8])==[True, True, False, False, False, True])
26 a.
def fusion(t1,t2):
"""
Entree: deux liste t1 et t2 triees.
Sortie: fusionne 2 listes triees
Cela signifie que chaque element
qui apparait x fois dans t1 (0 <= x)
et y fois dans t2
apparait x+y fois dans le resultat.
"""
res = [None]*(len(t1)+len(t2))
i1 = 0
i2 = 0
while i1 < len(t1) and i2 < len(t2):
if t1[i1] <= t2 [i2]:
res[i1+i2] = t1[i1]
i1 += 1
else:
res[i1+i2] = t2[i2]
i2 +=1
while i1 < len(t1):
res[i1+i2] = t1[i1]
i1 += 1
while i2 < len(t2):
res[i1+i2] = t2[i2]
i2 += 1
return res
b. La complexité est linéaire car chaque itération d’une des boucles consomme un élément d’une des
deux listes, donc il y a au plus len(t1)+len(t2) itérations. Et chaque itération fait un nombre constant
d’opérations.
27 L’idée est d’identifier i, puis de fusionner la liste [ t[i], t[i+1]…] avec la liste [ t[i-1], t[i-2]… ].
def find_min(t):
"""
Entree: un tableau t.
Suppose len(t)>0
Renvoie l'indice du minimum du tableau, le premier s'il y en a plusieurs
"""
i=0
i_min = 0
while i < len(t):
if t[i] < t[i_min]:
i_min = i
i += 1
return i_min
def trie_v(t):
"""
Entree: un tableau t constitué d'une sequence decroissante suivie d'une sequence croissante.
Trie t
"""
# Test :
print(find_min([3,1,9,-2])==3)
print(trie_v([5,3,2,6,8])==[2, 3, 5, 6, 8])
Noter que si on s’autorisait à adapter la fonction fusion pour qu’elle prenne les éléments des deux
listes en entrée par ordre décroissant, alors on pourrait construire les tableaux t1 et t2 directement sans
calculer le minimum, en parcourant t simultanément depuis le début et la fin.
28
s1 = 'nacre'
s2 = 'carne'
sorted(s1)==sorted(s2)
29 a. Cet usage rentre dans le cadre de ce qu’on a appelé dans le cours une représentation « canonique
» de ces ensembles. L’exercice se rapproche donc de l’exercice 8 « Tester si 2 multi-ensembles dont
égaux » qui calcule si deux multi-ensembles sont égaux (l’intersection de multi-ensembles est définie
comme suit : « pour chaque x qui apparait n1 >= 0 fois dans t1 et n2 >= 0 fois dans t2, renvoyer x min(n1,
n2) fois »).
Ici, on demande de traiter les listes commes des ensembles donc il faut ignorer les doublons. Sauf
qu’on indique d’emblée de supposer qu’il n’y a pas de doublons donc il n’y a pas de différence entre
ensembles ou multi-ensembles dans ce cas.
# Test
print(intersecte([1,4,2,7,5],[6,0,9,2,4])==[2,4])
Bien entendu, il existe une version native du type de donnée « ensemble », donc un programmeur
python écrirait plutôt : list(set(t1)&set(t2)) ou list(set(t1).intersection(t2)).
b. Il fallait identifier qu’on peut ici ramener (en informatique on dit « réduire » voir aussi ex 47 «
Borne inférieure linéaire ») le problème de calcul d’égalité à un problème d’intersection. Pour calculer
l’égalité lorsque l’on dispose d’un programme calculant l’intersection :
def egalite_ensembles(t1,t2):
return len(intersection_ensembles(t1,t2)) == len(t1) and len(intersection_ensembles(t1,t2)) == len(t2)
• même le tableau des distance n’est pas croissant: il décroît puis croît.
La solution est la suivante : On sait que le tableau des distances décroît, puis croît. Donc la « pente »
de la distance, elle, ne fait que croître, et on cherche le point où elle vaut 0. De plus on n’a pas besoin
du tableau des distance. Chaque fois que notre recherche « dichotomique » a besoin d’une valeur de
pente, on calculera cette valeur à partir des valeurs correspondantes du tableau t.
On considère que le choix est libre en cas d’ex-aequo.
31 a.
from math import sqrt
def distance_euclidienne(p1, p2):
"""
Entree:
- p1 et p2 deux paires (x,y) de flottants qui representent des points du plan.
Renvoie la distance euclidienne entre p1 et p2
"""
return sqrt((p2[0]-p1[0])**2 + (p2[1]-p1[1])**2)
# Test :
print(abs(distance_euclidienne((2,0), (0,2))-sqrt(8))<=.000001)
print(abs(distance_euclidienne((6,1), (3,5))-5)<=.000001)
b.
def distance_a_zero(p1):
return distance_euclidienne(p1, (0,0))
def tri_distance(t):
""" Entree: un tableau t de points (paire de nombres).
# Test :
print(tri_distance([(2,4),(1.3,3.2),(5,2)])==[(1.3, 3.2), (2, 4), (5, 2)])
c.
def hotes_successifs(p_depart,hotes):
"""
Entree:
- une position de depart
- une liste d'hotes (distincts de p_depart)
Renvoie:
une liste contenant la position de depart suivie des hotes dans l'ordre de visite par le pique-assiette.
En sachant qu'il choisit a chaque fois l'hote le plus proche parmi ceux qu'il n'a pas encore visite
Complexité: quadratique.
"""
deja_visite = {x: False for x in hotes}
liste_visites = [None]*(len(hotes)+1)
liste_visites[0] = p_depart
for i in range(len(hotes)):
# Invariant :
# les i+1 premiers éléments de liste_visite contiennent les éléments déjà visités, dans l'ordre de visite.
# deja_visite[x] == True ssi x a déjà été visité (l'information est aussi présente dans liste_visite mais on utilisera plutôt de
ja_visite, plus efficace)
dist_min = None
position_min = None
for x in hotes:
if not deja_visite[x] and (dist_min is None or distance_euclidienne(liste_visites[i],x) < dist_min):
position_min = x
dist_min = distance_euclidienne(liste_visites[i],x)
# Au moins une des itérations ci-dessus a satisfait la condition et mis à jour position_min.
# Test :
print(hotes_successifs((1,1),[(2,1.5),(2.5,.5),(4,2),(.5,3)])==
[(1,1),(2,1.5),(2.5,0.5),(4,2),(0.5,3)])
d.
from math import pi, sin, cos, asin, sqrt
def distance_au_zero(p):
""" entree: une paire (latitude Nord, longitude Ouest)
sortie: la distance du point au zero des routes de France.
Les coordonnées du zéro des routes de France (sur le parvis de Notre Dame de Paris) sont :
48° 51′ 12,24845″ N, 2° 20′ 55,62563″ E
Remarque: la longitude Ouest est l'inverse de la longitude Est.
"""
return geodistance_deg(p[0],p[1], valeur_en_degre(48,51,12.24845), -valeur_en_degre(2,20,55.62563))
def tri_distance(l):
""" trie une liste de coordonnées par distance croissante à 0"""
return sorted(l, key=distance_au_zero)
# Test:
print(tri_distance([(48.8,-5.3),(58.8,-30.3),(48.8,-2.3)])==[(48.8, -2.3), (48.8, -5.3), (58.8, -30.3)])
# Le tableau de coordonnées:
notre_dame = (valeur_en_degre(48,51,11), -valeur_en_degre(2,20,59))
tour_eiffel = (valeur_en_degre(48,51,30), -valeur_en_degre(2,17,40))
mont_saint_michel = (valeur_en_degre(48,38,10), valeur_en_degre(1,30,41))
madrid = (valeur_en_degre(40,26,00), valeur_en_degre(3,41,0))
saint_petersburg = (valeur_en_degre(59,56,15), -valeur_en_degre(30,18,31))
seattle = (valeur_en_degre(47,36,0), valeur_en_degre(122,20,0))
le_cap = (-valeur_en_degre(33,55,31), -valeur_en_degre(18,25,26))
buenos_aires = (-valeur_en_degre(34,36,29), valeur_en_degre(58,22,13))
canberra = (-valeur_en_degre(35,18,29.86), -valeur_en_degre(149,7,27.8))
# Une table bien conçue, du type de celles que l'on verra au chapitre 7
# stockerait le nom des lieux avec les coordonnées.
# Mais ici le tableau ne contient que les coordonnées (heureusement distinctes).
# On se propose ci-dessous à partir de la liste triée de coordonnées
# obtenue ci-dessus de calculer la liste des noms de lieux.
d = {'notre_dame' : notre_dame,
'tour_eiffel' : tour_eiffel,
'mont_saint_michel' : mont_saint_michel,
'madrid' : madrid,
'saint_petersburg' : saint_petersburg,
'seattle' : seattle,
'le_cap' : le_cap,
'buenos_aires' : buenos_aires,
'canberra' : canberra}
# Inverser le dictionnaire d
# pour obtenir un dictionnaire qui associe à chaque coordonnée la valeur correspondante.
d_inv = {v: k for k, v in d.items() } # Compléter cette compréhension de dictionnaire.
# Test :
print(lieux_tries[0]=='notre_dame' and lieux_tries[2]=='mont_saint_michel')
• Le dictionnaire occupe plus d’espace mémoire car il doit stocker les clés et les valeurs alors
qu’un tableau ne stocke que les valeurs.
• Sur un dictionnaire, on ne peut pas faire efficacement des requêtes plus complexes exploitant
l’ordre entre les d’intervalles : par exemple, renvoyer toutes les valeurs du tableau comprises
entre a et b, ou renvoyer la plus petite valeur supérieure ou égale à b.
33 Il faut d’abord comprendre ce que fait ce code. Il cherche une borne supérieure de la forme 𝑖 = 2𝑘
qui soit plus grande que 𝑥. On sait alors que 𝑥 est inférieur ou égal à 𝑖 et strictement supérieur à sa
valeur à l’itération précédente (𝑖/2). Dans un second temps, la boucle for cherche donc la valeur exacte
de 𝑥 en testant toutes les valeurs entre 𝑖/2 + 1 et 𝑖.
Comme on augmente 𝑘 de 1 à chaque itération du while et quitte la boucle dès que 𝑖 ≥ 𝑛, on prend le
plus petit 𝑘 possible ce qui garantit que i vaut alors au plus 2(𝑛 − 1), et au moins 𝑛.
Ceci fait log 2 (2(𝑛 − 1)) itérations (appels à l’arbitre) de la boucle while, qui est donc assez efficace.
Par contre on fait ensuite entre 1 et n/2 itérations dans la boucle for (c). Donc la complexité totale est
log 2 (2(𝑛 − 1)) + 𝑛/2 est linéaire dans le pire cas, ce qui n’est pas efficace.
Le « en général » fait que le problème est posé de manière informelle. Si c’est uniquement pour
diviser par 2 la complexité, le code ci-dessus semble inutilement compliqué. Mais on peut en fait
l’adapter pour obtenir une complexité logarithmique. Ce sont les itérations dans la boucle for qui sont
trop nombreuses dans le pire cas pour un grand n. Une fois qu’on a trouvé une valeur 𝑖 telle que 𝑖/2 <
𝑥 ≤ 𝑖, on va utiliser la recherche dichotomique entre i/2+1 et i+1, ce qui ramène le nombre total
def rech_naif():
i=1
nb_iter = 0
while arbitre(i) == 'nombre inferieur a n':
i = i*2
nb_iter = nb_iter+1
for i in range(i//2+1,i+1):
nb_iter = nb_iter+1
if arbitre(i) == 'nombre egal a n':
print(f"nombre d'appels a l'arbitre version naive: {nb_iter}")
return i
rech_naif()
rech_amelioree()
34 On va utiliser la bisection.
Bornes et valeur de tolérances sont à estimer « expérimentalement ». On peut s’aider en tracant la
courbe.
from math import sqrt, tanh
co = .6
t=5
g = 9.81
def f(x):
""" fonction utilisée pour la bisection sur le poids """
return v(x,g,co,t)-35
# Test:
print(abs(f(50)+1.13180529)<0.0001 and abs(f(100)-4.7486013)<0.0001)
# Visualiser la courbe de f:
import matplotlib.pyplot as plt
plt.clf()
nb_pas=100
liste_x = [1+2*i for i in range(nb_pas)]
liste_y = [f(h) for h in liste_x]
line0, = plt.plot(liste_x,liste_y, label='f(m)')
plt.grid()
plt.xlabel('m')
axs = plt.axes()
plt.legend(handles=[line0])
bisection(f,1,200,.001)
# ou bien:
#from scipy import optimize
#optimize.bisect(f,1,200)
35
def f(t, x):
""" entrée: tableau t trié par ordre croissant, valeur x
résultat: paire (lettre,indice) comme défini dans l'énoncé."""
a=0
b = len(t)-1
while a <= b:
m = (a + b) // 2
if t[m] == x:
return 's',m
elif t[m] < x:
a = m +1
else: # t[m] > x
b = m -1
# Test :
print(f([1,3,4,8],0)==('g', 0))
print(f([1,3,4,8],3)==('s', 1))
print(f([1,3,4,8],3.5)==('s', 2))
print(f([1,3,4,8],9)==('d', 4))
36 Voir le cours.
37 a. Commencons par considérer le problème inverse: si on souhaitait que les valeurs absolues
forment une suite décroissante, alors une solution serait de parcourir le tableau alternativement depuis
ses 2 extrémités en alternant entre un petit élément et un grand. Comme l’énoncé demande une suite
croissante, une solution simple est d’inverser la liste ainsi obtenue, par exemple en insérant les
éléments dans le tableau-résultat dans le sens inverse (de la fin vers le début).
def montagne_russe_par_la_fin(t):
""" Entree: tableau t non vide.
Sortie: renvoie les elements de t dans un ordre tel que les
valeur absolue entre 2 valeurs successives forment une suite croissante.
"""
s = sorted(t)
res = [None]*len(t)
def montagne_russe_par_la_fin_bis(t):
""" Entree: tableau t non vide.
Sortie: renvoie les elements de t dans un ordre tel que les
valeur absolue entre 2 valeurs successives forment une suite croissante.
"""
s = sorted(t)
res = [None]*len(t)
i=0
for i in range(len(t)//2):
res[len(t)-1-2*i] = s[i]
res[len(t)-1-2*i-1] = s[len(t)-1-i]
if len(t)%2==1:
res[-1] = s[len(t//2)]
return res
# Test :
b. Pour corser un peu l’exercice, nous voulons maintenant afficher directement la suite croissante, au
fur et à mesure du calcul (sans attendre d’avoir d’abord calculé tout le résultat et sans stocker en
mémoire un tableau supplémentaire en dehors de s); on s’autorise uniquement à calculer s=sorted(t) puis
doit ensuite énumérer en temps constant chaque nouvel élément du résultat. Ceci interdit donc de
produire le résultat dans le sens inverse comme ci-dessus.
L’algorithme est alors de prendre le chiffre du milieu dans le tableau d’entrée (arrondi à droite), puis
son voisin de droite, puis son voisin de gauche, etc. Pour que le code soit simple, le plus simple est
d’afficher à la fois la prochaine valeur à droite et la prochaine à gauche à chaque itération. On
distingue au départ le cas où la taille du tableau est paire du cas où elle est impaire, de façon qu’il reste
ensuite un nombre d’éléments pairs à afficher (ce qui simplifie la condition d’arrêt à la fin). On affiche
ainsi avant la boucle un unique élément si la taille du tableau est impaire, et deux si elle est paire.
def montagne_russe_affiche_par_le_debut(t):
""" Entree: tableau t non vide.
Sortie: afficher les elements de t dans un ordre tel que les
valeur absolue entre 2 valeurs successives forment une suite croissante.
"""
s = sorted(t)
n=1
i_min = len(s)//2
i_max = len(s)//2
print(s[i_max])
if len(s)%2 == 0:
i_min = len(s)//2-1
# Test :
montagne_russe_affiche_par_le_debut([2,1,5,3,8,6]) # affiche dans l'ordre: 5,3,6,2,8,1
38
m=[[1,5],[3,0]]
On pourrait motiver le problème de manière similaire en triant des points en fonction de leur
enneigement. Par contre le modèle serait inadapté pour une inondation d’origine maritime puisque une
chaîne de « collines » peut protéger une zone plus basse, le problème deviendrait alors un parcours de
graphe qui sera étudié au programme de terminale.
39 a.
• Cas où la première tentative (V1, E1) est un match: si (V2!=E2) alors renvoyer (V1,E1) (V2,E3)
(V3,E2).
# Test :
print(argmin([3,1,5,2,0,8])==4)
b.
def f(p):
a, b = p
return (b,a)
# Test :
print(f((3,1))==(1,3))
c. min([(2,3),(8,5),(4,1),(9,2)],key=f) renvoie: (4,1) puisque lorsque l’on applique f à tous les éléments du
tableau, le plus petit est (4,1) car (1,4)<(2,9)<(3,2)<(5,8).
"""
Noter que
`sorted(enumerate(t),key=inverse_paire)[0][0]`
n'est pas aussi satisfaisant car on trierait tout le tableau (complexité O(n log n) pour simplement récupérer le min).
"""
# Test :
print(argmin([3,1,5,2,0,8])==4)
41 a. 200 s
b. 20 s
c. L’algorithme habituel pour calculer la moyenne étant linéaire, la réponse est la même que pour la
question précédente : 20 s.
42 Les trois affirmations sont correctes. Ajouter des comparaison peut aboutir à placer 20 au centre
alors qu’il ne l’était pas, donc accélérer légèrement les futures recherche de 20 par dichotomie. A
l’inverse ces insertions peuvent décaler 20 vers une position moins favorable et donc ralentir.
D’ailleurs si on ajoute énormément d’éléments au tableau il est évident que cela ralentira un peu la
recherche dichotomique.
Mais tout ajout d’un nombre modéré de valeur aura peu d’impact sur une recherche unique car le
temps nécessaire pour exécuter d’une recherche dichotomique est extrêmement faible.
"""
Ce code est l'implémentation suggérée de la même fonction en C++ (où elle est appelée `lower_bound`).
https://en.cppreference.com/w/cpp/algorithm/lower_bound
Pour alléger le code on en a enlevé quelques éléments (déclaration de types et variables).
"""
"""
lower_bound(first, last, value):
count = std::distance(first, last);
while (count > 0) {
it = first; #it est une reference
step = count / 2; # / en C++ represente ici //
std::advance(it, step); # avance la reference d'un nombre de cases egal a step
if (*it < value) { # *it : "valeur de la case it"
mystere (Python) et lower_bound (C++) renvoient l’indice de la première valeur du tableau qui ne soit
pas strictement inférieur à la valeur cherchée. Cet indice est calculé par dichotomie.
Remarque : le code mystere est extrait de la fonction bisect_left de la librairie Python bisect, que nous
avons légèrement simplifié : https://github.com/python/cpython/blob/main/Lib/bisect.py
b. count = hi-lo
step = (hi-lo)//2 = mid - lo
44 a.
def position_moyenne(x, i_min, i_max, v_min, v_max):
"""
Entrée:
- 2 entiers i_min <= i_max,
- 2 valeurs v_min <= v_max
- valeur x telle que v_min <= x <= v_max
Renvoie:
l'entier i tel que la position de i entre i_min et i_max
correspond à la position de x entre v_min et v_max (cf exemples).
"""
return i_min+round((x-v_min)*(i_max-i_min)//(v_max-v_min))
# Test :
print(position_moyenne(20.0,0,4,0.,40.0)==2)
b.
import random
t = sorted(random.choices(range(1000000), k=10000))
recherche_dichotomique(t,7000) # probablement une dizaine
recherche_interpolation(t,7000) # probablement moins de 5
45
def recherche_dicho2D(f):
"""
resultat: indice (x,y) du tresor.
"""
x_min = 0
x_max = 999
y_min = 0
y_max = 999
while x_max > x_min or y_max > y_min:
x_mid = (x_min + x_max)//2
y_mid = (y_min + y_max)//2
vx,vy = f(x_mid, y_mid)
# Test:
def f(x,y):
x_objectif = 35
y_objectif = 650
vx = 0
vy = 0
if x > x_objectif:
vx = 1
elif x < x_objectif:
vx = -1
if y > y_objectif:
vy = 1
elif y < y_objectif:
vy = -1
return vx, vy
recherche_dicho2D(f)
46
def bisection(f, a, b, eps):
""" Entree: fonction f continue sur [a,b], a<b, eps>0, f(a)*f(b)<0
Renvoie h tel qu'il existe x dans [a,b] avec f(x)=0 et |x-h|<eps """
while True:
mid = (a+b)/2
if f(mid) == 0 or b-a<eps:
return mid
elif f(mid)*f(b)>0: # mid et b de meme signe
b = mid
else: # mid et a de meme signe
a = mid
47 a. Il s’agit de prouver un résultat assez évident sur la complexité en nombre d’operations du calcul
d’extremum sur un tableau non trié. Ce résultat a d’ailleurs été mentionné au chapitre 5 sans le
prouver formellement. Nous donnons ici une preuve détaillée.
Il suffit d’adapter la preuve de la complexité linéaire de la recherche dans un tableau, telle que
présentée dans le cours. Le problème est en apparence différent : on ne cherche pas ici une valeur,
mais plutôt à savoir quelle est la valeur minimale. Mais la preuve est quasiment identique : on va
montrer que si l’algorithme ne parcourt pas tout le tableau, un « adversaire » peut modifier les valeurs
non observées du tableau t pour produire un autre tableau t' sur lequel l’algorithme renvoie un résultat
incorrect.
48 Il s’agit d’appliquer une recherche dichotomique. Au départ l’intervalle des valeurs possible pour n
est $$[0,n_\max=50$$]. Grâce à la simplification permettant de faire une tentative sur une valeur 𝑥 <
0 ou 𝑥 > 50 après la première tentative, chaque nouvelle tentative peut simuler exactement une
comparaison de la recherche dichotomique. Donc on peut en fait se satisfaire de ⌊1 + log 2 (𝑛max ) +
1⌋ = 7 questions pour la version simplifiée du problème avec 𝑛max = 50. Concrètement, si
l’intervalle restant est 𝑙, . . . , 𝑟 et que la tentative précédente est 𝑥 alors la tentative suivante doit porter
sur la valeur 𝑥′ telle que (𝑥 + 𝑥′)/2 = (𝑙 + 𝑟)/2.
Remarque : si on ne s’autorise qu’à faire des recherches sur des entiers entre 0 et 50, il est beaucoup
plus dur de simuler efficacement la recherche dichotomique. On peut certes simuler chaque
comparaison de la recherche dichotomique en utilisant deux tentatives, mais ce n’est pas optimal. Voir
: https://ioi2010.org/competitiontask/day1/hottercolder/index.html
49 a. Il faut commencer par fixer une représentation d’une solution du problème. Pour simplifier le
code un peu, on va dans tout l’exercice supposer toutes les durées différentes et identifier les
voyageurs par leur durée On parlera donc du voyageur ti au lieu de préciser ``le voyageur i de durée
ti''. Cette simplification ne change rien à l’algorithme et évite de manipuler 2 tableaux pour faire se
correspondre durées et personnes (on pourrait adapter le code pour traiter le cas de personne mettant la
même durée pour traverser: l’algorithme est essentiellement le même).
#Test:
durees = [1,4,10]
mvts = [(1,10,'gd'),(1,1,'dg'),(1,4,'gd')]
print(valide(durees,mvts)==True)
durees = [1,4,10,11]
mvts = [(1,10,'gd'),(1,1,'dg'),(1,4,'gd')]
print(valide(durees,mvts)==False) # une personne a été oubliée sur la rive gauche.
durees = [1,5,10]
mvts = [(1,5,'gd'),(10,10,'dg'),(1,10,'gd')]
print(valide(durees,mvts)==False) # 10 ne peut pas revenir de la rive droite alors qu'il est encore à gauche.
def duree_totale(mvts):
"""
Entree:
- mvt est un tableau de triplets (i,j,s) avec
i et j deux personnes/durees et s qui vaut 'dg' ou 'gd'
=========
Renvoie:
la duree totale des mouvements
"""
duree_totale = 0
for t in mvts:
#Test:
durees = [1,2,5,8]
mvts = [(1,8,'gd'),(1,1,'dg'),(1,5,'gd'),(1,1,'dg'),(1,2,'gd')]
print(duree_totale(mvts)==17)
b.
durees = [1,2,5,8]
mvts = [(1,2,'gd'),(2,2,'dg'),(8,5,'gd'),(1,1,'dg'),(1,2,'gd')]
print(duree_totale(mvts)<=15) # = 15 en fait
c.
En résumé, la solution optimale combine 2 intuitions :
• il faut que les personnes les plus lentes traversent 2 par 2 pour gagner du temps ;
• il faut utiliser la personne la plus rapide pour ramener la torche en faisant des aller-retour.
On appellera gauche'' la rive de départ et droite'' la rive à laquelles veulent arriver les voyageurs.
Algorithme :
• on commence par trier les voyageurs par durée croissante. t0 < t1 < t2 < … < t(n-1). On identifie
les voyageurs et les durées (on suppose pour simplifier s’il y a 2 -ou plus- durées identiques peu
importe quel voyageur on prend à chaque fois parmi les 2). On parlera donc du voyageur i de
durée ti ;
• une fois qu’il reste au plus un voyageur lent : Tant qu’il reste plus de 2 voyageurs à gauche: \{ (0
et un voyageur traversent à droite) (0 ramène la torche à gauche) } ;
# Test :
print(solution([1,2,5,8]))
print(duree_totale(solution([1,2,5,8]))==15)
50 a. La vache va devoir parcourir une distance 3𝑑 dans le pire cas. En effet, elle doit se rendre à une
des extrémités. Si elle a choisit le mauvais cheval (exploré le mauvais côté) elle va faire un aller de
longueur 𝑑 avant de réaliser que le choix était incorrect. Il lui reste alors à parcourir une longueur 𝑑
pour revenir à son point de départ puis encore 𝑑 pour arriver au portail.
b.
def rencontre_portail(dist,direction,portail):
"""
Entrée:
- direction : vaut 1 ou -1 : 1 signifie "vers la droite", et -1 "vers la gauche"
- dist : un entier positif représentant une distance
- portail : un entier, positif ou négatif représentant:
- la distance du portail: 'abs(portail)'
- la direction du portail:
à droite si portail >0,sur place si portail ==0, à gauche sinon.
======
Renvoie True si portail est à distance moins de dist dans la direction.
"""
return 0 <= direction*portail <= dist
# Test:
print(rencontre_portail(8,1,8)==True)
print(rencontre_portail(8,-1,8)==False)
print(rencontre_portail(3,1,8)==False)
print(rencontre_portail(12,1,8)==True)
# Test :
print(vache(-1)==[(0,1),(1,0),(0,-1)])
print(vache(-3)==[(0,1),(1,0),(0,-2),(-2,0),(0,4),(4,0),(0,-3)])
print(vache(3)==[(0,1),(1,0),(0,-2),(-2,0),(0,3)])
print(vache(4)==[(0,1),(1,0),(0,-2),(-2,0),(0,4)])
def longueur(mouvements):
# Test :
print(longueur([(0,1),(1,0),(0,-1)])==3)
print(longueur([(0,1),(1,0),(0,-2),(-2,0),(0,3)])==9)
Remarque : il est évident que le code termine puisque la vache parcourt un nombre croissant de
mètres de chaque côté. Pour formaliser un variant de boucle une solution serait distinguer le cas où la
boucle se déplace dans la bonne direction du cas inverse et considérer les paires de boucles.
Montrons même si ce n’était pas demandé que la vache parcourt au plus 9𝑑. La vache effectue des
aller-retours dont la longueur forme une suite géométrique (2𝑖 pour l’aller et 2𝑖 pour le retour), la
distance parcourue totale est de la forme ∑𝑘𝑖=0 2 ∗ 2𝑖 = 2𝑘+1 − 1 + 𝑑 jusqu’à atteindre le portail. Le
``pire cas'' se produit lorsque la vache rate de justesse le portail lors d’une itération : elle va alors
dans le but d’insister sur la représentation par tableau d’une table, imposée par le programme officiel.
Toutefois, comme c’est indiqué dans le cours (dans des Notes), d’une part le résultat des interrogations
ne dépend pas de l’ordre des lignes (sauf le tri bien sûr), et d’autre part il ne dépend pas non plus de
l’algorithme utilisé pour coder l’opérateur (les définitions sont déclaratives). Dans tous les corrigés
électroniques, on va utiliser la version plus abstraite et plus proche des définitions autour des tables :
for ligne in table:
... ligne ...
Nous pensons que fournir dans le livre du prof cette deuxième vision illustre deux choix d’approche
pertinents concernant purement le codage en Python, en fonction des priorités de l’enseignant.e et des
possibilités des élèves au moment d’aborder ce chapitre.
Exercices Newbie
L’exercice 2 crée les représentations des tables de la Bibliothèque, et doit donc être fait avant tout
autre exercice Newbie sur Bibliothèque.
Exercices Initié
Comme dans la section Newbie, presque tous les exercices sur la Bibliothèque doivent disposer des
représentations des tables. Le corrigé de l’exercice 8 propose une manière simple, classique et
élégante de le faire sans dupliquer les codes de création de ces représentations.
Tests : Il faudrait bien sûr tester le code sur plusieurs entrées, c’est-à-dire ici sur plusieurs contenus
des tables concernées. En pratique c’est assez lourd à mettre en œuvre (il faut par exemple soit avoir
plusieurs jeux de contenus disponibles dans plusieurs fichiers pour chaque expression d’interrogation,
soit modifier ces contenus pendant la phase de test) et on a choisi de demander implicitement de tester
sur un seul contenu, celui créé dans l’exercice de création des représentations des tables.
Fonction d’affichage : L’indication dans le cartouche en début de la section Initié de par exemple
boucler sur le résultat de la méthode items() a pour but à l’origine d’insiter sur la représentation en
dictionnaire. Toutefois cela paraît finalement peut-être trop didactique, et dans les corrigés
Prérequis – TEST
1 l['age'], t[0]['age'] ; 2 Vrai ; 3 True, False ; 4 7 <= t[i] <= 9 ; 5 t[ 1 ] == u[ 2 ], t[ 2 ] == u[ 4 ], t[ 3 ] == u[
2 ], t[ 4 ] == u[ 0 ], t[ 4 ] == u[ 3 ]
def creer_les_sejours():
table = []
table.append({'IDS': 179, 'VILLE': 'New York', 'ACTIVITE': 'MOMA'})
table.append({'IDS': 93, 'VILLE': 'Cayeux', 'ACTIVITE': 'kitesurf'})
table.append({'IDS': 288, 'VILLE': 'Rio de Janeiro',
'ACTIVITE': 'jeux vidéo'})
table.append({'IDS': 112, 'VILLE': 'Buenos Aires', 'ACTIVITE': 'tango'})
table.append({'IDS': 56, 'VILLE': 'Cayeux', 'ACTIVITE': 'tango'})
return table
def afficher_table(tableau):
for dict in tableau:
print(dict)
print()
les_clients = creer_les_clients()
print('La représentation Python de la table CLIENT : ')
afficher_table(les_clients)
# Remarque : afficher_table affiche n'importe quel tableau de dictionnaires.
les_sejours = creer_les_sejours()
print('La représentation Python de la table SEJOUR : ')
afficher_table(les_sejours)
Voici la table résultat répondant à la question de VLV pour la ville Cayeux, pour le contenu des tables
ci-dessus dans le présent fichier.
NOM IDS
Rita 93
Dédé 93
Riton 56
v = 'Cayeux'
table_resultat_intermediaire_1 = selection_sur_ville(les_sejours, v)
print('La représentation Python de la table résultat des séjours proposés',
'à Cayeux :')
afficher_table(table_resultat_intermediaire_1)
table_resultat_intermediaire_2 = \
jointure_sur_activite(les_clients, table_resultat_intermediaire_1)
# Remarque : comme cela est commenté dans le Cours, on ne s'intéresse
# pas à l'ordre des lignes.
print("La représentation Python de la table résultat des clients dont",
"l'activité favorite est disponible à Cayeux :")
afficher_table(table_resultat_intermediaire_2)
table_resultat_final = projection_sur_nom_ids(table_resultat_intermediaire_2)
# Remarque : comme cela est commenté dans le Cours, on ne s'intéresse
# pas à l'ordre des lignes.
print('La représentation Python de la table résultat répondant à la',
'question de VLV pour Cayeux :')
QCM (CHECKPOINT)
1 c ; 2 a, b et c ; 3 a, b et c ; 4 c ; 5 c ; 6 d.
Un module de vente de produits pour une entreprise de
commerce
2021-07-23
def importer_produits():
with open('produits.csv') as f:
u = []
for dict in DictReader(f):
dict['idp'] = int(dict['idp'])
dict['prix'] = int(dict['prix'])
def importer_clients():
with open('clients.csv') as f:
u = []
for dict in DictReader(f):
dict['idc'] = int(dict['idc'])
dict['age'] = int(dict['age'])
u.append(dict)
return u
def importer_achats():
with open('achats.csv') as f:
u = []
for dict in DictReader(f):
dict['idp'] = int(dict['idp'])
dict['idc'] = int(dict['idc'])
u.append(dict)
return u
les_produits = importer_produits()
les_clients = importer_clients()
les_achats = importer_achats()
2.
def afficher(tableau, message):
print(message)
for dict in tableau:
print(dict)
print()
dirigeant_interro1(les_achats)
2.
# proj[idp,age](join(achat,client))
def dirigeant_interro2(table1, table2):
# tables achat, client
u1 = []
for ligne1 in table1:
for ligne2 in table2:
if ligne1['idc'] == ligne2['idc']:
u1.append({'idp': ligne1['idp'],
dirigeant_interro2(les_achats, les_clients)
3.
# proj[idp](join(achat,sel[age<18](client)))
def dirigeant_interro3(table1, table2):
# tables achat, client
u1 = []
for ligne in table2:
if ligne['age'] < 18:
u1.append(ligne)
u2 = []
for ligne1 in table1:
for ligne2 in u1:
if ligne1['idc'] == ligne2['idc']:
u2.append({'idp': ligne1['idp'],
'idc': ligne1['idc'],
'expedie': ligne1['expedie'],
'nom': ligne2['nom'],
'age': ligne2['age']})
u3 = []
for ligne in u2:
u3.append({'idp': ligne['idp']})
return u3
4.
# On effectue les jointures dans le bon ordre, ainsi qu'une
# projection au bon endroit pour se débarrasser des noms de
# produit, afin d'éviter la confusion avec les noms de client.
# On obtient le max au moyen du tri.
# proj[nom,prix](join(client,proj[idc,prix](join(achat,produit))))
def dirigeant_interro4(table1, table2, table3):
# tables achat, client, produit
u1 = []
for ligne1 in table1:
for ligne2 in table3:
if ligne1['idp'] == ligne2['idp']:
u1.append({'idp': ligne1['idp'],
'idc': ligne1['idc'],
'expedie': ligne1['expedie'],
'nom': ligne2['nom'],
'prix': ligne2['prix']})
u2 = []
for ligne in u1:
u2.append({'idc': ligne['idc'], 'prix': ligne['prix']})
u3 = []
for ligne1 in table2:
5.
# proj[nom](join(produit,proj[idp](join(achat,sel[nom=n](client)))))
def dirigeant_interro5(table1, table2, table3, n):
# tables achat, client, produit
# n = nom de client, par exemple'Rita'
u0 = []
for ligne in table2:
if ligne['nom'] == n:
u0.append(ligne)
u1 = []
for ligne1 in table1:
for ligne2 in u0:
if ligne1['idc'] == ligne2['idc']:
u1.append({'idp': ligne1['idp'],
'idc': ligne1['idc'],
'expedie': ligne1['expedie'],
'nom': ligne2['nom'],
'age': ligne2['age']})
u2 = []
dirigeant_verif1(les_produits)
2.
dirigeant_verif2(les_produits)
client_interro1(les_clients, 'Rita')
2.
# proj[nom,prix](produit)
def client_interro2(table):
client_interro2(les_produits)
insertion_achat(25, 92)
Application interactive
1.
Entrez un entier :
menu()
2.
Voir le fichier tp_vente_graph.py.dans le dossier TP-ve.
Exercices
Exercices Newbie
1 a.
billets[0]['nom']
b. La réponse est 2.
b.
les_abonnes = []
les_abonnes.append({'IDA': 27, 'NOM': 'Rita', 'AGE': 16, 'NBE': 10})
les_abonnes.append({'IDA': 29, 'NOM': 'Riton', 'AGE': 16, 'NBE': 52})
les_emprunts = []
les_emprunts.append({'IDL': 261, 'IDA': 27, 'ANNEE': 2021})
les_emprunts.append({'IDL': 179, 'IDA': 29, 'ANNEE': 2019})
les_emprunts.append({'IDL': 283, 'IDA': 27, 'ANNEE': 2020})
# On rajoute un emprunt par rapport à l'exemple du cours, afin
# d'avoir le même livre emprunté par deux abonnés dans la table
# résultat de l'exercice 4 :
les_emprunts.append({'IDL': 179, 'IDA': 27, 'ANNEE': 2020})
Note : Dans les exercices suivants qui utilisent la bibliothèque, il est nécessaire de créer au préalable
la représentation de la table LIVRE.
On choisit de coder sans dupliquer le code de création correspondant, en effectuant un import de cette
représentation. C’est légèrement plus complexe d’utiliser un import que de dupliquer le code, mais
c’est plus robuste et plus élégant.
Dans les exercices suivants dans lesquels on aura besoin des représentations des trois tables, on pourra
les importer comme suit :
from exo02 import les_livres, les_abonnes, les_emprunts
3 a.
AUTEUR = Kawabata
b.
sel[AUTEUR=Kawabata](LIVRE)
c.
from exo02 import les_livres
table_resultat = []
for i in range(len(les_livres)):
if les_livres[i]['AUTEUR'] == 'Kawabata':
table_resultat.append(les_livres[i])
4 a.
join(LIVRE,EMPRUNT)
b.
from exo02 import les_livres, les_abonnes, les_emprunts
table_resultat = []
for i in range(len(les_livres)):
for j in range(len(les_emprunts)):
if les_livres[i]['IDL']==les_emprunts[j]['IDL']:
table_resultat.append({
'IDL': les_livres[i]['IDL'],
'TITRE': les_livres[i]['TITRE'],
'AUTEUR': les_livres[i]['AUTEUR'],
'IDA': les_emprunts[j]['IDA'],
'ANNEE': les_emprunts[j]['ANNEE']})
b.
from exo02 import les_livres
t1 = []
for i in range(len(les_livres)):
if les_livres[i]['AUTEUR'] == 'Kawabata':
t1.append(les_livres[i])
table_resultat = []
for i in range(len(t1)):
table_resultat.append({'TITRE': t1[i]['TITRE']})
6 a. C’est le résultat d’une expression d’interrogation qu’on veut trier. On commence donc par écrire
cette expression d’interrogation. Dans cette première question on n’inclut pas le tri qui n’est pas un
opérateur.
proj[AUTEUR,TITRE](LIVRE)
b.
from exo02 import les_livres
u = []
for dict in les_livres:
u.append({'AUTEUR': dict['AUTEUR'], 'TITRE': dict['TITRE']})
u.sort(key=cle_de_tri)
# Bien sûr on affiche ensuite cette table, ne serait-ce que pour vérifier :
Exercices Initié
L’approche du code Python sans décomposition en tables résultats intermédiaires sera abordée en
détail dans l’exercice 15 (Calcul au vol de composition dans la Bibliotèque).
table_resultat = []
for i in range(len(les_emprunts)):
if les_emprunts[i]['IDA'] == 27:
table_resultat.append(les_emprunts[i])
afficher(table_resultat,
"Représentation Python de la table réponse à la question : \
Donner les emprunts de l'abonné d'identifiant 27 :")
table_resultat = []
for i in range(len(les_abonnes)):
if les_abonnes[i]['AGE'] > 15:
table_resultat.append(les_abonnes[i])
afficher(table_resultat,
b.
from csv import DictReader
def importer_livres():
with open('livres.csv') as f:
u = []
for dict in DictReader(f):
dict['IDL'] = int(dict['IDL'])
u.append(dict)
return u
les_livres = importer_livres()
c.
from affichage import afficher
afficher(les_livres,
'Représentation Python de la table LIVRES :')
# Les représentations Python les_livres et CSV livres.csv sont
# bien deux représentations du même contenu de la table LIVRE.
d.
Les fichiers abonnes.csv et emprunts.csv sont fournis ci-joint. Comme pour livres.csv, on a en fait
ajouté quelques lignes en plus de l’exemple du Cours, afin que les réponses des interrogations des
exercices ne soient pas vides.
def importer_abonnes():
with open('abonnes.csv') as f:
u = []
for dict in DictReader(f):
dict['IDA'] = int(dict['IDA'])
def importer_emprunts():
with open('emprunts.csv') as f:
u = []
for dict in DictReader(f):
dict['IDL'] = int(dict['IDL'])
dict['IDA'] = int(dict['IDA'])
dict['ANNEE'] = int(dict['ANNEE'])
u.append(dict)
return u
les_abonnes = importer_abonnes()
les_emprunts = importer_emprunts()
afficher(les_abonnes,
'Représentation Python de la table ABONNE :')
afficher(les_emprunts,
'Représentation Python de la table EMPRUNT :')
b. C’est impossible (c’est à dire qu’on peut prouver qu’il n’existe aucune expression d’interrogation
calculant la table réponse comme demandé, mais une telle preuve est hors programme). Pour répondre
il faudrait une jointure et une projection.
En revanche on peut répondre à la question « Donner les identifiants des livres ayant été empruntés » :
proj[IDL](EMPRUNT)
c.
from affichage import afficher
table_reponse = exo10a(les_livres)
afficher(table_reponse,
'Représentation Python de la table des titres des livres')
def exo10b(table):
# table doit posséder la colonne IDL
# Le paramètre est en fait la représentation d'une table
u = []
for ligne in table:
u.append({'IDL': ligne['IDL']})
return u
table_reponse = exo10b(les_emprunts)
afficher(table_reponse,
'Représentation Python de la table des identifiants des livres \
empruntés')
b.
from affichage import afficher
t = exo11(les_livres)
# Remarque : Pour pouvoir afficher le "\" on utilise une chaîne brute
# (raw string), indiquée par le "r" avant l'apostrophe d'ouverture :
afficher(t, r'Représentation Python de la table ren[AUTEUR\NOM](LIVRE) :')
12 a. Cet exercice porte sur le matériel de la section 2.1 du cours, comme les exercices précédents 3 et
8.
La réponse est :
sel[((AUTEUR=Kawabata) et non(TITRE=Nuée d'oiseaux blancs))
ou ((AUTEUR=Asimov) et (TITRE=Fondation))](LIVRE)
b.
# Calcul de la représentation Python de la valeur de
# l'interrogation a. (on dira parfois aussi "calcul de a.",
# en identifiant ainsi l'expression d'interrogation elle-même
# et sa valeur, comme par exemple en arithmétique) :
def exo12(table):
# Le paramètre est en fait la représentation d'une table,
# et la variable ligne la représentation d'une ligne, c'est à
# dire un dictionnaire, comme dans les corrigés des exercices
# précédents.
u = []
for ligne in table:
if ligne['AUTEUR'] == 'Kawabata' \
and not ligne['TITRE'] == "Nuée d'oiseaux blancs" \
or \
ligne['AUTEUR'] == 'Asimov' and ligne['TITRE'] == 'Fondation':
# On a choisi de ne pas utiliser les parenthèse pour aller
# vers la maîtrise des priorités des opérateurs Python.
# Cela est un peu au détriment de la lisibilité dans ce cas.
u.append(ligne)
return u
t = exo12(les_livres)
afficher(t, "Représentation Python de la table : \
sel[((AUTEUR=Kawabata) et non(TITRE=Nuée d'oiseaux blancs)) \
ou ((AUTEUR=Asimov) et (TITRE=Fondation))](LIVRE) :")
13 a. Cet exercice porte sur le matériel de la section 2.1 du cours, comme les exercices précédents 3,
8, 12.
La réponse est
sel[(IDA=27 ou IDA=29) et non(200<=IDL et IDL<=300)](EMPRUNT)
b.
from affichage import afficher
def exo13(table):
# Le paramètre est en fait la représentation d'une table (tableau)
# et la variable ligne la représentation d'une ligne (dictionnaire).
u = []
for ligne in table:
if (ligne['IDA'] == 27 or ligne['IDA'] == 29) \
and not(200 <= ligne['IDL'] and ligne['IDL'] <= 300):
u.append(ligne)
return u
t = exo13(les_emprunts)
afficher(t, "Représentation Python de la table : \
sel[(IDA=27 ou IDA=29) et non(200<=IDL et IDL<=300)](EMPRUNT) :")
14 a. Cet exercice porte sur les sections 2.2, 2.3, 2.5 du cours, comme l’exercice précédent 5.
La réponse est e = proj[TITRE,ANNEE](join(LIVRE,EMPRUNT)).
b.
# La sous-expression la plus interne de l'expression
# d'interrogation e est : e1 = join(LIVRE,EMPRUNT).
# L'expression englobante est : e2 = e = proj[TITRE,ANNEE](e1).
t = exo14(les_livres,les_emprunts)
afficher(t, "Représentation Python de la table : \
proj[TITRE,ANNEE](join(LIVRE,EMPRUNT)) :")
15 a. Cet exercice porte sur les sections 2.1, 2.2, 2.3, 2.5 du cours, comme les exercices précédents 5,
14.
La question est « Donner les titres des livres de Kawabata » et l’expression d’interrogation est
proj[TITRE](sel[AUTEUR=Kawabata](LIVRE)).
t = exo15(les_livres,les_emprunts)
afficher(t, "Représentation Python de la table : \
proj[TITRE,ANNEE](join(LIVRE,EMPRUNT)) :")
16 a. Cet exercice porte sur les sections 2.2, 2.3, 2.4, 2.5 du Cours, comme en particulier les exercices
11, 14, 15.
La réponse est :
e = proj[IDA](join(ren[AUTEUR\NOM](LIVRE),ABONNE))
t = exo16(les_livres,les_abonnes)
# Rappel : on utilise une chaîne brute comme dans l'exercice 11.
afficher(t, r'Représentation Python de la table : \
proj[IDA](join(ren[AUTEUR\NOM](LIVRE),ABONNE)) :')
Remarque : Un auteur abonné apparaîtra autant de fois qu’il/elle a écrit de livres. On s’intéressera à
l’élimination des doublons dans les exercices 24 et 37, mais pas ici. L’objectif dans les exercices sur
les expressions d’interrogation n’est pas de concevoir et coder des algorithmes sophistiqués, mais en
priorité de maîtriser les opérateurs sur les tables. (Toutefois certains exercices consistent à concevoir
t = exo17(les_livres,les_emprunts)
afficher(t, 'Représentation Python de la table : \
proj[IDA](join(sel[AUTEUR=Kawabata](LIVRE),EMPRUNT)) :')
On choisit d’illustrer le calcul au vol. Mais une matérialisation de tout ou partie des résultats
intermédiaires serait possible aussi bien sûr.
from affichage import afficher
from exo09 import les_livres, les_emprunts
t = exo18(les_livres,les_emprunts)
afficher(t, 'Représentation Python de la table : \
proj[IDA](join(sel[AUTEUR=Kawabata](LIVRE),EMPRUNT)) :')
Rappel : il peut y avoir plusieurs expressions ayant pour valeur la table réponse à une question en
français.
On choisit d’illustrer le calcul au vol. Mais une matérialisation de tout ou partie des résultats
intermédiaires serait possible aussi bien sûr.
Rappel : il peut y avoir plusieurs expressions ayant pour valeur la table réponse à une question en
français.
On choisit d’illustrer le calcul au vol. Mais une matérialisation de tout ou partie des résultats
intermédiaires serait possible aussi bien sûr.
21
proj[NOM,TITRE](sel[NOM=AUTEUR](join(
proj[NOM,IDL](join(sel[AGE<18 et NBE=500](ABONNE),
sel[ANNEE=2021](EMPRUNT))),LIVRE)))
b. On peut maintenant recopier les expressions solutions des questions !!a. des exercices 18 à 21,
remplacer chaque occurence d’opérateur par la fonction ci-dessus correspondante sans réfléchir, et
vérifier le résultat.
# exercice 18 :
# proj[IDA](join(sel[AUTEUR=Kawabata](LIVRE),sel[ANNEE>=2020](EMPRUNT)))
# exercice 20 :
# proj[TITRE](
# join(join(sel[AUTEUR=Kawabata](LIVRE),
# sel[ANNEE>=2020](EMPRUNT)),
# sel[NOM=Rita](ABONNE)))
# exercice 21 :
# proj[NOM,TITRE](sel[NOM=AUTEUR](join(
# proj[NOM,IDL](join(sel[AGE<18 et NBE=500](ABONNE),
# sel[ANNEE=2021](EMPRUNT))),LIVRE)))
def test_coherence_abonne(les_abonnes):
for i in range(len(les_abonnes)):
if not(0 < les_abonnes[i]['NBE'] and
les_abonnes[i]['NBE'] < 1000):
return False
return True
print(test_coherence_abonne(les_abonnes))
def condition_annee(l):
return 0 < l['ANNEE'] and l['ANNEE'] < 2022
def test_coherence_emprunt(les_emprunts):
for i in range(len(les_emprunts)):
if not condition_annee(les_emprunts[i]):
return False
return True
print(test_coherence_emprunt(les_emprunts))
b. Cet algorithme correspond à améliorer légèrement celui du cours en modifiant for j in range(len(t)) en
for j in range(i).
c.
from affichage import afficher
from random import *
def creer_doublons(s):
u = []
for i in range(20):
u.append(s[randint(0,len(s)-1)])
return u
s=[
{'a': 1, 'b': 2},
{'a': 2, 'b': 4},
{'a': 3, 'b': 1}
]
25 Cet exercice porte sur les sections 3.1 et 3.2 du cours. Un exercice similaire figure dans le TP.
Par rapport à l’algorithme du cours,,au lieu d’énumérer tous les couples i,j, on va énumérer tous les
couples i,j où I > j. On divise donc le nombre de couples par un peu plus que 2. Noter qu’on retire et
donc gagne aussi le test i != j qui devient toujours vrai.
Remarquer une subtilité de la complexité : si un doublon est aux deux bouts de la table, avec
l’algorithme du cours on le trouve au premier tour de la boucle externe, alors qu’avec celui-ci on le
trouve au dernier tour. On vient de diminuer le nombre de couples à considérer, on n’a pas prouvé que
cet algorithme est meilleur. C’est ce type de question qui est abordé dans le Chapitre 6.
L’algorithme du cours pour comparer :
for i in range(len(table)):
for j in range(len(table)):
if table[i]['IDA'] == table[j]['IDA'] and i != j:
return False
return True
def exo25a(table):
for i in range(len(table)):
for j in range(i): # au lieu de : for j in range(len(table))
valeur = exo25a(les_abonnes)
print('Le test de clé primaire sur IDA de la table ABONNE est : ', valeur)
# On ajoute des doublons et on reteste.
les_abonnes.append({'IDA': 33, 'NOM': 'Julie', 'AGE': 16, 'NBE': 10})
les_abonnes.append({'IDA': 29, 'NOM': 'June', 'AGE': 16, 'NBE': 10})
les_abonnes.append({'IDA': 33, 'NOM': 'Judith', 'AGE': 16, 'NBE': 10})
afficher(les_abonnes, 'Les abonnés, dont un triplon et un doublon dans IDA :')
valeur = exo25a(les_abonnes)
print('Le test de clé primaire sur IDA de la table ABONNE est : ', valeur)
b. Vocabulaire : pour préciser rigoureusement la définition de l’énoncé, une clé étrangère demande
que la colonne A de T soit une clé primaire. C’est bien le cas dans notre exercice. Sinon on parle
simplement de contrainte d’inclusion entre colonnes.
Dans le code suivant on chaoisit la deuxième méthode proposée dans l’énoncé.
# On choisit la deuxième approche.
def exo25b(table1, table2):
# paramètres : représentations de EMPRUNT, ABONNE
u = []
for ligne1 in table1:
present = False
for ligne2 in table2:
if ligne1['IDA'] == ligne2['IDA']:
present = True
break # sortir de la boucle "for ligne2"
if not present:
return False
return True
def exercice26a(t):
for i in range(len(t)-1):
j = i+1
while j > 0 and t[j]['NOM'] < t[j-1]['NOM']:
t[j-1], t[j] = t[j], t[j-1]
j = j-1
exercice26a(les_abonnes)
afficher(les_abonnes, '')
b.
def cle_exercice26b(l):
return l['NOM']
les_abonnes.sort(key=cle_exercice26b)
afficher(les_abonnes, '')
def cle_exercice27b(ligne):
return ligne['NBE']
les_abonnes.sort(key=cle_exercice27b, reverse=True)
afficher(les_abonnes,'Représentation de la table ABONNE après tri par sort :')
28 a.
from affichage import afficher
from csv import DictReader
def importer_clients():
with open('clients.csv') as f:
u = []
for dict in DictReader(f):
dict['idc'] = int(dict['idc'])
def importer_sejours():
with open('sejours.csv') as f:
u = []
for dict in DictReader(f):
dict['ids'] = int(dict['ids'])
u.append(dict)
return u
def importer_reservations():
with open('reservations.csv') as f:
u = []
for dict in DictReader(f):
dict['idp'] = int(dict['idp'])
dict['idv'] = int(dict['idv'])
dict['jour'] = int(dict['jour'])
u.append(dict)
return u
les_clients = importer_clients()
les_sejours = importer_sejours()
les_reservations = importer_reservations()
b.
# sel[non nom=Rita et non nom=Riton or age=18-1 and hobby!=patisserie](client)
def exo28b(table):
# table client
u = []
for ligne in table:
if (not ligne['nom']=='Rita' and not ligne['nom']=='Riton') \
or (ligne['age']==18-1 and ligne['hobby']!='patisserie'):
u.append(ligne)
return u
t = exo28b(les_clients)
afficher(t, """Représentation Python de la table :
sel[(non(nom=Rita) et non(nom=Riton)) or ((age=18-1) and (hobby!=patisserie))](
client) :
""")
c.
# proj[nom](join(client,ren[idp\idc](sel[jour=365](reservation))))
t = exo28c(les_clients, les_reservations)
afficher(t, """Représentation Python de la table :
proj[nom](join(client,ren[idp\idc](sel[jour=365](reservation)))) :
""")
d.
def exo28d(table1, table2):
# tables client, reservation
u = []
for ligne1 in table1:
for ligne2 in table2:
if ligne1['idc'] == ligne2['idp'] \
and ligne1['age'] > 16 \
and ligne1['hobby'] == 'kitesurf':
u.append({'jour': ligne2['jour']})
return u
t = exo28d(les_clients, les_reservations)
afficher(t, """Représentation Python de la table :
proj[jour](join(sel[age>16 et hobby=kitesurf](client),
ren[idp\idc](reservation))) :
""")
e.
t = exo28e(les_sejours, les_reservations)
afficher(t, """Représentation Python de la table :
proj[ville](join(sel[hobby=jeux video](sejour),
ren[idp\idc](sel[jour=33](reservation)))) :
""")
f.
def exo28f(table1, table2, table3):
# tables client, reservation, sejour
u = []
for ligne1 in table1:
for ligne2 in table2:
for ligne3 in table3:
if ligne1['idc'] == ligne2['idp'] \
and ligne2['idv'] == ligne3['ids'] \
and ligne1['hobby'] == ligne3['activite'] \
and ligne1['nom'] == ligne3['ville'] \
and 60 <= ligne2['jour'] \
and ligne2['jour'] <= 90:
u.append({'nom': ligne1['nom'], 'age': ligne1['age'],
'jour': ligne2['jour']})
return u
g. Remarque : deux des fichiers .csv à télécharger par les élèves présentaient (dans l’une des
versions) une configuration faisant que l’interrogation 28g a pour valeur une table vide :
• Un espace s’est glissé à la fin du hobby de Judith, et l’égalité n’est donc plus vérifiée.
• Le dernier jour de février en année non bissextile est 59, or Judith prend son séjour le jour 60.
Dans les deux cas, vérifier pourquoi l’interrogation est vide est très intéressant, de telles situations
étant monnaie courant en pratique.
Les versions ci-jointes ont été corrigées.
def exo28g(table1, table2, table3):
# tables client, reservation, sejour
u = []
for ligne1 in table1:
for ligne2 in table2:
for ligne3 in table3:
29 a.
inter(
proj[IDA](join(EMPRUNT, sel[TITRE=Kyoto](LIVRE))),
proj[IDA](join(EMPRUNT, sel[TITRE=Poemes](LIVRE))))
Remarque : sel[TITRE=Kyoto et TITRE=Poemes](LIVRE) est toujours vide car pour une ligne donnée la
condition est toujours fausse.
On calcule au vol puis matérialise les opérandes de l’intersection.
from affichage import afficher
from exo09 import les_emprunts, les_livres
t = exercice29a(les_emprunts, les_livres)
afficher(t, '')
b.
def exercice29b(les_emprunts, les_livres):
t1 = []
for i in range(len(les_livres)):
t = exercice29b(les_emprunts, les_livres)
afficher(t, '')
b.
from affichage import afficher
from exo09 import les_abonnes
def cle_exercice30(ligne):
return ligne['NOM']
les_abonnes.sort(key=cle_exercice30)
afficher31(tab_de_tab_livres, """
Représentation Python par tableau de tableaux de la table LIVRE :
""")
b. Si on ne veut pas connaître ou compter à la main la position des colonnes, on récupère cette
position en interrogeant la ligne contenant les noms de colonne. On initialise la table résultat par les
noms de colonne. On utilise une boucle par indice entier, et on commence à 1 en sautant la ligne des
noms de colonne. La lourdeur et le manque d’élégance viennent de l’hétérogénéité entre la ligne
schéma'' et les lignes contenu'', et du fait qu’on accède à une valeur après avoir calculé la position du nom
de sa colonne. Avantage : l’affichage, même simple, ne fait pas figurer sur chaque ligne les noms de
colonne.
La représentation par tableau de dictionnaires demande une fonction d’affichage un peu plus travaillée
que celle présentée dans ce chapitre pour ne pas afficher les noms de colonne, mais le matériel de ce
Manuel de 1ère permettrait de le faire (dans ce chapitre on a choisi de simplifier tout ce qui n’était pas
purement des concepts autour des tables).
t = exo31(tab_de_tab_livres)
afficher31(t, """
Représentation Python par tableau de tableaux de la table :
sel[AUTEUR=Kawabata(LIVRE) :
""")
b.
from csv import DictReader
def importer_livres_tsv():
with open('livres.tsv') as f:
u = []
for dict in DictReader(f, delimiter='\t'):
dict['IDL'] = int(dict['IDL'])
u.append(dict)
return u
les_livres = importer_livres_tsv()
from affichage import afficher
afficher(les_livres,
Exercices Titan
33 Cet exercice porte sur la section 2.5 du cours, les définitions fournies dans l’exercice 29, et fait
suite aux exercices 8 à 21 sur la Bibliothèque.
Pour tous les exercices non triviaux, une astuce peut être de réfléchir avec sous les yeux les trois tables
avec un contenu exemple, en « trouvant » les valeurs demandées dans un premier temps, puis ensuite
seulement en cherchant l’expression d’interrogation demandée (comme on l’a fait dans une question
de l’Activité par exemple).
Cet exercice est assez dur. Il peut être profitable de fournir plus d’indications que l’énoncé de la
version papier du Manuel. De plus, l’ordre de difficulté croissante des questions est probablement : b,
c, a, d.
Considérons d’abord la question plus simple : Donner les identifiants des abonnés ayant emprunté les
livres 179 et 261. Suivons l’indication. Il faut renommer des colonnes de EMPRUNT. Considérons :
e1 = join(EMPRUNT,ren[IDL\IDL2](ren[ANNEE\ANNEE2](EMPRUNT)))
Chaque ligne de EMPRUNT y étant jointe avec une copie de tous les emprunts de cet abonné, alors
chaque abonné y figure avec tous ses couples de livres. Une expression réponse est alors :
sel[IDL=179 et IDL2=261](join(EMPRUNT,
ren[IDL\IDL2](ren[ANNEE\ANNEE2](EMPRUNT))))
t = exo33a(les_livres, les_emprunts)
afficher(t, r'Représentation Python de la table : proj[IDA](join(join(sel[TITRE=Kyoto](LIVRE),join(EMPRUNT,ren[IDL\IDL2](re
n[ANNEE\ANNEE2](EMPRUNT)))),sel[TITRE=Poèmes](LIVRE))) :')
b. Comme dans les questions a. et d. ce sont les identifiants des abonnés qui sont demandés.
Remarque :
proj[IDA](sel[not TITRE=Kyoto](join(EMPRUNT,LIVRE)))
vaut : Les abonnés ayant emprunté au moins un livre qui n’est pas Kyoto (mais ils ont pu aussi
emprunter Kyoto).
Indication : les bons = tous - les méchants. Les bons = tous les abonnés - ceux ayant emprunté Kyoto.
Une expression réponse est alors :
diff(proj[ida](ABONNE),proj[IDA](join(EMPRUNT,sel[TITRE=Kyoto](LIVRE))))
c. Comme dans les questions a. et d. ce sont les identifiants des abonnés qui sont demandés.
Suivons l’indication. Il faut renommer des colonnes de EMPRUNT. Considérons :
join(EMPRUNT,ren[IDL\IDL2](ren[ANNEE\ANNEE2](EMPRUNT)))
Si une ligne contient des valeurs différentes pour IDL et IDL2, alors l’abonné a emprunté deux livres
différents, donc plusieurs. Une expression réponse est alors :
def exo33c(table1):
table2 = table1
u = []
for ligne1 in table1:
for ligne2 in table2:
if ligne1['IDA'] == ligne2['IDA'] \
and ligne1['IDL'] != ligne2['IDL']:
u.append({'IDA': ligne1['IDA']})
return u
t = exo33c(les_emprunts)
afficher(t, r'Représentation Python de la table : proj[IDA](sel[IDL!=IDL2](join(EMPRUNT,ren[IDL\IDL2](ren[ANNEE\ANNEE2](
EMPRUNT))))) :')
# e2 = proj[IDA](diff(e1,proj[IDA,IDL](EMPRUNT))) :
u2 = []
for ligne1 in u1:
present = False
for ligne2 in table2:
if ligne1['IDA'] == ligne2['IDA'] \
and ligne1['IDL'] == ligne2['IDL']:
present = True
if present == False:
u2.append({'IDA': ligne1['IDA']})
# diff(proj[IDA](ABONNE),e2)
u3 = []
for ligne1 in table3:
present = False
for ligne2 in u2:
if ligne1['IDA'] == ligne2['IDA']:
present = True
if present == False:
u3.append({'IDA': ligne1['IDA']})
return u3
34 a. Cet exercice porte sur la section 3 du cours, et généralise les exercices 23 et 25 sur la
Bibliothèque.
Clés primaires : IDA de ABONNE (cf exercice 25), IDL de LIVRE.
Clés étrangères : IDA de EMPRUNT vers ABONNE (cf exercice 25), IDL de EMPRUNT vers LIVRE.
b. Pour chaque abonné dans ABONNE identifié par son IDA, le nombre de lignes avec cet IDA dans
EMPRUNT ayant la même ANNEE doit être inférieur ou égal à son NBE dans ABONNE.
On utilise un algorithme peu efficace mais très simple. Pour chaque abonné et chacun de ses emprunts,
on mémorise l’année, puis on boucle sur tout EMPRUNT pour compter le nombre d’emprunts de cet
abonné cette année.
(Un meilleur algorithme serait de construire un histogramme sur les emprunts en une seule passe sur
EMPRUNT, contenant (IDA, ANNEE, cpt), puis de le parcourir une fois pour vérifier.)
from affichage import afficher
from exo09 import les_livres, les_emprunts, les_abonnes
a.
proj[nom](
join(client,
ren[idp\idc](
inter(proj[idp](join(reservation,
ren[ids\idp](sel[ville=New York](sejour)))),
proj[idp](join(reservation,
ren[ids\idp](sel[ville=Cayeux](sejour)))))))
b.
proj[nom](
join(client,
ren[idp\idc](
On illustre ici la propriété que sur deux tables à une colonne, on prouve facilement que la jointure est
l’intersection, aux doublons près. Cette identification est possible dans la mesure où on a admis qu’on
ne s’intéresserait pas aux doublons dans les valeurs des interrogations (dans l’idée qu’ils seront
éliminés). Les tables obtenues ne sont pas les mêmes au sens de la définition, mais si on ignore les
doublons et l’ordre des lignes alors ce sont les mêmes. Remarquons plus généralement ce qui suit. On
a dit ailleurs dans ce chapitre que plusieurs expressions d’interrogation « peuvent avoir la même
valeur » répondant à une question en français. Cela est vrai aux doublons et à l’ordre des lignes près.
On notera à ce sujet que l’intersection définie dans l’exercice 29 n’est pas commutative. Cette
propriété théorique, intéressante et inattendue mais aucunement gênante, disparaîtra en Terminale
quand une table sera définie, canoniquement, comme un ensemble (et non une liste comme imposé par
le programme officiel de 1ère ; liste qui fait par ailleurs un lien avec la représentation Python d’une
table). Une autre approche a été suivie dans le corrigé de l’exercice 33.
def exo35b(client, reservation, sejour):
# u1 = proj[idp](join(reservation,
# ren[ids\idp](sel[ville=New York](sejour))))
u1 = []
for ligne1 in reservation:
for ligne2 in sejour:
if ligne1['idv'] == ligne2['ids'] \
and ligne2['ville'] == 'New York':
u1.append({'idp': ligne1['idp']})
d. On pousse plus loin l’approche du 33a : c’est sur une table résultat intermédiaire qu’on fait la
jointure avec elle-même.
proj[nom,age](
join(client,
ren[ipd\idc](
sel[activite!=activite2](
join(proj[idp,activite](join(reservation,ren[ids\idv](sejour))),
ren[activite\activite2](
proj[idp,activite](join(reservation,
ren[ids\idv](sejour)))))))))
e. On renvoie à l’exercice 33. On appelle cette opération de calcul dans une table t(a,b) les a liés à tous
les b une division, ou quotient.
proj[nom](
join(client,
diff(proj[idc](client),
proj[idc](
diff(cart(proj[idc](client),proj[ids](sejour)),
ren[idp\idc](
ren[idv\ids](proj[idp,idv](reservation))))))))
# u2 = proj[idc](u1,
# ren[idp\idc](ren[idv\ids](proj[idp,idv](reservation))))
u2 = []
for ligne1 in u1:
present = False
# u3 = diff(proj[idc](client),u2))
u3 = []
for ligne1 in client:
present = False
for ligne2 in u2:
if ligne1['idc'] == ligne2['idc']:
present = True
break
if present == False:
u3.append({'idc': ligne1['idc']})
# u4 = proj[nom](join(client,u3))
u4 = []
for ligne1 in client:
for ligne2 in u3:
if ligne1['idc'] == ligne2['idc']:
u4.append({'nom': ligne1['nom']})
return u4
def cle_de_tri36(dict):
return dict['hobby'], dict['nom']
def exo36(client):
client.sort(key=cle_de_tri36, reverse=True)
return client
t = exo36(les_clients)
afficher(t, """Représentation Python de la table client trié par hobby, nom :
""")
def trier_hobby_nom(t):
def exo37(client):
if client == []:
return []
t = []
for ligne in client:
t.append({'hobby': ligne['hobby'], 'nom': ligne['nom']})
trier_hobby_nom(t)
afficher(t, """Représentation Python de la table proj[hobby,nom](client)
avant élimination des doublons :
""")
u = [t[0]]
dernier_ajout = t[0]
for ligne in t:
if ligne['hobby'] != dernier_ajout['hobby'] \
or ligne['nom'] != dernier_ajout['nom']:
u.append(ligne)
dernier_ajout = ligne
return u
t = exo37(les_clients)
afficher(t, """Représentation Python de la table proj[hobby,nom](client)
sans doublons et triée par hobby décroissant :
""")
def cle_de_tri38(dict):
return dict['IDL']
u = []
i=0
j=0
while i < len(livre) and j < len(emprunt):
while i < len(livre) and j < len(emprunt) \
and livre[i]['IDL'] != emprunt[j]['IDL']:
if livre[i]['IDL'] < emprunt[j]['IDL']:
i = i+1
else:
j = j+1
i_bas = i
j_bas = j
while i < len(livre) and j < len(emprunt) \
t = exo38(les_livres, les_emprunts)
afficher(t, 'Représentation Python de la table join(LIVRE,EMPRUNT) :')
def cle_de_tri39(dict):
return dict['NOM']
def afficher_index(index):
print('Position dans ABONNE du 1er nom de cette initiale pour chaque lettre :')
for cle in list(index):
if index[cle] != -1:
print(cle, ': ',index[cle])
les_abonnes.sort(key=cle_de_tri39)
afficher(les_abonnes, 'Représentation Python de la table ABONNE triée sur NOM :')
• On ne risque pas d’oublier d’appeler les fonctions de test : toutes les fonctions dont les noms
commencent par test_ ou terminent par _test sont appellées automatiquement ;
• pytestdonne un message plus informatif en cas d’erreur que Python. En effet, alors que Python
indique simplement qu’une assertion a échoué (avec le message éventuel que l’on a passé en
paramètre), pytest affiche l’assertion d’abord telle qu’elle apparait dans le code puis en
remplaçant chaque variable par sa valeur, ce qui permet de faciliter l’identification de l’erreur.
On pourra comparer l’utilisation de pytest avec les fichiers du TP, dont est fournie une version pour
pytest et une autre pour Python et Jupyter sans pytest.
TP - jeu de la vie
En plus du notebook Jupyter et du fichier Python contenant les corrigés du TP tel que présentés ci-
dessous, on trouvera dans le dossier TP_life_corrige les fichiers suivants :
• Code destiné aux élèves :
– life_eleve.py : code fourni aux élèves sur le site du manuel pour la partie 2 du TP ;
– : bibliothèque fournie aux élèves sur le site du manuel contenant les fonction
life_ui.py
d’interface ;
– aide_eleve.py : code qui peut être fourni aux élèves pour les aider dans la partie 2.
• Pour Jupyter :
Note : la bibliothèque life_ui.py doit être dans le même dossier que les fichiers Python ou Jupyter qui
l’utilise.
Prérequis – TEST
1 (1) b, (2) a, (3) a ; 2 (1) c, (2) a, (3) b ; 3 b, c et d ; 4 d ; 5 a et b ; 6 b.
equipes = tirer_au_sort(equipes)
gagnant = gerer_tournoi(equipes)
Raffiner la spécification
fonction jouer_match(equipeA : Equipe, equipeB : Equipe) → Equipe
var gagnant : Equipe
Saisir le gagnant entre equipeA et equipeB
retourner gagnant
1.
fonction gerer_tournoi(equipes : Tournoi) → Equipe
pre : le nombre d’équipes est une puissance de 2
def gerer_tour(equipes):
""" Fait jouer les équipes 2 par 2 et retourne les équipes restantes """
assert len(equipes) % 2 == 0 # nombre pair d'équipes
gagnants = []
for i in range(0, len(equipes), 2):
gagnants.append(jouer_match(equipes[i], equipes[i+1]))
assert len(gagnants) == len(equipes) / 2
return gagnants
def gerer_tournoi(equipes):
""" Fait plusieurs tours du tournoi jusqu'à ce qu'il y ait un gagnant """
assert len(equipes) == 16 # 16 équipes
tour = 1
while len(equipes) > 1:
print()
2.
def jouer_match(eq1, eq2):
""" La première équipe dans l'ordre alphabétique gagne """
if eq1 < eq2:
print(f' {eq1} joue contre {eq2} : {eq1} gagne')
return eq1
print(f' {eq1} joue contre {eq2} : {eq2} gagne')
return eq2
test_tournoi(europe, 'Allemagne')
test_tournoi(europe, europe[0])
QCM (CHECKPOINT)
1 e ; 2 b ; 3 a ; 4 b ; 5 d ; 6 d ; 7 c ; 8 b et c.
TP : Le Jeu de la Vie
Spécifier avant de programmer
type Cellules = Tableau[n][n] de Booléen
var cellules: Cellules
var fini: Booléen <- faux
cellules = init(config)
afficher(cellules)
tantque non fini faire
cellules <- generation(cellules)
afficher(cellules)
fin
if etat:
regle = [ False, False, True, True, False, False, False, False, False ]
else:
regle = [ False, False, False, True, False, False, False, False, False ]
return regle[voisins]
Avec pytest, il suffit de mettre le code ci-dessus dans un fichier et de lancer dans le terminal la
commande pytest nom_du_fichier.py. Sinon on appelle explicitement la fonction de test ainsi :
test_nouvel_etat()
def test_8voisins():
""" cas extrême : la cellule au centre a 8 voisins vivants """
mini_vie = [[True, True, True],
[True, True, True],
[True, True, True]]
assert nvoisins(mini_vie, 1, 1) == 8
print("OK")
Comme ci-dessus, avec pytest il n’est pas nécessaire d’appeler explicitement les fonctions de test :
test_4voisins()
test_8voisins()
Le nombre de voisins est incorrect car on a compté la cellule dont on compte les voisins parmi ses
propres voisins. On corrige l’erreur en ajoutant un test après les boucles pour retirer la cellule du
compte si elle est vivante.
def nvoisins(cellules, i, j):
# pré-conditions
assert i > 0 and i < len(cellules) -1
assert j > 0 and j < len(cellules[i]) -1
n=0
for ii in range(i-1, i+2):
for jj in range(j-1, j+2):
if cellules[ii][jj]:
n=n+1
if cellules[i][j]: # <== correction de l'erreur :
n=n-1 # retirer la cellule du compte
# post-condition
assert n >= 0 and n <= 8
test_4voisins()
test_8voisins()
def init(taille):
return [([False] * taille) for i in range(taille)]
def test_generation():
avant = init(10)
# "clignotant" dans la position horizontale sur la troisième ligne
avant[3][2] = True
avant[3][3] = True
avant[3][4] = True
apres = init(10)
# "clignotant" dans la position verticale sur la troisième colonne
apres[2][3] = True
apres[3][3] = True
apres[4][3] = True
test_generation()
Le test produit une erreur car on a oublié que la bande de cellules autour du tableau n’est pas utilisée,
elle est destinée à faciliter le calcul du nombre de voisins. On ajuste donc les bornes des deux boucles
pour ne parcourir que l’intérieur du tableau :
# deuxième version : encore erroné
def generation(cellules):
for i in range(1, len(cellules) -1):
for j in range(1, len(cellules[i]) -1):
nv = nvoisins(cellules, i, j)
cellules[i][j] = nouvel_etat(cellules[i][j], nv)
return cellules
test_generation()
Le test provoque à nouveau une erreur. Cette fois, le problème est qu’on modifie le tableau cellules
qu’on utilise en même temps pour calculer le nombre de voisins.
On utilise donc un nouveau tableau, initialisé à vide, pour calculer la nouvelle version.
# troisième version : c'est la bonne
def generation(cellules):
nouveau = init(len(cellules))
for i in range(1, len(cellules) -1):
for j in range(1, len(cellules[i]) -1):
nv = nvoisins(cellules, i, j)
nouveau[i][j] = nouvel_etat(cellules[i][j], nv)
return nouveau
test_generation()
cellules = init(20)
cellules[3][2] = cellules[3][3] = cellules[3][4] = True # clignotant
cellules[6][10] = cellules[7][9] = cellules[8][9] = cellules[8][10] = cellules[8][11] = True # glisseur
grille = creer_grille(cellules)
def step():
global cellules
cellules = generation(cellules)
afficher(cellules, grille)
start_stop_button(step)
Exercices Newbie
1 a.
fonction div(a: Entier, b: Entier) → Tuple(Entier, Entier)
var q: Entier <- 0
var r: Entier
tantque a > b faire
q <- q + 1
a <- a - b
fin
r <- a
retourner (q, r)
fin
b.
pré: a >= 0 and b > 0
post: q >= 0 and r < b and a == b*q + r
c.
def div(a, b):
assert a >= 0 and b > 0, "Paramètres incorrects"
a0 = a # pour mémoriser la valeur initiale
q=0
while a >= b:
q += 1
a -= b
r=a
assert q >= 0 and r < b and a0 == b*q + r, "Résultat erronné"
d.
# cas interdit
div(10, 0) == (10//1, 10%0)
# cas limite
div(10, 1) == (10//1, 10%1)
# cas limite
div(10, 20) == (10//20, 10%20)
# cas typique
div(10, 3) == (10//3, 10%3)
2 a.
fonction rechercher_tableau(x: , t: Tableau ) → Booléen
pour chaque élément t[i] faire
si t[i] == x alors
retourner vrai
fin
fin
retourner faux
fin
b.
c.
# fonction annexe pour l'invariant de boucle
def tranche(t, min, max):
""" retourne un tableau contenant les éléments de t d'indice min à max -1 """
res = []
for i in range(min, max):
res.append(t[i])
return res
# cas le pire
assert recherche_tableau('d', ['c', 'b', 'a']) == (False, 3)
3 a.
fonction dicho(t : Tableau de Nombre, val : Nombre) → Entier
var gauche : Entier <- 0
var droite : Entier <- indice de fin de t
tantque gauche inférieur ou égal à droite faire
milieu <- milieu de l’intervalle [gauche, droite]
si t[milieu] == val
alors retourner milieu fin
si t[milieu] < val
alors gauche <- milieu + 1 fin
sinon droite <- milieu - 1 fin
fin
retourner -1
fin
b.
pré-condition: t est un tableau trié
assertion: (droite - gauche) diminue
post-condition: (ligne 7) val dans t et t[milieu] == val
post-condition: (ligne 12) val n’est pas dans t
c.
def test_dicho_meilleur():
""" Teste le cas le meilleur : val au milieu de t """
assert dicho([1, 2, 3, 5, 7], 3) == (2, 1)
test_dicho_meilleur()
test_dicho_pire()
4 a.
Type Couleur = Chaine("Pique", "Cœur", "Carreau", "Trèfle")
Type Carte = Tuple(Nombre[2..14], Couleur)
Type Paquet = Tableau de Carte
def test_2():
# une bataille
cartes = [(2, "Pique"), (3, "Trèfle"), (4, "Cœur"),
(10, "Carreau"), (12, "Cœur"), (12, "Pique")]
assert jouer_bataille(cartes) == (0, 3)
def test_3():
# une bataille à la fin du jeu
cartes = [(3, "Pique"), (3, "Trèfle"), (10, "Cœur"),
d.
def carte_gagnante(c1, c2):
v1, _ = c1
v2, _ = c2
if v1 == v2:
return 0
if v1 < v2:
return -1
return 1
def jouer_bataille(cartes):
score1 = 0
score2 = 0
gain = 1
while len(cartes) > 1:
c1 = cartes.pop()
c2 = cartes.pop()
r = carte_gagnante(c1, c2)
if r == 0:
print(c1, c2, ': bataille !')
gain += 1
else:
if r == -1:
score1 += gain
print(c1, c2, ': joueur 1 gagne', gain, 'point(s)')
else:
test_1()
test_2()
test_3()
5 a.
from math import gcd
b.
pgcd(10, -1)
# (le programme boucle, il faut l'interrompre avec le bouton Stop)
# même chose si l'un des arguments est nul
c.
pré: a > 0 and b > 0
d.
fonction pgcd(a: Nombre, b: Nombre) → Nombre
si a ou b est nul alors retourner l’autre fin
si a et/ou b est négatif alors changer son signe fin
tanque a != b faire
retirer du plus grand de a et b la valeur de l’autre
if a < 0:
a = -a
if b < 0:
b = -b
while a != b:
if a < b:
b=b-a
else:
a=a-b
return a
def test_tri_trie():
""" tri d'un tableau trié """
assert trier(t_trie.copy()) == t_trie
def test_tri_inverse():
""" tri d'un tableau en ordre inverse """
assert trier([9-i for i in range(10)]) == t_trie
try:
test_tri_inverse()
except:
print('test_tri_inverse a échoué')
try:
test_tri_aleatoire()
except:
print('test_tri_aleatoire a échoué')
On voit que dans le cas du tableau trié il échange les deux premiers éléments qui sont pourtant déjà
triés. L’erreur n’est donc pas sur range(n) mais sur l’initialisation j = i+1 qui commence la recherche du
minimum sur le reste du tableau, alors qu’il faut le rechercher sur la fin du tableau y compris l’indice i.
Il faut donc remplacer j = i + 1 par j = i.
Du coup, on peut ajouter un test avant l’échange entre t[i] et t[j], puisqu’il est inutile d’échanger un
élément avec lui-même. (Mais le programme fonctionne sans cet ajout).
def trier(t):
n = len(t)
for i in range(n):
j = i # Commencer la recherche de minimum à partir de l'indice i
min = t[j]
for k in range(i+1, n):
if t[k] < min:
j=k
7
from PIL import Image
image = Image.open('poisson.jpg')
image.show() # afficher l'image
l, h = image.size # obtenir sa taille (largeur, hauteur)
image = image.resize((l // 2, h // 2)) # réduire taille
image = image.rotate(90) # rotation 90º
image = image.transpose(Image.FLIP_LEFT_RIGHT) # miroir
image.show() # afficher résultat
Exercices Initié
8 a.
fonction interets(s : Flottant, t : Flottant, n : Entier) -> Flottant
somme <- 0
pour n annees faire
somme <- somme + somme * t
fin
b.
def test(s, t, n):
S = interets(s, t, n)
T = s * (1+t) ** n
assert S == T, f'Erreur: {S} au lieu de {T}'
c.
def interets(s, t, n):
S=s
for i in range(n):
S += S*t
print(S)
return S
d.
def interets2(s, t, n):
S=s
for i in range(n):
S += round(S*t, 2) # arrondir le montant des intérêts
print(S)
return S
Si l’on fait l’arrondi des intérêts de chaque année comme ci-dessus, on cumule une erreur d’une année
sur l’autre. Une solution plus correcte (mais qui n’est pas celle qu’utilisent en général les banques) est
de faire l’arrondi sur le total :
def interets3(s, t, n):
S=s
for i in range(n):
S += S*t
print(round(S, 2))
return round(S, 2) # arrondir le résultat final
9 a.
type Jeu = tableau de Entier
Note : On peut observer que la coupe est identique au battage avec 2 sous-paquets.
b.
def test_battage():
""" Teste que deux battages = ordre initial """
assert battage(battage(paquet, 4), 4) == paquet
def test_americain():
""" Teste que paires + impaires = ordre initial """
battu = americain(paquet)
ordonne = [battu[i] for i in range(0, 52, 2)] + \
[battu[i] for i in range(1, 52, 2)]
assert ordonne == paquet
c.
def coupe(paquet):
""" Coupe le paquet en 2 """
return battage(paquet, 2)
test_coupe()
test_battage()
test_americain()
d.
fonction cycle_americain(paquet : Jeu) → Nombre
battu <- americain(paquet)
tantque battu != paquet faire
battu <- americain(battu)
fin
retourner le nombre de battages
fin
def cycle_americain(paquet):
""" Calcule le cycle du battage americain """
battu = americain(paquet)
cycle = 1
while battu != paquet:
10 a.
type Tab = Tableau de Nombre
fonction tri_fusion(t1 : Tab, t2 : Tab) → Tab
var t : Tab
t <- []
tantque il reste des éléments dans t1 ou t2 faire
si il n’y a plus d’éléments dans t1 alors
ajouter l’élément de t2 à t
sinon si il n’y a plus d’éléments dans t2 alors
ajouter l’élément de t1 à t
sinon si l’élément de t1 est plus petit que celui de t2 alors
ajouter l’élément de t1 à t
sinon
ajouter l’élément de t2 à t
fin
retourner t
fin
b.
On raffine la spécification pour pouvoir exprimer les variants/invariants de boucle :
fonction tri_fusion(t1 : tableau[n1] de Nombre,
t2 : tableau[n2] de Nombre)
→ tableau[n1+n2] de Nombre
var t : Tab
t <- []
i1 <- 0
i2 <- 0
tantque i1 < n1 ou i2 < n2 faire
i1prec <- i1 # pour tester le variant de boucle
i2prec <- i2 # pour tester le variant de boucle
si i1 == n1 alors
ajouter t2[i2] à t
i2 <- i2 + 1
sinon si i2 == n2 alors
ajouter t1[i1] à t
i1 <- i1 + 1
sinon si t1[n1] < t2[n2] alors
ajouter t1[i1] à t
i1 <- i1 + 1
sinon
ajouter t2[i2] à t
i2 <- i2 + 1
fin
# variant de boucle
assertion: i1 > i1prec ou i2 > i2prec
# invariant de boucle
assertion: t est trié et a i1+i2 éléments
fin
assertion: i1 == n1 et i2 == n2
retourner t
fin
t = []
i1 = 0
i2 = 0
if i1 == len(t1):
t.append(t2[i2])
i2 += 1
elif i2 == len(t2):
t.append(t1[i1])
i1 += 1
elif t1[i1] < t2[i2]:
t.append(t1[i1])
i1 += 1
assert est_trie(t)
return t
# t1 = [1..10], t2 = [1..10]
t10 = [i+1 for i in range(10)] # nombres de 1 à 10
t = [1+round(i/2-0.1) for i in range(0, 20)]
test_trifusion(t10, t10.copy(), t)
# t1 = [1..10], t2 = []
test_trifusion(t10, [], t10)
# t1 = [], t2 = [1..10]
test_trifusion([], t10, t10)
BONUS : Dans cette version, on réalise la fusion seulement tant qu’il y a des éléments à comparer
dans les deux tableaux (le test du premier tantque est un et ou lieu d’un ou). On copie ensuite la fin du
tableau le plus long. Cette version est un peu plus simple et économise les tests de la boucle principale
pour savoir si l’on est à la fin de l’un ou l’autre des tableaux.
fonction tri_fusion(t1 : tableau[n1] de Nombre,
t2 : tableau[n2] de Nombre)
→ tableau[n1+n2] de Nombre
var t : Tab
t <- []
i1 <- 0
i2 <- 0
tantque i1 < n1 et i2 < n2 faire
i1prec <- i1 # pour tester le variant de boucle
i2prec <- i2 # pour tester le variant de boucle
# variant de boucle
assertion: i1 > i1prec ou i2 > i2prec
# invariant de boucle
assertion: t est trié et a i1+i2 éléments
fin
# Cette assertion montre qu’un seule des deux boucles suivantes est exécutée
assertion: i1 == n1 ou i2 == n2
assertion: i1 == n1 et i2 == n2
retourner t
fin
t = []
i1 = 0
i2 = 0
11
def test_f():
""" teste que f lève bien une erreur pour x < 0 """
try:
f(-1) # OK si erreur
except AssertionError:
return
assert False, "f devrait provoquer une erreur"
test_f()
12 a.
fonction Eratosthene(n: Entier) → Tableau de Booléen
var premiers : Tableau[n] de Booléen <- [vrai] * n
pour i dans [2, n // 2] faire
pour les multiples m de i inférieurs à n faire
premiers[m] <- faux
fin
fin
retourner premiers
fin
c.
def Eratosthene(n):
premiers = [True] * n
for i in range(2, n):
for j in range(i*2, n, i):
premiers[j] = False
return premiers
def test_Eratosthene(n):
premiers = Eratosthene(n)
for i in range(n):
if premiers[i]:
print(i)
assert premiers[i] == est_premier(i)
test_Eratosthene(100)
d.
def Eratosthene(n):
premiers = [True] * n
for i in range(2, floor(sqrt(n))):
for j in range(i*2, n, i):
premiers[j] = False
return premiers
test_Eratosthene(100)
13 a.
def recherche_lineaire(x, t):
""" Retourne le nombre de comparaisons pour chercher x dans t """
nbcomp = 0
for y in t:
nbcomp += 1
if x == y:
return nbcomp
return nbcomp
nbcomp += 1
if t[idx_mid] == val:
return nbcomp
b.
fonction moyennes(n: Entier, p: Entier) → Tuple(Flottant, Flottant)
var L: Entier <- 0
var D: Entier <- 0
var t1, t2: Tableau[n] de Entier[0, n-1]
var val: Entier[0, n-1]
c.
from matplotlib.pyplot import plot, show
On observe que la recherche linéaire nécessite en moyenne n/2 comparaisons, tandis que la recherche
dichotomique croit très faiblement avec n.
14 a. Lorsque la fenêtre « déborde » du tableau, on ne considère que les éléments qui sont dans le
tableau. Ainsi pour le premier élément, on ne considère que les éléments d’indice 0 à f, et pour le
dernier, que ceux d’indice len(t)-f-1 à len(t)-1.
fonction moyenne_fenetre(t: Tableau[n], i: Entier, f: Entier) → Nombre
retourner la moyenne des éléments de t[i-f] à t[i+f] qui sont dans t
fin
b.
# cas limite: moyenne glissante d'un tableau vide ne cause pas d'erreur
assert moyenne_glissante([], 3) == []
c.
def moyenne_fenetre(t, i, f):
""" Retourne la moyenne des éléments entre t[i-f] et t[i+f] inclus.
Si des éléments sont en dehors du tableau, ils ne sont pas comptés.
"""
s = 0 # somme des valeurs des éléments dans la fenêtre
n = 0 # nombre d'éléments dans la fenêtre
return s/n
Les données de l'exemple suivant (tableau hospitalisations) sont dans le fichier élève moyenne_glissante.py
ou moyenne_glissante.ipynb.
# lissage sur une semaine : f = 3 (fenêtre de 7 jours)
lissage = moyenne_glissante(hospitalisations, 3)
for e in t:
if alerte and e <= fin:
alerte = False
elif not alerte and e >= debut:
alerte = True
if alerte:
s.append(100)
else:
s.append(0)
return s
if alerte:
s[i] = 100
return s
15
type Image = tableau[n][m] de Entier [0,255]
type Histogramme = tableau[256] de Entier
fonction histogramme(image : Image) → Histogramme
var histo : Histogramme <- [0] * 256
pour chaque point (i, j) de l’image faire
ajouter 1 à histo[getpixel(i, j)]
def histogramme(image):
histo = [0] * 256
n, m = image.size
for i in range(n):
for j in range(m):
histo[image.getpixel((i, j))] += 1
return histo
def couper(paquet):
""" Retourne deux sous-paquets résultant de la coupe """
n = len(paquet)
coupe = getcut(n) # l'indice de la première carte de second sous-paquet
a = [paquet[i] for i in range(coupe)] # premier sous-paquet
b = [paquet[i+coupe] for i in range(n-coupe)] # second sous-paquet
return a, b
def americain_reel(paquet):
""" Retourne les cartes mélangées par mélange américain probabiliste """
a, b = couper(paquet)
battu = []
b.
def getcut(n):
""" calcule la taille de la coupe """
assert n%2 == 0, 'nombre de cartes impair'
return n // 2
def americain(paquet):
""" Retourne les cartes mélangées par mélange américain probabiliste """
a, b = couper(paquet)
battu = []
test_americain()
c.
# fonction de mélange aléatoire
def randshuffle(t):
shuffle(t)
return t
def americain2(paquet):
return americainN(paquet, 2)
def americain3(paquet):
return americainN(paquet, 3)
def americain7(paquet):
return americainN(paquet, 7)
On observe que la mesure d’un mélange américain répété 7 fois est proche d’un mélange aléatoire.
Note : On peut comparer ce résultat à un mélange américain parfait, en remplaçant americain_reel par
americain dans le code ci-dessus.
def stats(melange):
""" Affiche l'histogramme pour 1000 mélanges """
# calculer l'histogramme de distances avec 1000 tirages
N = 1000
n = 52
histogram = None
for count in range(N):
t = melange([i for i in range(n)])
histogram = distances(t, histogram)
plot(histogram)
show()
17 a. Pour chaque indice i dans le texte t, on va regarder si les caractères r[j] correspondent à t[i+j].
Si on arrive au bout de r, on a trouvé la chaîne, sinon on passe à la valeure suivante de i.
Il faut faire attention à ne pas accéder des caractères en dehors de t. Pour cela, on fait varier i de 0
(début de la chaîne) à len(t) - len(r) (dernière occurrence possible).
Si ce nombre est négatif, donc si la chaîne à chercher est plus longue que le texte dans lequel on le
retourner -1
fin
b.
# test cas normal : chaîne au milieu du texte
assert rechercher('fait', 'il fait beau') == 3
c.
def rechercher(r, t):
""" Recherche si la chaîne r est dans le texte t.
Retourne son indice si trouvé, -1 sinon.
"""
if len(r) > len(t):
return -1
return -1
retourner occurrences
fin
occurrences = []
for i in range(0, len(t) - len(r) + 1):
# On ne peut pas utiliser une boucle `for`
return occurrences
rechercher_tout('tomato', 'tomatomatomatomato')
e. On part de l’algorithme de recherche de toutes les occurrences que l’on modifie comme suit.
On va construire progressivement la chaîne résultat res. A chaque recherche d’occurrence, si le mot
recherché r est trouvé, ou ajoute le mot de remplacement s à la chaîne résultat. Si le mot recherché
n’est pas trouvé, ou ajoute les caractères du texte t à la chaîne résultat. On aura pris soin de mettre ces
caractères dans une chaîne de travail tmp.
i <- 0
tantque i < len(t) - len(r) faire
tmp <- ""
pour chaque caractère r[j] de r faire
tmp <- tmp + t[i+j]
si r[j] ≠ t[i+j] alors
arrêter le parcours de r
fin
fin
res = ''
i=0
while i < len(t) - len(r):
j=0
ok = True
tmp = ''
while ok and j < len(r):
tmp = tmp + t[i+j]
if r[j] != t[i+j]:
ok = False # on sort de la boucle
j = j+1 # on passe au caractère suivant de r
On note qu’il reste une occurrence de tomato dans le résultat : lorsque l’on a remplacé le second tomato
par potato on a continué la recherche à partir du texte qui suivait l’occurrence de tomato, c’est-à-dire la
chaîne mato à la fin du mot. La chaîne de remplacement se termine par to, mais cela n’a pas été pris en
compte par la recherche.
18 a. Il suffit d’ajouter les valeurs associées à chaque chiffre, sauf si le chiffre suivant à une valeur
supérieure au chiffre courant, auquel cas on retire sa valeur du total (et la valeur du chiffre suivant
sera ajoutée à son tour). Cela ne fonctionne que si les nombres sont bien formés, comme on le verra à
la question b.
fonction romain_vers_entier(r: Chaîne) → Entier
var valeur: Entier <- 0
b.
# dictionnaire qui associe la valeur numérique de chaque chiffre romain
val_romain = {
'I': 1, 'V': 5, 'X': 10, 'L': 50,
'C': 100, 'D': 500, 'M': 1000
}
def romain_vers_entier(r):
""" Retourne la valeur entière d'un chiffre romain r bien formé. """
valeur = 0
for i in range(len(r)):
# valeur du chiffre courant
v = val_romain[r[i]]
# valeur du chiffre suivant (0 s'il n'y en a pas)
if i < len(r) -1:
vsuiv = val_romain[r[i+1]]
else:
vsuiv = 0
return valeur
romain_vers_entier('IIX')
`IIX' est un chiffre romain incorrect. Notre algorithme commence à ajouter 1 pour le premier `I' (car le
chiffre suivant a pour valeur 1), puis il retire 1 pour le second `I' (car le chiffre suivant a pour valeur
10), et enfin il ajoute 10 pour le `X'.
c.
Comme les règles sont les mêmes pour convertir les chiffres des unités, des dizaines et des centaines
mais avec des symboles différents, on spécifie une fonction chiffre_romain qui va convertir un chiffre en
fonction des trois symboles.
On peut ensuite spécifier la conversion d’un nombre entier en le décomposant en chiffres de milliers,
des centaines, des dizaines et des unités.
retourner romain
fin
return romain
d. La façon la plus simple de procéder est de calculer la valeur numérique du chiffre romain, de la
convertir à nouveau en un chiffre romain, et de regarder si le résultat est le même que le nombre
original. Si oui, le chiffre était valide, sinon il ne l’était pas.
• Les portes logiques, qui sont des circuits électroniques (indépendants du logiciel) ;
• Les opérateurs booléens, qui sont propres au langage et mettent en œuvre l’évaluation « court-
circuit » ;
Ces anciennes versions de Python permettent alors d’utiliser des caractères Unicode (donc en
particulier les lettres accentuées) dans les commentaires et les chaînes de caractères, mais pas les noms
de variables.
Prérequis – TEST
1 a et c ; 2 a et b ; 3 b ; 4 b ; 5 b ; 6 c ; 7 b et c.
r = '' # le résultat
# Extraire les bits un par un
while n != 0:
if n % 2 == 0:
r = '0' + r
else:
r = '1' + r
n = n // 2
return r
r = '' # le résultat
# Extraire les chiffres un par un
while n != 0:
r = chiffres_octal[n%8] + r
n = n // 8
return r
chiffres_hexa = '0123456789ABCDEF'
def hexa(n):
""" retourne la représentation hexadécimale du nombre positif n """
assert n >= 0
r = '' # le résultat
# Extraire les chiffres un par un
while n != 0:
r = chiffres_hexa[n%16] + r
n = n // 16
def decode_binaire(b):
""" Retourne le nombre représenté en binaire par la chaîne b """
# Parcourir les bits de gauche à droite pour calculer la valeur
n=0
for i in range(len(b)):
n = n*2
if b[i] == '1':
n += 1
return n
def decode_octal(o):
""" Retourne le nombre représenté en octal par la chaîne o """
# Parcourir les chiffres de gauche à droite pour calculer la valeur
n=0
for i in range(len(o)):
c = chiffre(o[i], chiffres_octal)
n = n*8 + c
return n
return n
# cas d'erreur
print(decode_octal('D4'), '=', int('D4', 8))
Note : On peut écrire ces fonctions de conversion de manière plus générale, en prenant la base en
paramètre :
chiffres_base = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
r = '' # le résultat
# Extraire les chiffres un par un
while n != 0:
r = chiffres_base[n%base] + r
n = n // base
return r
return n
Capacité de représentation
from math import log2
print(round(log2(1000)+0.5))
print(round(log2(1000000)+0.5))
QCM (CHEXKPOINT)
1 a,b et c ; 2 b,c,e et f ; 3 a5, b1, c3, d6, e4, f2 ; 4 a,b,d et g ; 5 a3, b1, c5, d2, e4 ; 6 c ; 7 c ; 8 e ; 9 b
et d
Exercices
Exercices Newbie
1 a.
A B A xor B
Vrai Vrai Faux
c.
2
A B A et B non (A et B) non A non B non A ou non B
Vrai Vrai Vrai Faux Faux Faux Faux
3
A B somme retenue A xor B A and B
Faux = 0 Faux = 0 0 0 Faux = 0 Faux = 0
5 Sur 16 bits, le nombre le plus grand est 0111 1111 1111 1111 soit 215 − 1.
Le nombre le plus petit est 1000 0000 0000 0000 qui est le complément à 2 de 0111 1111 1111 1111 +
1 = 1000 0000 0000 0000 soit −215 .
Le raisonnement est le même pour les nombres sur 32 et 64 bits. On utilise Python pour nous donner
les valeurs :
Sur 32 bits, l’intervalle est d’environ -2 milliards à +2 milliards. Sur 64 bits, il est d’environ -9
milliards de milliards à + 9 milliards de milliards.
6 a. La représentation binaire de 2𝑘 est un 1 suivi de k zeros, donc 2𝑘 − 1 est formé d’une suite de k
chiffres 1.
b. On note que 2023 = 2047 - 24. D’après la question a) le nombre 2047 est représenté par 11 bits à 1 :
0000011111111111.
D’autre part 24 = 16 + 8, donc sa représentation binaire est 11000. Retirer 24 de 2047 revient donc à
mettre à 0 les bits de rang 4 et 5, soit 2023 = 0000011111100111.
c. L’inversion des bits de 2023 donne : 1111100000011000. En ajoutant 1 on obtient -2023 =
1111100000011001.
7 a. Un nombre est divisible par 2 si sa représentation binaire se termine par 0. En effet, le bit de poids
faible (le plus à droite) représente une unité, et tous les autres bits représentant une puissance positive
de 2, donc un nombre pair.
Un nombre est divisible par 4 s’il est divisible par 2 et le quotient est à son tour divisible par 2. Diviser
un nombre binaire par 2 revient à retirer le bit le plus à droite (qui est un zéro si le nombre est pair).
On en conclut qu’un nombre est divisible par 4 si sa représentation binaire se termine par 2 zéros.
b. Multiplier un nombre représenté en binaire par 2 consiste à lui ajouter un bit à 0 à droite. Donc
2 × 𝑏𝑘 . . 𝑏0 = 𝑏𝑘 . . 𝑏0 0
c. Si un nombre binaire positif n se termine par un zéro, il est pair. Son complément à deux, qui
représente -n, se termine également par zéro. En effet le dernier bit est inversé et devient un 1, puis
8
0 0 1 0 1 0 1 0 42
0 0 1 1 1 0 0 0 56
Complément à deux de 56 :
1 1 0 0 1 0 0 0 -56
Donc 42 - 56 = -14.
Pour afficher toutes les valeurs possibles, on énumère tous les nombres binaires de 4 bits avec 4
boucles imbriquées, et on calcule la valeur correspondante :
for b1 in range(2):
for b2 in range(2):
for b3 in range(2):
for b4 in range(2):
valeur = b1*1/2 + b2*1/4 + b3*1/8 + b4*1/16
print(f'0.{b1}{b2}{b3}{b4} = {valeur}')
b.
# On utilise le tableau `frac` du code précédent
for x in range(10):
v = x / 10 # la valeur à approcher
11 a.
def flottant(s, m, e):
""" Valeur d'un nombre représenté par le signe s,
la mantisse m (sur 4 bits) et l'exposant e """
return s * (1. + m/16.) * 2**e
nombres = []
for signe in [-1, 1]:
for exposant in range(-3,5):
for mantisse in range(16):
nombres.append(flottant(signe, mantisse, exposant))
nombres.sort()
print(nombres)
b.
import matplotlib.pyplot as plt
plt.plot(nombres, '.')
plt.show()
On observe que la distribution n’est pas linéaire : il y a plus de nombres autour de 0 que sur le reste de
l’intervalle [-31, 31].
from math import pi
ecart = 1000
valeur = 0
# Chercher l'entrée du tableau des nombres la plus proche de pi
for n in nombres:
if abs(n - pi) < ecart:
ecart = abs(n - pi)
valeur = n
print(valeur)
65 à 90 lettres majuscules de A à Z
91 à 96 6 caractères spéciaux ([ ] ^ _ `)
b.
# On décrit la table par un tableau de tuples:
categories = [
( 0, 15, "un caractère non imprimable"),
( 16, 47, "un caractère spécial : espace ! \" # $ % & ' ( ) * + , - . /"),
( 48, 57, "un chiffre de 0 à 9"),
( 58, 64, "un caractère spécial (: ; < = > ? @)"),
( 65, 90, "une lettre majuscule de A à Z"),
( 91, 96, "un caractère spécial ([ \ ] ^ _ `)"),
( 97, 122, "une lettre minuscule de a à z"),
(123, 126, "un caractère spécial ({ | } ~)"),
(127, 127, "le caractère non imprimable « DEL »")
]
def ASCII_cat():
ASCII_cat()
13 Vérifier que l’on peut utiliser en Python (depuis sa version 3) des caractères accentués et des
alphabets non latins dans les noms de variables et de fonctions, mais pas des emojis. On pourra utiliser
le site https://unicode-table.com/fr pour trouver les codes UTF-8 des caractères.
from math import pi
π = pi
deux_π = pi * 2
def carré(x):
return x*x
carré(deux_π)
Exercices Initié
14 On rappelle que l’une des lois de De Morgan stipule que non (A ou B) == non A et non B.
On remarque que la partie à gauche de cette égalité n’utilise que les opérateurs non et ou (appliqués à
A et B), et que la partie à droite correspond à l’opérateur et appliqué à non(A) et non(B). En
remplaçant A et B à gauche par leurs négations respectives (non A, non B), on obtient alors non(non
A), non(non B) à droite, qui sont en fait équivalents à A et B.
On peut donc exprimer l’opérateur et en utilisant seulement non et ou de la façon suivante :
A et B == non (non(A) ou non(B))
On peut s’en assurer avec la table de vérité suivante :
On peut faire le même raisonnement avec l’autre loi de De Morgan, non (A et B) == non A ou non B,
afin d’exprimer l’opérateur ou en utilisant seulement non et et : En appliquant cette loi aux négations
respectives de A et B, on obtient
non (non A et non B) == non (non A) ou non (non B)
et donc
A ou B == non (non A et non B)
Là encore, on peut s’en assurer avec une table de vérité :
15 a.
A non A B A⇒B
Vrai Faux Vrai Vrai
b. A ⇒ B s’appelle « implication » car il est faux seulement lorsque A est vrai et B est faux : un fait
vrai ne peut pas (logiquement) impliquer un fait qui est faux. Par contre un fait qui est faux peut
impliquer un fait quelconque (vrai ou faux).
c.
A B s’appelle « équivalence » car il est vrai lorsque les valeurs de vérité de A et B sont identiques.
Remarque : C’est l’opposé de l’opérateur xor.
0+1 +0 =1 0
1+0 +0 =1 0
1+1 +0 =0 1
0+0 +1 =1 0
0+1 +1 =0 1
1+0 +1 =0 1
1+1 +1 =1 1
b.
b. Ainsi, Q mémorise laquelle des deux entrées est passée la dernière à 1 : S (« set » en anglais) permet
d’enregistrer un 1, et R (« reset » en anglais) permet de le remettre à 0. La sortie non Q est toujours
égale à la valeur opposée de Q.
print(t)
print(premier_zero([1,2,3,0,4,5,6]))
print(premier_zero([1,2,3,4,5,6,7]))
19 On rappelle qu’un nombre entier positif 𝑁 est égal à une somme de produits entre le n-ième chiffre
de sa représentation binaire (noté 𝑏𝑛 , pour n-ième bit) et la n-ième puissance de 2.
En base 10, on peut décaler les chiffres d’un nombre 𝑁 (« décaler la virgule ») en multipliant ou en
divisant 𝑁 par une certaine puissance de 10. Plus précisément, décaler les chiffres de 𝑘 chiffres vers la
gauche (respectivement vers la droite) revient à diviser (respectivement multiplier) par 10𝑘 . Par
exemple, on peut faire passer le chiffre des centaines (3) du nombre 12345 en position des unités en
divisant celui-ci par 102 (ce qui nous donne 123,45).
En base 2, on peut utiliser le même mécanisme pour « décaler les bits » d’un nombre, en multipliant
ou divisant celui-ci par 2𝑘 . On dispose également d’une propriété supplémentaire : d’après la formule
ci-dessus, le bit dit « de poids faible » (c’est-à-dire le chiffre le plus à droite) de la représentation
binaire d’un nombre indique sa parité (0 si le nombre est pair, 1 s’il est impair). Ainsi, si l’on décale le
• si 𝑁2 est impair, alors le bit de poids faible de 𝑁2 est égal à 1, et donc le n-ième bit de 𝑁1 est
égal à 1.
On en déduit donc que l’on peut tester si le 𝑛-ième bit de la représentation binaire d’un nombre positif
𝑁 est un 1 ou en 0 en divisant 𝑁 par 2𝑛−1 (afin de décaler le n-ième bit 𝑘 fois vers la droite, de façon à
en faire le bit de poids faible du résultat de la division), et en testant la parité du résultat.
Note : si réalise une division dans ce but, il faut ignorer la partie décimale du nombre (qui correspond
au nombre représenté par les bits qui se trouvaient à droite du n-ième bit de 𝑁, qui ne nous est
d’aucune utilité). On peut éviter cette étape en effectuant une division entière et en ignorant le reste
(par exemple, 42 // 2**5 en Python).
20 a. On propose le code de Gray sur 3 bits suivant : 000, 001, 011, 010, 110, 111, 101, 100.
On propose le code de Gray sur 4 bits suivant : 0000, 0001, 0011, 0010, 0110, 0111, 0101, 0100, 1100, 1101,
1111, 1110, 1010, 1011, 1001, 1000
b. Dans les deux cas précédents, on utilise la même procédure pour générer le code à partir d’une
séquence 𝑆 formant un code de Gray sur un bit de moins :
• pour la première moitié des nombres, on ajoute un 0 devant chaque nombre de 𝑆 ;
• pour la seconde moitié des nombres, on répète 𝑆 à l’envers, et on ajoute un 1 devant chaque
nombre.
On peut généraliser cette procédure pour un code de Gray à 𝑛 bits avec l’algorithme récursif suivant :
21 a. On réalise la même opération que pour trouver son complément à 2 : on remplace chaque chiffre
par son complément à 9 et on ajoute 1 :
• 999958 → 000041 = 41
• 41 + 1 = 42
r = ''
# Se ramener à un nombre positif
negatif = False
if n < 0:
n = -n
negatif = True
# Retourner le résultat
if negatif:
r = complement(r)
return r
def complement(b):
""" Retourne le complément à 2 du nombre binaire b """
c = '' # résultat
# construire le complément de chaque bit
for i in range(8):
# ajouter 1
return somme(c, '00000001')
def base10(b):
""" Retourne la représentation en complément à 2 du nombre binaire b """
negatif = False
# Se ramener à un nombre positif
if b[0] == '1': # nombre négatif si bit de poid fort == 1
b = complement(b)
negatif = True
# Retourner le résultat
if negatif:
return -n
return n
b42 = binaire(42)
print(b42, base10(b42))
bm42 = binaire(-42)
print(bm42, base10(bm42))
d = difference(b42, b42)
print(f'42-42 = {base10(d)}, {b42} - {b42} = {d}')
23 a.
• bfA = bit de poids fort de A, bfB = bit de poids fort de B
• Re = retenue entrante
• S = somme
• Rs = retenus sortante
bfA bfB Re S Rs
0 0 0 0 0
0 0 1 1 0
0 1 0 1 0
0 1 1 0 1
1 0 0 1 0
1 1 0 0 1
1 1 1 1 1
b.
• D = Dépassement
bA bB R S Rs D Commentaire
0 0 0 0 0 0 A et B positifs
0 0 1 1 0 1 A et B positifs - dépassement
0 1 0 1 0 0 A et B de signes opposés
0 1 1 0 1 0 A et B de signes opposés
1 0 0 1 0 0 A et B de signes opposés
1 0 1 0 1 0 A et B de signes opposés
1 1 0 0 1 1 A et B négatifs - dépassement
1 1 1 1 1 0 A et B négatifs
c. Un dépassement a lieu seulement lorsque les retenues entrante et sortante sont opposées, ce que l’on
peut tester par une porte logique xor : D = Re xor Rs
return bits
def bits_vers_chaine(bits):
""" Retourne une chaîne de caractère représentant
les bits du tableau `bits`.
"""
return "".join(map(str, bits))
# Quelques tests :
b. On peut utiliser l’algorithme de conversion d’un nombre entier en isolant la partie entière du
nombre à convertir, puis en convertissant celui-ci vers sa représentation binaire sur 8 bits. On propose
une implémentation en Python ci-dessous :
def bits_partie_entiere(nombre, nb_bits):
""" Retourne un tableau de bits représentant la partie entière
de `nombre` en binaire sur `nb_bits`.
"""
bits = []
partie_entiere = floor(nombre) # on isole la partie entière du nombre
return bits
# Quelques tests :
# 00101010
print(bits_vers_chaine(bits_partie_entiere(42, 8)))
c. On note un nombre décimal positif 𝑁 écrit dans cette représentation sous forme d’un 2-uplet (𝐸, 𝐷)
où :
- 𝐸 représente la partie entière pouvant être représentée sur 8 bits ;
- 𝐷 représente la partie décimale pouvant être représentée sur 8 bits.
On peut adapter l’algorithme d’addition de nombres entiers pour le faire fonctionner avec cette
représentation et additioner deux nombres 𝑁1 = (𝐸1 , 𝐷1 ) et 𝑁2 = (𝐸2 , 𝐷2 ) de la façon suivante :
- On commence par additionner les deux parties décimales avec l’algorithme d’addition de
nombres entiers : 𝐷 = 𝐷1 + 𝐷2
o S’il y a un dépassement de capacité, alors on a une retenue 𝑅 = 1
o Sinon, 𝑅 = 0
- On additionne ensuite les deux parties décimales et R avec l’algorithme d’addition de nombres
entiers : 𝐸 = 𝐸1 + 𝐸2 + 𝑅
o Cette-fois-ci, on ne peut rien faire d’autre que signaler un éventuel dépassement de
capacité
def creer_nombre_binaire(nombre):
""" Retourne un 2-uplet de tableaux de bits représentant
les parties entière et décimale du nombre fourni.
"""
return (
bits_partie_entiere(nombre, NB_BITS),
bits_partie_fractionnaire(nombre, NB_BITS),
)
def nombre_binaire_vers_chaine(nombre_binaire):
"""Retourne une chaîne de caractères représentant
les deux tableaux de bits du nombre binaire fourni.'
"""
e, d = nombre_binaire
return str((
bits_vers_chaine(e),
bits_vers_chaine(d)
))
def nombre_binaire_vers_flottant(nombre_binaire):
""" Retourne un nombre représentant le nombre binaire fourni. """
e, d = nombre_binaire
nombre = 0
return nombre
def afficher_nombre_binaire(nombre_binaire):
""" Affiche le nombre binaire fourni sous forme
de nombre flottant et de 2-uplet de bits.
"""
flottant = nombre_binaire_vers_flottant(nombre_binaire)
bits_en_chaine = nombre_binaire_vers_chaine(nombre_binaire)
print(f"{flottant} {bits_en_chaine}")
# L'ordre des bits dans le tableau doit être inversé (avec .reverse())
# car le reste du code travaille sur des tableaux de bits
# dont le premier bit (indice 0) est celui dit « de poids fort »
# (le chiffre associé au plus grand multiple dans la base),
# tandis qu'ici, l'ordre est inversé.
bits_somme.reverse()
return (bits_somme, retenue)
# Quelques tests :
nombre_binaire_1 = creer_nombre_binaire(nombre_1)
nombre_binaire_2 = creer_nombre_binaire(nombre_2)
afficher_nombre_binaire(nombre_binaire_1)
print(" +")
afficher_nombre_binaire(nombre_binaire_2)
print(" =")
afficher_nombre_binaire(somme)
print("")
test_somme(25, 75)
25 Soit D le nombre décimal positif. On veut calculer la mantisse m et l’exposant e tels que 𝐷 = (1 +
𝑚) × 2𝑒 avec 0 ≤ 𝑚 < 1.
Le principe est de diviser D par 2 tant que D est plus grand que 1, en incrémentant l’exposant. Il suffit
ensuite de multiplier D par 2 et de décrémenter l’exposant pour être sûr que le nombre résultant est
comprise entre 1 et 2, d’où on déduit la mantisse.
Si D est inférieur à 1, on le multiplie par 2 jusqu’à ce qu’il soit supérieur à 1 en décrémentant
l’exposant. On en déduit alors la mantisse.
Le code est le suivant :
def mantisse_exposant(D):
e=0
while D > 1.0:
D=D/2
e += 1
while D < 1.0:
D=D*2
e -= 1
return (D-1, e)
def test(x):
m, e = mantisse_exposant(x)
print(f'{x} = (1 + {m}) * 2**{e}')
assert (1+m) * 2**e == x
26 a.
def caractere_vers_hexa(caractere):
""" Retourne le code du caractère fourni en hexadécimal. """
return hex(ord(caractere))
def chaine_vers_chaine_hexa(chaine):
""" Retourne une chaîne de caractères représentant
les codes des caractères de la chaîne fournie,
chaque code étant formé de 2 chiffres hexadécimaux,
et séparé par un espace des codes voisins.
"""
codes_hexa = []
# Test :
print(chaine_vers_chaine_hexa("Bonjour !"))
b.
def code_hexa_vers_caractere(code_hexa):
""" Retourne le caractère du code fourni sous forme
de chaîne représentant un code en hexadécimal.
"""
def chaine_hexa_en_majuscules(chaine_hexa):
""" Transforme en majuscule les caractères codés en héxadécimal
sur 2 caractères (et séparés par des espaces)
et retourne la chaîne de codes hexadécimaux modifiés.
"""
codes_hexa = chaine_hexa.split(" ")
codes_hexa_majuscules = []
# Test :
print(chaine_hexa_en_majuscules(chaine_vers_chaine_hexa("Bonjour !")))
c.
def chaine_hexa_vers_chaine(chaine_hexa):
""" Retourne la chaine de caractères codée par la chaîne fournie,
return "".join(caracteres)
# Quelques tests :
print(chaine_hexa_vers_chaine(
chaine_vers_chaine_hexa("Bonjour !"))
)
print(chaine_hexa_vers_chaine(
chaine_hexa_en_majuscules(chaine_vers_chaine_hexa("Bonjour !")))
)
27 Dans cette première version, on ne traite pas le cas de Windows. On parcourt le texte pour voir s’il
contient seulement des ’\n’, seulement des ’\r’, ou bien un mélange des deux. Dans ce dernier cas, on
retourne immédiatement que le fichier est incohérent.
def fdl(texte):
""" retourne le type de fin de ligne de texte """
type = 'inconnu' # type deviné jusqu'à présent
return type
Pour traiter le cas de Windows, il faut regarder le caractère suivant lorsque l’on trouve un ’\r’. Une
façon de faire est de mémoriser le caractère précédent :
def fdl(texte):
""" retourne le type de fin de ligne de texte """
type = 'inconnu' # type deviné jusqu'à présent
precedent = '' # le caractère précédent
precedent = car
return type
Le fichier exo27.py contient le corrigé de cet exercice appliqué aux quatre fichiers fournis aux élèves :
def fdl_fichier(nom):
""" lire un fichier et l'analyser """
# ATTENTION : le code donné dans l'énoncé de l'exercice est incorrect !
f = open(nom, 'r', newline='')
fdl_fichier('texte1.txt') # Linux
fdl_fichier('texte2.txt') # Windows
fdl_fichier('texte3.txt') # MacOS Classic
fdl_fichier('texte4.txt') # inconnu
fdl_fichier('texte5.txt') # incohérent
Exercices Titan
28 a. V = vrai, F = faux, I = inconnu
A NOT A
V F
F V
I I
A B A AND B A OR B
V V V V
V I I V
F V F V
F F F F
F I F I
I V I V
I F F I
I I I I
b.
A B Non A Non A OR B
V V F V
V F F F
V I F I
F V V V
F F V V
F I V V
I V I V
I I I I
On observe que si A est Vrai, A ⇒ B a la valeur de B, comme dans le cas binaire. Si A est Faux, A ⇒
B est vrai quel que soit B, comme dans le cas binaire.
Enfin, si A est Incertain, A ⇒ B est vrai si B est vrai et incertain sinon. Cela est logique car, d’une
part, un fait vrai ou faux peut impliquer un fait vrai, donc a fortiori pour un fait incertain (I ⇒ V est
V). D’autre part, un fait incertain peut impliquer in fait faux s’il est faux lui-même, et a fortiori un fait
incertain (I ⇒ F et I ⇒ I sont tous les deux I).
29 a. Soit 𝑁 un nombre entier relatif dont la représentation binaire en complément à deux sur 𝑛 bits
est notée 𝑏𝑛 𝑏𝑛−1 ⋯ 𝑏1 . On rappelle que dans cette représentation par complément à deux, le bit de
poids fort (𝑏𝑛 ) vaut 0 si 𝑁 ≥ 0 et 1 si 𝑁 < 0.
On traite les cas des décalages à gauche et à droite séparément :
Dans le cas d’un décalage à gauche (multiplication par deux) :
Dans le cas d’un décalage à gauche, on vérifie que les deux propriétés suivantes restent vraies (hors
dépassements de capacité) : (1) la valeur absolue de 𝑁 est bien doublée et (2) le signe de 𝑁 est
préservé.
où 𝑁 ≪ 1 représente le nombre codé par un décalage à gauche d’un bit de la représentation binaire de
𝑁 avec un 0 entrant.
Montrons ensuite que la propriété (2) est préservée :
- Si 𝑁 < 2𝑛−2 , et donc 𝑏𝑛−1 = 0, alors le 𝑛-ième bit demeurera à 0 lors du décalage à gauche.
où la notation ⌊⋅⌋ représente l’arrondi à l’entier inférieur, et où 𝑁 ≫ 1 représente le nombre codé par
un décalage à droite d’un bit de la représentation binaire de 𝑁 avec un 1 entrant.
b. Un décalage à gauche provoque un dépassement de capacité si le signe du bit de poids fort (𝑏𝑛 )
change de valeur lors du décalage.
Comme expliqué dans la réponse à la question précédente, cela se produit dans les deux cas suivants :
Les deux opérations de multiplication et de division par la base (lignes 5 et 6) consistent simplement à
décaler le nombre vers la gauche ou vers la droite : en base 10, pour multiplier un nombre par 10, on
ajoute un 0 à droite, pour le diviser par 10, en enlève le chiffre de droite. De même, avec les nombres
binaires, pour multiplier un nombre par deux, il suffit de réaliser un décalage à gauche, c’est-à-dire de
décaler tous les bits d’un cran vers la gauche et de mettre un bit 0 à droite : 00101010 (42) x 2 =
01010100 (84). Cela fonctionne également pour des nombres négatifs lorsque l’on utilise le
complément à deux : 11110010 (-14) x 2 = 11100100 (-28) (Voir exercice 29).
D’autre part, en binaire, chaque chiffre d dans le pseudo-code ci-dessus vaut 0 ou 1, donc le produit A
* d (ligne 5) vaut 0 ou A. On n’a donc pas besoin de multiplier A par d. Le pseudo-code s’écrit comme
suit :
1 fonction multiplier_binaire(A, B)
2 résultat <- 0
b.
def decalage_gauche(A):
""" Décale à gauche le nombre A en faisant entrer un 0 """
return A+'0'
def decalage_droite(A):
""" Décale à droite le nombre A """
if len(A) == 0:
return ''
t = list(A) # tranformer en tableau
t.pop(len(A) -1) # retirer dernier élément
return ''.join(t) # retransformer en chaîne
def bit_poids_faible(A):
""" Retourne le bit de poids faible de A """
if len(A) == 0:
return '0'
return A[len(A)-1]
if r == '1':
total = r + total
return total
c. La multiplication de deux nombres codés chacun sur un mot de n bits peut demander jusqu’à 2n
bits. On risque donc un dépassement de capacité.
Celui-ci peut se produire au moment du décalage à gauche (ligne 4). Comme dans le cas de l’addition,
s’il n’est pas détecté, on obtient un résultat erronné : 01010100 (84) x 2 = 10101000 (-88) !!
Le débordement peut également se produire au moment de l’ajout de A (ligne 6).
31 a.
print(2**200)
# affiche "1606938044258990275541962092341162602522202993782792835301376"
b.
def fonction_vide():
return 0
big1 = 42**40
big2 = 40**58
# Additions
def add_grands_nombres():
return big1 + big2
# Multiplications
def mul_petits_nombres():
return 12345 * 67890
def mul_grands_nombres():
return big1 * big2
nb_repetitions = 10**7
c.
# On commence par écrire quelques fonctions utilitaires.
def creer_grand_nombre(n):
"""Crée et retourne un grand nombre positif représentant le nombre fourni."""
# Si n est 0, on retourne immédiatement le grand nombre correspondant.
if n == 0:
return [0]
return resultat
def grand_nombre_vers_chaine(grand_n):
"""Retourne une chaîne de caractère représentant le grand nombre fourni."""
parties_chaine = []
return "".join(parties_chaine)
nb_parties_gn1 = len(gn1)
nb_parties_gn2 = len(gn2)
resultat.insert(0, somme_parties)
numero_partie += 1
return resultat
grand_n1 = creer_grand_nombre(1)
print(grand_nombre_vers_chaine(grand_n1))
grand_n2 = creer_grand_nombre(999)
print(grand_nombre_vers_chaine(grand_n2))
grand_n3 = creer_grand_nombre(100100100)
print(grand_nombre_vers_chaine(grand_n3))
grand_n4 = creer_grand_nombre(34666524)
grand_n5 = creer_grand_nombre(32397179)
print(f"Somme : {grand_nombre_vers_chaine(somme_1)}")
print(f" {34666524 + 32397179} (version Python)")
grand_n6 = creer_grand_nombre(10**100 + 1)
print(f"Somme : {grand_nombre_vers_chaine(somme_2)}")
print(f" {10**100 + 1 + 10**100} (version Python)")
d.
def big_mul(grand_n1, grand_n2):
""" Multiplie les deux grands nombres fournis
et retourne le résultat sous forme d'un grand nombre.
"""
sous_produits = []
sous_produits.append(produit)
return resultat
# Quelques tests :
grand_n1 = creer_grand_nombre(6)
grand_n2 = creer_grand_nombre(7)
print(f"Produit : {grand_nombre_vers_chaine(produit)}")
print(f" {6 * 7} (version Python)")
grand_n3 = creer_grand_nombre(10**123)
grand_n4 = creer_grand_nombre(10**321)
print(f"Produit : {grand_nombre_vers_chaine(produit)}")
print(f" {10**123 * 10**321} (version Python)")
facteur_1 = 10**123
facteur_2 = 10**321
grand_n1 = creer_grand_nombre(facteur_1)
grand_n2 = creer_grand_nombre(facteur_2)
def produit_exercice():
return big_mul(grand_n1, grand_n2)
def produit_python():
return facteur_1 * facteur_2
# Sur un MacBook Pro de 2020 (4 coeurs cadencés à 2GHz) avec Python 3.9,
# la version Python est envron près de 300 000 fois plus rapide que celle de l'exercice !
b.
# codage héxadécimal et binaire des deux codes
print(hex(960), bin(960))
print(hex(128522), bin(128522))
Le codage hexadécimal de 960 est 3c0, son codage binaire est 01111000000. D’après la table du cours,
ce code Unicode est dans la deuxième ligne et codé sur 2 octets : 110xxxxx 10xxxxxx, dans lequel les x
sont remplacés par les bits du code binaire, soit 110|01111 10|000000 (on a inséré une barre verticale
pour séparer les préfixes du code UTF-8 des bits issus du numéro de caractère Unicode), qui est
représenté en hexadécimal par cf 80.
Le codage hexadécimal de 128522 est 1f60a, son codage binaire est 11111011000001010. D’après la table
du cours, ce code Unicode est dans la deuxième ligne et codé sur 4 octets : 11110xxx 10xxxxxx 10xxxxxx
10xxxxxx, dans lequel les x sont remplacés par les bits du code binaire (en complétant à gauche par des
0), soit 11110|000 10|011111 10|011000 10|001010, qui est représenté en hexadécimal par f0 9f 98 8a.
c.
'π'.encode() # b'\xcf\x80'
b'\xcf\x80'.decode() # 'π'
'😊'.encode() # b'\xf0\x9f\x98\x8a'
b'\xf0\x9f\x98\x8a'.decode() # '😊'
for b in '😊'.encode():
print(hex(b))
On doit installer un lien entre chaque lieu, soit 3 liens depuis le lieu A vers B, C et D, plus 2 autres
liens de B vers C et D, et enfin un dernier lien entre C et D. On doit donc installer 3 + 2 + 1 = 6 liens
pour relier les 4 lieux.
Le nombre de liens à mettre en place pour n lieux est égal à n*(n – 1)/2.
Limitation du nombre de liens
Approche 1
Unités de temps Lien A-B Lien B-C Lien C-D Lien D-A
Approche 2
Envois simultanés
1.a.
X A-B et B-C
Y A-B
Z B-C et C-D
b.
Chapitre 10 – Réseau 1
Unités de temps Lien A-B Lien B-C Lien C-D Lien D-A
Unités de temps Lien A-B Lien B-C Lien C-D Lien D-A
Autre configuration
Discussion : On voit que le lieu F doit stocker la donnée Y entre le temps 1 auquel il la reçoit depuis
H, et le temps 3 auquel il peut l’envoyer à G. Il doit donc disposer d’une capacité de stockage au
moins égale à la taille de la donnée Y (ou de la donnée X si on a choisi d’envoyer d’abord la donnée
Y).
Dans le cas d’une donnée de très grande taille, il peut être nécessaire de la découper en paquets plus
petits afin de ne pas dépasser la capacité de stockage des lieux intermédiaires.
Chapitre 10 – Réseau 2
Unités de temps Lien E-F Lien H-F Lien F-G Stockage de E
# créer le récepteur
receveur = socket.socket(type = socket.SOCK_DGRAM)
receveur.bind(('localhost', 56789))
# créer l'émetteur
emetteur = socket.socket(type = socket.SOCK_DGRAM)
# envoyer un message
emetteur.sendto(b"Hello world", ('localhost',56789))
Chapitre 10 – Réseau 3
Une adresse IP de la forme 129.175.15.1 doit apparaitre dans le code Python sous forme d’une chaîne de
caractères "129.175.15.1".
# créer l'émetteur
emetteur = socket.socket(type = socket.SOCK_DGRAM)
Machine B
import socket
def recoitAvecBit(receveur):
""" Attend la réception d'un message et retourne (donnee, bit, adresse) """
message, adresse = receveur.recvfrom(1500)
bit, donnee = decoderPaquet(message)
return (donnee, bit, adresse)
# créer le récepteur
receveur = socket.socket(type = socket.SOCK_DGRAM)
receveur.bind(('localhost',56789))
Chapitre 10 – Réseau 4
emetteur.sendto(x, (machine,port))
def recoitAvecBit(receveur):
""" Attend la réception d'un message et retourne (donnee, bit, adresse) """
message, adresse = receveur.recvfrom(1500)
bit, donnee = decoderPaquet(message)
return (donnee, bit, adresse)
# créer l'émetteur
emetteur = socket.socket(type = socket.SOCK_DGRAM)
# créer le récepteur
receveur = socket.socket(type=socket.SOCK_DGRAM)
receveur.bind(('localhost', 56790))
Machine B
import socket
def recoitAvecBit(receveur):
""" Attend la réception d'un message et retourne (donnee, bit, adresse) """
message, adresse = receveur.recvfrom(1500)
bit, donnee = decoderPaquet(message)
return (donnee, bit, adresse)
# créer le récepteur
receveur = socket.socket(type = socket.SOCK_DGRAM)
receveur.bind(('localhost',56789))
# créer l'émetteur
emetteur = socket.socket(type = socket.SOCK_DGRAM)
Chapitre 10 – Réseau 5
def recoitAvecBit(receveur):
""" Attend la réception d'un message et retourne (donnee, bit, adresse) """
message, adresse = receveur.recvfrom(1500)
bit, donnee = decoderPaquet(message)
return (donnee, bit, adresse)
Machine B
import socket
import random
def recoitAvecBit(receveur):
""" Attend la réception d'un message et retourne (donnee, bit, adresse) """
message, adresse = receveur.recvfrom(1500)
bit, donnee = decoderPaquet(message)
return (donnee, bit, adresse)
Chapitre 10 – Réseau 6
# envoyer l'accusé de réception avec le même bit
envoieAvecBit(b"", bit, emetteur, adresse[0], 56790)
def demarrer(port):
""" Initialise l'emetteur et le receveur (variables globales) """
global emetteur
emetteur = socket.socket(type = socket.SOCK_DGRAM)
global receveur
receveur = socket.socket(type = socket.SOCK_DGRAM)
receveur.bind(('localhost',port))
Chapitre 10 – Réseau 7
La taille des message est `taille`.
"""
octetAEnvoyer = 0 # numéro du prochain octet à envoyer
recu = "" # message reçu
octetRecu = 0 # numéro du dernier octet recu
messageComplet = b""
print()
print("Envoye : ", x)
print("Recu : ", messageRecu, len(recu))
print("Octet recu : ", octetRecu)
print("Octet a envoyer", octetAEnvoyer)
print("Fini")
Chapitre 10 – Réseau 8
tcp.demarrer(56789)
tcp.envoyer(b"Hello world", "localhost", 56790, 3)
Machine B
import comme_tcp as tcp
tcp.demarrer(56789)
tcp.envoyer(b"Hello world", "localhost", 56790, 3)
Exercices
Exercices Newbie
1 Un paquet IP est composé :
- les données à envoyer qui peuvent contenir elles-mêmes un entête d’un protocole de plus haut niveau
(par exemple TCP) suivi des données décrites par cet entête.
2001:660:1:1::/64 2001:660:1:2::/64
R1
M1 M2
2001:660:1:1::2 2001:660:1:2::1
2001:660:1:1::1 2001:660:1:2::2
6 a.
def decoupagePaquets(donnee, taille):
n=0
r = []
while n < len(donnee):
paquet = ""
l = min(taille, len(donnee) - n)
for i in range(l):
Chapitre 10 – Réseau 9
paquet = paquet + donnee[n+i]
r.append(paquet)
n = n + taille
return r
b.
def decoupagePaquetsIP(donnee, taille, source, destination) :
if taille <= 20 :
return []
p = decoupagePaquets(donnee, taille-20)
r = []
for d in p :
r.append([source, destination, d])
decoupagePaquetsIP('Mourir vos beaux yeux, belle Marquise, d’amour me font', 30, '123.12.1.2', '156.78.9.0')
7 a. Il faut 3 préfixes d’adresses différents, un sur chaque lien (machine 1 au routeur 1, routeur 1 au
routeur 2, routeur 2 à machine 2).
b.
2001:660:1:2::/64
2001:660:1:1::/64 2001:660:2:1::/64 M3
R1 R2
M1
2001:660:2:1::22
8 a. Le TTL (Time To Live) d’un paquet est décrémenté à chaque fois que le paquet est reçu par un
routeur. Il sera donc égal à 4 – 2 = 2.
b. Le paquet ne peut pas traverser plus que 3 routeurs. Au 4e routeur, le TTL tombera à 0 et sera
considéré comme ayant passé trop de temps dans le réseau et sera supprimé.
9 La commande ping fait en sorte que la machine à qui appartient l’adresse destination renvoie un
paquet vers l’adresse source. La réponse aura donc :
- source : 192.168.28.32
- destination : 192.168.1.10
10 L’avantage de la transmission par paquet est d’utiliser au mieux les capacités des réseaux : il n’est
pas utile de réserver le lien entièrement entre la source et la destination. Le paquet se rapproche de sa
destination à chaque fois qu’un lien est disponible, et n’occupe ce lien que le temps de la transmission
du paquet. La bonne réponse est c.
11 La transmission par paquet nécessite de pouvoir stocker le paquet si le lien n’est pas libre (réponse
a) et une adresse source et destination (réponse b) pour pouvoir envoyer le paquet vers sa destination
et prévenir la source en cas de problème.
Chapitre 10 – Réseau 10
12
Exercices Initié
14 1. L’adresse 10.0.1.3 appartient au préfixe 10.0.1.0/24.
2. L’adresse 2001:660:2:3:1::1 appartient au préfixe 2001:660:2:3::/64.
3. L’adresse 2001:660:2::1 n’appartient à aucun préfixe donné.
4. L’adresse 10.1.1.1 n’appartient à aucun préfixe donné.
5. L’adresse 10.3.1.1n’appartient à aucun préfixe donné.
Chapitre 10 – Réseau 11
16 a. L’adresse de la machine M5 doit appartenir au préfixe 2001:660:1:5::/64, par exemple :
2001:660:1:5::5.
b. Le préfixe d’adresse du réseau entre R3 et M4 est 2001:660:1:4::/64.
17
4 24
3
10.0.1.1
128.192.2.2
IPv4
Chapitre 10 – Réseau 12
19
20 a. En considérant qu’on transmet des données dans des paquets IP qui ont des entêtes de 20 octets,
chaque paquet Ethernet ne pourra transmettre que 50-20=30 octets. Il faudra donc 4 paquets pour
transmettre les 100 octets de donnée.
b. Le dernier paquet contiendra 10 octets de données.
10.0.2.221
10.0.45.23
23 a. A envoie les paquets P1, P2, P3 ; D envoie les paquets P3, P4.
Temps A B C D
1 P1
2 P2,P4 P1
3 P4,P5,P3 P2 P1
4 P5,P3 P4 P2
5 P3 P5 P4
6 P3 P5
7 P3
Chapitre 10 – Réseau 13
b. On doit prévoir un espace de stockage d’au moins 3 paquets dans B.
24 a.
192.168.1.0/24 192.168.2.0/24
R
M1 M2
192.168.1.10 192.168.2.10
192.168.1.1 192.168.2.2
b. Pour envoyer des données de M1 à M2, les paquets IP devront avoir 192.168.1.1 comme adresse
source, et 192.168.2.2 comme adresse de destination.
Le paquet devra transiter sur le lien entre M1 et R, puis sur le lien entre R et M2, dans 2 paquets
Ethernet différents.
De M1 à R :
Source : ac:ed:12:34:56:78
Destination : ac:ed:01:02:03:04
4
192..168.1.1
10.0.2.221
192.168.2.2
Données
De R à M2 :
Source : ac:ed:aa:aa:aa:aa
Destination : ac:ed:11:22:33:44
4
192..168.1.1
10.0.2.221
192.168.2.2
Données
25 L’entête IP est composé de 20 octets, de même que l’entête TCP. Chaque paquet peut donc
transporter 1 000 – 40 = 960 octets. Il faut donc 8 paquets pour envoyer les données de M1 à M2 et 7
paquets pour les données de M2 à M1. Cependant il y aura bien 8 paquets échangés dans les 2 sens,
Chapitre 10 – Réseau 14
puisque M2 doit confirmer la réception des données reçues de M1 même si elle n’a plus de données à
envoyer.
26 a. La taille maximale des données transmises est de 10 octets (soit des paquets de 50 octets).
b. A avait 15 octets à transmettre, B en avait 25.
Exercices Titan
27 a.
def envoieAvecBitAlterne(donnee):
bitDeControle = 0;
donneeAEnvoyer = None
b.
def envoiePaquet(a,b):
return b
import random
def envoiePaquet(a, b):
return random.randint(0, 1)
28 a.
Direction 1er octet Taille Numéro de séquence Reçu ?
A->B 0 10 0 Oui
B→A 0 10 11 Oui
A→B 11 5 11 Non
B→A 11 10 11 Oui
Chapitre 10 – Réseau 15
A→B 11 10 21 Oui
B→A 21 10 21 Oui
A→B 21 10 31 Oui
B→A 31 10 31 Oui
A→B 31 10 41 Oui
B→A 41 2 41 Oui
A→B 41 10 43 Oui
B→A 0 0 51 Oui
A→B 51 3 43 Oui
B→A 0 0 54 Oui
A→B 0 0 43 Oui
b.
A->B 0 10 0 Oui
B→A 0 10 11 Oui
A→B 11 5 11 Non
B→A 11 10 11 Oui
A→B 11 10 21 Oui
B→A 21 10 21 Non
A→B 21 10 21 Non
B→A 21 10 21 Oui
A→B 21 10 31 Oui
B→A 31 10 31 Oui
A→B 31 10 31 Non
Chapitre 10 – Réseau 16
B→A 31 10 31 Non
A→B 31 10 31 Oui
B→A 41 2 41 Oui
A→B 41 10 43 Non
B→A 0 0 41 Oui
A→B 41 10 43 Oui
B→A 0 0 51 Non
A→B 51 3 43 Non
B→A 0 0 51 Oui
B→A 0 0 54 Oui
A→B 0 0 43 Non
B→A 0 0 54 Non
A→B 0 0 43 Oui
A 2
B 1
C 1
D 5
E 2
F 2
Total 13
b. Les routeurs sont les machines qui permettent d’envoyer les paquets depuis une machine vers une
autre. Dans le schéma les machines A, D et E sont des routeurs.
Chapitre 10 – Réseau 17
c. Les paquets à envoyer sont les suivants, les tailles sont indiquées entre parenthèses.
1 P1
2 P2 P1
3 P3,P6 P2
4 P4,P7 P6 P3
5 P6 P5,P8,P14 P7 P4
6 P7 P14,P9,P15 P8 P5
7 P8 P9,P15,P10,P16 P14
8 P15,P10,P16,P11 P9
9 P9 P10,P16,P11,P12 P15
10 P16,P11,P12,P13 P10
12 P12,P13 P11
14 P12 P13
15 P13
Espace de 0 0 0 3*1500=4500 0 0
stockage octets
nécessaire
Chapitre 10 – Réseau 18
Chapitre 11 – Interaction sur le Web
• la syntaxe et le langage JavaScript, qui « ressemble » à Python mais est suffisamment différent
pour être source de nombreuses erreurs et incompréhensions ;
• la syntaxe et le langage de patrons HTML jinja que nous avons choisi d’utiliser pour faciliter la
création de sites Web dynamiques.
Aussi avons-nous pris le parti de focaliser le chapitre sur l'analyse de code existant, en demandant aux
élèves des modifications localisées, et ainsi de limiter la rédaction de code nouveau. C’est en
particulier le cas dans l’activité d’introduction, qui a surtout pour but de comprendre la mécanique
générale de l’interaction sur le Web et la façon dont les informations sont transmises du serveur au
client et du client au serveur.
À ce propos, la partie Bonus de l’activité d’introduction, présentée ci-dessous, peut servir de base à un
TP à la place du TP proposé sur l’explorateur de fichiers, ce dernier étant d’un niveau plus avancé. Par
ailleurs le TP sur l’explorateur de fichiers peut être avangageusement traité après le chapitre 12 qui
introduit le système de fichiers et les notions de chemin d’accès.
1 L’architecture du Web
• Chaînes : les chaînes multi-lignes sont entre guillemets inversées "`". Celles-ci sont également
des chaînes de formattage (comment f"…" en Python), mais les expressions sont indiquées par
${expression} au lieu de {expression}.
• Chaînes et Tableaux : La longueur d’une chaîne ou d’un tableau est s.length pour une chaîne,
t.length pour un tableau au lieu de len(s), len(t); L’indexation à partir de la fin (t[-1] pour le dernier
élément) n’existe pas en JavaScript.
• Dictionnaires : les clés n’ont pas besoin d’être entre guillemets (mais elles peuvent l’être si
besoin, par exemple si elles contiennent des espaces) ; par contre, on ne peut pas utiliser des clés
autres que des chaînes de caractères. L’accès à un élément du dictionnaire se fait en général par
la notation d.a, mais on peut aussi utiliser d["a"], comme en Python. Si la clé contient des espaces,
c’est la seule façon d’accéder à la valeur associée. Il existe un autre type, Map, pour créer des
dictionnaires avec des clés de type quelconque, mais il n’est pas utilisé dans ce cours.
• Conditionnelles : L’expression conditionnelle est entre parenthèses ; Il n’y a pas de elif, on peut
simplement écrire else if.
• Boucles bornées : Il existe plusieurs variantes de la boucle for. Nous présentons uniquement la
boucle avec indice explicite for (i = 0; i < n; i +=1). L’équivalent de la boucle Python for e in t s’écrit
for (e of t) en JavaScript. for (e in t) est légal en JavaScript, mais e parcourt les indices du tableau au
lieu de leurs éléments, ce qui est une source de confusion.
• Exceptions : Il existe une instruction try similaire à Python, mais elle n’est pas nécessaire dans ce
cours.
Prérequis – TEST
1 b et d ; 2 b et c ; 3 c ; 4 b ; 5 b.
Répondre à un formulaire
par
conversation.append(message)
L’ensemble du code solution est dans le dossier activite_web_corrige, dont le contenu est le suivant :
• dynserveur.py : fichier original dans lequel le code de nouveau_message.py a été intégré. Lancer
python dynserveur.py et ouvrir la page http://localhost:8000/conversation.html pour tester l’ajout de
messages.
Bonus
Le dossier activite_web_corrige contient une troisième version du code permettant de gérer plusieurs
conversations.
Pour cela on utilise un second formulaire, dont le patron est liste3.html, qui permet de créer une
nouvelle conversation et de lister les conversations en cours.
L’URL d’une conversation utilise maintenant un paramètre qui est le nom de la conversation :
http://localhost:800/conversation.html?nom=MaConversation.
Le patron conversation3.html est une version modifiée de conversation2.html où l’on ajoute le nom de la
conversation dans le titre et un champ caché dans le formulaire pour transmettre ce nom au serveur
lorsque l’on envoie un nouveau message.
Le fichier dynserveur3.py est une version modifiée de dynserveur2.py :
• Le tableau conversation des messages est remplacée par un dictionnaire conversations, dont chaque
clés est le nom d’une conversation et la valeur les messages de cette conversation (tableau) ;
• Deux nouvelles fonctions, page_liste et creer_conversation, sont liées aux pages dynamiques
/liste.html et /creer_conversation ;
• La fonction page_liste affiche le patron liste3.html et lui transmet la liste des noms de conversations
(mais pas leur contenu) ;
• La fonction creer_conversation récupère le nom de conversation entré par l’utilisateur, crée une
nouvelle conversation avec ce nom et renvoie la page de cette conversation.
Lancer python dynserveur3.py et ouvrir la page http://localhost:8000/liste.html pour créer une conversation et
ajouter des message. On peut vérifier, en ouvrant plusieurs onglets, qu’il est possible d’échanger des
messages indépendamment dans plusieurs conversations.
On remarquera cependant que si l’on créer une nouvelle conversation sous un nom existant, ladite
conversation est vidée de ses anciens messages. On remarquera aussi que le serveur « se plante » si on
charge l’URL conversation.html sans nom de conversation en paramètre ou avec un nom de conversation
inexistant. Le fichier dynserveur4.py résoud ces problèmes en renvoyant des messages d’erreur.
QCM (CHECKPOINT)
1 b, c et e ; 2 c ; 3 a 1, b 3, c 4, d 2 ; 4 c ; 5 a 5, b 4, c 3, d 2, e 1 ; 6 a, b et d ; 7 a 6, b 4, c 2, d 5, e 3,
f 1 ; 8 b ; 9 a.
Exemple d’images d’écrans. Si l’on saisit un nom de dossier invalide, on obtient une erreur 404 «
ressource non trouvée ».
juste après la ligne 29 où la variable fichiers est initialisée avec la liste des noms de fichiers.
1. Note : Pour cette partie, on utilise le fichier dynserveur2.py fourni aux élèves.
Les trois patrons utilisent la variable nom qui represente le nom du fichier. Le patron texte.html utilise
pageDynamique('/texte', fichier_texte)
pageDynamique('/image', fichier_image)
pageDynamique('/autre', fichier_autre)
• si c’est un autre type de fichier, on crée un lien vers la page dont le nom correspond au type
(texte, image, autre) et on lui passe le nom du fichier.
<h2>Contenu de {{dossier}}</h2>
<ul>
{% for f in liste %}
<li>
{% if f.type == 'dossier' %}
<a href="liste?dossier={{dossier}}/{{f.nom}}">{{f.nom}}/</a>
{% else %}
<a href="{{f.type}}?dossier={{dossier}}&nom={{f.nom}}">{{f.nom}}</a>
{% endif %}
</li>
{% endfor %}
</ul>
Attention : Ce nouveau patron est dans le fichier liste2.html, et il faut donc modifier dynserveur2.py pour
le référencer au lieu de liste.html.
On peut alors tester la navigation et l’affichage des différents types de fichiers en lançant python
dynserveur2.py et en allant sur la page http://localhost:8000/liste.
1. Note : Pour cette partie on crée le fichier dynserveur3.py à partir de dynserveur2.html et on copie les
patrons des trois types de fichiers dans texte3.html, image3.html et autre3.html. Il faut donc commencer par
changer les noms des patrons dans les appels à la fonction get_template de dynserveur3.py.
Les trois patrons modifiés sont les suivants. On observe qu’ils ne contiennent que la partie de code
HTML qui sera « collée » à l’intérieur de explorateur.html par la requête AJAX :
• texte3.html
<h2>Fichier {{nom}}</h2>
<hr>
<pre>
{{contenu}}
</pre>
• image3.html
<h2>Fichier {{nom}}</h2>
<hr>
<img src="{{dossier}}/{{nom}}">
• autre3.html
<h2>Fichier {{nom}}</h2>
<hr>
<p>Le contenu de ce fichier ne peut pas être affiché.</p>
pageDynamique('/fichier', fichier)
• image3.html
• autre3.html
Pour la liste des fichiers, le patron liste3.html est modifié comme suit :
<h2>Contenu de {{dossier}}</h2>
<ul>
{% for f in liste %}
{% if f.type == 'dossier' %}
<li onclick="charger_liste('{{dossier}}', '{{f.nom}}')">
<img src="/images/dossier-32.png">{{f.nom}}/
</li>
Pour tester l’explorateur de fichiers, lancers la commande python dynserveur3.py et ouvrir la page
http://localhost:8000/explorateur.html.
Exercices
Exercices Newbie
1
nom de
schema machine chemin d’acces ancre paramètres
1 http la.com //x//y//z
Notes :
• Le schema https dénote une connexion sécurisée. Les URLs 2 et 3 sont donc sécurisées.
• L’URL 1 est valide. Le chemin d’accès //x//y//z sera en général interprété comme /x/y/z.
• L’URL 2 est invalide car il n’existe pas de machine de nom ici. Les machines sur Internet ont des
noms avec au moins deux composantes (comme google.fr). Cette URL ressemble à
https://ici.la.com/ailleurs qui, elle, est correcte. Cette confusion est souvent utilisée par les tentatives
de hameçonnage.
• L’URL 3 est valide dans son principe, car on peut mettre un numéro IP à la place du nom de
machine. Cependant, dans IPv4, les 4 nombres sont compris entre 0 et 255, donc 123.456.78.90
n’est pas un numéro IPv4 valide.
• Les URL 4 et 5 sont valides, mais attention, dans l’URL 5 l’ancre est abc#xyz, et pas, par exemple
xyz : il ne peut y avoir qu’une ancre dans une URL.
– Dans l’URL 7, on a bien deux paramètres x qui vaut 1 et y qui vaut 2 (les paramètres sont
séparés par &) ;
– Dans l’URL 8, l’ancre apparait avant ce qui semble être les paramètres x et y, mais en fait
les paramètres doivent apparaître avant l’ancre. Ici, l’ancre vaut reponse?x=1&y=2 et il n’y
a pas de paramètres ;
– Dans l’URL 9, les paramètres sont bien avant l’ancre. Celle-ci vaut reponse et les
paramètres sont x qui vaut 1 et y qui vaut 2.
Le code Python ci-dessous est dans le fichier exo1-corrige.py et utilise la bibliothèque urlparse pour
confirmer ces réponses :
from urllib.parse import urlparse
print(urlparse('http://la.com//x//y//z'))
print(urlparse('https://ici/la.com/ailleurs'))
print(urlparse('https://123.456.78.90/12.345.678.90'))
print(urlparse('http://x.yz/#xyz'))
print(urlparse('http://x.yz/#abc#xyz'))
print(urlparse('http://base.fr/question?x=1?y=2'))
print(urlparse('http://base.fr/question?x=1&y=2'))
print(urlparse('http://base.fr/question/#reponse?x=1&y=2'))
print(urlparse('http://base.fr/question/?x=1&y=2#reponse'))
b.
POST /cherche/ HTTP/1.1
Host: un.serveur.fr
Content-Length: 26
nom=world-wide-web&lang=fr
3 a.
GET q?n=10&x=xyz HTTP/1.1
Host: ici.com
b.
POST q HTTP/1.1
Host: ici.com
Content-Length: 10
n=10&x=xyz
4
<html><head><title>La recherche a échoué</title>
</head><body>...
Le code 403 indique une erreur (car il commence par 4). L’erreur ``Forbidden'' indique que l’accès à la
ressource n’est pas autorisé (ce qui ne veut pas dire qu’elle n’existe pas).
5 L’ordre des balises de fin </i> et </b> est incorrect. Il manque la balise de fin du premier élément de
liste, et la balise de fin du paragraphe du second élément de liste.
6 a.
<html>
<head>
<title>Exercice 6</title>
</head>
<body>
<h2>Instructions</h2>
<p>Pour tester un document <i>HTML</i> :</p>
<ol>
<li>Ouvrir <a href="https://jsfiddle.net">jsfiddle.net</a></li>
<li>Taper le texte dans la fenêtre <b>HTML</b></li>
<li>Cliquer le bouton <b>Run</b>
<ul>
<li>Observer le résultat dans la fenêtre <b>Result</b></li>
<li>Corriger le code HTML si besoin</li>
</ul>
</li>
</ol>
</body>
</html>
b.
h2 {
font-family: sans-serif;
color: blue;
}
b{
font-family: sans-serif;
border: solid black 1px;
background-color: grey;
color: white;
}
.highlight {
background-color: salmon;
}
ol {
background: gold;
}
ul {
background: yellow;
}
li {
background: lightyellow;
7 a.
<html>
<head>
<title>Exercice 7</title>
</head>
<body>
<form action="https://qwant.com/" method="GET">
Rechercher : <input type="text" name="q">
b. La réponse à une requête au moteur duckduckgo.com pour le mot nsi est de la forme
https://duckduckgo.com/?q=nsi&t=h_&ia=web. On en déduit le formulaire suivant, avec les champs cachés t
et ia :
<form action="https://duckduckgo.com/" method="GET">
Rechercher : <input type="text" name="q">
<input type="hidden" name="t" value="h_"><br>
<input type="hidden" name="ia" value="web"><br>
<input type="submit">
</form>
La page HTML avec les deux formulaires est dans le fichier exo7-corrige.html.
8 a.
<html>
<head></head>
<body>
<input type="text" id="op1" size="10" >+
<input type="text" id="op2" size="10" >
<input type="button" value="=">
<input type="text" id="res" size="10" >
</body>
</html>
Note : On peut ajouter le style suivant pour que le texte des champs de saisie soit aligné à droite :
<style type="text/css">
input { text-align: right; }
b.
<script>
// récupère la valeur de l'élément d'identifiant id
function val(id) {
return parseInt(document.getElementById(id).value, 10)
}
// change le contenu de l'élément d'identifiant id
function setValue(id, v) {
document.getElementById(id).value = v
}
// ajoute les deux valeurs et afficher le résultat
function add() {
op1 = val("op1")
op2 = val("op2")
if (isNaN(op1) || isNaN(op2))
setValue("res", 'erreur')
else
setValue("res", op1+op2)
}
</script>
Pour que la fonction add soit appelée, il faut modifier le bouton = comme suit :
<input type="button" value="=" onclick=add()>
10 a. La réponse du serveur est la suivante : code 301 (TLS Redirect) et le champ Location est
https://wikipedia.org.
Le navigateur affiche la page d’accueil du site Wikipedia, mais avec un cadenas qui
indique qu’il a chargé la page avec HTTPS et non pas HTTP comme spécifié dans l’URL originale.
HTTP/1.1 301 TLS Redirect
Location: https://wikipedia.org/
b. La réponse a pour code 302 (Found) et le champ Location est une autre page du site Wikipedia. Le
navigateur affiche cette page au lieu de l’URL que l’on a entrée. (On obtient une page différente,
choisie au hasard, à chaque requête).
HTTP/1.1 302 Found
Location: https://fr.wikipedia.org/wiki/Charles_Mwando_Nsimba
c. Conclusion : lorsque le serveur retourne un code 301 ou 302, le navigateur fait une requête pour la
page dont l’URL est dans le champ Location de la réponse.
Le code ci-dessus affiche l’URL d’une page Wikipédia choisie au hasard, et non
``https://fr.wikipedia.org/wiki/Spécial:Page_au_hasard''.
b.
response = requests.get("https://fr.wikipedia.org/wiki/Spécial:Page_au_hasard",
allow_redirects=False)
Le code ci-dessus montre la différence entre les deux URLs (celle de la requêtre, et celle de
redirection).
Le fichier exo11-requetes-corrige.py contient le code ci-dessus pour exécution dans Python.
12 a.
ul {
border: 1px solid red;
}
li {
border: 1px solid blue;
}
c.
ul:hover {
border: 1px solid red;
}
ul:hover li {
border: 1px solid blue;
}
13 a.
<style>
#marge {
margin: 10px;
padding: 0;
}
#pad {
margin: 0;
padding: 10px;
}
</style
14 a.
<html>
<head>
<meta charset="utf-8">
<title>Une conversation</title>
</head>
<body>
<p class="moi">Bonjour</p>
b.
<style type="text/css">
body {
background-color: rgb(250,250,250);
}
p{
/*border: 5px solid black;*/
border-radius: 15px;
padding: 5px;
color: white;
font-family: sans-serif;
}
p.moi {
background-color: blue;
margin-left: 200px;
text-align: right;
}
p.autre {
background-color: darkgrey;
margin-right: 200px;
Bonus : On peut aisément ajouter deux boutons pour aller à la première et la dernière étape :
<input type="button" onclick="change(1)" value="|<">
<input type="button" onclick="change(5)" value=">|">
16 a. L’élément <span> sert à isoler le nombre de degrés N dans le texte N degrés : de cette façon, en
lui attribuant un attribut id (temperature) et en le sélectionnant avec la fonction document.getElementById
(dans la fonction modifierTexte), il est ainsi possible de modifier le nombre de degrés sans modifier le
reste du texte.
L’élément <input> dispose d’un attribut oninput="mettreTemperatureAJour()" : cela signifie qu’il réagit aux
événements de type input, en appelant la fonction mettreTemperatureAJour à chaque fois que la valeur de
l’élément <input> est modifiée. C’est par ce mécanisme que la valeur du <span> est modifiée lorsque la
tirette est déplacée (cf. le code de la fonction mettreTemperatureAJour).
b. On commence par ajouter un élément HTML pour afficher le résultat de la conversion :
<p>
Température en degrés Fahrenheit : <span id="temperature-convertie"></span>
</p>
Comme on l’a vu dans la question précédente, à chaque fois que la tirette est déplacée, la fonction
mettreTemperatureAJour est appelée (occasion 1). En outre, celle-ci est également appelée lorsque la page
HTML a fini d’être chargée : l’attribut onload="initialiser()" de l’élément <body> indique que la fonction
initialiser est appelée à cette occasion, et celle-ci appelle à son tour la fonction mettreTemperatureAJour
(occasion 2).
On modifie donc le code de la fonction mettreTemperatureAJour, afin que celle-ci convertisse la valeur de
l’élément <input> de degrés Celsius en degrés Farenhneit et affiche le résultat de la conversion dans
l’élément <span> que l’on vient d’ajouter :
modifierTexte('temperature-convertie', (temperature / (5/9)) + 32);
c. On commence par ajouter un élément HTML représentant le bouton de conversion. Afin de réagir
aux clics, on lui ajoute un attribut onclick, afin que chaque clic sur le bouton appelle une fonction
s’occupant d’inverser le sens de la conversion (que l’on définit plus bas) :
<input type="button" value="F <-> C" onclick="inverserSensConversion()">
• la formule utilisée pour convertir la valeur de l’élément <input> doit être adaptée.
Afin de pouvoir facilement modifier les termes Celsius et Fahrenheit, on remplace ceux-ci par des
• on ajoute une fonction mettreUnitesAJour, qui remplace le contenu des deux <span> devant contenir
des unités par les bonnes valeurs ;
• on ajoute la fonction inverserSensConversion, qui modifie le sens, met à jour les deux unités, et met à
jour la valeur convertie ;
• on modifie la fonction initialiser afin d’initialiser les unités une fois la page HTML chargée.
// Initialement, la conversion se fait de degrés Celsius vers degrés Farenheit.
let celsiusVersFarenheit = true;
function mettreTemperatureAJour() {
const temperature = valeurTemperature();
function mettreUnitesAJour() {
if (celsiusVersFarenheit) {
modifierTexte('unite-avant-conversion', 'Celsius');
modifierTexte('unite-apres-conversion', 'Farenheit');
}
else {
modifierTexte('unite-avant-conversion', 'Farenheit');
modifierTexte('unite-apres-conversion', 'Celsius');
}
}
function inverserSensConversion() {
// On inverse le sens de la conversion.
celsiusVersFarenheit = !celsiusVersFarenheit;
function initialiser() {
mettreTemperatureAJour();
mettreUnitesAJour();
17 a.
<html>
<head>
<meta charset="utf-8">
<title>Boite à secrets</title>
</head>
<body>
<h2>Boîte à secrets</h2>
<form action="/secret" method="POST">
Nom : <input type="text" name="nom"><br>
Mot de passe :
<input type="password" name="motdepasse"><br>
Secret à enregistrer :
<input type="text" name="secret"><br>
<input type="submit" value="Go !">
</form>
<hr>
<p id="message">{{message}}</p>
</body>
</html>
b.
# dictionnaire des secrets : la clé est le nom,
# la valeur est un tuple (mot de passe, secret)
if nom in secrets:
# récuperer le contenu du secret
mdp, s = secrets[nom]
if mdp == motdepasse:
# enregistrer le nouveau secret (si non vide)
if secret != '':
secrets[nom] = (motdepasse, secret)
msg = 'ancien secret : ' + s
else:
msg = 'mot de passe incorrect !'
else:
# créer un nouveau secret pour ce nom
secrets[nom] = (motdepasse, secret)
msg = 'Secret enregistré'
# retourner la page avec le message
template = get_template('secret.html')
return OK(render(template, {'message': msg}))
b. On modifie le code précédemment ajouté pour ajouter des attributs onclick aux différents boutons :
<input type="button" onclick="chiffre(7)" value="7">
<input type="button" onclick="chiffre(8)" value="8">
<input type="button" onclick="chiffre(9)" value="9">
<input type="button" onclick="operation('/')" value="/">
<br>
c. On complète le code des fonctions chiffre, operation, egale, et effacer afin de modifier les variables déjà
• operandeDroite doit contenir la valeur entrée dans la calculatte après qu’une opération ait été
selectionnée ;
• operationAEffectuer doit contenir l’opération sélectionnée (dont le résultat sera calculé si le bouton =
est cliqué) ;
• La valeur affichée doit être mise à jour lorsqu’un chiffre est ajouté, qu’une opération est
sélectionnée (afin de pouvoir entrer la seconde opérande), que l’affichage est effacé, ou qu’un
résultat est calculé.
function chiffre(n) {
// On "décale" la valeur déjà entrée vers la gauche
// (l'unité devient la dizaine, la dizaine devient la centaine, etc)
// et insère le chiffre sélectionnée à la place de l'unité.
operandeDroite = (operandeDroite * 10) + n;
modifierResultat(operandeDroite);
}
function operation(op) {
operationAEffectuer = op;
function effacer() {
// Si une entrée d'opération était en cours, elle est annulée.
operationAEffectuer = '';
operandeGauche = 0;
operandeDroite = 0;
modifierResultat(0);
}
function egale() {
// On calcule le résultat de l'opération à effectuer.
// Si aucune opération n'a été selectionnée et que le bouton '=' est appuyé,
// alors la valeur actuelle n'est pas modifiée.
let resultat = operandeDroite;
if (operationAEffectuer == '+') {
resultat = operandeGauche + operandeDroite;
}
else if (operationAEffectuer == '-') {
resultat = operandeGauche - operandeDroite;
}
else if (operationAEffectuer == '*') {
resultat = operandeGauche * operandeDroite;
}
else if (operationAEffectuer == '/') {
resultat = operandeGauche / operandeDroite;
}
modifierResultat(resultat)
}
À l’intérieur du dictionnaire (lui-même contenu dans un tableau), on retrouve les clés suivantes :
• word : une chaîne de caractères ;
• meanings : un tableau, contenant un autre dictionnaire qui possède les clés suivantes :
– definitions : un tableau, contenant un autre dictionnaire qui possède les clés suivantes :
langue = variables["langue"]
if reponse.status_code != 200:
return NotFound(f"Le mot \"{mot}\" n'a pas été trouvé.")
template = env.get_template("dictionnaire.html")
return OK(template.render(variables_patron))
pageDynamique("/dictionnaire", page_dictionnaire)
On crée également un fichier dictionnaire.html, qui contient le patron d’une page permettant d’afficher le
contenu d’une requête de façon structurée :
<html>
<head>
<meta charset="utf-8">
En visitant la page /dictionnaire?mot=M&langue=L, où M est un mot et L est une langue, on peut désormais
visualiser les définitions du mot (s’il existe).
c. On commence par ajouter un formulaire dans le corps (<body>) du patron dictionnaire.html.
Celui-ci doit permettre d’entrer un mot, de choisir la langue dans lequel celui-ci doit être cherché, et
d’exécuter la recherche, c’est-à-dire d’effectuer une requête HTTP GET avec une URL de la forme
/dictionnaire?mot=M&langue=L, où M est le mot et L est la langue. Pour cela, on fait attention à bien
spécifier l’attribut name sur le champ de texte (pour le mot) et les boutons radio (pour la langue), ainsi
que les attributs action et method sur l’élément <form>.
On pré-sélectionne également le bouton radio correspondant à la langue de la requête actuelle (avec un
attribut checked, sans valeur).
<form action="/dictionnaire" method="GET">
<label>Mot : <input type="text" name="mot"></label>
<input type="submit" value="Chercher"><br>
<label><input type="radio" name="langue" value="fr"
{% if langue == "fr" %} checked {% endif %}> Français</label>
<label><input type="radio" name="langue" value="es"
{% if langue == "es" %} checked {% endif %}> Espagnol</label>
On sépare également le formulaire de recherche de l’affichage du résultat par une barre horizontale
(un élément <hr>).
Enfin, on modifie la façon dont on affiche chaque synonyme/antonyme : on les place dans des liens
(éléments <a>) qui pointent vers une URL effectuant une requête pour ce mot (dans la langue
courante).
{% for s in d.synonyms %}
<a href="/dictionnaire?langue={{ langue }}&mot={{ s }}">{{ s }}</a>
{% endfor %}
et
{% for a in d.antonyms %}
<a href="/dictionnaire?langue={{ langue }}&mot={{ a }}">{{ a }}</a>
{% endfor %}
20 a. On remplace le code qui affiche le nom et le prix d’un article par un formulaire contenant
également un bouton pour ajouter l’article au panier (dans le patron articles.html).
On y inclut un champ de formulaire caché (nommé article_id) qui contient l’identifiant de l’article à
b. Afin d’ajouter chaque article ajouté dans le panier (représenté par un tableau d’identifiants
d’articles, article_ids_dans_panier), il faut que le serveur traite les requêtes HTTP POST pour le chemin
/ajouter_article (cf. les attributs de l’élément <form> que l’on vient d’ajouter au patron). À chaque requête
pour ce chemin, la variable article_id contiendra l’identifiant de l’article à ajouter au tableau
article_ids_dans_panier.
Pour cela, on s’inspire de la façon dont la page dynamique /vider_panier est définie dans panier.py : la
fonction page_vider_panier s’occupe de vider le tableau, mais elle délègue la génération de la réponse
(c’est-à-dire de la page HTML ou de l’erreur devant être renvoyée au client) à la fonction page_articles.
def page_ajouter_article(url, variables):
"""Retourne une réponse à une requête HTTP pour le chemin /ajouter_article."""
global article_ids_dans_panier
if "article_id" in variables:
article_ids_dans_panier.append(variables["article_id"])
pageDynamique('/ajouter_article', page_ajouter_article)
• tenter d’extraire le numéro de session session_id des variables fournies lors d’une requête HTTP.
def session_existe(session_id):
"""Retourne True s'il existe une session portant ce numéro, False sinon."""
global contenu_panier_par_session
def extraire_session_id(variables):
"""
Retourne le numéro de session de `variables` s'il est présent et valide.
Si celui-ci n'est pas présent dans `variables`, retourne "absent".
Si celui-ci est invalide ou inconnu, retourne "invalide".
"""
if session_existe(session_id):
return session_id
return "invalide"
if session_id == "invalide":
return NotFound("Numéro de session inconnu")
elif session_id == "absent":
article_ids_dans_panier = []
if session_id == "invalide":
return NotFound("Numéro de session inconnu")
elif session_id != "absent":
contenu_panier_par_session[session_id] = []
• crée, si nécessaire, une nouvelle session, et communique son numéro au patron (afin de l’inclure
dans les futures pages HTML générées).
global article_ids_dans_panier
if "article_id" in variables:
contenu_panier_par_session[session_id].append(variables["article_id"])
On modifie enfin les deux patrons afin d’inclure le numéro de session dans tous les formulaires
(<form>) s’il existe. Pour cela, on utilise un élément de formulaire caché (type="hidden") dont le nom est
session_id.
Activité d’introduction
Pour pouvoir effectuer l’activité d’introduction, il faut télécharger l’archive zip activite_corrige.zip, qui a
le même contenu que l’archive zip activite.zip fournie aux élèves, puis, une fois celle-ci décompressée,
aller dans le répertoire activite_corrige et exécuter le script init.sh comme expliqué dans le fichier lisez-
moi.txt.
• tresor.txt : https://fr.wikipedia.org/wiki/Objets_magiques_de_Harry_Potter et
https://www.owlapps.net/owlapps_apps/articles?id=1096176
• texte.txt : https://www.unicef.fr/dossier/convention-internationale-des-droits-de-lenfant
Les noms de personnes dans les fichiers g1.txt, g2.txt, eleves.txt et notes.txt ont été produits par le
générateur en ligne https://www.nomsdefantasy.com/frnames/short/.
Prérequis -TEST
1 c ; 2 b ; 3 b ; 4 a et c ; 5 b ; 6 e.
1. Le fichier tresor.txt est dans activite/parla/lycee/premiere/NSI. On suppose que l’on est déjà dans le
répertoire activite (comme indiqué dans le fichier lisez-moi.txt).
On peut donc accéder au répertoire souhaité directement ainsi :
% cd parla/lycee/premiere/NSI
% ls
% cat tresor.txt
2.
Chemins d’accès
Note : Si la destination de cp est un répertoire, le fichier y est copié sous le même nom. Les deux
commandes de copie ci-cessus peuvent donc s’écrire :
% cp parla/lycee/premiere/NSI/tresor.txt travail
% cp parici/Ouest/mystere.txt travail
Enfin on va dans le répertoire travail pour renommer le fichier mystere.txt en trouve.txt, on remonte et on
détruit le fichier original :
% cd travail
% mv mystere.txt trouve.txt
% cd ..
% rm parici/Ouest/mystere.txt
Fichiers texte
Doublons
Note : On suppose que l’on est dans le répertoire travail.
Pour savoir s’il y a des doublons, on va compter le nombre de lignes du fichier tresor.txt et d’un fichier
temporaire dans lequel on aura retiré les doublons, grâce à la commande uniq. Si le nombre de lignes
est le même, il n’y a pas de doublons, sinon, la différence nous indiquera le nombre de doublons.
Cependant, la commande uniq élimine les doublons seulement s’ils sont contigus. On va donc d’abord
trier le fichier.
% sort tresor.txt | uniq > tmp1
% wc tresor.txt tmp2
La première colonne est le nombre de lignes (la seconde le nombre de mots et la troisième le nombre
de caractères). En en déduit qu’il y a des doublons dans tresor.txt.
On compte 14 différences.
Note : Le nombre de lignes de tresor.txt était 62, celui de la version sans doublons 49, soit une
différence de 13, et non pas 14. Cela est dû au fait que la dernière ligne du fichier tresor.txt ne se
termine pas par une marque de fin de ligne et n’est pas comptée par wc.
Pour ne pas laisser trainer les fichiers temporaires, on les détruit :
% rm tmp1 tmp2
Villes françaises
Le fichier trouve.txt contient des noms de villes avec leur pays et leur population. Pour extraire les
villes françaises on va afficher les lignes qui contiennent France, puis on va les trier :
% grep France trouve.txt | sort
Droits d’accès
Les deux dernières commandes émettent un message d’erreur indiquant que l’on n’a pas la permission
pour l’opération demandée.
• cat secret.txt consiste à lire le contenu de secret.txt, cela signifie donc que l’on n’a pas le droit de
lecture.
On observe que les trois premières lettres de la commande ls -l sont soit un tiret, soit un r, soit un w. Le
r signifie lecture (read), tandis que le w signifie écriture (write) :
• --w pour secret.txt indique que l’on peut y écrire mais pas le lire ;
2. Pour pouvoir lire le contenu de secret.txt il faut lui ajouter le droit de lecture :
% chmod u+r secret.txt
% cat secret.txt
Pour copier texte.txt dans protege.txt, il faut ajouter le droit d’écriture à protege.txt, que l’on peut lui
retirer après la copie :
% chmod u+w protege.txt
% cp texte.txt protege.txt
% chmod u-w protege.txt
% cat protege.txt
La commande qui utilise le plus de temps CPU est la seconde, qui est le programme Python
permettant d’exécuter des notebooks Jupyter.
On peut compter le nombre de processus avec la commande ps a | wc.
Note : Sous Windows, même avec Cygwin, la commande ps n’affiche pas le temps d’exécution de
chaque processus, mais seulement son heure de début (colonne STIME). On peut cependant avoir des
informations détaillées sur les processus en lançant le Task Manager (taper « Task » dans la barre de
recherche du menu Start), et en cliquant sur « Plus de détails » dans la fenêtre du Task Manager. On a
alors le pourcentage de CPU utilisé par chaque programme et, dans l’onglet « Historique », le temps
d’exécution.
2. Le programme boucle.py affiche un message dans une boucle infinie. Il faut taper Control-C pour
l’arrêter.
Lorsqu’on l’exécute, on observe qu’il consomme beaucoup de CPU (on peut utiliser la commande ps a
| grep boucle.py pour n’afficher que la ligne intéressante). Voici les résultats de la commande à quelques
secondes d’intervalle :
97275 s000 R+ 0:00.98 python boucle.py
...
97275 s000 S+ 0:01.70 python boucle.py
Note : Sur une machine Linux ou MacOS, la commande top affiche en temps réel les processus par
ordre d’utilisation décroissante du CPU. Voici l’affichage après quelques secondes. On voit que le
programme Python utilise 58,5% du CPU. On peut obtenir la même information sous Windows avec
le Task Manager, comme indiqué ci-dessus.
PID COMMAND %CPU TIME #TH #WQ #PORTS MEM PURG CMPRS PGRP
97354 python3.7 58.5 00:06.37 1/1 0 10 4376K 0B 0B 97354
97352 top 6.7 00:03.33 1/1 0 26 9164K 0B 0B 97352
97349 mdworker_sha 0.0 00:00.09 4 1 61 7472K 0B 0B 97349
97348 com.apple.iC 0.0 00:00.13 4 3 64 3352K 0B 0B 97348
97236 bash 0.0 00:00.04 1 0 21 956K 0B 0B 97236
97235 login 0.0 00:00.04 2 1 31 1260K 0B 0B 97235
...
Dans la fenêtre où s’exécute le programme, on a l’affichage suivant, qui montre que le programme a
été arrêté :
...
Boucler ou ne pas boucler, telle est la question...
Boucler ou ne pas boucler, telle est la question...
Terminated: 15
%
Note : 15 est le numéro de « signal » qui a été envoyé au programme. Il existe plusieurs signaux qui
peuvent être interprétés différemment par le programme. 15 est le signal SIGTERM de demande « polie »
de terminaison. Le processus doit s’arrêter, mais a la possibilité d’effectuer une terminaison « propre
», par exemple en sauvegardant des fichiers.
Il existe aussi le signal 9, SIGKILL, qui ne peut être ignoré par le programme et doit être réservé aux cas
extrêmes. La commande kill -9 97361 enverrait ce signal dans notre exemple.
QCM (CHECKPOINT)
1 a, b et c ; 2 a et c ; 3 b et d ; 4 c ; 5 a et d ; 6 b, c, g et h ; 7 d ; 8 b ; 9 b ; 10 c ; 11 c ; 12 b ; 13 b ; 14
d ; 15 c.
Exercices
Exercices Newbie
2 Le système attribue des tranches de temps à chacun des programmes en cours d’exécution. Si le
programme ne termine pas son exécution pendant sa tranche de temps, il est suspendu, et le système
active un autre programme pour la prochaine tranche de temps.
3
• Classement Alexa d’Octobre 2020 : Ubuntu, RedHat, Debian, CentOS.
Linux est un système d’exploitation libre et gratuit, qui permet donc à tout un chacun d’utiliser le code
source pour réaliser une version dérivée. Ces versions dérivées (aussi appelées distributions) se
distinguent souvent seulement par les applications qu’elles incluent.
def f():
""" calculer la somme des n premiers nombres """
s=0
for i in range(n):
s = s+i
c. Sur l’ordinateur utilisé pour cet exercice, le nombre d’opérations élémentaires Python exécutables
en 1/60e de seconde est d’environ 250000.
5 a. L’affichage est le suivant (ls affiche les fichiers par ordre alphabétique) :
e
f2
b. f7 et f8 sont dans le répertoire g. Il faut donc remonter au répertoire parent puis descendre dans c puis
dans g :
% cd ../c/g
c. Le chemin relatif de b par rapport à g est ../../b : il faut remonter de deux niveaux puis descendre
dans b :
% ls ../../b
b. Avec la solution 1 :
% cd ../c/f
Avec la solution 2 :
% cd c/f
7 a. L’énoncé peut prêter à confusion : les fichiers « en-dessous » du répertoire c peut désigner soit le
sous-arbre de c, c’est-à-dire les dossiers f et g et leurs fichiers, soit seulement les fichiers (et non les
répertoires) en dessous de c, c’est-à-dire les fichiers f6, f7 et f8.
Pour la première interprétation, la séquence est la suivante :
% mkdir b/h
% mv c/f b/h
% mv c/g b/h
Note : L’exercice 19 introduit le « globbing », qui permet de référencer plusieurs fichiers : c/* désigne
tous les fichiers (et répertoires) qui sont immédiatement en-dessous de c. Avec cette notation,
l’exercice peut être résolu plus simpement ainsi :
Première interprétation :
% mkdir b/h
% mv c/* b/h
Deuxième interprétation :
% mkdir b/h
% mv c/*/* b/h
b. Dans la première interprétation, l’arbre final est le suivant (le nœud en vert est le nouveau
répertoire, ceux en rose sont ceux qui ont été déplacés) :
8 Un dossier ne peut être supprimé par la commande rmdir seulement s’il est vide. Il faut donc exécuter
les commandes suivantes :
% rm a/d/f3
% rm a/d/f4
% rmdir a/d
% rmdir a
Note : Avec le « globbing », les deux premières commandes peuvent être remplacées par rm a/d/*.
Mais il existe une manière encore plus radicale de procéder : la commande rm accepte une option, -r
(pour « rrécursif »), qui détruit les sous-répertoires avant les répertoires, en partant du bas de l’arbre.
Une solution à l’exercice est donc :
9
• cat f1 : permis car f1 a le droit de lecture ;
• f3: permis car f3 a le droit d’exécution, mais en général il faut écrire ./f3 pour exécuter un fichier
exécutable qui est dans le répertoire courant, car le système d’exploitation cherche les noms de
commandes dans un ensemble de répertoires prédéfinis ;
• cd d1: interdit car d1 n’a pas le droit d’exécution qui, pour les répertoires, est le droit de traverser
(ou d’aller dans) le répertoire ;
• f4: probablement interdit car f4 n’est pas une commande standard du système d’exploitation
(comme cp ou cat). Cependant, il est théoriquement possible qu’une commande de ce nom ait été
installée dans les répertoires de commandes du système ;
• : interdit car d2 n’a pas le droit d’écriture, on ne peut donc pas y créer de fichier (ni de
cp f1 d2/f
répertoire) ;
• : interdit car d1 n’a pas le droit d’exécution. Ceci peut sembler contre-intuitif car on a le
cp f2 d1/f
droit d’écriture dans d1, mais il s’avère que pour modifier le contenu d’un répertoire, il faut aussi
pouvoir le traverser ;
• : autorisé car même si l’on n’a pas le droit de traverser (ou aller dans) d1, on peut lire son
ls d1
contenu.
Note : On pourra tester les commandes dans le terminal en créant les fichiers et répertoires ainsi (la
commande echo F1 > f1 écrit la chaîne F1 dans le fichier f1, en créant f1 si besoin) :
% echo F1 > f1
10 Dans tous les cas il faut avoir le droit d’exécution sur d pour pouvoir accéder au fichier f1.
• ls -l d/f1 : aucune contrainte sur f1 ;
• mv d/f1 f2: droit de lecture sur d, aucune contrainte sur f1, droit d’écriture dans le répertoire
courant, f2 inexistant ou avec droit d’écriture ;
• grep toto f2 > d/f1 : droit de lecture sur f2, droit de lecture et d’écriture sur d, f1 inexistant ou avec
droit d’écriture.
11 En général non, car uniq ne supprime les doublons que s’ils sont sur des lignes successives. Le
résultat des deux commandes ne sera le même qui si cette condition est satisfaite dans le fichier f
(même si les lignes de f ne sont pas triées).
Tant que l’on ne tape pas entrée, le programme ne consomme pas de temps de calcul. En effet le
système sait que le programme attend une entrée, c’est-à-dire une interruption (voir Chapitre 1), et ne
lui alloue pas de tranche de temps tant que cette interruption ne se produit pas.
b. Dès que l’on tape Entrée, le programme entre dans la boucle et consomme du temps de calcul. Il
consommerait aussi du temps de calcul avec la boucle la plus simple possible :
while True:
pass
qui ne fait « rien », mais qui fait néanmoins travailler le processeur (comme la boucle a: JMP a en
assembleur – voir Chapitre 1).
14 a.
from timeit import timeit
def f():
""" calculer la somme des n premiers nombres """
s=0
for i in range(n):
s = s+i
b. Sur l’ordinateur utilisé pour rédiger ces réponses (Macbook Pro de 2018), la réponse est
Le temps d’exécution est légèrement plus important (5.45s au lieu de 5.13) mais n’est pas double.
d. Voici les résultats lorsque l’on augmente le nombre de lancements en parallèle :
• 4 lancements : 5,88s en moyenne
15 a.
m0 = taille_memoire()
n = 1000000
t = [i for i in range(n)]
m1 = taille_memoire()
mega = 1000000
b.
m0 = taille_memoire()
n = 1000000
t = [f'{i:8}' for i in range(n)]
m1 = taille_memoire()
mega = 1000000
print(f'début={m0/mega:.4}Mo, augmentation={(m1-m0)/mega:.4}Mo')
c.
m0 = taille_memoire()
n = 1000000
t = ['--------' for i in range(n)]
m1 = taille_memoire()
mega = 1000000
print(f'début={m0/mega:.4}Mo, augmentation={(m1-m0)/mega:.4}Mo')
• a. début=11.15Mo, augmentation=72.43Mo.
• a. début=11.11Mo, augmentation=8.086Mo.
Un tableau d’un million de nombres occupe 37 méga-octets. Un tableau d’un million de chaînes de 8
caractères toutes différentes occupe environ 72 méga-octets, mais un tableau d’un million d’alias de la
même chaîne occupe seulement 8 méga-octets.
De ce dernier chiffre on peut déduire que chaque élément de tableau nécessite 8 octets soit, sur une
machine avec des mots de 64 bits, un mot contenant l’adresse de la valeur de l’élément.
Le million de chaînes de 8 caractères nécessite donc 72-8 = 64 méga-octets, soit 64 octets pour chaque
chaîne. Enfin, le million de nombres entiers occupe 37-8 = 29 méga-octets, soit 31 octets par nombre.
m0 = taille_memoire()
• après t = [] : 53.89Mo
c. La taille de la mémoire utilisée n’a quasiment pas diminué après la dernière affectation, alors que le
tableau de 1 million d’éléments n’est plus accessible.
Plutôt que de « rendre » la mémoire au système d’exploitation, Python préfère la conserver pour la
réutiliser plus tard si nécessaire
• L’éditeur de texte signale que le fichier a été modifié « sur le disque » et demande quelle version
on veut conserver (cas le plus favorable).
De manière générale, l’accès simultané, en écriture, à un même fichier est une source potentielle de
perte de données car la gestion des conflits est à la charge des applications, le système ne fournissant
que quelques mécanismes de base, par exemple pour verrouiller un fichier temporairement.
19
% ls img*
% mkdir ../sauvegarde
% cp * ../sauvegarde
% grep import */*.py
% ls ../*/*.png ../*/*.jpg
20 a. Le plus simple est de retirer tous les droits et ensuite d’ajouter les droits de lecture-écriture pour
Si l’on veut cependant pouvoir accéder au dossier, retirer le droit d’écriture suffit :
% chmod a-w sauvegarde
Dans les deux cas, il faudra rétablir le droit lorsque l’on voudra modifier le contenu (pour ajouter un
fichier par exemple).
21 a. Comme dans l’exercice précédent, on retire tous les droits de lecture/écriture puis on ajoute le
droit de lecture pour le propriétaire, avant d’afficher le résultat :
% cat > protege
chmod a-rw $1
chmod u+r $1
ls -l $1
% chmod a+x protege
% ./protege f.txt
-r-------- 1 mbl staff 3 Jul 4 19:24 f.txt
Si l’on veut protéger le dossier sauvegarde (exercice précédent), il faut retirer la protection le temps de
la copie :
% cat > sauve
mkdir -f sauvegarde
chmod u+w sauvegarde
cp $1 sauvegarde
chmod a-w sauvegarde
echo fichier $1 sauvegardé
% chmod a+x sauve
% ./sauve f.txt
fichier f.txt sauvegardé
Bonus : On peut sophistiquer le procédé en ajoutant au nom de fichier la date, ce qui permet de garder
plusieurs versions successives du fichier. La commande date affiche la date, selon un format que l’on
peut personnaliser. Par exemple :
% date +%Y-%m-%d@%H:%M:%S
2021-07-04@16:05:35
le fichier sera préfixé par la date de la sauvegarde (une commande entre apostrophes inversées `’') est
remplacée par son résultat). Par exemple, la sauvegarde du fichier `toto.txt s’appelera 2021-07-04@16:05:35-toto.txt.
L’ordre de la date (année / mois / jour / heure) fera afficher les fichiers par ordre.
Exercices Titan
22 Lors de l’exécution du programme suivant, il faut cliquer sur la tortue et déplacer la souris.
L’affichage de la moyenne des écarts entre appels s’affiche lorsque l’on relâche le bouton de la souris.
Le programme est disponible dans le fichier tortue.py.
from turtle import ondrag, onrelease, mainloop
from time import time
mainloop()
On obtient des moyennes de l’ordre de 0,02s, soit environ 50 appels par seconde, ce qui correspond à
la fréquence d’échantillonage de la souris sur les systèmes d’exploitation actuels.
c. Lors de l’exécution des commandes ci-dessus, la commande diff produit l’affichage suivant :
7d6
< Amadis Buisson
18d16
< Florent Franck
37a36
> Séverin Frey
• < Amadis Buisson indique que c’est la ligne qui est dans le premier fichier mais pas le second ;
• 37a36indique qu’il faut ajouter une ligne en ligne 37 du premier fichier pour être identique au
second ;
Il semble donc y avoir trois erreurs : Amadis Buisson et Florent Franck sont dans les fichiers des
groupes mais pas dans celui de la classe, et inversement Séverin Frey est dans celui de la classe mais
pas dans ceux des groupes.
Pour savoir dans lequel des deux groupes sont Amadis Buisson et Florent Franck on peut utiliser la
commande grep qui recherche les lignes contenant une chaîne de caractères dans un fichier :
% grep Buisson g1.txt
Amadis Buisson
% grep Buisson g2.txt
Amadis Buisson
% grep Buisson eleves.txt
Amadis Buisson
% grep Franck g1.txt
Florent Franck
% grep Franck g2.txt
% grep Frey eleves.txt
Séverin Frey
On découvre donc que Amadis Buisson est dans les deux groupes, ainsi que dans le fichier de la classe
eleves.txt. Il était donc deux fois dans le fichier temporaire f1.txt contenant les deux groupes, mais une
seul fois dans la classe. Il faut donc le retirer de l’un des groupes.
Florent Franck, lui, apparait seulement dans le groupe 1 mais pas dans le groupe 2. Il est donc absent
du fichier de la classe sinon diff n’aurait pas trouvé de différence.
24 a. Le nom de famille est le deuxième mot de chaque ligne. La commande suivant affiche donc le
fichier selon l’ordre du nom de famille :
% sort -k 2 notes.txt
Manon-Clémentine Barbe 10
Alcée-Juliette Beaufils 19
Mauricet-Antoinette Bernard 9
Morgane Bonnaud 19
...
b. La note est le troisième mot de chaque ligne. Comme il s’agit d’un nombre et que l’on veut trier par
ordre décroissant, il faut utiliser les options n et r :
% sort -k 3nr notes.txt
Laurence Lamy 20
Alcée-Juliette Beaufils 19
Morgane Bonnaud 19
Mahaut Dupon 17
...
Angadrême Dupuy 8
Fernande Campos 8
France-Abel Malo 8
Anatole Marcel 7
c. Pour trier par nom de famille les élèves ayant la même note, on ajoute un option -k sur le deuxième
mot :
% sort -k 3nr -k 2 notes.txt
Laurence Lamy 20
Alcée-Juliette Beaufils 19
Morgane Bonnaud 19
Mahaut Dupon 17
...
Fernande Campos 8
Angadrême Dupuy 8
France-Abel Malo 8
Anatole Marcel 7
Charline-Chantal Brousse 6
Salomé Martineau 5
On observe que les trois élèves avec la note 8 sont maintenant par ordre alphabétique, ce qui n’était
pas le cas à la question b).
# effectuer la copie
system(f'cp {fichier} {copie}')
print(f'Sauvegarde dans {copie}')
b. Le fichier n’a pas été modifié et conserve donc sa date de dernière modification, tandis que la
sauvegarde a été créée lors de la copie et a donc comme date de dernière modification la date de la
sauvegarde.
c. Pour réaliser cette solution, il faut modifier l’avant-dernière ligne du programme (system(…)) comme
suit :
system(f'mv {fichier} {copie}') # renommer le fichier
system(f'cp {copie} {fichier}') # copier la sauvegarde vers l'original
def dernier(fichier):
""" retourne la dernière sauvegarde du fichier,
ou None s'il n'y pas de sauvegarde
"""
sauvegarde = None
n=1
existe = True
while existe:
if fichier+'.'+str(n) in liste:
sauvegarde = fichier+'.'+str(n)
else:
existe = False
n += 1
return sauvegarde
Lorsque l’on exécute python dernier.py *, on obtient le statut de sauvegarde de chacun des fichiers du
répertoire courant.
b. On remplace dans le programme de l’exercice précédent la saisie du nom de fichier par une boucle
sur la liste argv.
Ce programme est disponible dans le fichier sauvegarde2.py.
from os import listdir, system
# effectuer la copie
system(f'cp {fichier} {copie}')
print(f'Sauvegarde de {fichier} dans {copie}')
27 a.
from time import time
from os import system
debut = time()
for i in range(n):
t[i] = i
fin = time()
temps_tableau = fin - debut
b.
# écriture d'un million d'entiers dans un fichier
debut = time()
f = open('test.txt', 'w')
print(f'Rapport : {temps_fichier/temps_tableau:.4}')
Le rapport entre les deux temps dépend de l’ordinateur et du disque dur. Sur un Macbook Pro de 2018
avec disque SSD, le rapport est d’environ 3 à 4. Cependant sur un ordinateur avec un disque
mécanique, ce rapport serait beaucoup plus important.
Note : Le programme disque-memoire.py deux programmes ci-dessus.
Le chapitre au vu du programme
L’activité k-NN est tout à fait classique, et similaire aux ressources observables sur sklearn ou
eduscol. On a uniquement pris soin d’éviter autant que possible d’utiliser numpy pour rester dans le
cadre du programme, donc en représentant les données sous forme de listes de listes là où un
informaticien chevronné s’appuierait plutôt sur numpy et pandas.
L’activité sac à dos et le TP (rendu de monnaie et planification) sont des exemples encore une fois très
classiques d’algorithmes gloutons, les deux premiers étant d’ailleurs suggérés par le B.O. Ce ne sont
toutefois pas les exemples les plus simples!
Remarque générale sur les algorithmes gloutons : prouver l’optimalité d’un programme glouton ne
semble pas du tout dans l’esprit du programme (c’est bien plus compliqué que ce qui est attendu
dans les sujets d’épreuves publiés).
Par contre on peut envisager de faire réaliser aux élèves que lorsqu’on conçoit un algorithme
d’optimisation, après une première étape qui consiste à concevoir une approche gloutonne (ce qui est
en général facile), un informaticien va si possible dans un second temps prouver qu’elle est optimale
(plus dur) - à moins de se satisfaire de bons résultats expérimentaux sans chercher à garantir
l’optimalité.
Il y a 2 stratégies fréquentes pour prouver l’optimalité d’une stratégie gloutonne : - soit on montre que
Cours kNN
Il existe un débat portant sur la question de savoir si tout le ML fait partie de l’IA.
https://en.wikipedia.org/wiki/Machine_learning, tout dépendant en partie de si l’on adopte une
définition extensive ou pas de l’IA. Nous avons pris le parti de l’y inclure par commodité et pour
simplifier l’image mentale, la question étant a priori sans grand intérêt pratique.
TP Problème de planification.
https://en.wikipedia.org/wiki/Interval_scheduling
https://en.wikipedia.org/wiki/Activity_selection_problem
On ébauche ci-dessous la preuve que la stratégie "sélectionner à chaque étape l’activité qui termine le
plus tôt parmi les activités disponibles" est optimale. La preuve serait compréhensible pour un lycéen,
mais est compliquée.
Soient en effet 𝑡1 , 𝑡2 , … 𝑡𝑗 les tâches sélectionnées par cette stratégie, dans l’ordre d’exécution. S’il
existe un ordonnancement (optimal) qui contient 𝑘 tâches 𝑥1 , 𝑥2 … 𝑥𝑘 dans l’ordre d’exécution, alors
l’ordonnancement 𝑡1 , 𝑥2 … 𝑥𝑘 est aussi valide (car 𝑥2 , … 𝑥𝑘 sont forcément compatibles avec 𝑥1 vu le
critère glouton choisi pour définir 𝑡1 ). On procède ensuite par induction pour montrer que
𝑡1 , 𝑡2 , … 𝑡𝑗−1 𝑥𝑗 … 𝑥𝑘 est valide, et en déduire que 𝑗 = 𝑘.
Exercice 20 : Lotissement
Selon qu’on donne ou pas une indication, l’exercice peut mériter d’être classé Titan. On est sûr un cas
où l’algorithme "glouton" naïf (compléter le lot sans se préoccuper des propriétés restantes) échoue,
car il peut aboutir à ne pas pouvoir incorporer les dernières propriétés de la rue, ce qui n’est
implicitement pas permis. Une solution est d’appliquer en quelque sorte l’algorithme naif dans les 2
sens : une première fois de gauche à droite pour créer tous les lots sauf le dernier, puis une fois de
droite à gauche pour fusionner le dernier avec les lots précédents jusqu’à ce que sa valeur devienne
positive.
Exercice supplémentaire
Exercice supplémentaire : glouton Titan+ - trier un tableau de valeurs 0,1 ou 2. On veut trier le
tableau en échangeant les valeurs, et on veut minimiser le nombre d’échanges (quitte à parcourir
plusieurs fois le tableau pour déterminer les meilleurs éléments à échanger, on ne cherche pas ici à
minimiser les opérations autres que les échanges).
Indication : Commencer par compter combien de 0, 1 et 2 il y a dans le tableau. Ceci permet
d’identifier la partie du tableau qui devra contenir des "0" (au début), celle qui devra contenir des "1"
(au milieu), et celle qui devra contenir des "2".
• Une fois qu’un élément se trouve dans la partie qui lui est réservée (ex : un 0 dans la partie au
début du tableau), peut-il être judicieux de l’échanger?
• Supposons qu’on ait un 0 dans la partie qui sera réservée aux 1. Vaut-il mieux échanger ce 0 :
Exercices observés dans les sujets-types de l’épreuve de QCM de première sur eduscol :
On trouve surtout des exercices correspondant à ce chapitre dans le thème G : algorithmique.
• étant donné un graphique représentant plusieurs points dans le plan avec leur classe, un point en
particulier, et une valeur de k, identifier le résultat de kNN pour classifier le point (10G2)
• identifier la bonne description d’un des algorithmes du cours : kNN (20G3), rendu de monnaie
glouton (15G2), famille des algorithmes gloutons (16G6)
• savoir exécuter un des algorithmes du cours sur une instance donnée (ex: rendu de monnaie
glouton (9G5,26G6) …
Exercices observés dans les sujets-types de l’épreuve pratique de terminale sur eduscol :
Pour le moment nous n’avons presque rien observé ayant trait aux algorithmes gloutons ou aux kNN.
• le sujet-type 1 demande de calculer le plus proche voisin d’un point.
Prérequis – TEST
1 [x for x in d.keys()] / for k,v in d.items(): / print(f"la clef '{k}' a pour valeur {v}") ; 2 Utiliser un
invariant de boucle ; 3 t1[2] / t2[0][1] ; 4 a. iris['data'][1][1] ; b. t = [x[1] for x in iris['data']] ou t =
[iris['data'][i][1] for i in range(len(iris['data']))] ; 5 a 3, b 1, c 2 ; 6 Une copie du tableau t, triée en
fonction des deux premières colonnes (on trie d'abord sur la première colonne, puis trie les ex-aequos
en fonction de la seconde).
2.
def dataset_petale():
""" Cree a partir de iris une table (tableau de tableaux) de 150 lignes avec 3 colonnes.
On sait que
- iris['data'] est une table de 150 lignes avec 4 colonnes:
(sepal length, sepal width, petal length, petal width)
- iris['target'] est un tableau de 150 valeurs :
(code de la fleur), qui vaut 0 si la fleur est 'setosa', 1 si 'versicolor', 2 si 'virginica'.
dataset_p = dataset_petale()
print(dataset_p)
# Remarque: les jeux de donnees de sklearn peuvent aussi être directement chargés
# sous la forme d'une paire de tableaux numpy X,y, où:
# - X est le tableau des caractéristiques (data)
dataset_p == dataset_petale2()
3.
print(dataset_p[70])
print(dataset_p[126])
Deux fleurs d’espèces différentes ont exactement les mêmes caractéristiques de pétale donc tout
algorithme de classification ne saura pas différencier ces deux fleurs et prédira la mauvaise classe pour
au moins une des deux.
2. L’algorithme va prédire la classe virginica pour la fleur 85 puisque c’est la classe de son plus
proche voisin.
• y le tableau des classes que l’on souhaite prédire pour chaque objet.
Le code commence par créer un classifieur appelé clf en indiquant la valeur du paramètre k c.-à-d. le
nombre de voisins à prendre en compte. Puis on entraîne le modèle en appelant la méthode fit du
classifieur. Une fois le modèle entraîné, on peut
• prédire la classe de nouveaux objets (non annotés) en appelant la méthode predict du classifieur
sur un tableau X_bis.
• calculer la précision du modèle (sur un ensemble d’objets annotés) en appelant la méthode score
du classifieur.
X = [[d[0],d[1]] for d in dataset_p]
y = [d[2] for d in dataset_p]
# Test:
import numpy as np
print(np.allclose(X[0],[1.3999999999999999, 0.20000000000000001]))
print(np.allclose(X[2],[1.3, 0.20000000000000001]))
print(y[0]=='setosa')
x_fleur1 = [dataset_petale()[1][0],dataset_petale()[1][1]]
y_fleur1 = dataset_petale()[1][2]
x_fleur85 = [dataset_petale()[85][0],dataset_petale()[85][1]]
y_fleur85 = dataset_petale()[85][2]
#Test:
print(prediction_1_et_85.tolist()==['setosa', 'versicolor'])
print(abs(score_global_sur_X-.96)<0.01)
Ce n’est pas une bonne pratique d’utiliser les mêmes données pour entraîner le modèle et pour le
valider. Il aurait fallu découper l’échantillon en deux, entraîner sur une partie et valider sur l’autre.
Remarque : même en ``trichant'' ainsi et en validant sur les données déjà utilisées pour entraîner le
modèle, on remarque que le taux de prédictions sur l’ensemble de l’échantillon n’est pas 100%. Ce
n’est pas surprenant puisqu’on a vu ci-dessus qu’à partir des données disponibles il n’est pas possible
de prédire parfaitement l’espèce des fleurs (il y a 2 fleurs d’espèces différentes ayant les même
caractéristiques).
On sait que
- iris['data'] est une table de 150 lignes avec 4 colonnes:
(sepal length, sepal width, petal length, petal width)
- iris['target'] est un tableau de 150 valeurs :
(code de la fleur), qui vaut 0 si la fleur est 'setosa', 1 si 'versicolor', 2 si 'virginica'.
1. Pour 𝑊 = 7, le contenu optimal a pour valeur 33. On obtient cette valeur en prenant le premier et
troisième objet, donc avec [1,0,1,0,0]. Pour 𝑊 = 10, le contenu [0,1,1,0,0] est optimal et a pour valeur
34.
2.
vol = [2,5,5,4,9]
prix = [10,11,23,12,30]
# Test :
print(meilleur_candidat([0,0,0,0,0],[2,5,5,4,9],[10,11,23,12,30],10)==4)
print(meilleur_candidat([1,1,1,1,0,0],[2,5,5,4,9,24],[10,11,23,12,30,50],20)==4)
print(meilleur_candidat([1,0,0,1,0],[2,5,5,4,9],[10,11,23,12,30],5)==2)
print(meilleur_candidat([1,0,1,1,0],[2,5,5,4,9],[10,11,23,12,30],5)==1)
# Test:
print(glouton1(vol,prix,20)==([1, 0, 1, 1, 1], 75))
Entree:
- tableau vol de taille n: vol[i] = volume du paquet i
- tableau prix de taille n: prix[i] = prix du paquet i
- W: capacite (volume max) du sac a dos
(les prix et volumes sont supposes positifs)
# Test:
print(glouton1_optimise(vol,prix,20)==([1, 0, 1, 1, 1], 75))
3. Si l’on ne peut pas sélectionner le moindre paquet ou qu’on peut sélectionner les 3 simultanément,
alors glouton1 renvoie la solution optimale. Sinon, toute solution optimale sélectionne au plus 2
paquets, et chacun de ces paquets est moins cher que le plus coûteux paquet sélectionné par glouton1.
Donc la valeur d’une solution optimale est au plus le double de la valeur obtenue avec glouton1.
Entree:
- tableau vol de taille n: vol[i] = volume du paquet i
- tableau prix de taille n: prix[i] = prix du paquet i
- W: capacite (volume max) du sac a dos
(les prix et volumes sont supposes positifs)
Renvoie la solution gloutonne de remplissage du sac a dos:
- tableau contenu: contenu[i] = 1 ssi i fait partie de la solution
- la valeur totale de cette solution
Complexite: O(n * log n) (et en fait lineaire apres le tri).
"""
n = len(vol)
paquets_r_desc = sorted([(prix[i]/vol[i], prix[i], i, vol[i]) for i in range(n)], reverse=True) # pas le plus pythonesque, mais si
mple.
contenu = [0]*n
w_restant = w
for r,p,i,v in paquets_r_desc:
if v <= w_restant:
contenu[i] = 1
w_restant = w_restant - v
return contenu, sum(prix[j] * contenu[j] for j in range(n))
5. Pour montrer qu’en adaptant glouton1 comme indiqué on obtient une solution sous-optimale, il suffit
de considérer l’exemple d’entrée avec un sac de volume 𝑊 = 1. On obtiendrait alors une solution qui
mettrait 1/9 du dernier objet dans le sac, ce qui donne une valeur de 30/9 ≃ 3.33. Ce n’est pas la
solution optimale car on obtiendrait une valeur de 10/2 = 5 en prenant une quantité 1/2 du premier
objet. En utilisant glouton2, à chaque étape de l’algorithme soit le paquet ayant le meilleur ratio (parmi
les objets restants) rentre en entier dans le sac, auquel cas on l’y met en entier, soit il ne rentre pas en
entier, auquel cas on fractionne cet objet jusqu’à remplir le sac et l’algorithme est alors terminé.
def glouton2frac(vol,prix,w):
"""
Idee: trier par ratio (prix/vol) decroissant les objets.
Si on ne peut pas prendre la valeur x parce qu'elle a un volume trop eleve,
on ne pourra de toute facon plus jamais la prendre ensuite car le volume disponible ne peut que decroitre.
Entree:
- tableau vol de taille n: vol[i] = volume du paquet i
- tableau prix de taille n: prix[i] = prix du paquet i
- W: capacite (volume max) du sac a dos
(les prix et volumes sont supposes positifs)
Renvoie la solution gloutonne de remplissage du sac a dos:
- tableau contenu: contenu[i] = fraction de l'objet qu'on va inclure dans la solution
C'est un tableau de nombre flottants compris entre 0 et 1.
- la valeur totale de cette solution
Complexite: O(n * log n) (et en fait lineaire apres le tri).
"""
n = len(vol)
paquets_r_desc = sorted([(prix[i]/vol[i], prix[i], i, vol[i]) for i in range(n)], reverse=True) # pas le plus pythonesque, mais si
mple.
contenu = [0]*n
# Test:
print("Test glouton fractionnaire avec W=1 : ",glouton2frac(vol,prix,1)==([0.5, 0, 0, 0, 0], 5.0))
print(glouton2frac(vol,prix,1)==([0.5, 0, 0, 0, 0], 5.0))
print(glouton2frac(vol,prix,19)==([1, 0, 1, 0.75, 1], 72.0))
optimal_programmation_dynamique(vol,prix,20)
n_max = 27
x = [i for i in range(1,n_max)]
y1 = [glouton1_optimise(vol,prix,x)[1] for x in range(1,n_max)]
y2 = [glouton2_optimise(vol,prix,x)[1] for x in range(1,n_max)]
y3 = [optimal_programmation_dynamique(vol,prix,x) for x in range(1,n_max)]
line2, = ax.plot(x, y3, 'v:', markerfacecolor='None', linewidth=1, label='opt')
line1, = ax.plot(x, y2, 'x:', markerfacecolor='None', linewidth=1, label='glouton2')
line0, = ax.plot(x, y1, 'D:', markerfacecolor='None', linewidth=1, label='glouton1')
ax.set_yticks([20*i for i in range(5)])
ax.grid(True, which='both')
ax.legend(handles=[line0,line1,line2])
# ax.spines['left'].set_position('zero')
# Test :
print(glouton([1,2,5,10,20,50,100,200],8)==[1, 1, 1, 0, 0, 0, 0, 0])
3. La terminaison est évidente vu que l’on n’utilise pas de boucle non bornée; la boucle for termine en
n itérations puisque i est un variant qui vaut 𝑛 − 1 au départ. La complexité est linéaire (𝑛 opérations
de division et autant de modulos).
Correction : le programme satisfait l’invariant suivant : au début de chaque itération,
𝑟𝑒𝑠𝑡𝑎𝑛𝑡 + ∑𝑛−1 𝑗=1 𝑝𝑎𝑖𝑒𝑚𝑒𝑛𝑡[𝑖] × 𝑠[𝑖] = 𝑣.
4. Pour le montant 𝑣 = 6, (1,3,4) a une solution en 2 pièces ([0,2,0]) alors que la solution gloutonne
en nécessite 3 : [2,0,1].
Pour le montant 15, la stratégie gloutonne nécessite 4 pièces sur (1,5,10,12) : [3,0,0,1] alors qu’il
existe une solution avec 2 pièces: 10 et 5.
5.
# Test :
print(canonique3([1,2,5])) # True
print(canonique3([1,3,4])==False)
TP B : Problème de planification
1. On effectue d’abord la tâche (1,2.5) puis ensuite la tâche (2.5,4).
2.
def conflit(l,b,i):
"""
Entrée:
- tableau de n taches L (une tâche = paire de flottants)
- tableau de booléens b
- indice i
======
Modifie (effet de bord) b: indique les tâches incompatibles avec la tâche i
en mettant à False les indices correspondant dans b
======
Renvoie: le nombre de valeurs modifiées dans b (celles qui étaient à True et deviennent False).
# Test:
L_0=[(1,3),(3,6.5),(3,5),(6,7)]
L_1=[(0,3),(1,2.5),(2,3),(2.5,4)]
B=[True,False,True,True]
print(conflit(L_0,B,1)==2)
print(B==[True, False, False, False])
B=[True,False,True,True]
print(conflit(L_1,B,1)==2)
print(B==[False, False, False, True])
3. (i): pas optimale sur L1: choisit t1 qui ne peut ensuite pas être complétée, alors qu’il existe une
solution à 2 tâches (t2,t4).
(ii): parvient à faire 2 tâches sur L1 et 4 sur L2, possiblement optimale donc.
(iii): pas optimale sur L2: choisit la tâche du milieu ce qui donne une solution à 3 tâches, alors qu’il
existe une solution à 4 tâches.
(iv): pas optimale sur L1: choisit t3 qui ne peut pas être complétée, alors qu’il existe une solution à 2
tâches (t2,t4).
En conclusion, seule la stratégie (ii) est peut-être optimale ; on ne l’a pour l’instant pas prouvé mais
c’est la seule pour laquelle on n’a pas exhibé de contre-exemple.
4.
# Test :
L_1=[(0,3),(1,2.5),(2,3),(2.5,4)]
L_2=[(0,1),(1,2),(2,3),(3,4),
(0.1, 1.1), (0.2, 1.2), (0.3, 1.3), (0.4, 1.4),
(2.9, 3.9), (2.8, 3.8), (2.7, 3.7), (2.6, 3.6),
(1.5,2.5)]
print(glouton(L_0)==[(1, 3), (3, 5), (6, 7)])
print(glouton(L_1)==[(1, 2.5), (2.5, 4)])
print(glouton(L_2)==[(0, 1), (1, 2), (2, 3), (3, 4)])
6. Considérer l’exemple L2 ci-dessus, en rendant la deuxième tâche un peu plus longue que les autres
(.99, 2.01) au lieu de (1,2). Elle sera éliminée alors qu’elle fait partie de l’unique solution optimale.
Exercices k - NN
Exercices Newbie
1 a. On prédit la classe a'' car c’est la plus fréquente parmi les 3 voisins les plus proche (2 a'', 1 ``b'').
b. On prédit la classe ``a'' car c’est celle du plus proche voisin.
c. Pareil : on prédit la classe ``a'' car un point non annoté (qui ne fait donc pas partie de l’échantillon
d’entraînement) n’est pas compté parmi les plus proches voisins (seuls sont pris en compte les plus
proches voisins dans l’échantillon).
d. Pareil : on prédit la classe ``a'', car les points plus loins ne seront pas pris en compte vu qu’on se
limite aux k=3 plus proches.
2 Les 4 plus proches voisins sont les 3 premiers du tableau et le dernier, qui ont pour valeur 10, 15, 25
et 10. On prédit donc à l’objet x la valeur (10 + 15 + 25 + 10) / 4 = 15.
3 a. Fondamentalement, l’algorithme des k plus proches voisins considère les données en entrées
comme un multi-ensemble: l’algorithme ne traite pas différemment les divers éléments de
l’échantillon. Donc l’ordre n’a pas d’influence sur le résultat de l’algorithme tel qu’il est décrit
habituellement (et tel que décrit dans le cours).
Par contre, l’ordre des éléments peut modifier légèrement le résultat d’une implémentation de
l’algorithme. En effet, l’algorithme ne précise pas comment on choisit la partie entrainement'' et la partie
validation'' dans l’échantillon. Et il ne précise pas quelle classe on choisit en cas d’ex-aequo; une
4
☑ La méthode peut s’appliquer à des données ayant un nombre quelconque de dimensions
(caractéristiques); 1,2,3 ou plus : Vrai.
☑ L’algorithme ne peut identifier que les classes présentes dans l’échantillon : Vrai. Puisqu’on
attribue aux objets à classifier une classe de leurs voisins dans l’échantillon.
☑ C’est un algorithme qui en général a des taux d’erreurs raisonnablement bas : Vrai.
5 On peut utiliser l’algorithme des k plus proche voisins pour ``prédire'' des valeurs manquantes.
Exercices Initié
13 Voir le cours.
14 Adapter le cours
15 Les trois affirmations sont incorrectes.
☐ La méthode ne s’applique qu’aux données numériques : Faux. On peut définir une distance sur des
données quelconques, même s’il est vrai que c’est plus naturel sur les données numériques.
☐ Il est préférable de prendre le plus grand k possible : Faux. Il faut trouver le meilleur compromis,
qui est généralement de l’ordre de ]𝑜ù𝑠𝑡𝑒𝑚: [𝑚 est la taille de l’échantillon. Un trop petit k risque
d’être trop sensibles au bruit dans les données. Mais un trop grand k va simplement renvoyer la classe
la plus nombreuse dans l’échantillon, pour toutes les données (à moins de pondérer par la distance), en
plus d’augmenter le coût de la classification.
☐ On peut prouver mathématiquement que le nombre d’erreurs est borné par une constante : Faux. Le
taux d’erreur peut effectivement être borné par rapport au taux d’erreur qu’atteindrait le meilleur
algorithme sous certaines hypothèses (concernant la taille de l’échantillon). Mais le nombre d’erreurs
peut être arbitrairement grand; il est possible que l’échantillon ne contienne que 10 points mal placés,
et qu’ensuite toutes les données soient mal classifiées.
16 a. k = 1: 3 k = 2: 3 ou 1
17 a. Pour obtenir le résultat de classification, on attribue à chaque objet i sa classe la plus probable :
1 si la probabilité est 𝑝𝑖 > .5, 2 si 𝑝𝑖 < .5. Lorsque 𝑝𝑖 = .5 on peut choisir n’importe laquelle des
classes.
b. On annote chaque objet par un vecteur (tableau ou tuple) v tel que v[i] donne la probabilité que
l’objet soit de classe i. Une fois ces vecteurs calculés sur l’ensemble des objets par régression, on
déduit des vecteurs la classe la plus probable en renvoyant l’indice du maximum dans le vecteur.
18
from sklearn import datasets, neighbors
digits = datasets.load_digits()
X0 = digits['data']
y0 = digits['target']
# ou directement:
plt.show()
#X = X / X.max()
#n_echantillons = len(X_digits)
# On décompose le jeu de donnée en une partie d'entrainement et une servant à valider/évaluer le modèle
# Entraînement du modèle :
from sklearn import neighbors
clf = neighbors.KNeighborsClassifier(5)
clf.fit(X_train,y_train)
# Test :
print(abs(taux_de_prediction-.95)<.01)
image = X_valid[0]
label = y_valid[0]
print("Taille de l'image : ",len(image))
pred = clf.predict([image])
image_8x8 = [[image[i*8+j] for j in range(8)] for i in range(8)]
# ou : image_8x8 =np.reshape(np.array(image), (8,8))
plt.imshow(image_8x8, cmap=plt.cm.gray)
plt.title(f'prédit: {pred}, valeur réelle: {label}\n', fontsize = 20);
31 a. Il suffit de choisir pour l’échantillon un point tous les .02: 0, .02, .04… donc 1/.02 = 50 points.
b. Il va falloir former une grille. Donc 50 × 50 = 2500 points
Ce phénomène est appelé la malédiction (ou fléau) de la dimension (en anglais ``curse of
dimensionality''), une expression inventée par Richard Bellman en 1961 (un informaticien américain
connu en particulier pour l’invention de la programmation dynamique).
Exercices gloutons
Exercices Newbie
6 ❏ Une heuristique
Le problème n’est pas en lui-même une heuristique. Par contre, on peut utiliser une heuristique pour
résoudre le problème.
7 ❏ Algorithmes d’optimisation
❏ Un algorithme glouton est généralement rapide par rapport à d’autres méthodes pour résoudre le
problème.
❏ Un algorithme glouton est généralement facile à implémenter par rapport à d’autres méthodes pour
résoudre le problème
❏ Les choix effectués par un algorithme glouton utilisent généralement un critère simple
9 Voir le cours. Non l’approche gloutonne ne donne pas un algorithme pas optimal (pour la version
habituelle du problème).
11 Voir le cours.
12 a. 2 pièces de 2€, une pièce de .5€, et une de .2€, ce qui fait 4 pièces.
b. Non, car on commencerait par utiliser le billet de 5€ après quoi on ne serait plus en mesure de faire
l’appoint, alors qu’en renonçant à la solution gloutonne et en utilisant plutôt 3 pièces de 2€, on aurait
pu obtenir le montant exact.
Exercices Initié
19 a. Non, on le voit sur l’exemple 𝑣: le critère glouton rapporte 4.2 + 5 = 9.2, alors qu’on peut
obtenir 81.2
b.
def resultats_gloutons(v):
"""
Entree: un tableau de longueur paire: la valeur des manuscrits.
Affiche les objets et la valeur totale obtenus par R1 et R2 en jouant chacun la strategie gloutonne.
Renvoie les objets obtenus par R1 et ceux obtenus par R2.
"""
assert(len(v)%2==0)
# Test :
print(resultats_gloutons([1.2,4.2,80,5])==([5, 4.2], [80, 1.2]))
def valeur_R1(v):
"""
Entree: un tableau de longueur paire: la valeur des manuscrits.
Renvoie les objets et la valeur total obtenus par R1 en jouant la strategie pairs_ou_impairs.
# Test:
print(valeur_R1([1.2,4.2,80,5])==([1.2, 80], 81.2))
#Une autre approche avec compréhension, mais ce n'est pas celui qui a été donné à compléter:
def valeur_R1(v):
"""
Entree: un tableau de longueur paire: la valeur des manuscrits.
Renvoie les objets et la valeur total obtenus par R1 en jouant la strategie pairs_ou_impairs.
"""
# Test:
print(valeur_R1([1.2,4.2,80,5])==([1.2, 80], 81.2))
Noter que le total/les objets décrits ci-dessus pour la stratégie gloutonne supposent bien que les 2
joueurs ont choisi la stratégie gloutonne.
La stratégie pair-impair, elle, est optimale au sens où elle donne à R1 le meilleur résultat qu’il puisse
obtenir si R2 joue du mieux possible : R1 peut (s’il choisit d’abord la valeur à l’indice 0) garantir qu’il
va avoir un total valant au moins le total de toutes les valeurs aux indices pairs, quoi que R2 fasse. Et
s’il choisit d’abord la valeur à l’indice len(t)-1 il a la garantie d’obtenir au moins un total valant la
somme des valeurs aux indices impairs. Si R2 joue mal il peut espérer avoir plus.
20 Le critère est : parcourir les propriétés dans l’ordre le long de la rue. Tant que le lot a une valeur
négative, on lui ajoute une propriété. Dès qu’il a une valeur positive, on « termine » le lot.
Ceci donne presque la solution : le problème est qu’après avoir terminé un lot, il est possible que
toutes les valeurs restantes aient un total négatif. Une solution est de d’abord procéder comme ci-
dessus, puis, une fois les lots terminés, re-parcourir les lots depuis le dernier qui n’a pas pu être
complété et le fusionner avec les lots précédents jusqu’à ce qu’il devienne positif.
# Test :
rue = [
{'nom':'dechetterie','valeur':-2},
{'nom':'ruine','valeur':-1},
{'nom':'etang','valeur':1},
{'nom':'villa','valeur':4},
{'nom':'champ','valeur':2},
{'nom':'manoir','valeur':5},
{'nom':'depotoir','valeur':-6},
{'nom':'parking','valeur':1}]
if decompose(rue) == [
[
{'nom': 'dechetterie', 'valeur': -2},
{'nom': 'ruine', 'valeur': -1},
{'nom': 'etang', 'valeur': 1},
{'nom': 'villa', 'valeur': 4}
],
[
{'nom': 'champ', 'valeur': 2}
],
[
{'nom': 'manoir', 'valeur': 5},
{'nom':'depotoir','valeur':-6},
{'nom':'parking','valeur':1}]] :
print("Résultat correct sur le test")
else:
print("Résultat incorrect")
# Test :
liste_cours = [{'debut': 1, 'fin': 2},
{'debut': 1, 'fin': 3},
{'debut': 3, 'fin': 6},
print(optimise_salle(liste_cours)==
[[{'debut': 1, 'fin': 2}, {'debut': 2, 'fin': 7}],
[{'debut': 1, 'fin': 3}, {'debut': 3, 'fin': 6}]])
22 L’algorithme de planification de tâches vu en TP (critère glouton triant par heure de fin croissante).
23 a. Il s’agit en fait d’une approche gloutonne : on va prendre à chaque étape l’entier le plus grand
restant. Le plus simple est de commencer par trier le tableau puis de le parcourir pour reconstituer le
nombre.
def f(t):
s = sorted(t, reverse=True)
return int("".join(str(x) for x in s))
# ou bien:
def f(t):
s = sorted(t, reverse=True)
tot = 0
for x in s:
tot = 10*tot + x
return tot
b. Le principe est similaire, mais maintenant le nombre 4 passe avant 31, par exemple. L’algorithme
est beaucoup plus compliqué.
En fait, pour résoudre le problème on peut choisir plusieurs algorithmes:
• 1. construire naivement toutes les permutations, garder la plus grande.
• 1. commencer par répéter les chiffres de chaque nombre jusqu’à ce que tous les nombres aient la
Voir: https://rosettacode.org/wiki/Largest_int_from_concatenated_ints
# Solution 3:
import math
def f(t):
chaines = [str(x) for x in t]
longueur_max = max([len(s) for s in chaines])
def fonction_de_comparaison(s):
return (s*math.ceil(longueur_max/len(s)))[:longueur_max] # slice pas au programme mais on peut la simuler par comp
rehension
chaines_triees = sorted(chaines, key = fonction_de_comparaison, reverse=True)
return int("".join(chaines_triees))
24 On trie par ordre croissant les boissons et sandwiches. Puis on associe la boisson 𝑖 avec le
sandwich 𝑛 − 𝑖.
25 L’algorithme est un algorithme glouton: pour minimiser l’écart, il faut que les skis et les skieurs
soient triés dans le même ordre : le plus petit ski au plus petit skieur, etc puis le plus grand ski au plus
grand.
def f(l,h):
""" affiche le ski attribue a chaque skieur.
Entree:
- l est un tableau de nombres: la hauteur de chaque paire
# Test :
skieurs0 = [(175,"Sofia"),(168,"Sarah"),(172,"Maxime"),(180,"Ali")]
skis0 = [195,168,180,174]
print(f(skis0,skieurs0)==22)
# Test :
nenuphars_sans_distance_de_unvirgule1 = [1,1.3,2,3.05,4,4.1,5,5.15,6.2,7.1,8.15,9.2,10.2,11.2,12.2,13.2,14.25,15.3,16.35,
17.4,18.45,19.5,20]
print(grenouille_sauteuse(nenuphars_sans_distance_de_unvirgule1)==[1,2,3.05,4.1,5.15,6.2,7.1,8.15,9.2,10.2,11.2,12.2,13.
2,14.25,15.3,16.35,17.4,18.45,19.5])
27 Le principe est assez similaire à celui des grenouilles : on trie les tableaux par distance, on place un
gardien au depart, et on passe au tableau suivant tant qu’il est atteignable (surveillé) par le gardien.
Dès qu’un tableau est trop loin pour être surveillé on lui ajoute un gardien puis on poursuit avec le
tableau suivant. Il y a une petite différence: la grenouille devait s’arrêter au dernier nénuphar
accessible, le gardien, lui doit être placé au premier tableau inaccessible (c’est légèrement plus facile,
en fait, car on n’a pas besoin de regarder l’élément suivant avant de décider).
def nb_gardiens(t):
"""
Entree: t: tableau de nombres representant la distance des tableaux au debut du couloir.
Renvoie: le nombre de gardiens necessaires.
Complexite: lineaire
"""
d = 5 # distance couverte par un gardien : 5m
tableaux = sorted(t)
last_x = tableaux[0]
nb = 1
for x in tableaux:
if last_x + d < x:
last_x = x
nb += 1
return nb
# Test :
print(nb_gardiens([6,8,12.5,17])==2)
28 a. Pour [1,2,5]: on assemble d’abord 1 et 2 (coût 3) puis le bloc ainsi formé avec 5 (coût 8). Coût
total de 11. Pour [1,2,3,4]: on assemble d’abord 1 et 2 (coût 3) puis 3 et 3 (coût 6) puis 6 et 4 (coût
t = [1.5,2,4.2]
def f(h):
""" Renvoie le cout minimal pour empiler tous les blocs du tableau h.
"""
t = sorted(h)
cost = 0
while len(t) > 1:
next_bloc = heappop(t) + heappop(t)
cost += next_bloc
heappush(t, next_bloc)
return cost
# Test :
print(f(t)==11.2)
print(f([1,2,3,4])==19)
plt.subplot(1,2,1)
plt.plot(x,y,marker='.')
plt.plot(1,0,marker='s')
plt.plot(-2,0,marker='D')
plt.title('parcours glouton')
plt.subplot(1,2,2)
plt.plot(x2,y2,marker='.')
plt.plot(1,0,marker='s')
plt.plot(-2,0,marker='D')
plt.title('parcours optimal')
Exercices Titan
32 a. Prendre à chaque fois le train le plus rapide qui soit disponible dans la gare et qui s’arrête avant
𝑛. On descend de ce train à son dernier arrêt avant 𝑛.
b. Prendre à chaque fois le train le plus rapide qui soit disponible dans la gare et qui s’arrête avant 𝑛.
On descend de ce train à son arrêt le plus proche de 𝑛, que ce soit le dernier arrêt avant 𝑛 ou le premier
après 𝑛.
# Test:
print(sorted(voisins((0,0),4))==[(0, 1),(1, 0)])
print(sorted(voisins((1,0),4))==[(0, 0), (1, 1), (2, 0)])
print(sorted(voisins((2,2),4))==[(1, 2), (2, 1), (2, 3), (3, 2)])
def robot(m):
"""
Entrée: matrice m carrée de n*n valeurs distinctes.
# Test :
m=[[1,5,7,4],[2,6,10,12],[14,1,0,9],[8,11,15,3]]
print(robot(m)==[(0,0),(0,1),(0,2),(1,2),(1,3)])
Attention, certains exercices mentionnent des intervalles de lignes à remplacer (par exemple 13, 14).
Ces numéros sont modifiés dans la correction (fichiers prof/exercices_XXX_corrige.docx et
prof/exercices_XXX_corrige.ipynb) pour tenir compte des commandes %%javascript et %%ruby ajoutées au code
pour qu’il puisse s’exécuter.
Ruby s’exécute de la même manière que Python, et notamment ses "print" (puts en Ruby) s’affichent
dans la sortie de la cellule.
Cela n’est pas le cas pour JavaScript. Il faut utiliser la commande console.log(…); pour afficher du texte,
et ce texte ne s’affiche que dans la console de la page web elle-même. Il faut donc afficher cette
console pour vérifier ses résultats :
• Avec Mozilla Firefox : Depuis le menu, Développement web > Outils de développement web puis
sélectionner l’onglet Console ; ou utiliser le raccourci-clavier [Ctrl/Cmd]+[Shift]+[K].
• Avec Google Chrome : Depuis le menu, Plus d’outils > Outils de développeur puis sélectionner
l’onglet Console ; ou utiliser le raccourci-clavier [Ctrl]+[Shift]+[J] ([Cmd]+[Option]+[J] sous Mac).
• Avec Microsoft Edge : Depuis le menu, Plus d’outils > Outils de développement puis sélectionner le
volet Console ; ou utiliser le raccourci-clavier [Ctrl]+[Shift]+[J].
Prérequis – TEST
1 c ; 2 c ; 3 b ; 4 b ; 5 b.
Pas de mot-
son type printf(…); Pas d’exception #include
C clé
2. Dans les exemples de langages donnés, seul Python dispose d’un mot-clé spécifique assert. C,
JavaScript, Caml et Ruby testent leur pré- et post-conditions à l’aide de conditions if (ou unless en Ruby
lorsque cela facilite la lecture du code). Si la condition testée n’est pas replie, les langages qui
disposent d’erreurs/exceptions en font usage (throw en JavaScript, raise en Caml et Ruby) ; l’équivalent
en C consiste à afficher un message d’erreur (printf) et à terminer le programme (exit(-1);).
JavaScript, C { }
Ruby do ou { end ou }
lignes */
QCM (CHEKPOINT)
1 b (un appel dans le code à une fonction non définie peut ne jamais être détecté si cet appel n'est
jamais exécuté) ; 2 b (ou en tout cas pas systématiquement ; il faudra probablement le recompiler) ; 3
a ; 4 b ; 5 a ; 6 a ; 7 b ; 8 a ; 9 b ; 10 b (C++ oui, Ruby non) ; 11 a ; 12 b (JavaScript utilise le mot-clé
function pour définir une fonction) ; 13 a (mais elle sera toujours interprétée comme un entier) ; 14 a
Exercices
Exercices Newbie
1 Ada Lovelace (1815 – 1852) est considérée comme une pionnière de l’informatique.
Elle est principalement connue pour avoir réalisé le premier véritable programme informatique, lors de
son travail sur un ancêtre de l’ordinateur. Elle a également entrevu et décrit certaines possibilités
offertes par les calculateurs universels, allant bien au-delà du calcul numérique et de ce qu’imaginaient
ses contemporains.
2 Typage implicite, Caml est capable de déduire le type d’une variable en fonction de son usage.
3 Le premier langage assembleur date de 1949.
4 Il existe par exemple le langage interprété Linotte qui utilise une syntaxe en français.
5 Vrai
6 Vrai, car C est un langage à typage explicite.
8 La fonction pairs_differents(x, y) retourne True si x et y sont pairs et différents l’un de l’autre, False sinon.
Exercices Initié
9
function pairs_differents(x, y) {
return (x%2 == 0) && (y%2 == 0) && (x != y);
}
// Tests
console.log(pairs_differents(1, 6));
console.log(pairs_differents(4, 4));
console.log(pairs_differents(4, 2));
console.log(pairs_differents(3, 7));
• print((1>3)==0)
11 a.
function inverse_tableau(t) {
let inverse = [];
b. Non, car il n’y a pas d’alternative simple en Python pour parcourir un ensemble. L’équivalent de for
x in table: nécessite par exemple de créer une nouvelle variable pour compter les éléments de table dans
une boucle while. for apporte plus qu’une simple commodité d’écriture, contrairement par exemple à
unless en Ruby.
12
texte = input("Un nombre :")
print(texte, "n'est pas un nombre" if not texte.isdigit() else "est impair" if int(texte) % 2 == 1 else "est pair")
14
[ [ "X" if i==j or i==9-j else " " for j in range(10) ] for i in range(10) ]
Exercices Titan
15 a. Oui car on peut effectuer les mêmes opérations avec if et while, il suffit d’inverser les conditions.
b.
function diviseurs(x) {
let resultat = [];
let test = Math.floor(x/2);
while (test != 1) {
if (x % test == 0) {
resultat.push(test);
}
test -= 1;
}
return resultat;
}
16 a. En Ruby : notez l’usage de puts et unless, ou encore l’usage de do-then-else pour délimiter les
blocs.
c_e(40)
Solution en JavaScript
function c_e(val_max) {
if (val_max < 2) {
console.log("aucun");
}
let valeurs = [];
for (let k = 0 ; k <= val_max + 1 ; k += 1) {
valeurs.push(true);
}
for (let i = 2 ; i < val_max ; i += 1) {
if (valeurs[i]) {
c_e(40);