Livre Correction Nsi

Télécharger au format pdf ou txt
Télécharger au format pdf ou txt
Vous êtes sur la page 1sur 431

Chapitre 1 – Au cœur de l’ordinateur

Notes sur le chapitre


Nous avons choisi d’ouvrir le manuel avec ce chapitre pour démystifier l’ordinateur en expliquant
qu’il s’agit d’une machine très « bête » mais très rapide. Cependant, ce chapitre n’est pas un prérequis
pour les chapitres suivants, jusqu’au chapitre 9 (des 0 et des 1) qui parle du codage binaire et des
portes logiques. L’enseignant peut donc choisir de commencer directement par le chapitre 2 et couvrir
le chapitre 1 plus tard.
Le simulateur utilisé dans l’activité d’introduction est le plus simple que nous avons pu trouver tout en
présentant une interface relativement conviviale. Nous l’avons modifié de l’original
(https://github.com/c2r0b/vnmsim) et adapté pour les besoins du manuel.
Les principales caractéristiques de ce simulateur sont :
• un seul registre, l’accumulateur (en plus du RI et du CP) ;

• des adresses mémoire symboliques (X, Y, Z, etc.) au lieu d’adresses numériques ;

• un jeu d’instructions minimal.

Le cours utilise le langage d’assemblage du simulateur (à l’exception de l’utilisation d’adresses


mémoires numériques dans le premier exemple du cours et de l’instruction DEF ensuite, puisque le
simulateur utilise seulement des adresses mémoire symboliques).
Les exemples de programmes du chapitre sont disponibles dans le menu Exemples du simulateur :
• ADDITION (activité d’introduction et cours)

• EQUAL (activité d’introduction)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


• MAX (cours)

• 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/#

• AQA Assembly Language Simulator by Peter Higginson -


http://www.peterhigginson.co.uk/AQA/

• OCR A Level CPU Simulator - https://tools.withcode.uk/cpu/

Chapitre 1 – Au cœur de l’ordinateur


1
Activité : Simulateur de machine de Von Neumann
Exécuter un programme

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


l’instruction qui est exécutée ensuite est l’instruction à l’adresse 1 au lieu de celle à l’adresse 4.
Le programme « boucle », c’est-à-dire qu’il exécute les instructions des adresses 1 (ADD Y) et 2
(STO Z) de manière répétitive. La valeur de la cellule mémoire Z augmente donc de 2 à chaque
instruction.
Le programme ne s’arrête pas : il exécute une boucle infinie.
2. Non, le programme n’a pas exécuté la ligne 3. La valeur de Z est 0 (instruction STO Z à l’adresse
mémoire 4).
3. Oui, cette fois-ci le programme a exécuté l’instruction à l’adresse 3 (LOD #1) et stocké son résultat
(1) dans la cellule mémoire Z. Z vaut donc 1.
4. L’instruction JMZ n effectue un saut à l’adresse n si l’accumulateur contient une valeur différente de
0, sinon elle ne fait rien et on passe à l’instruction suivante.
Le programme met 0 dans Z si X est égale à Y, et 1 sinon. Il teste donc l’égalité de deux variables.

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.

Chapitre 1 – Au cœur de l’ordinateur


2
EXERCICES

Exercices Newbie
1 • Le contenu du compteur de programme est envoyé à la mémoire.
• L’instruction est lue de la mémoire.

• Le compteur de programme est incrémenté.

• Le code opération est envoyé à l’UAL.

• L’adresse du code opération est envoyée à la mémoire.

• Le contenu de la mémoire est envoyé à l’UAL.

• L’UAL effectue l’opération entre l’accumulateur et la donnée 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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


JMP a X
JMZ b X

3 a. Pour X = 10, Y = 15 et Z = 20, le programme met 50 dans W.


Pour X = 10, Y = 20 et Z = 20, il met 100 dans W.
Pour X = 10, Y = 12 et Z = 20, il met 20 dans W.
b. Le programme met dans W la proportion de Y par rapport à l’intervalle [X, Z], en pourcentage.
Dans le premier cas 15 est au milieu de [10, 20], donc W vaut 50 %. Dans le second cas, 10 est le
début de l’intervalle [10, 20], donc W vaut 0 %. Dans le dernier cas, 12 représente 20 % de l’intervalle
[10, 20].
Si on analyse le programme on voit qu’il calcule d’abord W = X - Z (trois premières lignes), puis il
calcule (X - Y) * 100 (trois lignes suivantes), qu’il divise par W pour stocker le résultat dans W (deux
lignes suivantes). On peut donc l’écrire sous la forme :
ranger la valeur de (X - Z) dans W
ranger la valeur de (X - Y) * 100 / W dans W

soit
ranger la valeur de (X - Y) * 100 / (X - Z) dans W

qui est la règle de trois pour calculer une proportion.

Chapitre 1 – Au cœur de l’ordinateur


3
c. Le programme est disponible dans le fichier exo3.json et peut être chargé dans le simulateur par
cliquer-tirer.

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


HALT

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.

Chapitre 1 – Au cœur de l’ordinateur


4
8

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

• photo : 4000 x 3000 x 4 = 48 Mo

• vidéo (5 mins) : 20 x 5 = 100 Mo

• film (2 h) : 20 x 120 = 2,4 Go

b.

• 500 Go / 450 Ko = 1 111 111 livres

• 500 Go / 48 Mo = 10 416 photos

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


• 500 Go / 100 Mo = 5 000 vidéos

• 500 Go / 2,4 Go = 208 films

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

Chapitre 1 – Au cœur de l’ordinateur


5
b. Non, la mémoire n’apparait pas dans ce diagramme. Elle est connectée au bus de donnée en haut («
D0-D7 bidirectional Data Bus ») et au bus d’adresse en bas à droite (« A0-A15 Address Bus »).

12 a. 2 MHz / (4 + 11) / 2 = 266 666 instructions par seconde en moyenne.


b. 20 000 000 / 266 666 = 75
c. 1012 / 266 666 = 3 750 000

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


b.
• X = 10 et Y = 3 : Z vaut 1

• X = 10 et Y = 2 : Z vaut 0

c. Z vaut 0 si X est divisible par Y, sinon il vaut 1.


d. Ce programme est disponible dans le fichier exo14.json et peut être chargé dans le simulateur par
cliquer-tirer.

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

Chapitre 1 – Au cœur de l’ordinateur


6
SUB Y
STORE Z ; retirer Y de Z
JMP b ; boucler
a: HALT

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


cr1:SUB #1
JMPP cr1 ; boucle du compte à rebours
LOAD LED
ADD #1
STORE LED ; augmenter intensité LED
CMP #100 ; tester intensité maximale
JMPN on ; boucler si non atteinte
off:LOAD #1000 ; démarrer compte à rebours
cr2:SUB #1
JMPP cr2 ; boucle du compte à rebours
LOAD LED
SUB #1
STORE LED ; diminuer intensité LED
JMPP off ; boucler si positive
JMP on ; recommencer

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.

19 La table est issue de la page Wikipedia https://en.wikipedia.org/wiki/Moore%27s_law.

Chapitre 1 – Au cœur de l’ordinateur


8
On peut produire le graphe à l’aide du code ci-dessous.
Un graphe similaire est visible ici :
https://upload.wikimedia.org/wikipedia/commons/c/c7/Comparison_semiconductor_process_nodes.sv
g
On observe que la diminution est pratiquement linéaire dans ce graphe. Comme l’échelle verticale est
logarithmique, cela signifie que la diminution est exponentielle : elle est divisée par un facteur
constant chaque année.
x = [1971, 1974, 1977, 1981, 1984, 1990, 1993, 1996, 1999, 2001, 2003, 2005, 2007, 2009, 2012, 2014, 2016, 2018, 2020]
y = [10000, 6000, 3000, 1500, 800, 600, 350, 250, 180, 130, 90, 65, 45, 32, 22, 14, 10, 7, 5]

from matplotlib.pyplot import plot, yscale, show


yscale("log")
plot(x, y)
show()

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Exercices Titan
20 a.

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


fin: STORE R
HALT

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


nombre de conflits, soit n/8.
Cependant, après un tel conflit, il y a une chance sur deux que l’autre processeur accède à nouveau à la
mémoire et crée un nouveau conflit. Le processeur va « perdre » ce conflit la moitié du temps, et donc
attendre à nouveau dans un quart des cas, soit (n/8)/4 = n/32 cas. Il faut aussi prendre en compte la
possibilité d’un nouveau conflit, à nouveau dans un quart des cas, soit (n/32)/4 = n/128, et ainsi de
suite, mais les probabilité deviennent négligeables pour les besoins de cette analyse.
En considérant seulement ces trois cas, un programme de n instructions mettra donc en moyenne
n + n/8 + n/32 + n/128 instructions à s’exécuter, soit un ralentissement de
(1/8 + 1/32 + 1/128)*100 = 16,4 %.
c. D’après la question précédente, chaque programme mettra environ 1,16 secondes, et comme ils
s’exécutent en même temps, c’est aussi le temps total d’exécution.
Ce temps est à comparer à 2 secondes pour exécuter les deux programmes à la suite l’un de l’autre sur
un mono-cœur. L’utilisation d’un bi-cœur permet donc un gain de 42 %.
d. Si on considère 4 programmes qui s’exécutent en parallèle sur 4 cœurs, chacun accédant à la
mémoire une instruction sur deux en moyenne, on va saturer l’accès à la mémoire puisque, en
moyenne, 2 programmes voudront accéder à la mémoire en même temps à chaque cycle d’instruction.
Il en résulte que le temps d’exécution total ne peut pas être inférieur à deux fois le temps d’exécution
de chaque programme.
Les quatre programmes qui prendraient chacun 1 seconde pour s’exécuter nécessiteront donc au moins
2 secondes en tout sur un quadricœurs, au lieu de 4 secondes si on les exécutait l’un après l’autre, soit
une accélération de 50 % au maximum.
Chapitre 1 – Au cœur de l’ordinateur
11
25 a. En première approximation, chaque 10 instructions, le processeur doit exécuter l’équivalent de
deux instructions supplémentaires, soit un ralentissement de 20 %.
Cependant, un seul processeur peut utiliser le bus à la fois, donc s’il y a deux processeurs qui utilisent
le bus chacun 20 % du temps, le risque de conflit est 0,2*0,2 = 0,04, soit 4 %. Le ralentissement sera
donc plutôt de 24 %.
b. Chaque programme mettra environ 1,24 secondes et ils s’exécutent en même temps, donc le temps
total est 1,24 secondes au lieu de 2 secondes si les deux programmes s’exécutaient l’un après l’autre,
soit un gain de 38 %.
c. Ici aussi, en première approximation, chaque programme est ralenti de 20 %. Cependant, la
probabilité de conflit d’accès au bus est plus élevée qu’avec 2 processeurs.
En effet, considérons la table suivante où chaque colonne A, B, C, D représente un processeur et
chaque ligne une configuration possible : 0 = pas d’accès à la mémoire, 1 = accès à la mémoire.

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)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


0 1 1 1 oui (3 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 %.

Chapitre 1 – Au cœur de l’ordinateur


12
Le temps total d’exécution des 4 programmes en parallèle est donc 1,27 secondes, au lieu de 4
secondes s’ils s’exécutaient sur un mono-processeur.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re

Chapitre 1 – Au cœur de l’ordinateur


13
Chapitre 2 – Commencer à programmer
• Note générale sur les fichiers qui accompagnent chaque chapitre
Pour la plupart des chapitres (à l’exception notable du Chapitre 1), nous fournissons un ensemble de
Notebooks Jupyter à l’attention des enseignants, en plus des fichiers fournis aux élèves et des corrigés
du présent Livre du Professeur ( XX est le numéro du chapitre) :
• cXX-cours-eleve.ipynb: exemples de code du cours. Ce notebook est principalement destiné aux
élèves, afin qu’ils puissent facilement expérimenter avec les constructions illustrées dans le
cours.

• cXX-activite-corrige.ipynb : corrigé de l’activité du chapitre, destiné aux enseignants.

• cXX-TP-corrige.ipynb : corrigé du TP du chapitre, destiné aux enseignants.

• cXX-exercices-newbie-corrige.ipynb : corrigés des exercices Newbie, destiné aux enseignants.

• cXX-exercices-initie-corrige.ipynb : corrigés des exercices Initié, destiné aux enseignants.

• cXX-exercices-titan-corrige.ipynb : corrigés des exercices Titan, destiné aux enseignants.

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


copier-coller le code dans un environnement Python, comme par exemple EduPython.
• Note générale sur les bibliothèques turtle et nsi_turtle
Plusieurs chapitres, dont le présent Chapitre 2, utilisent la bibliothèque Python turtle, qui est en
principe intégrée à toute installation Python. Cependant celle-ci ne fonctionne pas bien depuis Jupyter.
Aussi fournissons-nous la bibliothèque nsi_turtle, destinée à être utilisée à la place de turtle à la fois
dans Jupyter et directement dans Python. Pour cela le fichier nsi_turtle.py doit être dans le même dossier
que les fichiers Python ou Jupyter qui l’utilisent.
Dans Jupyter, nsi_turtle utilise la bibliothèque ipyturtle (https://github.com/gkvoelkl/ipython-turtle-
widget) qui doit au préalable est installée, soit directement depuis le terminal avec la commande pip
install ipyturtle, soit depuis EduPython avec la commande « installer un module avec pip » du menu «
Outils ». nsi_turtle fournit l’ensemble des fonctions de la bibliothèque turtle qui sont utilisées dans le
manuel. Cependant, pour des raisons techniques, certaines fonctions sont inopérantes : affichage de
texte et gestion des événements.
est également utile avec un environnement Python autre que Jupyter, tel que EduPython : Elle
nsi_turtle
améliore la gestion des événements, et configure la tortue pour dessiner instantanément plutôt qu’avec
une animation. Elle permet aussi que le même programme peut être utilisé dans Jupyter ou
directement en Python.

Chapitre 2 – Commencer à programmer 1


Notes sur le chapitre
Section 4.1 - Conditionnelles et blocs
Nous n’introduisons pas l’expression conditionnelle x = y if c else z, qui nous semble source de confusion
à ce stade de l’apprentissage du langage.

Section 4.2 - Boucles


La fonction random utilisée dans les exemples doit être importée depuis la bibliothèque random:
from random import *

ou
from random import random

random() retourne un nombre en virgule flottante compris entre 0 (inclus) et 1 (exclus).


Nous n’introduisons pas les instructions break et continue de Python qui permettent de sortir d’une
boucle ou de sauter à la prochaine itération. Il nous semble préférable de maîtriser les boucles sans ces
instructions car celles-ci peuvent conduire à de mauvaises habitudes de programmation. Par contre
nous utilisons le fait que l’instruction return (voir ci-dessous) interrompt l’exécution de la fonction
pour retourner immédiatement une valeur. Cela peut encourager une meilleure modularité du code en
créant des fonctions simples qui utilisent cette propriété, comme dans l’exemple de la fonction min du
cours.

Section 5 - Définition de fonctions


Les termes « paramètre » et « argument » sont souvent utilisés de manière interchangeable en
informatique. Ici on utilise de préférence le terme « paramètre » pour la définition de la fonction et «
argument » pour l’appel. On utilise parfois (mais pas dans ce manuel) les termes de « paramètre
formel » pour la définition et « paramètre réel » pour l’appel.
De même le terme « procédure » ou « commande » est parfois utilisé pour des fonctions qui ne

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


retournent pas de valeur (et ont donc seulement des effets de bord). Dans ce manuel nous utilisons
uniquement le terme « fonction ». (Techniquement, une fonction Python qui ne retourne pas de valeur
retourne la valeur None. La valeur None est introduite au Chapitre 4.)

Section 5.3 - Variables locales et globales


Les règles de visibilité de Python sont une source d’erreurs même pour les programmeurs chevronnés.
Le fait qu’une référence à une variable globale ne nécessite pas de déclarer celle-ci en tant que telle
(avec l’instruction global) si elle est utilisée seulement en lecture est commode mais dangereux.

Activité : La tortue Python


Tracer un carré
forward(100)
right(90)
forward(100)
right(90)
forward(100)
right(90)
forward(100)

Utiliser une variable


cote = 50
forward(cote)
right(90)
forward(cote)
right(90)

Chapitre 2 – Commencer à programmer 2


forward(cote)
right(90)
forward(cote)

Utiliser une boucle

1. L’angle est de 2π/n (ou 360/n en degrés).


2.
n = 12
cote = 50
angle = 360/n
for i in range(n):
forward(cote)
right(angle)

Définir et utiliser une nouvelle fonction


1.
def polygone(n, cote):
""" Tracer un polygone a n cotes de longueur "cote" """
for i in range(n):
forward(cote)
right(360/n)

2.
for i in range(36):
polygone(3, 100)
right(10)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


QCM (CHECKPOINT)
1 b, c et f ; 2 e et f ; 3 a ; 4 a ; 5 a, b, e, h et i ; 6 b et c ; 7 b ; 8 d ; 9 c ; 10 5 ; 11 c ; 12 a, b et c.

TP A : Un carré en ASCII Art


def carre(cote):
""" Affiche un carré """
# Les lignes de notre dessin
for i in range(cote):
if i == 0 or i == cote-1:
# Première ou dernière ligne : on affiche `cote` fois le symbole 'X '
print("X " * cote)
else:
# Lignes intermédiaires : on affiche seulement le premier et le
# dernier 'X ', avec des (doubles) espaces entre les deux.
# Les règles de priorités des opérations + et * s'appliquent.
print("X " + " " * (cote-2) + "X ")

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.

Chapitre 2 – Commencer à programmer 3


# Les lignes de notre dessin
for i in range(cote):
if i == 0 or i == cote-1 or i == mediane:
# Première ou dernière ligne, ou ligne médiane.
print("X " * cote)
else:
# Lignes intermédiaires : on affiche seulement le premier, médian,
# et dernier 'X ', avec des (doubles) espaces entre les deux.
# Les règles de priorités des opérations + et * s'appliquent.
print("X " + " "*(mediane-1) + "X " + " "*(cote-mediane-2) + "X ")

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# 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:
# On construit les lignes restantes caractère par caractère.
ligne = ""
for j in range(cote):
# Extrémités :
if j == 0 or j == cote-1:
ligne += "X "

# Diagonale montante :
elif j == cote - i - 1:
ligne += "X "

# Diagonale descendante dans la seconde moitié :


elif j == i and i >= mediane:
ligne += "X "

else:
ligne += " "

print(ligne)

origami(9)

def origami2(cote):
""" Affiche un carré avec une diagonale et une demi-diagonale"""

Chapitre 2 – Commencer à programmer 4


# Note : Version alternative, plus "matheuse"
mediane = cote // 2 # Important à définir quand `cote` n'est pas impair.

# 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)
elif i <= mediane:
# Première moitié : on n'affiche que la diagonale montante (ainsi
# que les extrémités de la ligne).
print("X " + " " * (cote-i-2) + "X " + " " * (i-1) + "X ")
else:
# Seconde moitié : on affiche les deux diagonales et les extrémités.
# Extrémité gauche :
ligne = "X "
# Espaces jusqu'à la diagonale montante :
ligne += " " * (cote-i-2) + "X "
# Espaces jusqu'à la diagonale descendante :
ligne += " " * (2 * (i-mediane) - 1) + "X "
# Espaces jusqu'à l'extrémité droite :
ligne += " " * (2 * mediane - i - 1) + "X "
print(ligne)

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


if j == mediane - i or j == mediane + i:
ligne += "X "
else:
ligne += " "
print(ligne)

# 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

print("X " * (base + 2)) # Première ligne pleine

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)

Chapitre 2 – Commencer à programmer 5


# Dernière ligne.
print("X " * (base + 2)) # Dernière ligne pleine.

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)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


sapin(9, 4, 2)

def sapin(hauteur_feuillage, hauteur_tronc, rayon_tronc):


""" Affiche la forme générale d'un sapin avec une étoile au sommet """
# 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 += "\\"
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)

Chapitre 2 – Commencer à programmer 6


sapin(9, 4, 2)

def sapin(hauteur_feuillage, hauteur_tronc, rayon_tronc):


""" Affiche un sapin avec ses feuilles """
# 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 : 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)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


for i in range(hauteur_tronc):
print(ligne)

sapin(9, 4, 2)

from random import random

def sapin(hauteur_feuillage, hauteur_tronc, rayon_tronc, proba_boule):


""" Affiche un sapin avec son feuillage et des boules de Noël """

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

Chapitre 2 – Commencer à programmer 7


ligne += "o"
else:
# 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)
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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


print(2 + 3*5 + 4)
print((5+2)*3 + 4)
print(4 + 5*(2+3))

3
a = 10
b = 20
(a+b)/2

4 x = 0 : True, False, False, False


x = 10 : False, False, True, True
x = -20 : Flase, True, True, True
# Note : l'exercice est à faire "à la main" !
x=0
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)

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)

Chapitre 2 – Commencer à programmer 8


print()
x = -20
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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


s=s*i
print('n =', n,', s =', s)

n=0
s=0
while s < 20:
n += 1
s=s+5
print('n =', n,', s =', s)

# Attention : ce programme boucle, il faudra l'interrompre


# soit avec ^C dans Python, soit avec la commande Interrupt dans Jupyter
# (ou le bouton avec un carré noir)
n=0
s=1
while s != 100:
n += 1
s=s*2
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):

Chapitre 2 – Commencer à programmer 9


factorielle = factorielle * i
print(factorielle)

9 a. n vaut 5 à la fin du programme.

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


11
def pair(n):
return n % 2 == 0

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.

b. Le programme affiche 42.

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.

Chapitre 2 – Commencer à programmer 10


from math import pi

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)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


16
bm = " belle Marquise,"
vby = " vos beaux yeux"
vyb = " vos yeux beaux"
mf = " me font"
m = " mourir"
a = " d'amour"
print(bm+vby+mf+m+a)
print(a+m+mf+","+bm+vby)
print(vyb+a+mf+","+bm+m)
print(m+vby+","+bm+a+mf)
print(mf+vyb+m+","+bm+a)

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)

Chapitre 2 – Commencer à programmer 11


18
def xor(a, b):
""" Retourne vrai si a ou b est vrai mais pas les deux"""
return (a or b) and not (a and b)

print(True, 'xor', True, '=', xor(True, True))


print(True, 'xor', False, '=', xor(True, False))
print(False, 'xor', True, '=', xor(False, True))
print(False, 'xor', False, '=', xor(False, False))

19 a.
# On définit une fonction pour faciliter le test
def et(a, b):
return not (not a or not b)

print(True, 'et', True, '=', et(True, True))


print(True, 'et', False, '=', et(True, False))
print(False, 'et', True, '=', et(False, True))
print(False, 'et', False, '=', et(False, False))

b.
# On définit une fonction pour faciliter le test
def ou(a, b):
return not (not a and not b)

print(True, 'ou', True, '=', ou(True, True))


print(True, 'ou', False, '=', ou(True, False))
print(False, 'ou', True, '=', ou(False, True))
print(False, 'ou', False, '=', ou(False, False))

20
t = 25

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


if t < -10:
print("Il va faire très froid")
elif t < 0:
print("Il va faire froid")
elif t < 15:
print("Il va faire frais")
elif t < 25:
print("Il va faire bon")
elif t < 30:
print("Il va faire un peu chaud")
elif t < 35:
print("Il va faire chaud")
else:

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

Chapitre 2 – Commencer à programmer 12


jours = 31
if m < 7 and m % 2 == 0 or m > 7 and m % 2 == 1:
jours = 30
if m == 2:
jours = 28
if bissextile:
jours = 29

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

def nb_jours_mois(mois, annee):


""" retourne le nombre de jours du mois
de l'annee donnee """
jours = 31
if mois < 7 and mois % 2 == 0 or mois > 7 and mois % 2 == 1:
jours = 30
if mois == 2:
jours = 28
if est_bissextile(annee):
jours = 29
return jours

print('juillet 2022 a', nb_jours_mois(7, 2022), 'jours')


print('février 2020 a', nb_jours_mois(2, 2020), 'jours')

b.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def nb_jours(jour, mois, annee):
""" retourne le nombre de jours
depuis le debut de l'annee """
njours = 0
# calculer la somme des nombres de jours des mois précédents
for m in range(1, mois):
njours = njours + nb_jours_mois(m, annee)
return njours + jour

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.

Chapitre 2 – Commencer à programmer 13


# On calcule d'abord le jour0 de l'année 2022
nouvelan2022 = jour_semaine(31, 12, 2021, 4) + 1

# Maintenant on peut calculer le jour demandé


fetnat2022 = jour_semaine(14, 7, 2022, nouvelan2022)
if fetnat2022 == 3:
print("Le 14 juillet 2022 est bien un jeudi")
else:
print("il semble y avoir une erreur...")

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

Valeurs après chaque tour de boucle, pour a = 10 et b = 15

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


• r = 10, a = 15, b = 10

• r = 5, a = 10, b = 5

• r = 0, a = 5, b = 0

b. Le PGCD est le plus grand nombre qui divise à la fois 𝑎 et 𝑏.

Si 𝑎 divise 𝑏, alors PGCD(𝑎, 𝑏) = 𝑎. Sinon on peut écrire 𝑎 = 𝑏 × 𝑞 + 𝑟 et on montre que


PGCD(𝑎, 𝑏) = PGCD(𝑏, 𝑟).
Le programme calcule donc r (reste de la division de a par b) et remplace a par b et b par r jusqu’à ce
que le reste soit nul. À la fin de la boucle, a est le PGCD de a et b.

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)

Chapitre 2 – Commencer à programmer 14


27
a = 1234

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)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


e.
for i in range(0,16):
v = sin(i*3.14*2/15)
if v > 0:
print(' '*20 + ' '*(round(v*20) -1) + '*')
else:
l = round(-v*20)
print(' '*(20-l) + '*')

29
# En dehors de Jupyter on peut importer turtle à la place de nsi_turtle
from nsi_turtle import *

# Importer la fonction `random` du module `random`


from random import random

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

Chapitre 2 – Commencer à programmer 15


30 a.
n1 = 0
n2 = 1
print(1, n1)
print(2, n2)
for i in range(98):
print(i+3, n1 + n2)
n = n1 # se souvenir de n1 avant de changer sa valeur !
n1 = n2
n2 = n + n2

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!')

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


Syracuse(123)

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

# BONUS - afficher les durées de vol des 150 premières valeurs de n


# Ceci nécessite la notion de tableau introduite au Chapitre 4
vols = []
for n in range(149):
vols.append(Syracuse_vol(n+1))
from matplotlib.pyplot import plot, show
plot(vols)
show()

Chapitre 2 – Commencer à programmer 16


c.
def Syracuse_altitude(n):
""" Retourne l'altitude de la suite de Syracuse """
altitude = 0
while n != 1:
if n > altitude:
altitude = n
if n % 2 == 0:
n = n // 2
else:
n = n*3 + 1
return altitude

print(Syracuse_altitude(120))
print(Syracuse_altitude(123))
print(Syracuse_altitude(130))

# BONUS - afficher les durées de vol des 150 premières valeurs de n


# Ceci nécessite la notion de tableau introduite au Chapitre 4
altitudes = []
for n in range(149):
altitudes.append(Syracuse_altitude(n+1))
from matplotlib.pyplot import plot, show
plot(altitudes)
show()

32 a. Note : Cette question est à réaliser « à la main ».


Ce programme affiche une série de lignes constituées de 0 à 9 caractères *.
*
**
***

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


****
*****
******
*******
********
*********

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 :

Chapitre 2 – Commencer à programmer 17


total = 5
for i in range(1, 7): # valeur du premier dé
j = total - i # valeur du second dé
if j > 0 and j < 7: # s'assurer que c'est une valeur possible
print(i, j)

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


j = total - i # valeur du second dé
if j > 0 and j < 7: # s'assurer que c'est une valeur possible
n=n+1
print('total = ', total, ',', n, 'possibilités')

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

Chapitre 2 – Commencer à programmer 18


if prix // 5 > 0:
print(prix // 5, 'billets de 5€')
prix = prix % 5

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


if n10 > nb_10euros:
# ne pas utiliser plus de billets que disponible
n10 = nb_10euros
if n10 > 0:
print(n10, 'billets de 10€')
a_rendre = a_rendre - n10*10
nb_10euros = nb_10euros - n10

# 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

if a_rendre > 0: # on n'a pas pu rendre la monnaie

Chapitre 2 – Commencer à programmer 19


print('Pas suffisamment de monnaie !')
print('Il manque', a_rendre, '€')

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

def rendre(a_rendre, valeur, nombre):


""" Retourne le nombre de pieces ou billets de la valeur à rendre en fonction
du nombre de pièces ou de billets de cette valeur dans la caisse
"""
n = a_rendre // valeur
if n > nombre:
# ne pas utiliser plus de billets que disponible
n = nombre
if n > 0:
print(n, 'fois', valeur, '€')
# NOTE : on ne PEUT PAS mettre à jour `a_rendre` et `nombre` ici
# pour mettre à jour la caisse, car il s'agit d'un paramètre,
# donc changer la valeur de `nombre` ne change PAS
# la variable passée en paramètre (par exemple nb_50euros),

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# et de même pour la valeur de `a_rendre` (même si elle porte
# le même nom que la variable globale `a_rendre`).
return n

n50 = rendre(a_rendre, 50, nb_50euros)


a_rendre = a_rendre - n50 * 50
nb_50euros = nb_50euros - n50

n20 = rendre(a_rendre, 20, nb_20euros)


a_rendre = a_rendre - n20 * 20
nb_20euros = nb_20euros - n20

n10 = rendre(a_rendre, 10, nb_10euros)


a_rendre = a_rendre - n10 * 10
nb_10euros = nb_10euros - n10

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

if a_rendre > 0: # on n'a pas pu rendre la monnaie


print('Pas suffisamment de monnaie !')
print('Il manque', a_rendre, '€')

Chapitre 2 – Commencer à programmer 20


35 a.
def interets_annuels(montant, taux):
return montant * taux / 100

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.

37 a. Erreurs de syntaxe : Il manque : à la fin de la première ligne et à la fin de la ligne for i.


Erreur de logique : L’expression 1/i*i vaut toujours 1 car \ et * ont la même priorité et dont elle est
interprétée de gauche à droite : (1*i)/i.
Erreur de logique : La dernière ligne ne retourne pas de valeur.
Erreur d’exécution : on calcule 1/i pour i valant 0, et provoque une division par zéro.
b.
def somme_inv_carres(n):
s=0
for i in range(1, n+1):

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


s = s + 1/(i*i)
return s

# On définit une fonction pour comparer le résultat


# de la somme des inverses des carrés à π*π/6
from math import pi
def comparer(n):
s = somme_inv_carres(n)
v = pi*pi/6
print(s, v, s - v)

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)

print('compteur =', compteur)

Chapitre 2 – Commencer à programmer 21


b.
compteur = 0
total = 0

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)

print('compteur =', compteur)


if compteur > 0:
print('moyenne =', total/compteur)

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


itération : elle semble infinie. En observant la trace des valeurs produites, on peut observer
l’inexactitude des nombres flottants en Python (et des opérations que l’on fait avec). En particulier, x <
1.0 au début de la 11-ième itération, et x > 1.0 au début de la 12ème itération :
...
0.9999999999999999 (11-ième itération)
1.0999999999999999 (12-ième itération)
...

É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

# Avec cette version-ci, la boucle s'arrête bien après 11 itérations.

c.
x = 0.0
while x <= 3.0:

Chapitre 2 – Commencer à programmer 22


print(x)
x = x + 0.1

# La dernière valeur de x est désormais 2.9 (environ).

# En supposant que l'on souhaite que ce programme compte jusqu'à 3 (inclus)


# par incréments de 0.1,
# cela signifie qu'il manque la dernière itération (celle affichant 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

def sont_egaux(n1, n2):


"""Retourne True si n1 et n2 sont suffisamment proches pour être
considéres comme égaux, et False dans le cas contraire.
"""
return abs(n2 - n1) < EPSILON

def est_inferieur_ou_egal(n1, n2):


"""Retourne True si n1 est inférieur à n2 ou suffisamment proche
pour être considéré égal à n2, et False dans le cas contraire.
"""
return n1 < n2 or sont_egaux(n1, n2)

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# Premier cas (de 0 à 1.0 inclus).

x = 0.0
while est_inferieur_ou_egal(x, 1.0):
print(x)
x = x + 0.1

# Deuxième cas (de 0 à 3.0 inclus).

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

# Si n est égal à 0 ou à 1, on conclut immédiatement que n n'est pas premier.


if n == 0 or n == 1:
return False

# Sinon, on teste tous les diviseurs potentiels de n dans l'intervale [2, n-1].
n_est_premier = True

for diviseur_potentiel in range(2, n):


if n % diviseur_potentiel == 0:
n_est_premier = False

Chapitre 2 – Commencer à programmer 23


return n_est_premier

# Tests devant retourner True (les nombres sont premiers) :


print(est_premier(2))
print(est_premier(3))
print(est_premier(97))

# Tests devant retourner False (les nombres ne sont pas premiers) :


print(est_premier(1))
print(est_premier(4))
print(est_premier(537))

b.
def est_premier(n):
"""Retourne True si n est premier, et False dans le cas contraire."""

# Si n est égal à 0 ou à 1, on conclut immédiatement que n n'est pas premier.


if n == 0 or n == 1:
return False

# Sinon, on teste tous les diviseurs potentiels de n dans l'intervale [2, n-1].
diviseur_potentiel = 2

while diviseur_potentiel < n:


if n % diviseur_potentiel == 0:
return False
diviseur_potentiel = diviseur_potentiel + 1

return True

c.
def est_premier(n):

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


"""Retourne True si n est premier, et False dans le cas contraire."""

# Si n est égal à 0 ou à 1, on conclut immédiatement que n n'est pas premier.


if n == 0 or n == 1:
return False

# Sinon, on teste tous les diviseurs potentiels de n


# dans l'intervale [2, sqrt(n)].
diviseur_potentiel = 2
plus_grand_diviseur_potentiel = sqrt(n)

while diviseur_potentiel <= plus_grand_diviseur_potentiel:


if n % diviseur_potentiel == 0:
return False
diviseur_potentiel = diviseur_potentiel + 1

return True

d.
def affiche_nombres_premiers(m):
"""Affiche les m premiers nombres premiers."""

n=1
nb_premiers_affiches = 0

# Tant que l'on n'a pas affiché m nombres premiers,


# on teste les nombres un à un et affiche chaque nombre qui est premier.
while nb_premiers_affiches < m:

Chapitre 2 – Commencer à programmer 24


# Si n est premier, on l'affiche, et on incrémente
# le nombre de nombres premiers déjà affichés.
if est_premier(n):
print(n)
nb_premiers_affiches = nb_premiers_affiches + 1

# On passe au nombre suivant.


n=n+1

# Test affichant la liste des nombres premiers compris entre 1 et 100 :


affiche_nombres_premiers(25)

41 a.
# On suppose que les signes sont représentés par les chaînes de caractères
# "pierre", "papier", et "ciseaux".

def compare_deux_signes(signe_joueur_a, signe_joueur_b):


"""Retourne 1 si le signe du joueur A a l'avantage sur celui du joueur B,
-1 si c’est l'inverse, et 0 s’il y a égalité.
"""

# Si les deux signes sont identiques, on retourne 0 (égalité).


if signe_joueur_a == signe_joueur_b:
return 0

# Sinon, on teste si le joueur A gagne, et on retourne 1 si c'est le cas.


# Note : attention aux parenthèses, qui sont importantes pour écrire le test
# sur plusieurs lignes dans la condition du if !
if ((signe_joueur_a == "pierre" and signe_joueur_b == "ciseaux")
or (signe_joueur_a == "papier" and signe_joueur_b == "pierre")
or (signe_joueur_a == "ciseaux" and signe_joueur_b == "papier")):
return 1

# Si on atteint cette étape, cela signifie que le joueur A n'a pas gagné

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# ET qu'il n'y a pas égalité :
# on peut donc en conclure que le joueur B gagne, et on retourne -1.
return -1

# Quelques tests :

# Tests devant retourner 1 (victoire du joueur A)


print(compare_deux_signes("pierre", "ciseaux"))
print(compare_deux_signes("papier", "pierre"))
print(compare_deux_signes("ciseaux", "papier"))

# Tests devant retourner 0 (égalité)


print(compare_deux_signes("pierre", "pierre"))
print(compare_deux_signes("papier", "papier"))
print(compare_deux_signes("ciseaux", "ciseaux"))

# Tests devant retourner -1 (victoire du joueur B)


print(compare_deux_signes("ciseaux", "pierre"))
print(compare_deux_signes("pierre", "papier"))
print(compare_deux_signes("papier", "ciseaux"))

b.
from random import random

def tire_signe_au_hasard():
"""Retourne un des trois signes possibles du jeu
en le choissisant au hasard.
"""

Chapitre 2 – Commencer à programmer 25


nombre_aleatoire = random() # nombre compris entre 0 et 1

if nombre_aleatoire < 1/3:


return "pierre"
elif nombre_aleatoire < 2/3:
return "papier"
else:
return "ciseaux"

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

# Si on atteint cette étape, cela signifie que comparaison_des_signes vaut


# soit 1 (victoire du joueur A), soit -1 (victoire du joueur B).
# La valeur à retourner correspond donc au produit de cette valeur avec
# le nombre de points en jeu.
return comparaison_des_signes * nb_points_en_jeu

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


c.
score_joueur_a = 0
score_joueur_b = 0

nb_parties_a_simuler = 50

# On simule nb_parties_a_simuler (en débutant par la partie numéro 1, et non 0).


for numero_partie in range(1, nb_parties_a_simuler + 1):
resultat_partie = simule_une_partie_avec_gagnant()

# Le nombre de points gagnés par l'un des joueurs


# est la valeur absolue du résultat de la partie.
score = abs(resultat_partie)

# On attribue ces points au joueur A ou au joueur B


# en fonction du signe du résultat de la partie.
if resultat_partie > 0:
score_joueur_a = score_joueur_a + score
else:
score_joueur_b = score_joueur_b + score

# On affiche des informations sur la partie que l'on vient de simuler,


# dont le nombre d'égalités ayant menées à répéter la partie (le cas échéant).
if score > 1:
print("Partie", numero_partie, ": A", score_joueur_a, "-",
score_joueur_b, "B,", score -1, "égalité(s)")
else:

Chapitre 2 – Commencer à programmer 26


print("Partie", numero_partie, ": A", score_joueur_a, "-",
score_joueur_b, "B")

42 a.
# On importe la fonction sinus et la constante PI (utiles pour cet exercice).
from math import sin, pi

# On définit la fonction f et la valeur V ici :

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

while x <= max and estimation_antecedent_trouvee == False:


# On vérifie si V est dans l'intervalle ]f(x), f(x + pas)[.
# Si c'est le cas, on assigne True à la variable estimation_antecedent_trouvee
# afin de stopper la boucle while.
# Sinon, on incrémente x du pas indiqué (et la boucle continue).
if f(x) < V and f(x + pas) > V:
estimation_antecedent_trouvee = True
else:
x = x + pas

# Si un antécédent x de V par la fonction f a été trouvé

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# dans l'intervalle [min, max], on l'affiche.
# Sinon, on affiche un message indiquant qu'il n'a pas été trouvé.
if estimation_antecedent_trouvee:
print(x)
else:
print("Aucun antécédent de V n'a été trouvé dans l'intervale [", min, max, "]")

# En exécutant le code pour f = sin et V = 0.5, on trouve la valeur 0.5.

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

# Le code ci-dessous est identique à celui de la question précédente.


while x <= max and estimation_antecedent_trouvee == False:
if f(x) < V and f(x + pas) > V:
estimation_antecedent_trouvee = True
else:
x = x + pas

if estimation_antecedent_trouvee:
print(x)

Chapitre 2 – Commencer à programmer 27


else:
print("Aucun antécédent de V n'a été trouvé dans l'intervale [", min, max, "]")

# En exécutant le code pour f = sin et V = 0.5, on trouve la valeur 0.52.

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

# Afin de décider si la précision de l'estimation est suffisante, on peut tester


# si la différence entre l'avant-dernière estimation et la dernière estimation
# est inférieure à un certain seuil.
min_difference_entre_estimations = 1e-6

def precision_estimation_est_suffisante(derniere_estimation, avant_derniere_estimation):


return abs(derniere_estimation - avant_derniere_estimation) < min_difference_entre_estimations

# Afin que la première estimation se déroule correctement, il faut donc choisir


# la valeur initiale de estimation_precedente telle que
# abs(x - estimation_precedente) >= min_difference_entre_estimations
# lors de la première itération de la boucle extérieure.
estimation_precedente = x + (2 * min_difference_entre_estimations)

# Boucle externe : on recommence l'estimation jusqu'à obtenir une précision suffisante.


while estimation_antecedent_trouvee == False:
# Boucle interne : on parcourt l'intervalle [min, max] pour estimer x.
while x <= max and estimation_antecedent_trouvee == False:

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


if f(x) < V and f(x + pas) > V:
estimation_antecedent_trouvee = True
else:
x = x + pas

# On vérifie si la précision de l'estimation est suffisante :


# si c'est le cas, on arrête la boucle externe (il n'y a rien à faire de plus) ;
# sinon on adapte min et max, réduit le pas, et on recommence la boucle interne
if not precision_estimation_est_suffisante(x, estimation_precedente):
estimation_antecedent_trouvee = False
min = x
max = x + pas
pas = pas / 10 # on choisit un pas dix fois plus petit

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, "]")

# En exécutant le code pour f = sin et V = 0.5, on trouve 0.5235979999999998.

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 :

Chapitre 2 – Commencer à programmer 28


estimation = 0.5235979999999998

print("Estimation de x dans sin(x) = 0.5 :", estimation)


print("Valeur de sin(PI/6) :", sin(pi/6))

difference = abs((pi/6) - estimation)


print("Différence entre l'estimation et PI/6 :", difference)

# La différence entre l'estimation et l'antécédent réel est très faible :


# 7.755982990298449e-07.
# On peut également noter qu'elle est bien inférieure à la constante 1e-6
# choisie plus tôt. On conclut donc que l'estimation a bien fonctionné.

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)

# Affiche 0, 2, et les 8 multiples de deux suivants (4, 6, 8...).

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


Dans le premier cas (boucle for), le nombre d’itérations ne varie pas, et les valeurs affichées sont
doublées.
Cela vient du fait que l’expression range(10) n’est pas affectée par le fait que l’on modifie la variable i
dans le corps de la boucle. Au début de la n-ième itération, la variable i prend toujours la valeur n, peu
importe la façon dont cette variable a été modifiée lors de l’itération précédente. Pour cette raison, la
boucle produit autant de valeurs que précédemment, et celles-ci sont simplement doublées par
l’instruction i = i * 2 avant d’être affichées.
Dans le second cas (boucle while), le nombre d’itérations passe de 10 à 4, et la relation entre les
valeurs affichées est moins évidente.
Cela vient du fait que la condition d’arrêt de la boucle (i < 10) dépend de la valeur de i, qui n’est pas
``réinitialisée'' à n au début de la n-ième itération.
À chaque itération, la valeur de i est doublée, affichée, puis incrémentée de 1. C’est cela qui définit la
relation moins évidente entre les valeurs affichées :
(itération 1) 0 * 2 =0
(itération 2) (0 + 1) * 2 = 1*2 = 2
(itération 3) (2 + 1) * 2 = 3*2 = 6
(itération 4) (6 + 1) * 2 = 7*2 = 14

Chapitre 2 – Commencer à programmer 29


La valeur de i augmente ainsi plus rapidement et dépasse 10 lors de la quatrième itération : la
condition d’arrêt devient donc vraie, et la boucle se termine avant le cinquième itération, après n’avoir

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


affiché que 4 valeurs.

Chapitre 2 – Commencer à programmer 30


Chapitre 3 – Un peu d’interaction

Notes sur le chapitre


Le programme de la spécialité annonce dans son préambule que « les interfaces qui permettent la
communication avec les humains, la collecte des données et la commande des systèmes » sont un «
élément transversal » du programme. Cependant, les éléments du programme consacrés aux «
interactions entre l’homme et la machine » concernent seulement l’interaction sur le Web. Ces
éléments sont couverts dans le chapitre 11 de ce manuel.
Il nous a cependant semblé important d’introduire quelques éléments de programmation d’interfaces
textuelles et graphiques qui ne fassent pas appel aux technologies du Web et qui puissent être utilisés
aisément par les élèves dans leurs programmes Python. C’est pourquoi ce chapitre présente les
principes de base de l’interaction textuelle et de l’interaction graphique.
L'interaction textuelle aborde l’affichage de texte, avec notamment les chaînes de formatage, et la
saisie de données textuelles et numériques, ce qui permet d’introduire la gestion d’erreurs avec
l’instruction try. L’utilisation de l’instruction try lorsque l’on essaie de lire une entrée numérique nous
semble la meilleure solution à ce stade car Python offre peu d’alternatives sauf à utiliser des concepts
avancés (expressions régulières) ou des librairies spécialisées. Par exemple, une méthode telle que
s.isdigit() permet de savoir si s est constitué uniquement de chiffres, mais n’est pas suffisante pour savoir
si s représente un nombre flottant ou négatif : '12.45'.isdigit() retourne False, de même que '-12'.isdigit().
L'interaction graphique utilise une bibliothèque spécialement développée pour ce manuel, nsi_ui

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


(«ui» signifie « user interface », c’est-à-dire « interface utilisateur »). Cette bibliothèque exporte un
ensemble de fonctions permettant de créer et présenter les interacteurs de base. Elle fonctionne dans
les environnements de programmation Python tels que EduPython grâce à la bibliothèque standard
Python tkinter, et dans l’environnement Jupyter avec la bibliothèque d’interacteurs de Jupyter ipywidgets.
Les interacteurs ont été choisis pour être comparable à ceux qui seront introduits au Chapitre 11 pour
l’interaction avec les formulaires sur le Web.
Le principal écueil de ce chapitre est la notion de fonction de rappel, qui est la pierre angulaire de la
programmation par événements qui caractérise les interfaces graphiques. Une fonction de rappel est
une fonction dont le nom est passé en paramètre à une autre fonction, qui peut ainsi l’appeler
lorsqu’elle en a besoin. On retrouvera ce principe au chapitre 6 avec la fonction sort de Python pour
trier des tableaux, qui peut prendre en paramètre une fonction de comparaison d’éléments.
Par ailleurs la programmation par événements conduit souvent à utiliser des variables globales afin
que les différentes fonctions de rappel puissent partager l’état du programme. Les variables globales
sont considérées comme une mauvaise pratique en programmation, aussi faut-il essayer de limiter leur
usage au strict minimum.
Note : La compatibilité de la bibliothèque nsi_ui avec la bibliothèque graphique Qt, annoncée page 51
du manuel, n’a finalement pas été jugée utile et n’est donc pas fournie.

Activité : Contrôler la tortue


Des boutons pour lancer des fonctions
def polygone(n, cote):
""" Trace un polygone de n cotes de taille `cote` """
for i in range(n):
forward(cote)
right(360/n)

button("Effacer", clear)

Chapitre 3 - Un peu d’interaction 1


main_loop()

Des tirettes pour contrôler les paramètres


tirette_taille = slider("Taille", 10, 100)
tirette_ncotes = slider("Nombre de cotes", 3, 50)

def creer_polygone():
polygone(get_int(tirette_ncotes), get_int(tirette_taille))

button("Polygone", creer_polygone) # bouton pour faire le dessin


button("Effacer", clear)

main_loop()

Piloter la tortue au clavier


def gauche():
left(10)

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


Exercices

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 !')

input_bool('Fait-il beau ? ')

2 a.
temp = input('Température à convertir : ')
print(float(temp)*9/5 + 32)

b.

Chapitre 3 - Un peu d’interaction 2


temp = input('Température à convertir : ')
unite = input('Centigrades (C) ou Fahrenheit (F) ? ')
if unite == 'C':
print(float(temp)*9/5 + 32)
elif unite == 'F':
print((float(temp) - 32) * 5/9)
else:
print('Unité non reconnue')

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

input_defaut("Ville de départ ? ", "Paris")

5
from nsi_ui import *

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# Note : nécessaire seulement dans Jupyter
clear_ui()

# Monnaies et taux de change


monnaie1 = "EUR" #input('Monnaie 1 ?')
monnaie2 = "USD" #input('Monnaie 2 ?')
taux = 1.20 #input('Taux de conversion ?')

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

Chapitre 3 - Un peu d’interaction 3


6 a.
from nsi_ui import *

# Note : nécessaire seulement dans Jupyter


clear_ui()

# champs de saisie de texte pour entrer les valeurs de a et b


champA = entry("Valeur de a")
champB = entry("Valeur de b")

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

main_loop() # lancer l'interface

b.
# Note : nécessaire seulement dans Jupyter
clear_ui()

def ajouter():

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


""" 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, f'Somme = {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, 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()

Chapitre 3 - Un peu d’interaction 4


7
from nsi_ui import *

# Note : nécessaire seulement dans Jupyter


clear_ui()

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def recule_minute():
""" Retirer 1 des minutes M """
global M, H
M -= 1
if M < 0:
M = 59
recule_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()

Chapitre 3 - Un peu d’interaction 5


8 a. La bibliothèque ipyturtle utilisée pour remplacer turtle dans Jupyter ne traite pas les événements
d’entrée. Dans la version Jupyter de la correction, on remplace l’appui sur les touches du clavier par
des boutons de la bibliothèque nsi_ui. Le fichier exo8.py contient le code du corrigé de cet exercice pour
exécution en dehors de Jupyter.
from nsi_ui import *

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def pentagone():
for i in range(5):
forward(50)
right(72)

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

input_int('Entrer un entier : ')

b.

Chapitre 3 - Un peu d’interaction 6


def input_float(message):
""" Demande un nombre décimal à l'utilisateur """
while True:
try:
return float(input(message))
except ValueError:
print('erreur de saisie - recommencer')

input_float('Entrer un nombre décimal : ')

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

def input_float_interval(message, min, max):


""" Demande un nombre décimal dans l'intervalle donné """
v = input_float(message)
while v < min or v > max:
if v < min:
print('Valeur trop petite')
else:
print('Valeur trop grande')
v = input_float(message)
return v

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


input_int_interval('Entree une valeur entre 0 et 100', 0, 100)

10 a.
from random import randint

secret = randint(0, 100) # nombre à trouver


trouve = False
while not trouve:
n = input_int('Entrer votre : ') # exercice 9
if n < secret:
print(' trop petit')
elif n > secret:
print(' trop grand')
else:
print('Bravo !')
trouve = True

b.
from random import randint

secret = randint(0, 100) # nombre à trouver


trouve = False
nessais = 0 # nombre d'essais
while not trouve:
n = input_int('Entrer votre : ') # exercice 9
nessais = nessais + 1
if n < secret:
print(' trop petit')

Chapitre 3 - Un peu d’interaction 7


elif n > secret:
print(' trop grand')
else:
if nessais <= 6:
print(f'Bravo ! trouvé en {nessais} essais')
else:
print(f'Trouvé en {nessais} essais')
trouve = True

c.
from random import randint

secret = randint(0, 100) # nombre à trouver


trouve = False
nessais = 0 # nombre d'essais
minimum = 0 # valeur minimale de l'intervalle
maximum = 100 # valeur maximale de l'intervalle

while not trouve:


n = input_int('Entrer votre : ') # exercice 9
nessais = nessais + 1
if n < secret:
if n <= minimum:
print(' je vous croyais plus malin !')
else:
print(' trop petit')
minimum = n

elif n > secret:


if n >= maximum:
print(' je vous croyais plus malin !')
else:
print(' trop grand')

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


maximum = n

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

while insectes * 3 < maximum:


insectes = insectes * 3
njours = njours + 1

print(f'{insectes} insectes en {njours} jours')

Chapitre 3 - Un peu d’interaction 8


b.
from nsi_ui import *

# Utile seulement avec Jupyter


clear_ui()

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

entree_facteur = slider('Facteur de reproduction', 1, 10)


entree_initial = slider('Population initiale', 1, 100)
entree_maximum = slider('Population maximale', 1, 10000)
button('Simuler', simulation)

main_loop()

13
from nsi_turtle import * # Dans Python on peut importer turtle
from nsi_ui import *

# Utile seulement avec Jupyter


clear_ui()

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def decompte():
""" Appelé chaque seconde du décompte """
compteur = get_int(compteur_texte)
compteur = compteur - 1
set_text(compteur_texte, compteur)
if compteur <= 0:
stop_animate()
write("Decollage")
left(90)
forward(100)

def demarre():
""" Lance le chronomètre """
clear()
compteur = get_int(compteur_texte)
animate(decompte, 1000)

compteur_texte = entry("Compteur :")


button("Start", demarre)

main_loop()

14
from nsi_ui import *

# Utile seulement avec Jupyter


clear_ui()

Chapitre 3 - Un peu d’interaction 9


# temps de début du dernier tour
# (0 ou bien dernier appui sur le bouton Lap)
dernier_tour = 0

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


chrono_texte = entry("Chronometre : ")
set_width(chrono_texte, 12)
button("Start", demarre)
button("Lap", tour)
button("Stop", arrete)
tour_texte = entry("Dernier tour : ")
set_width(tour_texte, 12)

main_loop()

15 a.
from turtle import *

def affiche_nombre(heure):
write(heure)

from nsi_ui import *

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)

Chapitre 3 - Un peu d’interaction 10


goto(0, 0)

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)

def affiche_heure(H, M):


""" Affiche les aiguilles correspondant à l'heure H:M """
pensize(3)
dessine_aiguille((H % 12) * 30, 20)
pensize(1)
dessine_aiguille(M * 6, 40)

def efface_heure(H, M):


""" Affiche les aiguilles correspondant à l'heure H:M """
pencolor("white")
pensize(10)
dessine_aiguille((H % 12) * 30, 20)
dessine_aiguille(M * 6, 40)
pensize(1)
pencolor("black")

affiche_heure(10, 10)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


efface_heure(10, 10)
affiche_heure(16, 40)

c.
# Note : nécessaire seulement dans Jupyter
clear_ui()

# L'heure courante
H = 16
M = 40

# On reprend l'interface de l'exercice 8 en ajoutant


# les appels à efface_heure et affiche_heure pour
# mettre à jour l'horloge analogique
#
def avance_heure():
""" Ajouter 1 à l'heure H """
global H, M
efface_heure(H, M)

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

Chapitre 3 - Un peu d’interaction 11


global H, M
efface_heure(H, M)

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 *

# Note : nécessaire seulement dans Jupyter


clear_ui()

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)

Chapitre 3 - Un peu d’interaction 12


def CtoF():
""" Convertit la température du slider en degrés Fahrenheit
et affiche le résultat
"""
t = get_int(temperature)
set_text(result, t * 9/5 + 32.)

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

# Utile seulement avec Jupyter


clear_ui()

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)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


main_loop()

18
from turtle import *

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

Chapitre 3 - Un peu d’interaction 13


# entrée de texte
text = entry("Texte : ")
set_font('Helvetica')

# boutons pour l'alignement


begin_vertical()
button("Gauche", aligne, 'left')
button("Centre", aligne, 'center')
button("Droite", aligne, 'right')
end_vertical()

# tirette pour la taille


begin_vertical()
taille = slider("Taille", 10, 50, taille)

# boutons pour le style


begin_horizontal()
button("Normal", style, 'normal')
button("Gras", style, 'bold')
button("Italique", style, 'italic')
end_horizontal()

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


Une meilleure stratégie est de garder en mémoire le plus petit nombre pour lequel l’utilisateur a
indiqué que le nombre cherché est plus grand que lui (minimum), et le plus grand nombre pour lequel
l’utilisateur a indiqué que la nombre cherché est plus petit que lui ( maximum). On réduit ainsi
l’intervalle à chaque tour.
Ici, on choisit un nombre au hasard entre le minimum et le maximum.
from random import randint

minimum = 0 # le nombre le plus petit pour l'instant


maximum = 100 # le nombre le plus grand pour l'instant
trouve = False # True lorsque l'on a trouvé le nom
nessais = 0 # nombre de coups

def choix(min, max):


return randint(min+1, max-1)

print('Choisissez un nombre dans votre tête.')


print('À chacune de mes propositions, répondre')
print(' < si votre nombre est plus petit que celui que je propose')
print(' > si votre nombre est plus grand que celui que je propose')
print(" = si j'ai deviné le nombre")

while not trouve:


essai = choix(minimum, maximum)
nessais = nessais + 1
reponse_ok = False
while not reponse_ok:
reponse = input(f'{essai} ? (repondre <, > ou =) ')

Chapitre 3 - Un peu d’interaction 14


reponse_ok = True
if reponse == '>':
minimum = essai
elif reponse == '<':
maximum = essai
elif reponse == '=':
trouve = True
print(f"J'ai trouvé en {nessais} coups !")
else:
reponse_ok = False
print('réponse incorrecte')

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 *

from random import randint

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


minimum = 0 # le nombre le plus petit pour l'instant
maximum = 100 # le nombre le plus grand pour l'instant
trouve = False # True lorsque l'on a trouvé le nom
nessais = 0 # nombre de coups

def choix(min, max):


return randint(min+1, max-1)

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

while not trouve:


# Note : la fonction clear() de nsi_curses efface l'écran
clear()

essai = choix(minimum, maximum)


add_str(40, 20, f'{essai}')
nessais = nessais + 1

reponse_ok = False
while not reponse_ok:

Chapitre 3 - Un peu d’interaction 15


reponse = wait_key() # attente d'une touche
reponse_ok = True
if reponse == '>':
minimum = essai
elif reponse == '<':
maximum = essai
elif reponse == '=' || reponse == ' ':
# On ajoute la barre d'espace pour faciliter le jeu
# car le caractère '=' est difficile d'accès
trouve = True
clear()
add_str(30, 20, f"J'ai trouvé en {nessais} coups !")
else:
reponse_ok = False
clear()
add_str(30, 20, 'réponse incorrecte')

if minimum == maximum or minimum+1 == maximum:


clear()
add_str(30, 20, 'Vous avez triché !')
trouve = True

add_str(30, 21, 'Taper une touche pour quitter')


c = wait_key()
end_curses()

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


clear()
add_str(30, 20, f'Trop lent ! vous avez perdu')
elif reponse == '>':
...

21 a.
from nsi_ui import *

# Utile seulement avec Jupyter


clear_ui()

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

Chapitre 3 - Un peu d’interaction 16


# La deuxième rangée
begin_horizontal()
button('4', rien)
button('5', rien)
button('6', 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 *

# Utile seulement avec Jupyter

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


clear_ui()

# Le nombre en train d'être entré


a=0

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

Chapitre 3 - Un peu d’interaction 17


begin_horizontal()
button('7', chiffre, 7)
button('8', chiffre, 8)
button('9', chiffre, 9)
button('/', rien)
end_horizontal()

# 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()

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


c. Note : la logique des fonctions chiffre et operation est ce qui fait la difficulté de cet exercice. Il faut en
effet mémoriser la saisie du premier opérande et de l’opération, et appliquer l’opération une fois la fin
du second opérande saisie, c’est-à-dire lorsque l’on tape soit =, soit une autre opération. On utilise
deux variables globales pour mémoriser le premier opérande ( nb_en_attente) et l’opération
(op_en_attente), ainsi qu’un booléen (debut_nombre) qui signale le début de la saisie d’un nouveau
nombre.
from nsi_ui import *

# Utile seulement avec Jupyter


clear_ui()

# Le nombre en cours de saisie


a=0
# True si on est prêt à saisir un nouveau nombre
debut_nombre = True
# Le nombre déjà saisi, en attente d'une opération
nb_en_attente = 0
# L'opération en attente de saisie du second nombre
op_en_attente = ""

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 :

Chapitre 3 - Un peu d’interaction 18


# on mémorie le nombre courant,
# et on en commence un nouveau
nb_en_attente = a
a=d
debut_nombre = False
else:
# On est en cours de saisie d'un nombre :
# ajouter le chiffre d à sa droite
a = a*10 + d
set_text(res, a)

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


elif op_en_attente == '/':
if a == 0:
nb_en_attente = 0
div_zero = True
else:
a = nb_en_attente / a
# Note : il n'y a rien à faire si l'opération en attente est =

# préparer la prochaine saisie


debut_nombre = True
op_en_attente = op

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

Chapitre 3 - Un peu d’interaction 19


# La rangée du haut
begin_horizontal()
button('7', chiffre, 7)
button('8', chiffre, 8)
button('9', chiffre, 9)
button('/', operation, '/')
end_horizontal()

# 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()

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


main_loop()

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.

Chapitre 3 - Un peu d’interaction 20


def avance():
forward(2)

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)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def droite():
right(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')

Chapitre 3 - Un peu d’interaction 21


onkey(start_stop, 'Space')

left(90)
main_loop()

d. On prend le même programme en remplaçant la fonction avance par celle-ci :


def avance():
# Note : on teste si on est sorti après avoir bougé la tortue
# Ainsi, une fois la tortue sortie, on peut la faire tourner,
# puis la faire redémarrer pour qu'elle rentre dans la zone.
forward(vitesse)
# On utilise des bornes plus petites que dans l'énoncé
# (200 au lieu de 400) pour tester plus facilement
borne = 200
if abs(xcor()) > borne or abs(ycor()) > borne:
print('Stopped !')
start_stop()

e. On prend le même programme en remplaçant la fonction avance par celle-ci :


def avance():
# Note : on teste si on est sorti après avoir bougé la tortue
# Ainsi, une fois la tortue sortie, on peut la faire tourner,
# puis la faire redémarrer pour qu'elle rentre dans la zone.
forward(vitesse)
# On utilise des bornes plus petites que dans l'énoncé
# (100 au lieu de 400) pour tester plus facilement
borne = 100
if abs(xcor()) > borne:
setheading(180 - heading())
forward(vitesse)

if abs(ycor()) > borne:

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


setheading(360 - heading())
forward(vitesse)

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 *

# S'assurer que les déplacements de la tortue sont instantanés


speed(0)

def bouger_tortue(x, y):


""" Déplacer la tortue à la position x, y """
penup() # pour ne pas laisser de trace
goto(x, y)
pendown()

# fonction de rappel lorsque l'on clique dans la fenêtre de la tortue


onscreenclick(bouger_tortue)

main_loop()

b.
# importer nsi_turtle de préférence à turtle
from nsi_turtle import *

Chapitre 3 - Un peu d’interaction 22


# fonction de rappel lorsque l'on déplace la tortue avec la souris
ondrag(goto)

main_loop()

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re

Chapitre 3 - Un peu d’interaction 23


Chapitre 4 – Types structurés

Notes sur le chapitre


Le but de ce chapitre est d’introduire les types construits fondamentaux que sont les tuples, les
tableaux et les dictionnaires, en mettant en avant leurs différences plutôt que leurs similarités.

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

On trouve un exemple d’utilisation de singleton à la fin de l’exercice 38.


Accès aux éléments - C’est à dessein que nous ne mentionnons pas l’accès indexé à un tuple ( date[0]),
ni l’énumération par la boucle for pour éviter la confusion avec les tableaux. (La note en bas de la page
65 mentionne cependant les opérations sur les tableaux qui sont applicables aux tuples.)
Nous préférons utiliser systématiquement l’affectation multiple pour extraire les éléments d’un tuple.
Cela nous parait plus cohérent avec leur nature, en particulier du fait que les éléments peuvent être de
types différents. Cela permet aussi de donner des noms expressifs aux éléments extraits.
Immuabilité - Le dernier paragraphe de la section 2 introduit l’aspect immuable des tuples. La fin de
ce paragraphe et l’exemple donné n’est pas vraiment dû à l’immuabilité (ce serait la même chose avec
un tableau). Il s’agit ici de rappeler qu’une variable informatique n’est pas la même chose qu’une
variable mathématique : lorsqu’une variable apparaît à droite d’une affectation, elle est « remplacée »
par sa valeur au moment de l’affectation.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


Les tableaux et les listes
Le programme indique : « L’aspect dynamique des tableaux de Python n’est pas évoqué. Python
identifie listes et tableaux. » Nous avons donc introduit les tableaux de taille fixe, contenant des
éléments de même type (bien que nous ne vérifions pas cette contrainte en pratique).
Cependant, nous avons aussi choisi d’introduire les listes, de manière minimale, afin de faciliter la
construction progressive d’un tableau (ce qui sera le cas systématiquement dans le chapitre 7 avec les
tables de données) avec la méthode append et, dans quelques cas, la modification du tableau avec les
méthodes insert et pop. Nous avons d’ailleurs noté que les méthodes de listes sont régulièrement
utilisées dans les pages d’ÉduScol.
En effet, il nous semble préférable de construire un tableau avec :
t = []
for i in range(n):
t.append(i*i)

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 :

Chapitre 4 – Types structurés 1


t = [0] * n
for i in range(n):
t[i] = i*i

Par ailleurs les compréhensions sont introduites au chapitre 5.

Les chaînes de caractères


Nous ne l’avons pas indiqué dans le cours, mais il peut être utile de mentionner que l’on peut convertir
une chaîne en tableau avec la fonction list(ch).
De façon générale, nous n’avons pas introduit les conversions ( tuple(l), list(t), dict(), str(), etc), qui nous
semblent de nature à créer la confusion entre les différents types.

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


Prérequis – TEST
1 b ; 2 b ; 3 a ; 4 b ; 5 b ; 6 b.

Activité : Relevé de températures


Afficher le relevé

# Exemple : station de Boston, année 2020


releve_Boston = [(-10, 23.3), (-11.1, 17.8), (-7.2, 22.2), (-0.5, 16.7),
(1.1, 28.3), (8.3, 33.3), (15, 35), (15.6, 34.4),
(6.7, 29.4), (-2.2, 25.6), (-4.4, 23.9), (-8.9, 17.2)]
plot(releve_Boston)
show()

Afficher la température Moyenne

def moyenne(t):
tmin, tmax = t
return (tmin + tmax) / 2

print(moyenne(releve[0]))

Chapitre 4 – Types structurés 2


def moyennes_releve(releve):
""" retourne un tableau des moyennes des températures de releve """
moyennes = [0] * len(releve) # créer un tableau rempli de 0
for i in range(len(releve)):
moyennes[i] = moyenne(releve[i])
return moyennes

from matplotlib.pyplot import plot, show


plot(moyennes_releve(releve))
show()

Afficher les trois courbes et ajouter une légende

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

from matplotlib.pyplot import legend


plot(ajouter_moyennes(releve))
legend(["min", "max", "moy"])
show()

Enregistrer les relevés de plusieurs villes

for ville in releves.keys():


plot(moyennes_releve(releves[ville]))
legend(releves.keys())
show()

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


QCM (CHECKPOINT)
1 c ; 2 b ; 3 d ; 4 b et c ; 5 b ; 6 d ; 7 a ; 8 b ; 9 d.

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

# On continue de demander tant que le(s) caractère(s) entré(s)


# ne sont pas une unique lettre, un tiret ou une apostrophe.
while True:
entree = input("Veuillez entrer une unique lettre en minuscule, une apostrophe ou un tiret :")
if len(entree) == 1 and entree in "abcdefghijklmnopqrstuvwxyz-'":
# Correct
return entree
# En l'absence de `return` la boucle continue d'itérer.
print("Cette entrée est incorrecte, veuillez réessayer.")

# 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`.

Chapitre 4 – Types structurés 3


Par exemple :
- remplace("boom", "b---", "o") retourne "boo-"
- remplace("boom", "b---", "m") retourne "b--m"
- remplace("boom", "b---", "b") retourne "b---"
- remplace("boom", "b---", "q") retourne None
"""

if lettre not in reference:


# La lettre proposée n'est pas dans le mot recherché
return None

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

# Sinon on reconstruit le nouveau mot à trous.


mot_resultat = ""
for i in range(len(reference)):
if reference[i] == lettre:
mot_resultat += lettre
else:
mot_resultat += actuel[i]

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


mot_actuel = "-" * len(mot) # Le mot à trous, vide au début.
erreurs = 0 # Le nombre d'erreurs effectuées.

while erreurs < nb_erreurs:


lettre = demander_lettre()
remplacement = remplace(mot, mot_actuel, lettre)

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

# Si on sort de la boucle on a atteint le nombre maximal d'erreurs


print(f"Perdu, le mot était '{mot}'.")

# Test
pendu("test", 4)

4. Bonus : Dessiner le pendu


Le code suivant peut être fourni aux élèves. Il fournit la fonction dessiner_potence(i) qui dessine la i-ème
étape de la potence.

Chapitre 4 – Types structurés 4


from turtle import *

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)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


"""
fun, arg = segment
fun(arg)

# Ces variable définissent les différents positions et taille de la potence


base = 40 # base potence
h = 200 # hauteur potence
l = 100 # longueur potence
a = 40 # equerre potence
c = 10 # longueur de la corde

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

# Ce tableau défini chaque étape de la potence sous la forme d'un tuple


# (fonction à appeler, paramètre de la fonction)
pendu = [
(polyligne, [(0, 0)]),
(polyligne, [(base, 0), (-base, 0)]), # base de la potence
(polyligne, [(0, 0), (0, h)]), # poteau
(polyligne, [(0, h), (0, h-a), (a, h), (0, h), (l, h), (l, h-c)]),
(cercle, (l, h-c, t)), # tete
(polyligne, [(l, h-c-t), (l, h-c-t-v)]), # corps
(polyligne, [(l, h-c-t-e), (l+m, h-c-t-e-b)]), # bras droit

Chapitre 4 – Types structurés 5


(polyligne, [(l, h-c-t-e), (l-m, h-c-t-e-b)]), # bras gauche
(polyligne, [(l, h-c-t-v), (l+p, h-c-t-v-j)]), # jambe droite
(polyligne, [(l, h-c-t-v), (l-p, h-c-t-v-j)]), # jambe gauche
(polyligne, [(0, 0)]),
]

# Dessiner la i-ème étape de la potence


def dessiner_potence(i):
dessiner(pendu[i])

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)

while erreurs < nb_erreurs:


lettre = demander_lettre()
remplacement = remplace(mot, mot_actuel, lettre)

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:

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


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

# Si on sort de la boucle on a atteint le nombre maximal d'erreurs


print(f"Perdu, le mot était '{mot}'.")

pendu_potence("test")

TP B : Dessiner avec la tortue


1. Afficher un dessin
a.
from turtle import *

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

Chapitre 4 – Types structurés 6


goto(x, y)

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)

2. Enregistrer un dessin à main levée

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


a.
# Note 1 : la tortue Jupyter ne reconnait pas les événements
# onscreenclick, ondrag, etc. Il faut donc exécuter ce code sous Python.
#
# Note 2 : sous Python :
# from turtle import *
# speed(0) # pour pour que l'interaction soit fluide

def sauter(x, y):


""" Déplace la tortue sans dessiner à la position x, y """
penup()
goto(x, y)
pendown()

onscreenclick(sauter) # bouger la tortue lorsqu'on clique dans la fenêtre

def tracer(x, y):


""" Dessine un trait jusqu'à la position x, y"""
goto(x, y)

ondrag(tracer)

b.
dessin = []

def debut(x, y):


""" Initialise le dessin"""
global dessin
dessin = []

Chapitre 4 – Types structurés 7


onclick(debut)

def tracer(x, y):


""" Trace et enregistre le dessin """
dessin.append((x, y))
goto(x, y)

ondrag(tracer)

c.
from nsi_ui import *

def redessiner():
""" Dessine le dessin enregistre """
clear()
dessine(dessin)

button("Dessiner", redessiner)

3. Enregistrer et rappeler des dessins nommés

# le dictionnaire qui stocke les dessins


dessins = {
"maison": maison,
"avion": avion,
"ordi": ordi
}

def enregistrer():
""" enregistre un dessin sous le nom de la zone d'entrée """
nom = get_string(entree_nom)
if nom != "":
dessins[nom] = dessin

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def dessiner():
""" dessine le dessin dont le nom est dans la zone d'entrée """
nom = get_string(entree_nom)
if nom in dessins:
dessine(dessins[nom])

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)

Chapitre 4 – Types structurés 8


print(milieu((10, 20), (20, 30)))

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

# a1 == a2 : comparer les mois


if m1 < m2:
return True
if m1 > m2:
return False

# m1 == m2 : comparer les jours


if j1 < j2:
return True
return False

print(anterieur((14, 7, 2022), (24, 12, 2021)))


print(anterieur((14, 7, 2022), (15, 8, 2022)))
print(anterieur((14, 7, 2022), (1, 7, 2022)))

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


j1, m1, a1 = d1
j2, m2, a2 = d2
age = a2 - a1

# (j1, m1, a2) est la date anniversaire


if not anterieur((j1, m1, a2), d2):
age = age - 1
return age

print(age((20, 7, 2004), (14, 7, 2022)))


print(age((20, 7, 2004), (15, 8, 2022)))

3
def nom_date(personne):
""" Retourne le nom de famille et la date de naissance """
(prenom, nom), (jour, mois, an) = personne
return (nom, an)

p = (("Ada", "Lovelace"), (10, 12, 1815))


print(nom_date(p))

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:

Chapitre 4 – Types structurés 9


return 0

# calculer la somme des éléments de t


m=0
for e in t:
m=m+e

# diviser par leur nombre pour obtenir la moyenne


return m / len(t)

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

# attention à la borne supérieure pour ne pas déborder du tableau !


for i in range(len(t) -1):
if t[i] > t[i+1]:
return False
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 = []

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# Note : on pourrait écrire directement
# for x, y in t:
for p in t:
x, y = p
tx.append(x)
ty.append(y)
return (tx, ty)

t = [(0,0), (0,100), (50,150), (100,100), (100,0)]


print(unzip(t))

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)

t = [(0,0), (0,100), (50,150), (100,100), (100,0)]


print(unzip(t))

b.

Chapitre 4 – Types structurés 10


def zip(tx, ty):
""" Crée un tableau de tuples à partir de tx et ty"""
t = []
# Note : l'énoncé ne le précise pas : si tx et ty sont
# de longueurs différentes, on prend la plus courte
n = min(len(tx), len(ty))
for i in range(n):
t.append((tx[i], ty[i]))
return t

tx = [0, 0, 50, 100, 100]


ty = [0, 100, 150, 100, 0]
print(zip(tx, ty))

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

tx = [0, 0, 50, 100, 100]


ty = [0, 100, 150, 100, 0]
print(zip(tx, ty))

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


n = 0 # compte du nombre d'éléments True
dernier = 0 # indice du dernier élément True
for i in range(1000):
if divisible_7[i]:
n=n+1
dernier = i
print(n, 'nombres divisibles par 7 et < 1000')
print('le plus grand est', dernier)

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

Chapitre 4 – Types structurés 11


valeur = 0
# prendre les caractères de gauche à droite
for car in nombre:
# trouver la valeur du caractère
chiffre = 0
for i in range(10):
if car == chiffres[i]:
chiffre = i
# Note : un caractère inconnu sera pris comme 0
valeur = valeur * 10 + chiffre
return valeur

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:

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


valeur = valeur * 10 + chiffre(car)
return valeur

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

Chapitre 4 – Types structurés 12


""" Retourne True si mot respecte les contraintes d'un mot de passe """
# Vérifier la longueur
if len(mot) < 8:
return False

# Initialiser les 4 autres conditions


a_majuscule = False
a_minuscule = False
a_chiffre = False
a_car_special = False

# Mettre à jour les conditions avec chaque lettre du mot


for lettre in mot:
if lettre in minuscules:
a_minuscule = True
elif lettre in majuscules:
a_majuscule = True
elif lettre in chiffres:
a_chiffre = True
else:
a_car_special = True

# Retourner True si les 4 conditions sont vérifiées


return a_majuscule and a_minuscule and a_chiffre and a_car_special

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


print('Aa'.isupper())
print('abc'.islower())
print('Aa'.islower())
print('éàç'.islower())
print('Été'.isalpha())
print('123'.isdigit())

11
# Avec la méthode split :
def nb_mots(phrase):
""" Retourne le nombre de mots dans phrase """
return len(phrase.split(' '))

print(nb_mots("Le petit chat est mort"))

# Sans la méthode split :


def nb_mots(phrase):
""" Retourne le nombre de mots dans phrase """
nbmots = 0
for car in phrase:
if car == ' ':
nbmots += 1
# ne pas oublier le dernier mot !
return nbmots + 1

print(nb_mots("Le petit chat est mort"))

Chapitre 4 – Types structurés 13


12
# Avec la méthode split :
def longueur_mots(phrase):
""" Retourne un tableau des longueurs des mots de phrase """
l = []
for mot in phrase.split(' '):
l.append(len(mot))
return l

print(longueur_mots("Le petit chat est mort"))

# Sans la méthode split :


def longueur_mots(phrase):
""" Retourne un tableau des longueurs des mots de phrase """
longueurs = [] # tableau des longueurs
lmot = 0 # longueur du mot courant
for car in phrase:
if car == ' ':
# fin d'un mot
longueurs.append(lmot)
lmot = 0
else:
# mot courant
lmot = lmot + 1
# ne pas oublier le dernier mot !
longueurs.append(lmot)
return longueurs

print(longueur_mots("Le petit chat est mort"))

13
capitales = {'France': 'Paris', 'Italie': 'Rome',
'Espagne': 'Madrid', 'Allemagne': 'Berlin',

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


'Angleterre' : 'Londres', 'Grèce' : 'Athènes'}

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

# La fonction pays_de s'écrit alors simplement :


def pays_de(ville):
""" Retourne le pays dont `ville` est la capitale, ou None si inconnu """
if ville in pays:
return pays[ville]
return None

print(pays_de('Madrid'))
print(pays_de('Bruxelles'))

Chapitre 4 – Types structurés 14


14 a.
def age(personne):
""" Retourne l'age de la personne ou None si inconnu """
if personne in personnes:
return personnes[personne]['age']
return None

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


print(carte_valide((1, 'Coeur')))
print(carte_valide((11, 'trefle')))

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

Chapitre 4 – Types structurés 15


# fonction auxiliaire pour retourner une liste de noms de cartes
def noms_cartes(cartes):
if len(cartes) == 0:
return 'paquet vide'
noms = nom_carte(cartes[0])
for i in range(1, len(cartes)):
noms = noms + ', ' + nom_carte(cartes[i])
return noms

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def distance(p1, p2):
""" Retourne la distance euclidienne entre deux points """
x1, y1 = p1
x2, y2 = p2
dx = x2 - x1
dy = y2 - y1
return sqrt(dx*dx + dy*dy)

print(distance((5, 10), (9, 13)))

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

Chapitre 4 – Types structurés 16


print(dimensions(dessin2))
print(dimensions(dessin3))

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


inverse = []
for e in t:
inverse.insert(0, e)
return 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

Chapitre 4 – Types structurés 17


print(inverse_tableau3([1, 2, 3, 4, 5]))

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

# Afficher l'histogramme des fréquences


bar(range(len(freq)), freq)
show()

# Afficher l'écart relatif entre les


# valeurs min et max des fréquences
# pour avoir une idée de l'uniformité
ecart = (max(freq) - min(freq)) * 100 / n
print(f'n = {n}, ecart = {ecart}%')

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


test_random(1000)

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)

# Note : par rapport au corrigé du manuel,


# on définit une fonction qui prend en paramètre
# le nombre m de tirages pour faciliter les tests
def test_des(m):
""" Affiche l'histogramme de m tirages de 2 dés """
# tirages[i] = nombre de fois où on tire i
tirages = [0] * 12
for i in range(m):

Chapitre 4 – Types structurés 18


tirages[des()-1] += 1
print(f'{m} tirages')
# affichage
bar(range(1,13), tirages)
show()

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

# Note : par rapport au corrigé du manuel,


# on définit une fonction qui prend en paramètres
# le nombre n de dés et m de tirages pour faciliter les tests
def test_ndes(n, m):
""" Affiche l'histogramme de m tirages de n dés """
# tirages[i] = nombre de fois où on tire i
tirages = [0] * (6*n+1)
for i in range(m):
tirages[ndes(n)] += 1
print(f'{n} dés, {m} tirages')
bar(range(6*n+1), tirages)
show()

test_ndes(10, 1000)
test_ndes(10, 100000)

c.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# Note : par rapport au corrigé du manuel,
# on passe le nombre de tirages n en paramètre
def pile_face(n):
""" Retourne le nombre de fois qu'on tire pile sur n tirages """
pile = 0
for i in range(n):
pile += randint(0, 1)
return pile

# compter les résultats de m séries de n tirages


def test_loi_normale(n, m):
""" Affiche l'histogramme de m séries de n tirages à pile ou face """
# tirages[i] = nombre de fois où on tire i fois pile
tirages = [0] * (n+1)
for i in range(m):
tirages[pile_face(n)] += 1
print(f'{m} séries de {n} tirages')
bar(range(n+1), tirages)
show()

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.

Chapitre 4 – Types structurés 19


tranches = [(0, 10000, 0), (10000, 25000, 11), (25000, 75000, 30), (75000, 150000, 41), (150000, -1, 45)]

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)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


ty.append(montant_impot(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

Chapitre 4 – Types structurés 20


return taux

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def retire_element_bug(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
for i in range(len(t)):
if e == t[i]:
# on a trouvé une occurrence
t.pop(i)
print('trouvé', e) # pour montrer qu'on manque la 2e occurrence
present = True
# on a parcouru tous les éléments
return present

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

Chapitre 4 – Types structurés 21


# on a trouvé une occurrence
# comme pop retire l'élément, on ne change pas i
t.pop(i)
present = True
else:
i = i + 1 # on passe à l'élément suivant
# on a parcouru tous les éléments
return present

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


t = [1, 2, 2, 3, 4]
print(t)
print(retire_element3(t, 2))

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

print(meme_contenu([1, 2, 3, 4], [4, 2, 3, 1]))


print(meme_contenu([1, 2, 3, 4], [4, 2, 3, 1, 5]))
print(meme_contenu([1, 2, 3, 4], [4, 2, 1]))

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

Chapitre 4 – Types structurés 22


du tableau un à un, ce qui nécessite à chaque fois une recopie du tableau. Cependant, cette méthode ne
fonctionne que si les éléments des tableaux sont immuables, car ils sont utilisés comme clés d’un
dictionnaire.
def meme_contenu2(t1, t2):
""" Retourne True si t1 et t2 ont les mêmes éléments
(même s'ils sont dans un ordre différent)
"""
# il faut que les tableaux soient de même longueur
if len(t1) != len(t2):
return False

# créer un dictionnaire avec les éléments de t2 comme clés


# et leur nombre d'occurrences comme valeurs
occ = {}
for e in t2:
if e in occ: # élément déjà présent : incrémenter le compte
occ[e] = occ[e] + 1
else: # nouvel élément : initialiser le compte
occ[e] = 1

# parcourir t1 et décompter les occurrences de occ


for e in t1:
if e not in occ:
return False # e n'est pas dans t2
else:
occ[e] = occ[e] - 1 # décompter une occurrence
if occ[e] == 0: # plus d'occurrences
del occ[e] # retirer du dictionnaire

# reste-t-il des entrées dans le dictionnaire ?


return len(occ) == 0

print(meme_contenu2([1, 2, 3, 4], [4, 2, 3, 1]))

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


print(meme_contenu2([1, 2, 3, 4], [4, 2, 3, 1, 5]))
print(meme_contenu2([1, 2, 3, 4], [4, 2, 1]))

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

Chapitre 4 – Types structurés 23


22 a.
from random import randint

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


return 0 # bataille

print(duel((4, 'Pique'), (7, 'Carreau')))


print(duel((7, 'Pique'), (4, 'Carreau')))
print(duel((7, 'Pique'), (7, 'Carreau')))

b.
# on suppose que le code de l'exercice 15 a été exécuté

from random import randint

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

Chapitre 4 – Types structurés 24


elif gagnant == 2:
print(noms_cartes([c1, c2]), "- joueur 2 l'emporte")
else:
print(noms_cartes([c1, c2]), '- Bataille !')
return (duels, gagnant)

duels, gagnant = jouer(jeu)


print(f'joueur {gagnant} gagne, {duels} point(s)')

c.
# Mélanger le jeu
jeu_melange = melanger(jeu)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def tirer(paquet):
""" Tire une carte au hasard en la retirant du jeu """
# s'assurer qu'il reste des cartes
if len(paquet) == 0:
return None
# prendre la première carte
return paquet.pop(0)

# Attention : si on a une bataille sur la dernière carte,


# le programme va planter car `jouer` ne teste pas
# si `tirer` retourne None.
# Cette fonction de `jouer` corrige cela :
def jouer(paquet):
""" Jouer un tour de bataille et retourner un tuple (duels, gagnant)"""
duels = 0
gagnant = 0
while gagnant == 0:
# tirer deux cartes, s'il en reste
c1 = tirer(paquet)
c2 = tirer(paquet)
if c1 == None or c2 == None:
print('plus de cartes !')
return (0, 0)
# jouer
gagnant = duel(c1, c2)
duels += 1
# afficher le résultat
if gagnant == 1:
print(noms_cartes([c1, c2]), "- joueur 1 l'emporte")
elif gagnant == 2:
print(noms_cartes([c1, c2]), "- joueur 2 l'emporte")
else:
print(noms_cartes([c1, c2]), '- Bataille !')
return (duels, gagnant)

duels, gagnant = jouer(jeu_melange)


print(f'joueur {gagnant} gagne {duels} point(s)')

BONUS : une fonction qui joue une partie complète.


def partie():
""" Joue une partie sur un jeu entier """
jeu_melange = melanger(jeu)
# le score des deux joueurs
scores = [0, 0]
# tant qu'il y a des cartes
while len(jeu_melange) > 0:
# jouer une bataille
duels, joueur = jouer(jeu_melange)
# mettre à jour les scores

Chapitre 4 – Types structurés 25


scores[joueur-1] += duels
return scores

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

capitales = {'France': 'Paris', 'Italie': 'Rome',


'Espagne': 'Madrid', 'Allemagne': 'Berlin',
'Angleterre' : 'Londres', 'Grèce' : 'Athènes'}

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)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


c. Non. Si plusieurs entrées du dictionnaire ont même valeur, dans le dictionnaire inversé seule la
dernière sera prise en compte, et les autres seront perdues.
dico = {'Paris': 'France', 'Londres': 'Angleterre', 'Lyon': 'France'}
inv = inverse_dict(inverse_dict(dico))
print(inv)
print(inv == dico)

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

# afficher les scores des 3 mots

Chapitre 4 – Types structurés 26


for mot in ["pizza", "whisky", "dedramatiser"]:
print(f"Le mot '{mot}' totalise {valeur_mot(mot)} points.")

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

for mot in ["entrais", "ratines", "satiner", "riantes", "transie"]:


print(f"'{mot}' vaut {valeur_mot(mot)} points.")

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

for mot in ["entrais", "ratines", "satiner", "riantes", "transie"]:


print(f"'{mot}' vaut {lettre_compte_triple(mot, 3)} points quand la 4e lettre compte triple.")

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


nvilles = 0
for ville in villes_coordonnees:
# on extrait les coordonnées de chaque ville
lon, lat = villes_coordonnees[ville]
if lat < 23.43: # and lat > -23.43: # entre les 2 tropiques
nvilles += 1
print(ville)

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

for pays in villes_pays:


villes = villes_pays[pays]
# les trois variables pour accumuler les données

Chapitre 4 – Types structurés 27


population = 0
longitude = 0
latitude = 0
# énumérer les villes pour accumuler les données
for ville in villes:
population += villes_population[ville]
vlong, vlat = villes_coordonnees[ville]
longitude += vlong
latitude += vlat
# calculer les résultats
longitude /= len(villes)
latitude /= len(villes)
nvilles += len(villes)
print(pays, population, len(villes), longitude, latitude)

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


print(f"Il reste {nombre_taches_incompletes()} tâches à completer dans todo_list.")

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.

• Intégrer un second dict qui lie explicitement chaque énoncé à un nombre.

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

Cette dernière solution est présentée ci-dessous.


def tache_par_numero(liste, entree):
""" Retourne la tâche dont le numéro est la chaine `entree`,
ou None si `entree` n'est pas un numéro ou la tâche n'existe pas.
"""
# convertir la chaine `entree` en nombre entier
if not entree.isdigit():
print(f" '{numero}' n'est pas un numéro valide.")
return None
numero = int(entree)

Chapitre 4 – Types structurés 28


# énumérer les tâches pour trouver celle avec le bon numéro
n=1
for tache in liste.keys():
if n == numero:
return tache
n += 1
print(f" La tâche de numéro {numero} n'existe pas !")
return None

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]")

if commande == "+": # ajout de tâche


tache = input(" Nom de la tâche à ajouter :")
# On ajoute la tache en question, non effectuée (False) par défaut.
todo_list[tache] = False

elif commande == "-": # retrait de tâche


numero = input(" Numéro de la tâche à retirer :")
tache = tache_par_numero(todo_list, numero)
# On retire la tache du dictionnaire
if tache is not None:
del todo_list[tache]

elif commande == "v": # changement de statut de tâche


numero = input(" Numéro de la tâche dont il faut changer le statut :")
tache = tache_par_numero(todo_list, numero)
# On inverse le statut de la tache
if tache is not None:
# Rappel : not(True) = False, not(False) = True

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


todo_list[tache] = not todo_list[tache]

elif commande == "?": # les des tâches


num = 1
# On affiche les taches
# Note : le sujet indique de n'afficher que les tâches à effectuer
# ici on affiche toutes les tâches avec leur statut.
# Il suffit de ne rien afficher dans la branche `if`
# pour n'afficher que les tâches restant à effectuer
for tache, effectuee in todo_list.items():
if effectuee:
print(f" {num} v {tache}")
else:
print(f" {num} {tache}")
num += 1

elif commande == "q": # fin


fini = True

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

Chapitre 4 – Types structurés 29


return None
return (mots[0], mots[1], mots[2])

print(entree_annuaire("Jean Dupont 0987654321"))


print(entree_annuaire("in correct"))

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)

elif cmd == 'r': # rechercher un nom complet


entree = input('prénom nom ? ')
nom = entree.split(' ')
if len(nom) == 2:
cle = (nom[0], nom[1]) # la clé de l'annuaire
if cle in annuaire:
print(annuaire[cle])
else:

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


print('nom inconnu')
else:
print('nom incomplet')

elif cmd == 'f': # terminer


fini = True

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(' ')

if len(nom_recherche) == 2: # on recherche un nom complet


cle = (nom_recherche[0], nom_recherche[1])
if cle in annuaire:
# le nom complet est dans l'annuaire
print(entree, annuaire[cle])

elif len(nom_recherche) == 1: # on recherche un prenom ou un nom


# pour chaque clé (prenom, nom) de l'annuaire,
# on regarde si le nom cherché correspond au prénom ou au nom
for cle, tel in annuaire.items():
prenom, nom = cle
if entree == prenom or entree == nom:
print(entree, tel)
else:
print('nom incorrect')

Chapitre 4 – Types structurés 30


annuaire = {}
ajoute_annuaire(annuaire, entree_annuaire("Jean Dupont 0987654321"))
ajoute_annuaire(annuaire, entree_annuaire("Marie Dupont 0123456789"))
ajoute_annuaire(annuaire, entree_annuaire("Jean Durand 0546372819"))

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)

elif cmd == 'r': # recherche d'un nom


entree = input('prénom et/ou nom ? ')
recherche_annuaire(annuaire, entree)

elif cmd == 'f': # terminer


fini = True

29 a.
index = {}

def ajoute(index, mot):


""" Ajoute mot à l'index, à la clé de son initiale """
initiale = mot[0]
if initiale in index:
# ajouter aux mots qui ont cette initiale
index[initiale].append(mot)
else:
# créer un nouveau tableau pour cette initiale
index[initiale] = [mot]
return index

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


for mot in mots:
ajoute(index, mot)
print(index)

b.
# On utilise la fonction retire_element de l'exercice 21

def retire(index, mot):


""" Retirer mot de l'index """
initiale = mot[0] # la première lettre du mot
if initiale in index:
# le mot est dans l'index, il faut le retirer du tableau
if retire_element(index[initiale], mot):
# s'il n'y a plus de mots avec cette initiale,
# la supprimer du dictionnaire
if index[initiale] == []:
del index[initiale]

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.

Chapitre 4 – Types structurés 31


b.
def compte_mots(mots):
""" Prend une liste de mots en paramètre et retourne un dictionnaire
contenant le nombre d'occurrences de chaque mot
"""
occurrences = {}
for mot in mots:
mot = mot.lower()
if mot in occurrences:
# le mot est déjà dans le dictionnaire : incrémenter son compte
occurrences[mot] += 1
else:
# le mot est nouveau : l'ajouter au dictionnaire
occurrences[mot] = 1
return occurrences

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:

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


print('Pas un nombre :', ch)
return 0

def calcul(op1, oper, op2):


""" Retourne le résultat de l'opération op1 oper op2 """
if oper == '+':
return op1 + op2
if oper == '-':
return op1 - op2
if oper == '*':
return op1 * op2
if oper == '/':
if op2 != 0:
return op1 / op2
print('division par zéro')
return 0
return 0

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

op1 = entier(expr[0]) # premier opérande


oper = expr[1] # opération
op2 = entier(expr[2]) # second opérande

Chapitre 4 – Types structurés 32


print(calcul(op1, oper, op2))
return True

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]

# vérifier que la variable commence par une lettre


if not var[0].isalpha():

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


return False

# enregistrer la variable et sa valeur


variables[var] = entier(val)
print(variables[var])
return True

# Note : nouvelle version de `entier`


def entier(ch):
""" Si ch est un nom de variable, retourner sa valeur.
Sinon retourner la valeur entière de la chaîne,
ou 0 si elle ne représente pas un nombre
"""
# Note : la méthode isalpha retourne True si la
# chaîne ne contient que des caractères alphabétiques
if ch[0].isalpha():
if ch in variables:
return variables[ch]
else:
print('Variable inconnue: ', ch)
return 0
# Convertir ch en nombre entier
try:
return int(ch)
except ValueError:
print('Pas un nombre :', ch)
return 0

def calculette():

Chapitre 4 – Types structurés 33


""" Interface de la calculette """
fini = False
while not fini:
entree = input('opération (. pour finir) : ')
if entree == '.':
fini = True
else:
if not operation(entree) and not affectation(entree):
print('Opération non reconnue')

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]

# vérifier que la variable commence par une lettre


if not var[0].isalpha():
return False

if len(expr) == 3: # ['var', '=', 'valeur']


# enregistrer la nouvelle valeur de la variable
variables[var] = entier(expr[2])
print(variables[var])
elif len(expr) == 5: # ['var', '=', 'op1', 'oper', 'op2']
# effectuer l'opération et enregistrer le résultat

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


op1 = entier(expr[2])
oper = expr[3]
op2 = entier(expr[4])
variables[var] = calcul(op1, oper, op2)
print(variables[var])
else:
return False
return True

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.

olympique1 = [("Natation 400m Femmes", 2004, "Laure Manaudou"),


("Natation 400m Femmes", 2012, "Camille Muffat")]

olympique2 = {
"Natation 400m Femmes": {
2004: "Laure Manaudou",
2012: "Camille Muffat"
},
}

Chapitre 4 – Types structurés 34


33 a. On va utiliser un dictionnaire dont les clés sont l’unité de départ et les valeurs un dictionnaire
dont les clés sont l’unité d’arrivée et les valeurs le facteur de conversion.

# 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 ajouter_conversion(unite_depart, unite_arrivee, facteur):


""" Ajoute deux conversions (depart <-> arrivee) au dictionnaire"""
# créer les unités si nécessaire
if unite_depart not in conversions:
conversions[unite_depart] = {}
if unite_arrivee not in conversions:

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


conversions[unite_arrivee] = {}
# définir les facteurs de conversion
conversions[unite_depart][unite_arrivee] = facteur
conversions[unite_arrivee][unite_depart] = 1 / facteur

def convertir(valeur, unite_depart, unite_arrivee):


""" Affiche le résultat de la conversion de `valeur`, si elle existe """
# vérifier que la conversion existe
if unite_depart not in conversions or \
unite_arrivee not in conversions[unite_depart]:
print("conversion non disponible")
return
# afficher le résultat
conversion = valeur * conversions[unite_depart][unite_arrivee]
print(f'{valeur} {unite_depart} = {conversion} {unite_arrivee}')

def convertir_tout(valeur, unite_depart):


""" Affiche toutes les conversions possibles de `valeur` """
# vérifier que l'unité existe
if unite_depart not in conversions:
print("conversion non disponible")
return
# affichier toutes les conversions
for unite_arrivee in conversions[unite_depart]:
convertir(valeur, unite_depart, unite_arrivee)

def liste_unites():
""" Affiche la liste des unités connues """

Chapitre 4 – Types structurés 35


for unite in conversions:
print(unite)

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(' ')

if len(cmd) == 1 and cmd[0] == '?':


# commande ? : lister les unités
liste_unites()

elif len(cmd) == 5 and cmd[2] == '=':


# commande xx u1 = yy u2 : ajouter la conversion
if nombre(cmd[0]) and nombre(cmd[3]):
ajouter_conversion(cmd[1], cmd[4], float(cmd[3]) / float(cmd[0]))
else:
print('erreur')

elif len(cmd) == 4 and cmd[2] == 'en':


# commande xx u1 en u2 : afficher la conversion de xx
if nombre(cmd[0]):
convertir(float(cmd[0]), cmd[1], cmd[3])
else:
print('erreur')

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


elif len(cmd) == 3 and cmd[2] == '?':
# commande xx u ? : afficher toutes les conversions de xx
if nombre(cmd[0]):
convertir_tout(float(cmd[0]), cmd[1])
else:
print('erreur')

elif len(cmd) == 1 and cmd[0] == '.':


# commande . : terminer
fini = True

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

Chapitre 4 – Types structurés 36


return False
print(f'Ajout de 1 {unite_depart} = {facteur} {unite_arrivee}')
conversions[unite_depart][unite_arrivee] = facteur
return True

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

# Maintenant on ajoute les conversions du tableau `ajouts`


for depart, arrivee, f in ajouts:
if ajouter_1conversion(depart, arrivee, f):
encore = True

# cette fonction remplace la version précédente


def ajouter_conversion(unite_depart, unite_arrivee, facteur):
""" Ajouter les conversions depart <-> arrivee et toutes les conversions
A -> C pour lesquelles deux conversions A -> B et B -> C existent

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


"""
# ajouter les deux conversions depart <-> arrivee
ajouter_1conversion(unite_depart, unite_arrivee, facteur)
ajouter_1conversion(unite_arrivee, unite_depart, 1/facteur)
# ajouter la fermeture transitive
completer_conversions()

# 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'),

Chapitre 4 – Types structurés 37


"lait": (0.5, 'l'),
"oeufs": (6, 'unités')
}

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}')

def afficher_etape(n, etape):


""" Afficher l'étape de numéro n """
print(f'Etape {n} : {etape}')
# on extraits les mots du texte de l'étape
mots = etape.split(' ')
# pour chaque mot qui est dans le dictionnaire des ingrédients,
# on affiche la quantité correspondant
for mot in mots:
if mot in ingredients:
quantite, unite = ingredients[mot]
afficher_ingredient(mot, quantite, unite)

# Test : afficher toutes les étapes


for i in range(len(etapes)):
afficher_etape(i+1, etapes[i])

c.
def liste_ingredients(ingredients):
""" Afficher la liste des ingrédients """
print('Ingrédients :')
for ingredient in ingredients:
quantite, unite = ingredients[ingredient]

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


afficher_ingredient(ingredient, quantite, unite)

def liste_etapes(etapes):
""" Afficher la liste des étapes """
print('Étapes :')
for i in range(len(etapes)):
print(f' {i+1}. {etapes[i]}')

def recette(etapes, ingredients):


""" Interface textuelle pour suivre la recette """
fini = False
num_etape = 0 # indice prochaine étape
while not fini:
cmd = input('?')

if cmd == '': # afficher la prochaine étape (ou Fini! à la fin)


if num_etape == len(etapes):
print('Fini !')
fini = True
else:
afficher_etape(num_etape+1, etapes[num_etape])
num_etape += 1

elif cmd == '?': # afficher la liste des ingrédients


liste_ingredients(ingredients)

elif cmd == '??': # afficher la liste des étapes


liste_etapes(etapes)

Chapitre 4 – Types structurés 38


elif cmd == '-' : # revenir une étape en arrière
if num_etape > 0:
num_etape -= 1
if num_etape > 0:
afficher_etape(num_etape, etapes[num_etape-1])

elif cmd == '.': # terminer


fini = True

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)

def afficher_ingredient(ingredient, quantite, unite):


""" Afficher l'ingrédient dans l'unité souhaitée, si possible """
quantite, unite = convertir_quantite(quantite, unite)
if unite == 'unités':
print(f' {quantite} {ingredient}')
else:
print(f' {quantite} {unite} de {ingredient}')

ajouter_conversion('g', 'oz', 0.035274)


ajouter_conversion('l', 'fl.oz', 35.1951)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


recette(etapes, ingredients)

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')]

def ajoute(jour, heure, matiere):


""" Ajoute une entrée si le créneau est libre,
et retourne True, ou False si le créneau est pris
"""
# chercher si le créneau existe
for (j, h, m) in edt:
if jour == j and heure == h:
return False

# ajouter le créneau
edt.append((jour, heure, matiere))
return True

print(ajoute('mardi', '10h', 'NSI'))

Chapitre 4 – Types structurés 39


print(ajoute('lundi', '9h', 'anglais'))
print(edt)

Solution avec la seconde représentation :


edt = {'lundi': {'9h': 'histoire'}}

def ajoute(jour, heure, matiere):


""" Ajoute une entrée si le créneau est libre,
et retourne True, ou False si le créneau est pris
"""
# tester si le créneau est libre
if jour in edt and heure in edt[jour]:
return False

if jour in edt: # ajouter le creneau


edt[jour][heure] = matiere
else: # créer l'entrée de l'edt pour le jour
edt[jour] = {heure: matiere}
return True

print(ajoute('mardi', '10h', 'NSI'))


print(ajoute('lundi', '9h', 'anglais'))
print(edt)

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]

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def bissextile(a):
""" Retourne True si l'année a est bissextile """
if a % 4 == 0:
if a % 100 == 0:
if a % 400 == 0:
return True # les multiples de 400 sont bissextiles
return False # les multiples de 100 ne sont pas bissextiles
return True # les multiples de 4 sont bissextiles
return False

def jours_mois(m, a):


""" Retourne le nombre de jours du mois m de l'année a """
if m == 2 and bissextile(a):
return 29
return jours_par_mois[m]

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

Chapitre 4 – Types structurés 40


if j < 0 or j > jours_mois(m, a):
print(date, ": le jour est incorrect")
return False

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)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def jours_par_an(a):
""" Retourne le nombre de jours dan l'année a """
if bissextile(a):
return 366
return 365

# Note : on utilise la fonction `anterieur` de l'exercice 2

def nombre_jours(d1, d2):


""" Retourne le nombre de jours entre d1 et d2 """
if not date_valide(d1) or not date_valide(d2):
return 0

# on se ramène au cas où d1 est antérieur à d2


negatif = False
if not anterieur(d1, d2):
# intervertir les deux dates
d1, d2 = d2, d1
negatif = True

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

Chapitre 4 – Types structurés 41


for a in range(a1+1, a2): # années a1+1 à a2-1
njours += jours_par_an(a)
njours += jours_depuis_1jan(d2) # début de l'année a2

if negatif:
return -njours
return njours

print(nombre_jours((14, 7, 2022), (14, 7, 2022)))

print(nombre_jours((14, 7, 2022), (15, 8, 2022))) # même jour


print(nombre_jours((15, 8, 2022), (14, 7, 2022))) # 1 mois et 1 jour
print(nombre_jours((14, 7, 2021), (14, 7, 2022))) # 1 an
print(nombre_jours((14, 7, 2022), (14, 7, 2022))) # 2 ans
print(nombre_jours((14, 7, 1789), (14, 7, 2022))) # longtemps !

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 = []

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


for cle in dict:
les_elements.append((cle, dict[cle]))
return les_elements

dict = { 'a': 1, 'b': 2, 'c': 3}


print(cles(dict))
print(valeurs(dict))
print(elements(dict))

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

from turtle import *

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)

La version suivante permet d’exécuter des commandes à 0, 1 ou 2 paramètres :

Chapitre 4 – Types structurés 42


# Commmande à deux paramètres pour tester
def polygone(n, cote):
""" Trance un polygone de n cotés """
for i in range(n):
forward(cote)
left(360 / n)

# Liste de commandes à 0, 1 ou 2 paramètres.


# attention aux virgules pour les singletons !
trajectoire = [(penup,), (goto, 100, 100), (pendown,), (polygone, 5, 50)]

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 = {}

# Note : on utilise la fonction `date_valide` de l'exercice 36

def ajouter_tache(intitule, date_limite, priorite):

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


""" Ajoute une tâche à todo_list sous la forme d'un intitulé,
une date limite et une priorité. La date doit être au format
(int, int, int), et la priorité un entier entre 1 et 5. """

# Date doit être valide


if not date_valide(date_limite):
return None # Il y a déjà des messages d'erreurs dans date_valide.

# Priorite est un entier entre 1 et 5.


if not(1 <= priorite <= 5):
print("Le paramètre priorite doit être un entier entre 1 et 5.")
return None

# Bonus : un message d'alerte quand l'intitulé figurait déjà dans la liste.


if intitule in todo_list:
print(f"Attention : '{intitule}' figurait déjà dans la todo_list.")

todo_list[intitule] = (False, date_limite, priorite)

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)

Chapitre 4 – Types structurés 43


ajouter_tache("Coiffeur", (5, 10, 2022), 4)
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

print(f"La tâche '{intitule}' ne figure pas dans la liste.")


return None

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# afficher la tâche si elle satisfait les critères
if not effectuee and anterieur(date, date_tache):
print(decrire_tache(intitule))

todo_list["Test2"] = ( True, (15, 11, 2000), 2)


todo_list["Test3"] = (False, (15, 11, 2000), 3)
todo_list["Test4"] = ( True, (20, 11, 2000), 3)
todo_list["Test5"] = (False, (20, 11, 2000), 4)

trop_tard((16, 11, 2000))

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

def belote_simple(atout, cartes):


""" Retourne le gagnant d'un tour. `atout` est la couleur d'atout,

Chapitre 4 – Types structurés 44


`cartes` un dictionnaire donnant la carte jouée par chaque joueur.
On applique une version simplifiée des règles de la belote.
"""
# On vérifie que l'atout est une couleur valide
if atout not in couleurs:
print(f"Atout '{atout}' incorrect.")
return None

meilleure_carte = 0 # Meilleure carte jusqu'à maintenant


meilleure_carte_atout = False # Est-ce que la meilleure carte est un atout
meilleur_joueur = None # Nom du joueur gagnant jusqu'à maintenant

for joueur, carte in cartes.items():


if not carte_valide(carte):
print(f"La carte {carte} n'est pas valide, {joueur} est disqualifié.")
return None

valeur, couleur = carte


if couleur == atout:
# Carte à l'atout :
# gagne par défaut si la meilleure carte n'était pas un atout,
# OU si cette meilleure carte (à l'atout) avait une valeur inférieure.
if not meilleure_carte_atout or meilleure_carte < valeur:
meilleur_joueur = joueur
meilleure_carte = valeur
meilleure_carte_atout = 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:
meilleur_joueur = joueur
meilleure_carte = valeur
meilleure_carte_atout = False

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


return meilleur_joueur

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

def belote(atout, cartes):


""" Retourne le gagnant d'un tour. `atout` est la couleur d'atout,
`cartes` un dictionnaire donnant la carte jouée par chaque joueur.
On applique les vraies règles de la belote.
"""
# On vérifie que l'atout est une couleur valide
if atout not in couleurs:
print(f"Atout '{atout}' incorrect.")
return None

meilleure_carte = 0 # Meilleure carte jusqu'à maintenant


meilleure_carte_atout = False # Est-ce que la meilleure carte est un atout
meilleur_joueur = None # Nom du joueur gagnant jusqu'à maintenant

for joueur, carte in cartes.items():

Chapitre 4 – Types structurés 45


if not carte_valide(carte):
print(f"La carte {carte} n'est pas valide, {joueur} est disqualifié.")
return None

valeur, couleur = carte


gagnant = False # est-ce que cette carte est gagnante

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

# mettre à jour le gagnant


# Note : l'utilisation de `gagnant` évite de répéter 3 fois
# les affectations qui suivent dans le code ci-dessus
if gagnant:
meilleur_joueur = joueur
meilleure_carte = valeur
meilleure_carte_atout = couleur == atout

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")}))

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


print(belote("Carreau", {"J1": (14, "Trèfle"), "J2": (9, "Carreau"), "J3": (11, "Carreau")}))

Chapitre 4 – Types structurés 46


Chapitre 5 – Les tableaux

Notes sur le chapitre


Les éléments de correspondance avec le programme sont donnés sans garantie, au vu de notre
compréhension du B.O. et des sujets observés début juin 2021.
Le but de ce chapitre est de familiariser l’élève à la manipulation de tableaux. En particulier : parcours
d’un tableau pour lister les occurrences d’une valeur, calcul d’extremas, et calcul de la moyenne d’un
tableau. La construction d’un tableau par compréhension est présentée. Enfin les matrices sont
introduites, implémentées par des listes de listes (de valeurs homogènes).
Ce chapitre est complété en particulier par le chapitre 6, qui présentera les algorithmes de tri et la
recherche dichotomique sur un tableau, puis le chapitre 7 qui présentera les tables et la manipulation
de tables.

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


Parmi les exercices types trouvés sur Eduscol, on rencontre beaucoup (une majorité) d’exercices
portant sur les chapitres 5 et 6 de notre manuel; tableaux et tableaux triés. Nous détaillerons les
exercices fréquents ci-dessous.

Parcourir des valeurs itérables :


Comme mentionné ci-dessus, la fonction Python "enumerate" n’est pas mentionnée dans le
programme, et on ne la rencontre a priori pas dans les sujets types sur Eduscol. Il n’est donc pas
indispensable de la maîtriser, mais elle permet d’écrire les codes élégamment, en particulier lorsque
l’on a besoin de manipuler l’indice dans une compréhension.
Le programme indique que les étudiants sont censés pouvoir itérer sur un tableau. Nous nous sommes
contentés de ce savoir-faire, sans préciser le concept d’itérateurs (qui nous parait hors programme), et
en mentionnant très brièvement les principaux types "itérable" en Python.
En réalité, la syntaxe Python for x in t1 cache une boucle while faisant appel à un itérateur pour parcourir
les éléments de t1, en partant du premier, jusqu’à ce que next() ne trouve plus d’élément suivant. Un
itérateur est un objet qui référence un élément de la liste et possède une fonction next() permettant de
déplacer la référence vers l’élément suivant dans la liste. La plupart des langages de programmation
implémentent de tels itérateurs pour faciliter le parcours des conteneurs.
Remarque : l’exemple du palindrome aviva illustre un cas où l’on "doit" parcourir le tableau par indice
plutôt qu’en itérant avec for x in tableau, vu qu’on doit manipuler deux indices de la chaîne
simultanément. Par contre, le code utilisé n’est pas très efficace. En effet, le code parcourt en fait 2
fois chaque partie du tableau. Un code un peu plus efficace arrêterait le parcourt par indice dès qu’il
atteint la moitié du tableau, comme dans la solution de l’exercice 8 du Chapitre 4 : for i in range(len(s)//2).

Chapitre 5 – Les tableaux 1


Remarque : le sujet-type 20 de l’épreuve de terminale propose une approche différente pour tester les
palindromes :
• inverser le sens de la chaîne ;

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

Construire (et parcourir) un tableau par compréhension


Nous présentons les compréhensions (de listes) de manière sans doute plus détaillée que nécessaire, un
étudiant peu à l’aise pouvant probablement dans un premier temps se limiter aux compréhensions
simples, non imbriquées avant de passer aux compréhensions imbriquées.
Dans la perspective d’apprendre à utiliser efficacement Python, les compréhensions imbriquées
permettent d’écrire de manipuler efficacement les matrices construites sous formes de tableaux de
tableaux (et donc de répondre plus rapidement à certains exercices du manuel). De plus, on trouve sur
un sujet-type de qcm sur Eduscol des exemples (ex : sujet 1B1, 2B6) de compréhension complexe
pour créer une matrice : [[x for j in range(p)] for i in range(n)], donc au moins la première des deux formes ci-
dessous doit être maîtrisée par l’élève.
Quand l’on utilise des compréhensions imbriquées, il est bon de distinguer les 2 constructions ci-
dessous mettant en oeuvre des compréhensions imbriquées :
• [[f(i,j) for i in range(a)] for j in range(b)] qui produit une matrice avec b lignes et a colonnes. Dans ce
code, la boucle externe est for j in range(b) : pour chaque j on crée un tableau [f(i,j) for i in range(a)].

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


l’on avait écrit le code sous la forme :
for i in range(a):
for j in range(b):
...

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)

# liste de liste: [[f(i,j) for i in range(a)] for j in range(b)]


res = []
for j in range(b):
res.append([f(i,j) for i in range(a)])

print(res) #[[(0, 0), (1, 0), (2, 0)], [(0, 1), (1, 1), (2, 1)]]

Chapitre 5 – Les tableaux 2


# ce qui revient à :
res = []
for j in range(b):
res2 = []
for i in range(a):
res2.append(f(i,j))
res.append(res2)

print(res) #[[(0, 0), (1, 0), (2, 0)], [(0, 1), (1, 1), (2, 1)]]

# "simple" liste: [f(i,j) for i in range(a) for j in range(b)]


res = []
for i in range(a):
for j in range(b):
res.append(f(i,j))

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 :

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


for x in [[2,4],[1,3]]:
print(x)

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

Organisation des tableaux en mémoire


L’organisation mémoire des tableaux n’est pas au programme, mais nous trouvons que c’est très utile
d’en avoir une petite idée pour visualiser le tableau et comprendre ses propriétés.
Notre affirmation selon laquelle la mémoire d’un ordinateur est un tableau est une simplification, mais
cette représentation sous forme de tableau est néanmoins appropriée : les adresses mémoires
correspondent bien à des indices d’un tableau. La gestion de cette mémoire (ex : à quelle adresse du
tableau sera stockée telle ou telle variable, quand la mémoire est libérée, etc.) dépend des langages de
programmation et varie même selon les implémentations d’un même langage.

Chapitre 5 – Les tableaux 3


Les list pythons, comme les vector C++, les array Javascript ou les ArrayLists Java sont des tableaux
dynamiques. Python considère que les objets d’un tableau peuvent toujours être de taille variable,
donc il utilisera systématiquement des références vers les objets contenus même pour un tableau
d’entiers (ce choix est logique car Python autorise à insérer efficacement de nouvelles données en fin
de tableau et n’impose pas que ces nouvelles données soient elles aussi des entiers).
Pour les plus curieux : dans les tableaux dynamiques, la quantité d’espace supplémentaire allouée à
chaque fois que le tableau doit être recopié obéit à des règles un peu complexes. Pour se faire une idée,
on peut considérer qu’on multiplie l’espace réservé au tableau par 2 à chaque fois qu’on manque
d’espace. Les vraies règles sont expliquées en ce qui concerne l’implémentation cpython dans le code
: https://github.com/python/cpython/blob/main/Objects/listobject.c#L61
Noter qu’il existe en Python des types de tableau non dynamique, correspondant exactement au
tableau théorique mentionné par le programme : les array, disponibles dans le module array, et, plus
couramment, ceux utilisés dans la bibliothèque numpy. Sauf que la syntaxe est un peu différente donc
dans le cadre du baccalauréat il ne semble pas pertinent d’utiliser ce type de donnée et nous nous
cantonnons aux list, suivant en cela les recommandations du BO de ne pas parler de numpy.

Pour aller plus loin : tableaux et références


Cette partie est nettement plus compliquée, et la notion même de référence ou de "mutabilité" semble
hors programme (noter que certaines ressources sur Eduscol abordent tout de même la mutabilité).
Les références soulèvent des difficultés que les élèves vont presque certainement rencontrer en
pratique s’ils écrivent eux-même des codes non triviaux à partir de rien. Mais l’esprit du programme
nous semble conduire à éviter ces difficultés. Tant que les élèves se contentent de compléter du code à
trou, ou écrivent uniquement des algorithmes simples déjà vus en cours, ils ne devraient pas avoir
besoin de comprendre les références. Dans notre esprit, la section sur les références de ce chapitre est
essentiellement mise à la disposition des élèves les plus curieux.
Pour éviter d’observer des comportements potentiellement "surprenants" en présence de références à
des données muables, on évitera généralement de demander aux élèves de créer de matrice ex nihilo,

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


et on évitera de modifier des valeurs passées en paramètres dans une fonction. Si on s’autorisait à
modifier les valeurs passées en paramètres de fonction, on pourrait avoir à expliquer la sémantique
"pass-by-object-reference" de Python, particulièrement difficile à appréhender donc hors de propos au
lycée.

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)

Chapitre 5 – Les tableaux 4


print(t)
# ce programme ne termine pas !

Remarque générale sur les exercices :


Il y a beaucoup d’exercices, de durée et niveau variés. Plusieurs sont vraiment longs (jeu d’échecs,
manipulations d’images…). L’enseignant pourra bien sûr adapter les énoncés au niveau des élèves, par
exemple en ajoutant des indications (ou en en retirant éventuellement).
Il en va de même pour le TP compression, qui est sans doute un peu long tel quel, donc on pourra
utiliser le corrigé pour transformer certaines questions peu guidées en questions "à trou".

Exercice 44 zoomer sur une image :


• La fonction calcule_distance sera sans doute assez difficile à trouver pour un élève peu habitué à
manipuler de la sorte division euclidienne et modulos. L’enseignant peut donner le code de la
fonction sans diminuer l’intérêt de l’exercice : il reste bien des manipulations de tableaux dans
les autres questions (une solution intermédiaire serait de donner le code pour un des 4 voisins du
pixel, et laisser l’élève en déduire les 3 autres formules).

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

Exercice 57 algorithme de vote majoritaire Boyer-Moore


Attention à ne pas confondre avec l’algorithme de Boyer-Moore pour la recherche d’un motif dans un
texte, qui sera vu en terminale.

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

• accéder par indice dans un tableau, un tuple ou une matrice (1C4)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


• trouver le résultat d’une opération append (1C6)

• calculer une moyenne (sujet-type 0 question 37) …

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)

• la recherche d’une valeur (nombre d’occurrences, indice…) dans un tableau. (sujets


8,14,21,22,24,30)

• le calcul de la moyenne des nombres dans un tableau. (sujets 2,4,9(moyenne pondérée),16,27)

Autres exercices ayant trait à ce chapitre :


• dans le sujet 20, une fois l’indice du minimum dans un tableau calculé, on accède à la valeur
correspondante dans un second tableau (donc l’exercice est aussi lié au parcours de table, voir
chap 7 de ce manuel) ;

• 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) ;

Chapitre 5 – Les tableaux 5


• sujet 26: Compléter des codes de manipulation d’image : binariser (0 sous/ 1 sur une valeur
seuil), calculer le négatif,renvoyer les dimensions ;

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

Remarque sur les exercices du cours visualisant des matrices


on visualise dans ce chapitre les matrices comme une grille de pixels (ex 41,42,43,58). Mais
fondamentalement on peut tout aussi bien l’afficher sous forme d’Ascii-art comme au chapitre 2, et
comme certains exercices proposés sur eduscol.

Exercices supplémentaires : manipulations de bases


Il ne s’agit ici pas véritablement de "problèmes", mais juste de manipulations basiques pour se
familiariser avec les tableaux.
• vérifier si il existe dans le tableau deux nombres voisins tels que le second vaut 1 de plus que le
précédent

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


fonction ord…

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

Exercice supplémentaire : filtrer les coordonnées diagonales.


Un tableau p liste des points du plan sous forme de paires d’entiers. a) Écrire en une ligne une
expression qui renvoie la liste des points de p situé sur la diagonale ascendante (droite y=x) b) Même
question pour la demi-droite partant de (0,0) passant par (5,5). Pour p=[(4, 4), (0, 0), (1, 0), (1, 1), (3, 2), (-2, -
2)], par exemple, on renverra`[(4, 4), (0, 0), (1, 1), (-2, -2)]` pour la première question et [(4, 4), (0, 0), (1,
1)] pour la seconde.

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

Exercice supplémentaire : Dessiner une image


Pour compléter l’exercice 41 "Dessiner une image", on pourrait aussi envisager de faire découvrir les
dimensions par l’élève en analysant l’image de Wikipedia pour déterminer les dimensions de la croix
https://commons.wikimedia.org/wiki/File:Kroaz_Du.svg
(itertools.groupby n’est pas connu donc probablement à remplacer par un code un peu plus lourd).
Il faut d’abord prendre l’image au format png.
from PIL import Image
import numpy as np

Chapitre 5 – Les tableaux 6


import matplotlib.pyplot as plt

filename = '/chemin_du_fichier/Kroaz_Du.png'
m = np.array(Image.open(filename))/255).tolist()
# m = charge_img(filename) # avec le code du TP

from itertools import groupby

[(label, sum(1 for _ in group)) for label, group in groupby(m[0])]


# indique :
[([1.0, 1.0, 1.0, 1.0], 200),
([0.0, 0.0, 0.0, 1.0], 50),
([1.0, 1.0, 1.0, 1.0], 200)]

[(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)]

Exercice supplémentaire : Indices négatifs


Écrire un code qui renvoie la dernière valeur d’un tableau t sans utiliser les indices négatifs. On peut
même corser la question en demandant d’écrire une fonction
acces_par_indice_positif_ou_negatif(tableau, indice).
Solution :
def acces_par_indice_positif_ou_negatif(tableau, indice):
if indice >= 0:
return tableau[indice]
else:
return tableau[len(tableau)+indice]

Exercice supplémentaire : Distance entre deux points

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


Dans cet exercice, les points p1 et p2 sont représentés par des tableaux ou par des n-uplets de même
dimension, de sorte que p1[2] donne la 3e coordonnée de p1, et len(p1) la dimension.
a) Écrire une fonction d_euclid(p1,p2) qui calcule la distance euclidienne entre 2 points.
b) Écrire une fonction d_taxi(p1,p2) qui calcule la distance de Manhattan entre 2 points, aussi appelée
taxi-distance. Pour des points en dimension 2 placés sur une grille carrée, il s’agit du nombre de
déplacements élémentaires (tronçons de longueur 1 sur l’axe X et l’axe Y) pour aller d’un point à
l’autre. Plus généralement, d_taxi(𝑝1 , 𝑝2 ) = ∑𝑛𝑖=1 | 𝑝1 (𝑖) − 𝑝2 (𝑖)| où 𝑛 est la dimension de 𝑙𝑒𝑛(𝑝1).

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

Chapitre 5 – Les tableaux 7


def d_hamming(p1,p2):
return sum([1 if p1[i] != p2[i] else 0 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

Exercice supplémentaire : Scytale/bâton de Plutarque


Cet exercice fait écho à l’exercice sur le code de César ou celui sur le code de Vigenère. Il s’agit
d’encoder et décoder avec un bâton de Plutarque, aussi appelé scytale.
Le principe du chiffre est complètement différent car c’est un chiffrement par transposition au lieu
d’un chiffrement par substitution. L’idée est qu’on va enrouler un ruban de papier (en biais) autour
d’un cylindre (le bâton de Plutarque). Soit 𝑛 le nombre de tours effectués par le ruban jusqu’au bout
du bâton. Pour simplifier on supposera que la longueur du message est 𝑘 ∗ 𝑛 un multiple de 𝑛, même
si la supposition n’est pas nécessaire.
On va écrire le message horizontalement le long du bâton : les 𝑛 premiers caractères forment ainsi une
ligne le long du bâton, puis on écrit dessous les 𝑛 suivants… jusqu’à ce qu’on ait écrit les 𝑘 "lignes"
différentes autour du cylindre.
On déroule ensuite le ruban r. Le message original se trouve désormais écrit aux positions r[0] r[k] r[2k]…
r[(n-1)*k] r[1] r[1+k] r[1+2k]…

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

Méthodes s’appliquant aux list


Pour programmer efficacement en Python, il est utile au long terme de se familiariser avec les
méthodes disponibles sur les listes, séquence et itérables. Mais l’esprit du programme ne semble pas

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


de connaître ces fonctions :
Méthodes sur les list Python : t.count(elt) compte le nombre de fois où x apparaît dans t. Certaines
modifient le tableau : t.append(elt), t.reverse(), t.sort(), t.clear(), t.pop().
Méthodes acceptant un iterable (donc une list) : min(t), max(t), sum(t), all(t), any(t), (et zip, plus complexe).
Méthodes acceptant une sequence (donc une list) : len(t)

Références sur le web :


Pour plus de détails concernant les itérateurs et surtout les types séquentiels (list, tuples, range) en
Python : https://docs.python.org/fr/3/library/stdtypes.html
(ou doc anglophone : enlever "fr/")
Voir aussi par exemple le post (non-officiel) : https://towardsdatascience.com/python-basics-iteration-
and-looping-6ca63b30835c
Sur l’intérêt des itérateurs Python, leurs limitations, la PEP 234 est intéressante :
https://www.python.org/dev/peps/pep-0234/#dictionary-iterators
Pour les compréhensions : https://towardsdatascience.com/python-basics-list-comprehensions-
631278f22c40
On trouve beaucoup de petits exercices raisonnablement simples sur w3resource.
https://www.w3resource.com/python-exercises/list/

Chapitre 5 – Les tableaux 8


Et beaucoup des exercices de ce site indiquées comme portant sur les fonctions correspondent dans
notre manuel au présent chapitre sur les tableaux : https://www.w3resource.com/python-
exercises/python-functions-exercises.php
Pour apprendre à manipuler les list Pythons en découvrant les méthodes applicables aux list :
https://docs.python.org/fr/3/tutorial/introduction.html?highlight=list#lists
https://docs.python.org/fr/3/tutorial/datastructures.html
Voir aussi la liste de méthodes native (celles acceptant un iterable acceptent en particulier une list) :
https://docs.python.org/fr/3/library/functions.html?highlight=all

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.

Activité : Un jeu de Snake


Note : Le code de cette activité n’est pas exécutable dans Jupyter. Les étapes du corrigé sont
disponibles dans le dossier activite_snake_corrige. Le texte ci-dessous reprend seulement les parties de
code qui changent d’une version à la suivante.
Tester les contrôles
"""
Ce fichier presente l'interface pour contrôler les mouvements
du serpent (en appelant les fonctions de notre librairie snake_ui).

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


"""

from turtle import forward, left, right, onkey


from snake_ui import animate, main_loop, reinitialiser_delai, modifier_delai, _step

# _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)

# ======== Associer une action aux touches du clavier: ========

Chapitre 5 – Les tableaux 9


# Tourner la tete du serpent avec les fleches
onkey(gauche, 'Left')
onkey(droite, 'Right')

# Accelerer/decelerer
onkey(accelerer, 'Up')
onkey(ralentir, 'Down')

# ======== Lancement de l'animation: ========

# Avancer toutes les 200 (par defaut) ms


reinitialiser_delai(200)
animate(avance)

# 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.
"""

from turtle import forward, left, right, onkey


from snake_ui import animate, main_loop, reinitialiser_delai, modifier_delai, _step, _position_initiale, erase, position, in_w
indow

# au depart, le serpent est roule sur lui-meme en bas a gauche


# _step == 40

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# _position_initiale == (0,0)
snake = [ _position_initiale ] * 10

def bouge(snake, head):


""" Met a jour le tableau snake """
# decaler les coordonnees du serpent vers la gauche
for i in range(len(snake) -1):
snake[i] = snake[i+1]
# mettre la nouvelle position de la tete
snake[-1] = head

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

Chapitre 5 – Les tableaux 10


def avance():
avance_serpent1(_step)

# ...

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.

Gérer les (auto)collisions du serpent


def auto_intersecte(snake):
""" Retourne True si la tete du serpent egale l'une de ses positions """
head = snake[-1]
for i in range(len(snake) -1):
if snake[i] == head:
return True
return False

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)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


# recuperer la position de la tete et mettre a jour le serpent
head = position()
bouge(snake, head)
# voir si le serpent se marche dessus
if (not in_window(head)) or auto_intersecte(snake): # parentheses superflues mais plus clair
print('Fin du jeu !')
animate(None)

# ======== LA SUITE REPREND tel quel snake0, sauf pour avance() ========
# ======== Liste des actions: ========

def avance():
avance_serpent2(_step)

# ...

Placer des obstacles


2.
"""
- On cree des obstacles dont la position est enregistree par des 1
dans la matrice m de toutes les positions accessibles au serpent
(les autres cases de la matrice valant 0).
"""

from snake_ui import animate, main_loop, reinitialiser_delai, modifier_delai, _step, _position_initiale, erase, position, in_w
indow, position_aleatoire, dessiner_obstacle

Chapitre 5 – Les tableaux 11


# au depart, le serpent est roule sur lui-meme en bas a gauche
# _step == 40
# _position_initiale == (0,0)
snake = [ _position_initiale ] * 10

m = [[0]*602 for i in range(601)] # adapter en fonction de canvas_width, canvas_height


# On gaspille beaucoup d'espace dans cette matrice car seule une colonne/ligne sur 40 est accessible puisqu'on se deplace p
ar pas de 40, mais c'est un peu plus pratique que les positions correspondent exactement a une case de la matrice (plutot qu
e devoir diviser par 40).

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

# on conserve les fonctions bouge et auto_intersecte précédentes


# ...

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 !')

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


animate(None)

# ======== 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.
"""

Chapitre 5 – Les tableaux 12


from snake_ui import animate, main_loop, reinitialiser_delai, modifier_delai, _step, _position_initiale, erase, position, in_w
indow, position_aleatoire, dessiner_obstacle, dessiner_bonus

# on conserve le reste du code antérieur sauf avance_serpent3


# ...

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# voir si le serpent se marche dessus
if auto_intersecte(snake):
print('Fin du jeu !')
animate(None)

# ======== 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
"""

# On garde tout le code antérieur


# ...

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:

Chapitre 5 – Les tableaux 13


found = True
m[x][y] = 2
dessiner_bonus(x,y)

# on ajoute l'appel à creer_bonus (marqué # <=== AJOUT)


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
creer_bonus() # <=== AJOUT
# voir si le serpent se marche dessus
if auto_intersecte(snake):
print('Fin du jeu !')
animate(None)

# On garde le reste du code antérieur


# ...

QCM (CHECKPOINT)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


1 a ; 2 Aucun des 3 ; 3 b, 4 a, 5 b, 6 Pareil sauf qu'on a en plus 2 c et 4 b et on n'a plus 4 a ; 7 1 c, 2 a,
3 b ; 8 c ; 9 a ; 10 d ; 11 1 c, 2 f ; 12 c ; 13 b ; 14 d.

TPA Une marche aléatoire (1D) : la ruine du joueur


from random import randint
def creer_une_marche():
t = [20]*100# creer un tableau t de 100 entiers de valeur 20: [20,...]
for i in range(99):
t[i+1] = t[i] + (2 * randint(0,1) - 1)
return t

marche = creer_une_marche()
import matplotlib.pyplot as plt
plt.plot(marche)
plt.show()

def creer_marche(nb_joueurs, c):


"""
Entree : 2 entiers,
- nb_joueur : le nombre de joueurs
- c : le capital initial de chaque joueur.
Sortie :
- un tableau de tableaux (matrice nb_joueur * 100).
Chacun des nb_joueur tableaux est la marche d'un joueur de capital initial c, et a pour longueur 100.
Contrairement a creer_une_marche, une marche qui atteint 0 reste ensuite a 0.

Chapitre 5 – Les tableaux 14


"""
marches = [None] * nb_joueurs
for n in range(nb_joueurs):
marche = [c] * 100
for i in range(99):
if marche[i] == 0:
marche[i+1] = 0
else:
marche[i+1] = marche[i] + (2 * randint(0,1) - 1)
marches[n] = marche
return marches

# Test:
print(creer_marche(2,4))

def calcul_stats_v1(nb_joueurs, c):


""" genere une matrice de nb_joueurs marches, et renvoie la liste des joueurs ruines """
marches = creer_marche(nb_joueurs, c)
id_joueurs_ruines = [i for i in range(len(marches)) if marches[i][-1] == 0]
return id_joueurs_ruines

# 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

# Pour vérifier plus sérieusement la correction,


# il faudrait afficher la matrice des marches pour vérifier.
# Si l'énoncé n'imposait pas de générer la marche à l'intérieur de la fonction,
# il aurait aussi été raisonnable de passer la matrice des marches en argument de la fonction.

def calcul_stats_v2(nb_joueurs, c):


""" genere une matrice de nb_joueurs marches, et renvoie la liste des joueurs ruines.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


Affiche:
- la proportion de joueurs ruines
- le capital moyen des joueurs (ruines ou non) au bout des 100 etapes.
- le temps moyen avant la ruine pour les joueurs ruines, ou -1 s'il n'y en a pas.
on considere qu'un joueur est ruine au temps i>=0 si sa marche vaut 0 a partir de l'indice i.
"""
marches = creer_marche(nb_joueurs, c)
id_joueurs_ruines = [i for i in range(nb_joueurs) if marches[i][-1] ==0]
proportion_ruine = len(id_joueurs_ruines)/nb_joueurs
capital_total = 0
for i in range(nb_joueurs):
capital_total = capital_total + marches[i][-1]
capital_moyen = capital_total/nb_joueurs
# solution alternative plus pythonesque mais faisant appel a sum(tableau):
# capital_moyen = sum([marches[i][-1] for i in range(nb_joueurs)])/nb_joueurs
temps_moyen_ruine = -1
if len(id_joueurs_ruines) > 0:
temps_total = 0
for i in id_joueurs_ruines:
j=0
while (marches[i][j] > 0):
j += 1
temps_total += 1
# on peut aussi directement calculer le nombre de valeurs non nulles dans la matrice avec une double boucle for.
temps_moyen_ruine = temps_total/len(id_joueurs_ruines)
print("proportion de joueurs ruinés:", proportion_ruine)
print("capital moyen:", capital_moyen)
print("temps moyen:", temps_moyen_ruine)

Chapitre 5 – Les tableaux 15


# Test
print(calcul_stats_v2(1,4))
print(calcul_stats_v2(3,5))

calcul_stats_v2(150,6)

calcul_stats_v2(150,80)

TP B Compression d’image par réduction de palette et diffusion d’erreur


1.a.
# L'idée ici c'est que l'élève va d'abord télécharger l'image sur l'ordinateur puis de donner le chemin vers le fichier image.
# Mais pour pouvoir exécuter le corrigé tel quel,
# nous proposons ici d'utiliser la librairie requests qui permet de faire une requête HTTP GET à l'url et ainsi télécharger l'imag
e "à la volée"
# A priori, ce n'est pas ce qui est attendu des élèves.
import requests
img = charge_img(requests.get("https://upload.wikimedia.org/wikipedia/commons/thumb/c/c2/Le_mont_rose_%28au_fo
nd%29_vu_de_l%27aiguille_du_midi_%287061036537%29.jpg/640px-Le_mont_rose_%28au_fond%29_vu_de_l%27aiguille
_du_midi_%287061036537%29.jpg", stream=True).raw)
affiche(img, "Image d'origine")

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


c.
def calcule_palette_uniforme(n_colors):
"""
Entrée: n_color >= 2
Renvoie une palette (codebook) avec n niveaux de gris.
La palette est un tableau 1D de n_colors flottants,
commencant par 0 (noir), terminant par 1 (blanc).
L'intervalle entre deux couleurs est constant.
"""
assert(n_colors>1)
return [i/(n_colors-1) for i in range(n_colors)]

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.

Chapitre 5 – Les tableaux 16


Renvoie le flottant le plus proche de pixel dans palette
Donne la priorité à la premiere couleur dans l'ordre en cas d'ex-aequo.
"""
c = palette[0]
for x in palette:
if abs(pixel-x) < abs(pixel-c):
c=x
return c

# 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)]

affiche(quantifie_image_palette(img,calcule_palette_uniforme(10)), "10 couleurs, sans diffusion d'erreur")


affiche(quantifie_image_palette(img,calcule_palette_uniforme(20)), "20 couleurs, sans diffusion d'erreur")
affiche(quantifie_image_palette(img,calcule_palette_uniforme(2)), "2 couleurs, sans diffusion d'erreur")

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def quantifie_image_diffusion(img, palette):
"""
Entrée: img: list of list of float, palette: list of float
Renvoie: la nouvelle image, quantifiee en utilisant la palette et
en appliquant l'algorithme floyd-steinberg de diffusion d'erreur.
"""
h, w = dimensions_image(img)
nouvelle_image = [[img[i][j] for j in range(w)] for i in range(h)]
for j in range(w):
for i in range(h):
old_px = nouvelle_image[i][j]
nouvelle_image[i][j] = couleur_approchee(old_px, palette)
quant_error = old_px - nouvelle_image[i][j]
if j+1 < w:
nouvelle_image[i][j+1] += quant_error*5/16
if i > 0:
nouvelle_image[i-1][j+1] += quant_error*3/16
if i+1 < h:
nouvelle_image[i+1][j] += quant_error*7/16
if j+1 < w:
nouvelle_image[i+1][j+1] += quant_error*1/16
return nouvelle_image

affiche(quantifie_image_diffusion(img,calcule_palette_uniforme(10)), "10 couleurs, avec diffusion d'erreur")


affiche(quantifie_image_diffusion(img,calcule_palette_uniforme(20)), "20 couleurs, avec diffusion d'erreur")
affiche(quantifie_image_diffusion(img,calcule_palette_uniforme(2)), "2 couleurs, avec diffusion d'erreur")

Chapitre 5 – Les tableaux 17


Exercices

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# On pourrait remarquer que dans cette solution on renvoie une référence vers la ligne
# plutôt qu'une copie de la ligne (l'énoncé ne précise pas ce qui est souhaité)
# Mais les références ne sont pas au programme.
# Pour renvoyer une copie, au lieu de return m[i] on écrirait: return [x for x in m[i]]

# Test :
print(m_l([['a','b'], ['d','z']],1) == ['d','z'])

4 a. La fonction calcule somme des éléments sur la diagonale d’une matrice.


b.
# Réponse : i<j

# 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)]

Chapitre 5 – Les tableaux 18


for i in range(4):
for j in range(4):
if i+j==3 :
m[i][j] = 1
m

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

# Cette question demande plus de réflexion.

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


def moyenne(t):
"""
Entrée : un tableau t
suppose que t non vide.
Renvoie: la moyenne des éléments du tableau.
"""
tot = 0
for x in t:
tot = tot + x
return tot/len(t)
# ou : tot/len(t) if len(t)>0 else None
# si on ne veut pas d'erreur quand t est vide.

# 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

Chapitre 5 – Les tableaux 19


# Test :
m = [[1,34], [2,3]]
print(f(m)==40)

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 :

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


print(trouve(4, [5,4,1,3,4])==[1,4])

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

Chapitre 5 – Les tableaux 20


9 a. Le résultat est dans les deux cas un tableau contenant le carré des nombres pairs entre 6 et 0, par
ordre décroissant: [36, 16, 4, 0]. Les calculs sont sensiblement les mêmes mais la première solution est
préférable car la seconde solution commence par lister tous les nombres entre 6 et 0 avant d’éliminer
ceux qui ne sont pas pairs, alors que la premièe n’évalue que les valeurs nécessaires.
# Vérification:
print([x*x for x in range(6,-1,-2)])
print([x*x for x in range(6,-1,-1) if x%2==0])

b. La liste vide [].

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]

# on a ici une compréhension mettant en oeuvre une boucle imbriquée.

12
#[ x for x in t if x>5 ]

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


t=[1,6,3,9]
print([ 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)

print(res)# res vaut bien [100, 54.2, 10]

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)

Chapitre 5 – Les tableaux 21


15
t0 = [1,4,5]

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]

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


return res

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

Chapitre 5 – Les tableaux 22


"""
temp = t[0]
res = 0
for i in range(len(t)-1,-1,-1):
if t[i] > temp:
res = i
temp = t[i]
return res

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

[x.copy() for x in t0] # Ou bien:

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


[[x for x in ligne] for ligne in t0]
# Attention, t0 = t est incorrect.
# Mais t = t0.copy() ou t = [x for x in t0] sont aussi incorrects: l'opération t0[0][1]=3 modifierait aussi t, comme vu en cours.

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

Chapitre 5 – Les tableaux 23


# Test:
print(m_c([['a','b'], ['d','z']],0)==['a','d'])

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


k = k+1
if k==4:
return True
return False

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

Chapitre 5 – Les tableaux 24


- indices de colonne i et de rangée j.
Renvoie True ssi la matrice g admet un alignement suivant une des 4 directions ci-dessus
en partant de i,j.
dx,dy in [1,0),(0,1),(1,1),(1,-1)]
"""
couleur = g[i][j]
if couleur is None:
return False
for (dx,dy) in [(1,0),(0,1),(1,1),(1,-1)]:
alignement = True
if i + 3*dx <= 6 and 0 <= j+3*dy <= 5:
k=1
while k < 4 and g[i+k*dx][j+k*dy] == g[i][j]:
k = k+1
if k == 4:
return True
return False

# 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

# Solution alternative mais moins elegante :


# la solution ci-dessus est meilleure car il est toujours preferable de "factoriser le code":

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


# un code factorisé est plus facile à maintenir.

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:

Chapitre 5 – Les tableaux 25


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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


print(verifie_jeu2(g2)) #True

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

Chapitre 5 – Les tableaux 26


return -1

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


ax.add_patch(Rectangle((-.5, -.5), 7,6,zorder=1))
#ax.add_patch(Circle((1,1),r=1)
#display plot
plt.show()

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)

Chapitre 5 – Les tableaux 27


26
def inverse(t):
n = len(t)
r = [None]*n
for i in range(n):
r[n-1-i] = t[i]
return r

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


t[i] = 3*t[i-1]+1
return t

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

Chapitre 5 – Les tableaux 28


tel que nb[x] indique le nombre d'occurrences de x dans t
"""
nb = [0]*101
for x in t:
nb[x] = nb[x] + 1
return nb

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


print(len(premier)==101)
print((premier[0],premier[1],premier[2],premier[100])==(False, False, True, True))

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)

premier = [False, False]


premier = premier + [True]*(n-1)

for x in range(2,n):
if x:
y = x*x
while y<=n:
premier[y] = False
y=y+x

# Test:

Chapitre 5 – Les tableaux 29


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)

# pour afficher les premiers:


[i for i,x in enumerate(premier) if x==True]

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


# par exemple si on commence à .4 et l'altitude suivante est .6 alors nbtemp = 1, ztemp =.2
for i in range(1,len(t)):
if t[i] >= t[i-1]:
nbtemp +=1
z_tot += t[i] - t[i-1]
ztemp += t[i] - t[i-1]
else:
if nbtemp > longueur_max:
longueur_max = nbtemp
z_maxl = ztemp
ntempb = 0
ztemp = 0
return (longueur_max, z_tot, z_maxl)

# 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)):

Chapitre 5 – Les tableaux 30


if t[i] == t[i-1]:
nb += 1
else:
res.append((nb,t[i-1]))
nb = 1
res.append((nb,t[len(t)-1]))
return res

# 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})

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


36
def vplage(t):
"""
Entrée: tableau t d'entiers
Renvoie le codage par différence du tableau
"""
if t == []:
return []
res = [0]*len(t)
res[0] = t[0]
for i in range(1,len(t)):
res[i] = t[i] - t[i-1]
return res

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

Chapitre 5 – Les tableaux 31


def mouvements_tour(echiquier,couleur,x,y):
res = []
for direction in [(1,0), (0,1), (-1,0), (0,-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

# 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')

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


m[4][4]=('noir','tour')
print(sorted(mouvements_roi(m,'noir',1,2))==[(0,1),(0,2),(0,3),(1,1),(1,3),(2,1),(2,2),(2,3)])
print(sorted(mouvements_roi(m,'noir',4,5))==[(3,4),(3,5),(3,6),(4,6),(5,4),(5,5),(5,6)])

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]

Chapitre 5 – Les tableaux 32


couleur, nom = piece
if nom == 'roi':
return mouvements_roi(echiquier,piece[0],x,y)
else: #nom == 'tour'
return mouvements_tour(echiquier,piece[0],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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


m = [[None]*8 for i in range(8)]
m[5][2]=('noir','tour')
m[5][5]=('blanc','roi')
m[1][2]=('noir','roi')
print(echec(m, 'blanc'))

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

Chapitre 5 – Les tableaux 33


h.
def echec_et_mat(echiquier,couleur):
"""
Suppose que c'est à couleur de jouer.
Renvoie mat/pat/survie en fonction de la situation au tour courant.
"""
couleur_adverse = {'blanc':'noir', 'noir':'blanc'}[couleur]
listes_mvts_valides = []
for x in range(8):
for y in range(8):
if echiquier[x][y] is not None:
if echiquier[x][y][0] == couleur and mouvements_valides(echiquier, x, y) != []:
return 'survit'
# Si on arrive à ce point c'est qu'on n'a pas trouvé de mouvement valide
if echec(echiquier,couleur):
return "mat"
else:
return "pat"

m = [[None]*8 for i in range(8)]


m[5][2]=('noir','tour')
m[5][5]=('blanc','roi')
m[1][2]=('noir','roi')
print(echec_et_mat(m,'blanc')=='survit')

m = [[None]*8 for i in range(8)]


m[1][5]=('noir','tour')
m[0][0]=('blanc','roi')
m[0][2]=('noir','roi')
print(echec_et_mat(m,'blanc')=='pat')

m = [[None]*8 for i in range(8)]


m[7][0]=('noir','tour')

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


m[3][0]=('blanc','roi')
m[3][2]=('noir','roi')
print(echec_et_mat(m,'blanc')=='mat')

#Si la librairie chess est installée:


import chess
import chess.svg

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:

Chapitre 5 – Les tableaux 34


s += str(none_counter)
none_counter = 0
board_string += s
if y > 0:
board_string += '/'
# else we could add w - - 0 1 to indicate white, turns...
return board_string

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

m = [[None]*8 for i in range(8)]


m[7][4]=('noir','tour')
m[3][0]=('blanc','roi')
m[3][2]=('noir','roi')
board_to_svg(m)

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


return res

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

def mettre_echec_et_mat(echiquier, couleur):


"""
On suppose que le joueur couleur commence.
Renvoie un mouvement qui lui permet de mettre mat l'autre joueur s'il y en a un.
"""
mvt = []
autre_couleur = 'noir' if couleur == 'blanc' else 'blanc'

Chapitre 5 – Les tableaux 35


for x in range(8):
for y in range(8):
if echiquier[x][y] is not None and echiquier[x][y][0] == couleur:
for nx, ny in mouvements_valides(echiquier, x, y):
echiquier_hypothetique = [t.copy() for t in echiquier]
echiquier_hypothetique[nx][ny] = echiquier[x][y]
echiquier_hypothetique[x][y] = None
if echec_et_mat(echiquier_hypothetique, autre_couleur) == 'mat':
return nx, ny
return None

m = [[None]*8 for i in range(8)]


m[7][4]=('noir','tour')
m[3][0]=('blanc','roi')
m[3][2]=('noir','roi')
print(mettre_echec_et_mat(m,'noir')==(7,0))

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)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


return v

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

Chapitre 5 – Les tableaux 36


tv = [3,2]
to = ['Nord','Nord']
#collision car 0 rattrape 1

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

# On utilise une boucle imbriquée pour énumérer les paires de voitures.


# Puis on filtre ces paires pour ne garder que celles pour lesquelles croisement renvoie true.
# Pour obtenir chaque paire une seule fois,
# une solution est de ne considérer que les paires avec i<j,
# ce qui peut se faire comme suit:
liste_croisements = [ (i,j) for i in range(len(tx)) for j in range(i,len(tx)) if croisement(i,j)]

print(liste_croisements == [(0, 1)] or liste_croisements == [(1, 0)])

# Solution équivalente avec append :


liste_croisements = []
for i in range(len(tx)):
for j in range(i,len(tx)):
if croisement(i,j):

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


liste_croisements.append((i,j))

print(liste_croisements == [(0, 1)] or liste_croisements == [(1, 0)])

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

def encode_naif(texte, decalage):


"""
Entrée: un texte en clair, la valeur du décalage
Renvoie: le même texte encodé avec le code de césar; chaque lettre est 'décalée' du décalage vers la droite,
de manière cyclique; après le 'z' on revient à 'a'.
Utilise string.ascii_lowercase.
"""
t = string.ascii_lowercase
s = ''
for x in texte:

Chapitre 5 – Les tableaux 37


s = s+t[(rang_naif(x)+decalage)%26]
return s

def decode_naif(texte, decalage):


"""
Entrée: un texte chiffré, la valeur du décalage
Renvoie: le même texte encodé avec le code de césar; chaque lettre est 'décalée' du décalage vers la droite,
de manière cyclique; après le 'z' on revient à 'a'.
Utilise string.ascii_lowercase.
"""
t = string.ascii_lowercase
s = ''
for x in texte:
s = s + t[(rang_naif(x)-decalage)%26]
return s

# Test:
print(encode_naif('letempsestpluvieux',1)=='mfufnqtftuqmvwjfvy')
print(decode_naif('mfufnqtftuqmvwjfvy',1)=='letempsestpluvieux')

# Une solution plus intelligente :


# La fonction Python: chr(i)
# renvoie la chaîne représentant un caractère dont le code de caractère Unicode est le nombre entier i.
# chr(97) == 'a'
# chr(122)== 'z'
# La fonction Python: ord(i)
# prend une chaîne Unicode d'un caractère et renvoie la valeur du point de code.
# La fonction ord est donc l'inverse de chr.

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


"""
return ord(lettre)-97

# NB Cette solution choisit d'utiliser directement 'ord' dans encode/decode


# mais on peut bien sûr remplacer par 'rang'.
def encode(texte, decalage):
"""
Entrée: un texte en clair, la valeur du décalage
Renvoie: le même texte encodé avec le code de césar; chaque lettre est 'décalée' du décalage vers la droite,
de manière cyclique; après le 'z' on revient à 'a'.
Utilise chr et ord.
"""
s = ''
for x in texte:
s = s+chr((ord(x)-97+decalage)%26+97)
return s

def decode(texte, decalage):


"""
Entrée: un texte chiffré, la valeur du décalage
Renvoie: le même texte encodé avec le code de césar; chaque lettre est 'décalée' du décalage vers la droite,
de manière cyclique; après le 'z' on revient à 'a'.
Utilise chr et ord.
"""
s = ''
for x in texte:
s = s+chr((ord(x)-97-decalage)%26+97)
return s

Chapitre 5 – Les tableaux 38


# Test:
print(rang('a')==0)
print(rang('z')==25)
print(encode('letempsestpluvieux',1)=='mfufnqtftuqmvwjfvy')
print(decode('mfufnqtftuqmvwjfvy',1)=='letempsestpluvieux')

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)

def code_caractere(caractere, i, cle, carre_vigenere):


"""
Entrée:
- un caractère du texte d'entrée
- sa position (indice) i dans le texte d'entrée
- une clé de chiffrement cle (pouvant être de longueur plus courte que i)
- le carré de vigenere

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Renvoie: le caractère (chiffré) utilisé pour coder le caractère donné en entrée"""
return carre_vigenere[rang(caractere)][rang(cle[i%len(cle)])]

# Test:
print(code_caractere('L',0,'SECRET',carre_vigenere) == 'D')

def code(message, cle):


"""
Entrée:
- un message à chiffrer
- une clé de chiffrement cle
Renvoie: le texte t chiffré.
"""
carre_vigenere = [[chr((i+j)%26+65) for j in range(26)] for i in range(26)]
message_chiffre = [None]*len(message)
for i in range(len(message)):
message_chiffre[i] = code_caractere(message[i],i,cle, carre_vigenere)
return "".join(message_chiffre)

# 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é.
"""

Chapitre 5 – Les tableaux 39


carre_vigenere = [[chr((i+j)%26+65) for j in range(26)] for i in range(26)]
message_dechiffre = [None]*len(m)
for i in range(len(m)):
j=0
no_ligne = rang(c[i%len(c)])
while carre_vigenere[no_ligne][j] != m[i]:
j = j+1
message_dechiffre[i] = carre_vigenere[0][j]
return "".join(message_dechiffre)

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


• un autre pour les lettres aux positions 1,1+k,1+2k… etc.

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]

Chapitre 5 – Les tableaux 40


drapeau[len(drapeau)-1][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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


img2 = ajoute_cadre(img)

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.

Chapitre 5 – Les tableaux 41


Si on voulait une image un peu moins pixellisée on pourrait calculer la moyenne des pixels voisins,
mais l’exercice serait plus compliqué.
import urllib
url = 'https://upload.wikimedia.org/wikipedia/commons/thumb/c/c9/Kingfisher-2046453.jpg/320px-Kingfisher-2046453.jp
g'

m=[[ 1., 2., 3., 4.],


[ 0., 12., 13., 0.],
[ 0., 22., 23., 0.],
[ 31., 0., 0., 34.]]

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


return (np.array(Image.open(filename))/255).tolist()

def affiche(matrice, tit=''):


"""
Entrée: matrice: list of list of float (tit: string)
Affiche l'image stockée par la matrice.
Si chaque valeur de la matrice est :
- un float entre 0 et 1. , alors échelle de gris 0 noir, 1. blanc
- un triplets de 3 flottants entre 0 et 1, alors couleur (r,g,b), avec (0,0,0) noir, (0,0,.5) bleu un peu sombre, (1.,1.,1.) blanc

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

# Solution en itérant naïvement:


def agrandit1_naif(m,k):
"""
Entrée: une matrice m de valeurs (la matrice représente une image), un entier k.
Renvoie une nouvelle matrice m2 qui répète k fois chaque colonne et ligne de la matrice m pour 'agrandir' l'image.
Solution qui itère naïvement (pas par compréhension).
"""
m2 = [ [None for j in range(len(m[0])*k)] for i in range(len(m)*k)]

Chapitre 5 – Les tableaux 42


for i in range(len(m)*k):
for j in range(len(m[0])*k):
m2[i][j] = m[i//k][j//k]
return m2

# Solution avec une compréhension:


def agrandit1(m,k):
"""
Entrée: une matrice m de valeurs (la matrice représente une image), un entier k.
Renvoie une nouvelle matrice m2 qui répète k fois chaque colonne et ligne de la matrice m pour 'agrandir' l'image.
Solution qui par compréhension.
"""
m2 = [ [x for x in v for j in range(k)] for v in m for i in range(k) ]
return m2

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

print("agrandissement par répétition naif :\n",agrandit1_naif(mc,2))


print("agrandissement par répétition, compréhension :\n",agrandit1(mc,2))
img1 = agrandit1(mc,2)
affiche(mc,'image de départ')
affiche(img1,'image agrandie par répétition')

def moyenne_ponderee(distances,valeurs):
"""

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Entrée: un tableau de d distances (entiers), un tableau de d pixels (triplets) de valeurs (flottants entre 0 et 1.).
Renvoie: le triplet obtenu en faisant la moyenne des autres pixels (triplets) pondérés par leur distance.
Si une des distances est nulle, la valeur correspondante est retournée.
"""
d=len(distances)
for i in range(d):
if distances[i] == 0:
return valeurs[i]
numer = [0,0,0]
denom = 0
for i in range(d):
denom = denom +1/distances[i]
for c in range(3):
numer[c] = numer[c] + valeurs[i][c]/distances[i]
for c in range(3):
numer[c] = numer[c]/denom
return numer

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

Chapitre 5 – Les tableaux 43


def calcule_distances(m,i,j,k):
"""
Entrée: la matrice d'origine `m`, et trois entiers `i,j,k` avec `i` et `j` les coordonnées d'un pixel dans l'image agrandie d'un f
acteur k.
Renvoie le tableau des distances de ce pixel à ses 4 voisins, ainsi que le tableau des valeurs des 4 pixels correspondant.

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'

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


img = charge_img(urllib.request.urlopen(url))
affiche(img,'image de départ')
affiche(agrandit2(img,2),'image agrandie par interpolation')

45
s='abflazl'
''.join([x for x in s if x>'h'])

46 a. La fonction retourne le minimum de x et y.


b.
def argmax(g,a,b):
return [a,b][g(b)>g(a)]

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.

Pour cela, la fonction combine un tableau de booléens et un compteur.


On utilisera isinstance(z,int) pour vérifier qu'une valeur z est un entier.
"""
v=[False]*10 # plutôt que de
n=0
for z in t:

Chapitre 5 – Les tableaux 44


if isinstance(z,int) and 1<=z<=9 and not v[z]:
v[z]=True
n += 1
return n==9

# 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):

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


if not verifie_tableau([g[j][i] for j in range(9)]):
return False
return True

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

Chapitre 5 – Les tableaux 45


,[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_carre([[3,1,4,8,2,5,9,6,7] for i in range(9)])==False)
print(verifie_carre([[3,1,4,8,2,5,9,6,7] for i in range(8)]+[[3,1,4,8,6,5,9,6,7]])==False)

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]

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


v1.append(v2)
print(v1) # [[2, 3, 4], [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)):

Chapitre 5 – Les tableaux 46


l = t[len(t)-i-1].split('(')
s = inverses[l[0]]+'('+l[1]
res.append(s)
return res

# 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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


def f(l1, l):
"""
Entrée: 2 tableaux (list) l1 et l
Renvoie: True ssi l1 est sous-liste de l (suite d'éléments consécutifs dans l).
"""
n1 = len(l1)
n = len(l)
trouve = False
for i in range(n-n1):
j=0
while (j+1 < n1 and l[i+j] == l1[j]):
j = j+1
if j+1 == n1:
return True
return False

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

Chapitre 5 – Les tableaux 47


from math import pi, tan
def position(a1,a2):
"""
Entrée:
- l'angle a1 entre l'axe Ox et l'émetteur depuis la station 1
- l'angle a2 entre l'axe Ox et l'émetteur depuis la station 2
On suppose que 0<a1<180; l'émetteur reste à des coordonnées (x,y) avec y>0.
Renvoie: la position (x,y) de l'émetteur.
"""
m = tan(a2)/tan(a1)
return 10*m/(m-1), 10*m/(m-1)*tan(a1)

[position(t1[i],t2[i]) for i in range(len(t1))]

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:

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


l.append([x-1,y,z])
if x < max_x:
l.append([x+1,y,z])
if y > 0:
l.append([x,y-1,z])
if y < max_y:
l.append([x,y+1,z])
if z > 0:
l.append([x,y,z-1])
if z < max_z:
l.append([x,y,z+1])
return l

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

Chapitre 5 – Les tableaux 48


def cherche_chemin(m, origin=[0,0,0], destination=[9,9,9]):
"""Retourne le chemin d'origin à destination, et [] s'il n'y en a pas.

A tout instant la case courante a au plus 2 voisins dont 1 est le précédent,


donc jamais plus d'une nouvelle case
On pourrait distinguer avec des conditions :
- les cas où il y a 1 ou 2 voisins,
- les cas où le voisins précédent apparait en position 0 ou 1.
Mais il est ici un peu plus simple de filtrer par compréhension
"""
if voisins_dans_tuyau(m,origin) == []:
return 'Pas de chemin'
else:
chemin = [origin]
case_courante = voisins_dans_tuyau(m,origin)[0]
nouvelles_cases= [c for c in voisins_dans_tuyau(m,case_courante) if c!= chemin[-1]]
while nouvelles_cases != [] and nouvelles_cases[0] != destination:
#print("courante:",case_courante)
#print("next:",nouvelles_cases)
chemin.append(case_courante)
case_courante = nouvelles_cases[0]
nouvelles_cases= [c for c in voisins_dans_tuyau(m,case_courante) if c!= chemin[-1]]
if nouvelles_cases!= []:
return chemin
else:
return []

# 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]]:

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


a[c[0]][c[1]][c[2]]=1
print(cherche_chemin(a) == [[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]])

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]

Chapitre 5 – Les tableaux 49


57 a.
def majorite_boyer_moore(t):
""" Algorithme de Vote majoritaire de boyer moore
Si t contient une valeur v au moins len(t)/2 fois, alors renvoie v
Sinon renvoie une valeur quelconque parmi celles dans t
Complexite : ...
Espace utilise en memoire : juste une valeur et un entier.

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


utilisant une quantité de mémoire limitée.
b.
def verifie(t,v):
""" renvoie True ssi v est majoritaire dans t
Complexite: lineaire
"""
nb = 0
for x in t:
if x == v:
nb += 1
return nb >= len(t)/2

# 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.
"""

Chapitre 5 – Les tableaux 50


# initialiser
r = [[0]*len(m[0]) for i in range(len(m))]
# ou directement r = [[0 for x in ligne] for ligne in m]
for y in range(len(m)):
for x in range(len(m[0])):
r[(y+dy)%len(m)][(x+dx)%len(m[0])] = m[y][x]
return r

# 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]])

import matplotlib.pyplot as plt


plt.subplot(1,2,1)
plt.imshow(m,cmap='Greys_r')
plt.title("image d'origine")
plt.subplot(1,2,2)
plt.imshow(decale(m,0,1),cmap='Greys_r')
plt.title("image décalée dx=0, dy=1")
plt.show()

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


valeur = 0
for y in range(3):
for x in range(3):
valeur += masque[y][x]*m[(i+1-y)%dy][(j+1-x)%dx]
return valeur

# 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])

Chapitre 5 – Les tableaux 51


nm = [[0 for x in t] for t in m]
for i in range(dy):
for j in range(dx):
nm[i][j] = convolution_px(m,masque,i,j)
return nm

# on pourrait aussi directement utiliser une compréhension

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

import matplotlib.pyplot as plt


plt.subplot(1,2,1)
plt.imshow(m,cmap='Greys_r')
plt.title("image d'origine")
plt.subplot(1,2,2)
plt.imshow(convolution(m,masque),cmap='Greys_r')
plt.title("image obtenue par convolution")
plt.show()

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


59
# L'énoncé propose de se limiter aux tuples à 2 coordonnées, donc des points de dimension d=2.
# Nous suggérons d'essayer de résoudre le problème pour des points de dimension d quelconque.
# On commencera dans la fonction par déduire d de la longueur du premier point.
def f(mat):
""" Entrée: mat est une matrice contenant n points de dimension d (pour un certain n et d).
Cette matrice est représentée
- soit par un tableau de tuples (d-uplets)
- soit par un tableau de tableaux (de longueur d).
Renvoie un tableau représentant le barycentre.
"""
n = len(mat)
d = len(mat[0])
return [sum(mat[j][i] for j in range(n))/n for i in range(d)]

# 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])

Chapitre 5 – Les tableaux 52


Chapitre 6 – Trier et chercher dans les tableaux

Notes sur le chapitre


Les éléments de correspondance avec le programme sont donnés sans garantie, au vu de notre
compréhension du B.O. et des sujets observés début juin 2021.
Le but de ce chapitre est de présenter les algorithmes de tri et la recherche dichotomique. C’est aussi
une occasion de plus pour familiariser l’élève à la manipulation de tableaux. Enfin, nous avons trouvé
intéressant d’emmener l’élève à réfléchir à la question "pourquoi/quand trier" plutôt que de se limiter à
la question de savoir "comment trier" sur laquelle le programme semble plutôt mettre l’accent.
Ce chapitre complète le chapitre 5 (manipulation de tableaux) et est complété en particulier par le
chapitre 7 qui présentera les tables et la manipulation de tables.

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.

Analyser un algorithme : qu’est-ce qu’un algorithme?


Il peut être intéressant d’emmener l’élève à réfléchir sur ce qu’est un algorithme. Nous n’avons pas
réellement présenté ce point dans le manuel, or c’est un élément structurant dans les sciences du

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


numérique.
Cette question peut être complétée par plusieurs autres questions : - "qu’est-ce qu’un ordinateur" à
laquelle répond le chapitre 1, - "qu’est-ce qu’un programme", en filigrane au chapitre 1 - "qu’est-ce
que l’informatique ?" une réponse possible étant : la science du traitement automatique -ou du moins
rationnel- de l’information.
On peut définir un algorithme comme une suite finie d’instructions, qui décrit un calcul,
implémentable sur un ordinateur. L’algorithme peut être décrit par du pseudo-code et est implémenté
dans un langage de programmation (on peut bien sûr directement décrire l’algorithme dans un
programme sans passer par le pseudo-code, mais l’algorithme, c’est à dire la définition abstraite des
étapes du calcul, n’est pas "dépendant" du langage).
Anecdotes historiques : Les babyloniens utilisaient déjà des algorithmes pour calculer la division
euclidienne vers -2500 av J.C. Le terme "algorithme" vient du mathématicien persan Muḥammad ibn
Mūsā al-Khwārizmī, c’est à dire "originaire du Khwarezm" (une région au sud de la mer d’Aral).
Nommé responsable d’une "maison de la sagesse" (grand centre culturel/bibliothèque Abbaside) à
Baghdad en 820, il est l’auteur de plusieurs manuels (résolution d’équations algébriques du 1er et 2nd
degré, description des chiffres indiens, astronomie, géographie). Son influence a été majeure, à la fois
dans le monde islamique et dans l’occident latin, que ce soit à travers la transmission des savoirs
antiques, ou l’introduction de la numération décimale.
Le terme "algorithme", comme expliqué ci-dessus, provient de son nom. Le terme "algèbre" provient
du titre de son ouvrage sur la résolution d’équations. Enfin, il a contribué à la diffusion de la
numération décimale (dite "chiffres arabes") à travers son ouvrage sur les chiffres indiens dans le
monde islamique. Ces chiffres indo-arabes seront ensuite progressivement adoptés en occident,

Chapitre 6 – Trier et chercher dans les tableaux 1


notamment sous l’impulsion de Gerbert d’Aurillac (qui les appellera chiffres arabes, les ayant
découvert à Cordoue) et de Fibonacci.
Un point important dans le concept d’algorithme est l’idée d’une succession finie d’étapes discrètes.
On peut faire le parallèle avec les machines dont le mouvement est réglé par les "battements" d’une
horloge (depuis l’invention de l’échappement au moyen âge).
La volonté de formaliser la notion d’algorithme remonte aux travaux de D. Hilbert à la fin des années
1920. En réponse, les logiciens-mathématiciens des années 1930 : Gödel, Church, Turing, etc. ont
élaboré des modèles mathématiques permettant de représenter l’ensemble des algorithmes (toutes les
fonctions "calculables" par un ordinateur). Ces modèles sont les fonctions récursives pour Kurt-
Friedrich Gödel, le lambda-calcul pour Alonzo Church, et les machines de Turing pour Alan Turing.
Ces travaux ont d’ailleurs montré que certaines fonctions n’étaient pas "calculables" par un ordinateur
selon ces définitions, sujet qui sera abordé en terminale.
Source : Wikipedia.
https://en.wikipedia.org/wiki/Algorithm https://en.wikipedia.org/wiki/Muhammad_ibn_Musa_al-
Khwarizmi https://fr.wikipedia.org/wiki/Al-Khw%C3%A2rizm%C3%AE

Complexité algorithmique : quel coût mesure-t-on ?


Le coût est calculé dans une unité mesurant un temps de calcul théorique, le "nombre d’opérations".
Cette estimation se base sur un modèle de machine théorique, dans laquelle les opérations
arithmétiques (addition, multiplication, division), l’accès à une case d’un tableau, etc. sont considérés
comme coûtant "une" unité de temps. Lorsque l’on analyse la complexité en temps de calcul d’un
algorithme, on calcule rarement le nombre d’opérations de manière exacte : ce nombre n’est souvent
ni calculable ni exprimable simplement. En plus, il n’est pas vraiment pertinent du fait que l’on a
idéalisé le comportement de la machine. La plupart du temps, on se contente d’estimer le terme
dominant de la complexité, à une constante multiplicative près, en fonction de la taille 𝑛 de l’entrée,
pour la pire entrée de taille (au plus) 𝑛. Tous les termes dominés (termes de degré moindre dans un
polynôme) sont alors ignorés.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


Ceci ne nous concerne pas dans le contexte du baccalauréat, les informaticiens utilisent aussi d’autres
modèles de calculs (machines de Turing, machines RAM) où les opérations arithmétiques ont un coût
dépendant du nombre de bits de l’élément manipulé (par exemple, des nombres décimaux peuvent être
manipulés avec une précision arbitraire, et cela a un coût). Par ailleurs, dans le cadre de ce manuel
nous analysons la complexité sur une entrée donnée, ou sur le pire cas. Mais il est aussi possible
d’analyser la complexité en moyenne sur l’ensemble des entrées de taille 𝑛. En ce qui concerne les
algorithmes du programme (tri par insertion et sélection, recherche dichotomique), la complexité en
moyenne est similaire (à une constante près) à la complexité dans le pire cas. Enfin, une autre mesure
de complexité que nous avons à peine abordé dans le manuel consiste à mesurer l’espace mémoire
nécessaire au lieu de mesurer le temps de calcul. Cette mesure n’est pas réellement pertinente pour les
algorithmes du programme, peu gourmands en mémoire.

Complexité algorithmique : limites de la mesure de complexité asymptotique


Un algorithme sera dit "linéaire" s’il existe une constante 𝑐 (valable pour tout 𝑛) telle que pour toute
entrée de taille 𝑛, 𝑓(𝑛) < 𝑐 ∗ 𝑛. Par exemple, un programme qui prendrait en entrée un tableau et
ferait moins de 10 opérations sur chaque élément de l’entrée sera de complexité linéaire puisque 𝑐 =
10 ou 𝑐 = 200 conviennent. Un programme qui ferait 100 000 opérations pour chaque élément de
l’entrée serait aussi linéaire : il suffit de prendre la constante 𝑐 = 100 000.
En négligeant les constantes, on est amené à ne s’intéresser qu’à la complexité asymptotique, c’est à
dire la forme que prend la complexité quand la taille de l’entrée tend vers l’infini. En pratique, cette
analyse donne presque toujours une bonne estimation du comportement de l’algorithme sur des
données de taille suffisamment grande (ex : tableau de taille > 100, etc.). Par contre, sur de petits
tableaux d’une dizaine d’éléments, ce genre d’analyse n’a aucun sens. Par ailleurs, sur de petites
données, des éléments rarement pris en compte dans le modèle vont avoir une grande influence sur le

Chapitre 6 – Trier et chercher dans les tableaux 2


temps d’exécution réel : - est-ce que l’algorithme va pouvoir exploiter efficacement le cache du
processeur ou devra aller chercher des données "loin" en mémoire (principe de localité des données). -
est-ce que le processeur va être capable d’effectuer simultanément plusieurs instructions de
l’algorithme à l’aide d’instructions ad hoc implémentées au niveau électronique.
Ce que l’élève devrait à notre avis retenir en matière de complexité :
• connaître la complexité des algorithmes du cours (les 2 tris, les 2 recherches) ;

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

Complexité algorithmique : le cas des tris


Comme la complexité asymptotique n’est pas pertinente pour des entrées de petites tailles, il arrive
qu’un programme quadratique comme insertion sort (qui utilise efficacement le processeur et le cache)
soit plus efficace sur de petits tableaux qu’un programme en 𝑛log𝑛. C’est la raison pour laquelle C++
utilise des tris efficaces (avec un comportement en moyenne ou pire cas en 𝑛log𝑛 ) lorsque le tableau
est grand, et le tri par insertion (en 𝑛2 ) sur des tableaux de taille < 16. Python et Java, eux, utilisent
Timsort, qui, pour faire simple, est une optimisation assez complexe du tri par insertion (une forme
d’hybride entre le tri fusion en 𝑛log𝑛 et le tri par insertion. Cette hybridation permet de ramener
globalement la complexité de l’algorithme à 𝑛log𝑛, tout en mettant à profit des séquences déjà triées
par ordre croissant ou décroissant dans le tableau donné en entrée. Ces questions d’implémentation

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


n’ont a priori pas d’intérêt pour un lycéen, en tout cas pas dans le cadre du programme.

Complexité des recherches linéaires et dichotomique : recherche linéaire


Le B.O. indique à propos du parcours séquentiel d’un tableau qu’on "montre que le coût est linéaire".
Ceci laisse une grande marge d’interprétation : borne inférieure ou supérieure, dans le pire cas ? Avec
une preuve formelle, en se contentant de le mentionner, ou en observant le temps d’exécution en
pratique ? Il nous a paru qu’au niveau du lycée il n’était pas essentiel de fournir une preuve formelle.
Le paragraphe 2.2.2 "preuve d’optimalité" peut être ignoré sans grande perte. Les preuves que nous
avons écrites pourraient être formalisées plus rigoureusement encore par induction. Par exemple, pour
montrer que sur un tableau de taille 𝑛 la recherche linéaire fait au plus 𝑛 comparaisons :

• on observe que c’est vrai sur un tableau de taille 𝑛 = 1 ;

• 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 𝑛.

On conclut que la propriété est vraie pour tout 𝑛.


Il est douteux qu’il y ait grand intérêt à formaliser à ce point une preuve triviale.

Recherche dichotomique : module bisect


Pour plus d’infos sur le module Python permettant d’effectuer des recherches dichotomiques, on
pourra consulter la doc : https://docs.python.org/3.8/library/bisect.html mais bien sûr l’esprit du
programme n’est pas d’utiliser ce module, mais plutôt de comprendre le fonctionnement et l’intérêt de

Chapitre 6 – Trier et chercher dans les tableaux 3


la recherche dichotomique (et aussi d’introduire le concept d’analyse/preuve d’algorithme sur cet
exemple classique). Pour les plus curieux, le code source dans la librairie cpython est
https://github.com/python/cpython/blob/master/Lib/bisect.py Ce code source est très similaire au code
donné dans le cours, et nous nous en sommes inspiré pour rédiger l’exercice 43.

Méthodes de tri natives en Python : au-delà des list


• Il est possible de trier d’autres conteneurs de données que des list; sorted() accepte ainsi d’autres
itérables, et les tris par insertion et sélection peuvent aussi être adaptés à d’autres itérables, par
exemple une liste chaînée.

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

Anecdote : recherche dichotomique : danger des variantes


Il n’est sans doute pas pertinent d’exiger d’un étudiant de savoir écrire par lui même des variantes
nécessitant d’adapter le code de la recherche dichotomique. En effet, Knuth (toujours lui, mais c’est la
référence pour ce genre d’algorithmes classiques) indique que si l’idée de cette recherche est simple, il
est très facile de se tromper dans des détails.
Voir : https://en.wikipedia.org/wiki/Binary_search_algorithm#Implementation_issues
Ce constat que l’implémentation de la recherche binaire recèle des subtilités est à nuancer cependant :
les principales (mais pas les seules) difficultés apparaissent lorsque l’on étudie des variantes de la
recherche dichotomique ou que l’on s’intéresse au problème de débordement des entiers (overflow),
ce qui n’est bien sûr pas le cas au lycée.

Anecdote : remarque sur le tri par insertion

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


On parle parfois de "tri du joueur de carte", car un joueur de carte amateur peut trier ses cartes en les
insérant une par une dans la partie du tas qu’il a déjà triée, ce qui est le principe du tri par insertion.
Nous attirons ci-dessous l’attention sur quelques subtilités soulevées par les définitions dans le
manuel.

Anecdote : première collection de taille importante triée; tablette d’Inakibit-Anu


Une référence intéressante pour la culture sur la tablette d’Inakibit-Anu, un article CACM'1972 de
Knuth : http://www.realtechsupport.org/UB/NP/Numeracy_BabylonianAlgorithms_1977.pdf Knuth y
fait remarquer (p675,676) que :
• la tablette se trouve au Louvre

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

Question/Réponse : correction d’algorithmes


Pourquoi avons-nous précisé en 2.1 qu’on s’intéressait uniquement aux exécutions sur des données
valides pour définir la correction/terminaison ?

Chapitre 6 – Trier et chercher dans les tableaux 4


Réponse : parce-que si l’on donne des entrées qui ne satisfont pas les pré-requis de l’algorithme le
comportement de l’algorithme peut être arbitraire. Ex : exécuter la recherche dichotomique sur un
tableau non trié. Ou donner un entier à une fonction attendant une chaîne de caractères… Les
programmes ne vérifient pas toujours que les données en entrée sont valides (ce n’est pas toujours
possible, ou serait trop coûteux : on ne va pas parcourir tout le tableau pour vérifier qu’il est trié dans
la recherche dichotomique). Ce n’est pas pour autant qu’on devrait les considérer incorrects.

Question/Réponse : invariants des tris


Pourquoi avons-nous précisé dans l’invariant du tri par insertion que le reste du tableau (la partie à
droite des i+1 premières cases) n’est pas modifié ? Et dans le tri par sélection que l’ensemble des
éléments ne varie pas et que l’on s’est contenté de permuter des éléments ?
Réponse : ces conditions permettent de montrer facilement que l’invariant reste vrai d’une étape à
l’étape suivante en se contentant d’analyser les modifications apportées par la boucle. On retrouve
souvent dans la littérature une présentation simplifiée de l’invariant qui se contente d’indiquer que les
i premières cases sont triées.

Question/Réponse : recherche dichotomique


A quoi servent les indices left,right ?
On pourrait se demander quelle est l’utilité d’enregistrer explicitement les indices des bords de
l’intervalle, vu que l’on ne compare la valeur recherchée qu’avec le milieu. Les bords de l’intervalle
servent à calculer la valeur du milieu, et à détecter quand l’intervalle devient vide. En fait, d’autres
implémentations de la recherche dichotomique sont possibles (voir par exemple "uniform binary
search"), mais moins intuitives, donc on utilise généralement la version présentée.

Question/Réponse : code du tri par sélection


Est-il nécessaire de tester si j_min != i dans le code du tri par selection ?
Réponse : non, c’est juste une optimisation pour éviter un échange inutile. C’est le sujet de l’exercice
16.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


TP sur la Méthode dichotomique
Intérêt du TP :
• faire découvrir la méthode dichotomique et en lien avec la recherche dichotomique dans un
tableau. On y montre plus précisément en fin de TP que la recherche dichotomique dans le
tableau n’est qu’un cas particulier de la méthode dichotomique ;

• présenter les applications courantes de la méthode dichotomique : résolution d’équations,


approximation de racines ou autres nombres possiblement irrationnels ;

• 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

Chapitre 6 – Trier et chercher dans les tableaux 5


simuler la variante du cours renvoyant -1 : il suffit d’évaluer la fonction sur l’indice renvoyé pour voir
si c’est la valeur cherchée ou non. * Les questions 6, 7, 8 et 9 du TP sont des questions d’ouverture,
probablement à réserver aux élèves curieux ou "à l’aise". Dans l’ensemble, ce TP comporte beaucoup
de questions de réflexion exigeantes, qui nous ont paru intéressantes mais ne sont pas vraiment dans
l’esprit des épreuves-types du programme.

TP : Remarques sur la figure


Le nombre de pas utilisés pour tracer la fonction n’est pas nécessairement lié à la précision eps
cherchée pour la fonction. Il s’agit uniquement de se faire une idée des variations de la fonction sur
l’intervalle.
La figure qui trace la courbe permet aussi de réaliser qu’il existe des racines hors de l’intervalle mais
qu’elles n’ont pas le moindre sens physique. La fonction f donnée pour la flottabilité n’est valide que
sur l’intervalle. En réalité la résultante des forces reste constante à gauche de h = 0 et à droite de
h = 4.2, et n’est donc représentée par f qu’entre 0 et 4.2. A partir du moment où le navire est
totalement immergé (h = 4.2), la résultante des forces ne varie plus car la poussée d’Archimède est
constante. De même, si on imagine le navire totalement hors de l’eau la résultante des forces ne varie
plus car il n’y a plus de poussée d’Archimède.

TP : remarques sur l’équation et le modèle adopté


L’équation donnée ne correspond pas à celle d’une coque de navire (il faudrait intégrer le volume en
fonction du profil de la coque). Pour simplifier, on a utilisé la formule correspondant à une sphère de
rayon r = 2.1 et densité spécifique (i.e., par rapport à l’eau) de .7. L’équation du volume d’eau déplacé
étant (Spherical Cap) : 𝜋/3 ⋅ ℎ2 ⋅ (3𝑟 − ℎ), les forces en présences sont illustrées sur l’image.
Si on voulait une formule un peu plus réaliste, on pourrait approcher la forme d’un sous-marin en
surface par un cylindre de longueur l et rayon r. Le volume déplacé (Horizontal Cylindrical Segment)
est donné par 𝑙(𝑟 2 ⋅ cos − 1((𝑟 − ℎ)/𝑟) − (𝑟 − ℎ)√2 ⋅ 𝑟 ⋅ ℎ − ℎ2 La formule est plus compliquée et
n’est plus un polynôme en ℎ. Par contre la formule reste une "formule close" relativement simple et
permet de relier l’exercice à l’actualité, les sous-marins (de classe Suffren en ce moment pour la
Marine Nationale) effectuant une pesée après les travaux pour mesurer la flottabilité (on dit plutôt :

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


effectuer leur pesée statique). http://cols-bleus-
fr.s3.amazonaws.com/exemplaires/pdf/Plong%C3%A9e%20et%20retour%20en%20surface%20v10.p
df https://lefauteuildecolbert.blogspot.com/2020/04/sna-ng-suffren-premiere-pesee-statique.html

TP : remarque sur la méthode dichotomique


Quand on ne connait pas "à l’avance" les bornes inférieure et supérieure de l’intervalle (valeurs a, b
telle que g(a)g(b) < 0) pour initialiser la recherche dichotomique d’une racine, on peut calculer de
telles bornes :
• en les sélectionnant visuellement en traçant la fonction g avec matplotlib, comme ce qu’on a fait
dans le TP, même si l’on connaissait déjà les bornes dans le cadre de ce TP.

• 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

Chapitre 6 – Trier et chercher dans les tableaux 6


https://www.pourlascience.fr/sr/logique-calcul/calculs-et-coulissements-2740.php (ou "mathématiques
pour le plaisir", issu du même article, p118)
https://en.wikipedia.org/wiki/15_puzzle
http://villemin.gerard.free.fr/Puzzle/Taquin.htm

Exercice 39 Écrous et vis


On pourrait demander de proposer un algorithme efficace en moyenne lorsque l’on ne peut pas
comparer les écrous entre eux (ni les vis entres elles).
Cet exercice met en oeuvre l’idée du tri rapide (Très difficile en terminale, encore plus en première
donc il faut guider).
On peut aussi envisager une variante où on comparerait des clés et des verrous, et où on aurait
uniquement pour réponse "la clé correspond" ou "la clé ne correspond pas". Dans ce cas-là, en
l’absence de relation d’ordre, on n’a pas d’autre moyen que de tester chaque clé avec chaque verrou
jusqu’à ce qu’on trouve la clé correspondant à chaque verrou. On peut demander combien de
tentatives vont alors être faites dans le pire cas ? (question tout à fait accessible en première)
La réponse est 𝑛 + (𝑛 − 1)(𝑛 − 2) … + 1 = 𝑛(𝑛 + 1)/2 (on peut s’arrêter à l’avant dernière 𝑛(𝑛 +
1)/2 − 1 si on se contente de la certitude que la clé convient sans tester, mais c’est un détail).

Exercice 48 : "Tu gèles-tu brûles"


Pour rendre l’activité ludique, on peut utiliser plusieurs rounds pour limiter l’impact d’un coup de
chance. On peut aussi comparer le nombre de questions de l’élève au nombre de questions de
l’algorithme optimal.
On pourrait autoriser l’élève JA à changer de nombre tant que son nouveau choix reste cohérent avec
les réponses déjà données (c’est à dire qu’il va tâcher de construire un pire cas par rapport aux
réponses déjà données). Mais en pratique adapter la réponse de manière cohérente sera très difficile
au-delà des toutes premières tentatives (ça peut néanmoins permettre d’éviter les "coups de chances"

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


sur les premières tentatives).
Trouver une stratégie vraiment optimale est possible, mais… c’est un exercice de niveau "olympiade"
donc cela ne peut pas être demandé à un lycéen.
https://ioi2010.org/competitiontask/day1/hottercolder/index.html

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.

Activité/exercice supplémentaire : Pesée avec une balance à plateau


Cet exercice peut aussi servir à introduire la recherche dichotomique par un exemple concret avant le
cours.
Supposons qu’on ait une balance à 2 plateaux classique, et des masses de 1g chacune en quantité
suffisante. La balance à plateau sera supposée précise à 1 gramme près, au sens où le plateau de
gauche descend si son poids excède celui du plateau de droite de 1g ou plus, et réciproquement. On
suppose aussi que les plateaux restent en l’équilibre quelques secondes lorsque leur poids diffère de
moins d’un gramme.
On cherche à peser une enveloppe dont on sait qu’elle pèse entre 12 et 19 grammes. Combien de
pesées suffisent pour identifier le poids de l’enveloppe ? Pour simplifier on va supposer que la
dernière pesée doit être une vérification du poids et que les plateaux y sont donc à l’équilibre. Plus

Chapitre 6 – Trier et chercher dans les tableaux 7


généralement, combien de pesées suffisent pour identifier le poids d’une enveloppe dont on sait
qu’elle pèse entre a et b grammes ?
Solution : Pour 12 et 19, il suffira dans le pire cas de 3 pesées : on commence par 16, puis si
l’enveloppe est plus légère que 16g, on tente 14g, si elle est plus lourde, 18g. Si la seconde pesée n’est
pas une égalité, il reste uniquement une dernière pesée à effectuer pour vérifier le poids (par exemple
si plus léger que 16 et plus lourd que 14, on vérifie 15).
De façon plus générale, il s’agit d’une recherche dichotomique dans le tableau [a,a+1,…,b]. Il faut donc
⌊log 2 (𝑛) + 1⌋ pesées, où n est le nombre d’éléments du tableau, c’est-à-dire b-a+1.

Exercice supplémentaire : recherche sur une interface web


Le réseau social (fictif) xxyyzz permet d’afficher des listes d’amis.
• Le site affiche tous les amis de la liste choisie, par ordre alphabétique, sur plusieurs pages : 20
amis par page (sans recoupement. Par exemple, la première page contiendra les amis Agnès,
Akira, Alice, Amandine, Anne, Ariel, Arthur, …, Aurore, puis la page 2: Ayoub, Azalée,
Bertrand…).

• 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).

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Exercice supplémentaire (Titan) : argmin, argmax
Dans l’esprit des exercices 7 et 13 du chapitre précédent
a. (Newbie/Initié) Calculer sur un tableau t l’indice du minimum de t (le premier tel indice si il y en a
plusieurs) à l’aide d’une boucle for. b. Définir une fonction f qui prend en argument un tuple (paire)
p=(a,b) et renvoie (b,a). c. (Titan) La méthode min possède aussi un argument key qui fonctionne
comme celui de sort et sorted. Que renvoie min([(2,3),(8,5),(4,1),(9,2)],key=f)? En déduire une solution pour
calculer l’indice du minimum, comme en a, mais en utilisant la méthode de tri Python sans aucune
boucle for.
Indication : Comme min (même avec un paramètre key) renvoie une valeur et non un indice, on pourra
utiliser enumerate pour que les valeurs triées "contiennent" l’indice. Sauf que si l’on se contente de
min(enumerate(t)) on trierait des paires (indice, valeur). On veut trier les paires (valeur, indice)…

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

Chapitre 6 – Trier et chercher dans les tableaux 8


b.
def inverse_paire(p):
a, b = p
return (b,a)

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

Exercice supplémentaire : Recherche de médiane


Soit t un tableau.
a. Écrire un programme qui calcule efficacement la médiane de t.
b. Quelle serait la complexité de l’algorithme qui calculerait la médiane de t comme suit :
Tant que t contient plus de 2 éléments différents de None, * parcourir t pour calculer : l’indice 𝑖min
d’une valeur (différente de None) minimale dans t, l’indice 𝑖max d’une valeur (différente de None)
maximale dans t * mettre les positions i_\min et i_\max à None dans t. Renvoyer une des 2 valeurs de
t.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Remarques : * on peut demander de généraliser la question a) au calcul d’un élément de rang 100
dans un tableau contenant plus de 100 éléments. * on pourrait se demander si la solution en 𝑛log𝑛 est
le mieux que l’on puisse faire en a). De manière peut être surprenante, la réponse est "non", on peut
calculer la médiane (ou n’importe quel rang) en temps linéaire, en utilisant une technique appelée
"médiane des médianes". Mais cet algorithme linéaire, de type "diviser pour régner", est trop
compliqué pour le programme de première, et serait même assez difficile à comprendre pour un élève
de terminale.
Solution a.
sorted(t)[len(t)//2]

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

Chapitre 6 – Trier et chercher dans les tableaux 9


• Estimer la complexité asymptotique d’un algorithme

• 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…)

Autres exercices ayant trait à ce chapitre :


• dans le sujet 11, il s’agit de compléter un tri bulle ;

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

Quelques autres références intéressantes :

Recherche dichotomique, application au problème de pesée, mastermind et lien avec


l’identification
https://capes-nsi.org/data/uploads/2020_epreuve_2.pdf

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


L’exercice de pesée (identifier par dichotomie une pièce de poids différent des autres sur une balance
à 2 plateaux) se prête facilement à un exercice pour lycéens. Le mastermind est un peu plus
compliqué, car la réponse à chaque tentative apporte plus d’un bit d’information.
• Vaguement en lien avec la recherche dichotomique, activité "débranchée" (sans code, mais
nécessitant l’ordinateur) du concours castor :

exercices Bambous et Curseurs https://concours.castor-informatique.fr/index.php


C’est un exercice amusant d’informatique (conçu pour des collégiens un peu futés) qui met en oeuvre
des idées très similaire à la recherche dichotomique (en fait l’idée est de construire un arbre binaire de
recherche équilibré):
• Tri tournoi : Utiliser une méthode de tri par tournoi pour identifier efficacement le non seulement
le max mais aussi le second meilleur d’un ordre. Probablement un peu trop loin du programme.
Une version très simple de tournoi apparait ultérieurement au chapitre 10 du manuel.

• Quelques idées sur: https://www.w3resource.com/python-exercises/data-structures-and-


algorithms/index.php mais la plupart sont sans doute trop difficiles pour des élèves de premières
(à moins de beaucoup guider, même avec du code à trou). De plus il s’agit pour la plupart de ces
exercices d’implémenter différents algorithmes de tris.

• Animations de tris :

Les pages Wikipedia en regorgent. Voir aussi :

Chapitre 6 – Trier et chercher dans les tableaux 10


https://www.geeksforgeeks.org/insertion-sort-visualization-using-matplotlib-in-python/
https://high-python-ext-3-algorithms.readthedocs.io/ko/latest/chapter1.html#id9
On trouve aussi en ligne des vidéos de tris illustrés par des chorégraphies/danses roumaines, mais cela
peut sembler plus distrayant que pédagogique.
• Un benchmark qui liste les records en matière de tri (rapidité, débit, consommation électrique) :

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.

Activité : Tris et recherches

Recherche dichotomique : le principe


1. On effectue au plus 3 accès au tableau sur un tableau de taille 6 comme celui-ci. Pour un tableau de
taille 1, il faut et suffit d’un accès, sur un tableau de taille 2, de 2 accès. Et pour un tableau de taille 3,
2 accès aussi car une fois qu’on a évalué la case du milieu, on sait s’il faut évaluer celle de droite ou
de gauche selon que la valeur au milieu est trop grande ou trop petite. NB: on peut deviner que ce
serait pareil pour un tableau de taille 7 puisque cela reviendrait à avoir 3 cases à gauche et à droite du
milieu au lieu de 2 à gauche et 3 à droite.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


2. Non, car les indices sont des entiers. Or idx_left + idx_right peut être pair ou impair. En utilisant la
division euclidienne, on s’assure que idx_mid est entier. Par contre, on aurait tout aussi bien pu utiliser
un arrondi supérieur. Pour s’en convaincre on pourrait raisonner que si on trie par ordre croissant le
tableau avec les valeurs opposées (-160, -130, etc.) alors la case sélectionnée comme milieu par la
division euclidienne équivaut à l’arrondi supérieur sur le tableau de signe contraire.
3. Le code termine toujours et renvoie un indice ou -1 car le variant reste valide. Le code s’exécute
bien sans lever de message d’erreur. Quand la valeur n’est pas présente, le code renvoie -1. Par contre,
quand la valeur est présente dans le tableau il n’y a aucune garantie de trouver la valeur; il est tout à
fait possible de renvoyer -1. Le résultat n’a donc pas de sens évident. En pratique on ne va donc jamais
utiliser la recherche dichotomique sur un tableau non trié.

Mesurer l’efficacité de la recherche dichotomique dans le pire cas


1.
def f1():
return recherche_lineaire(range(1,10**6+1),10**6+7)
def f2():
return recherche_dichotomique(range(1,10**6+1),10**6+7)
def f3():
return recherche_lineaire(range(1,10**6+1),1)
def f4():
return recherche_dichotomique(range(1,10**6+1),1)

print("recherche linéaire d'une grande valeur :\t", chrono(f1,3))


print("recherche dichotomique d'une grande valeur :\t", chrono(f2,3))

Chapitre 6 – Trier et chercher dans les tableaux 11


print("recherche linéaire d'une petite valeur :\t", chrono(f3,3))
print("recherche linéaire d'une petite valeur :\t", chrono(f4,3))

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


– d’autre part on ne peut pas à l’oeil nu identifier de façon claire une courbe
logarithmique, contrairement à une droite (ce serait possible en théorie sur une échelle
semilogx puisque l’échelle semilogx ramène la courbe logarithmique à une droite) ;

– enfin et surtout, même si on augmentait le nombre de répétitions et n’affichait que la


courbe de la recherche dichotomique pour que l’échelle soit satisfaisante, on observerait
probablement une courbe très irrégulière.

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

Chapitre 6 – Trier et chercher dans les tableaux 12


QCM (CHEKPOINT)
1 a 4, b 4, c 1, d 2 ; 2 a ; 3 b ; 4 b ; 5 b ; 6 c (du moins pour l'algorithme habituel, celui vu en cours) ;
7 a 1, b 2, c 3 ; 8 i+1,len(t) / t[j_min], t[i].

TP - Racine d’un polynôme


1.
"""
Ce n'était pas demandé mais on peut rendre la fonction un peu plus générale
et la figure plus exploitable. Une solution plus simple est donnée après ce commentaire.

r = 2.1
d = .7
def f(h):
return - h**3 + 3*r*h**2 - 4*d*r**3

import matplotlib.pyplot as plt


plt.clf()
nb_pas = 100
liste_x = [0.05*i for i in range(0,int(4.2/.05)+1)]
# equivalent a : liste_x = [x for x in numpy.arange(0,4.21,0.05)]

liste_y = [f(h) for h in liste_x]


line0, = plt.plot(liste_x,liste_y, label='f(h)')
plt.grid()
plt.xlabel('h')

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


plt.legend(handles=[line0])
plt.show()
"""
def f(h):
return - h**3 + 6.3*h**2 - 25.93

import matplotlib.pyplot as plt

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

Chapitre 6 – Trier et chercher dans les tableaux 13


"""
nb_iterations = 0
while True:
nb_iterations += 1
print(f'a:{a}, b:{b}, nb iterations:{nb_iterations}')
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

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:

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


nb_iterations += 1
print(f'a:{a}, b:{b}, nb iterations:{nb_iterations}')
mid = (a+b)/2
if abs(f(mid))<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

bisection(f, 0 , 4.2, .0000001) # 2.67

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

Chapitre 6 – Trier et chercher dans les tableaux 14


même dû) vérifier dans le code de bisection que les paramètres étaient valides, et lever une exception
sinon.
Ici, pour que rac renvoie bien la racine carrée sur tout 0<x<1 et éviter le mauvais emploi de bisection, il
suffit de noter que la racine carrée d’un nombre positif est comprise entre 0 et max(1,x). Donc on peut
remplacer le code de rac par :
def rac(x): # assert(x>0)
def smaller_sq(k):
return k*k - x
return bisection(smaller_sq,0,max(1,x),0.0001)

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 ;

• la méthode bisection divise de manière répétée l’intervalle par 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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


généralement un peu plus l’intervalle (en prenant mid-1, mid+1 par rapport à la division entière)
n’est en soi pas essentiel, même si c’est une manière élégante de maintenir les invariants et
variants et donc d’éviter les erreurs dans les cas ``limites'' (tableau de taille inférieure ou égale à
4, valeur non trouvée, etc).

• 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

Chapitre 6 – Trier et chercher dans les tableaux 15


8. On ramène le problème à la recherche de racine. On pourrait objecter que la fonction n’est pas
continue. Mais on n’a pas réellement besoin que la fonction f soit continue pour appliquer la méthode
dichotomique. Il suffit de garantir qu’il existe une racine entre x1 et x2 pour tous a ⇐ x1 < x2 ⇐ b dès que
f(x1) et f(x2) sont de signe contraire.

• La continuité de f garantit la propriété ci-dessus (par le théorème des valeurs intermédiaires).

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


y = w(z)
if y==True:
return 1
elif y==False:
return -1
else:
return 0

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

Chapitre 6 – Trier et chercher dans les tableaux 16


parcourir les cases autour de l’indice renvoyé par la bisection (c’est à dire les cases faisant partie de
l’intervalle final) pour vérifier si l’on y trouve la valeur.
from math import floor
def recherche_dicho(t, val):
""" Ramene la recherche dichotomique a un probleme de bisection """
a=0
b = len(t)-1
def f(x):
return t[floor(x)] - val
i = round(bisection(f,a,b,2))
for j in range(max(0,i-1),min(len(t),i+2)):
if t[j] == val:
return j
return -1

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Remarque : sur l’entrée donnée, cela n’a pas d’impact, mais sur d’autres entrées, il serait important
de noter qu’on commence par multiplier la chaîne avant de trier.
Par exemple : sorted([x*2 for x in ['32','3','4'] ]) == ['3232', '33', '44'] alors que [x*2 for x in sorted(['32','3','4']) ] ==
['33', '3232', '44']

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

Chapitre 6 – Trier et chercher dans les tableaux 17


4 Il suffit d’inverser la comparaison t[j] < t[j-1] devient t[j] > t[j-1].
L’invariant sera qu’à la fin de la i-ème itération les i+1 premières cases du tableau contiennent les i+1
premières cases du tableau originel, triées par ordre décroissant.
L’idée c’est que trier t par ordre décroissant selon un certain ordre c’est la même chose que trier t par
ordre croissant pour la relation d’ordre inversée. Donc quel que soit l’algorithme de tri par
comparaison, on le transforme en tri par ordre décroissant si on inverse le signe des comparaisons (de
valeurs du tableau): `<' devient `>', `>' devient `<', et `==' reste `=='.
La solution t[j] >= t[j-1] satisferait aussi les conditions de l’énoncé Mais c’est plus difficile à prouver
(c’est vrai parce que t[j] ⇐ t[j-1] reste un tri par ordre croissant, mais ce n’est pas immédiat à prouver.

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


2, 314138, 182211, 176198, 173089, 169733, 157650]

# 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)]

[sorted(p,reverse=True)[i] for i in range(10)]

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]

Chapitre 6 – Trier et chercher dans les tableaux 18


On prend un élément et l’échange avec l’élément à sa droite si ce dernier est plus petit. Ainsi, un grand
élément va remonter vers la droite jusqu’à ce qu’il rencontre plus grand que lui auquel cas ce plus gros
élément « prend le relai ». On retrouve ce principe dans le tri bulle, qui ressemble un peu au tri par
insertion, mais est moins efficace (voir ex 14).

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

tri par insertion n*n n*n n*n

tri par sélection n*n n n*n

b.
Taille 4 :

algorithme échanges comparaisons

tri par insertion 6 6

tri par sélection 4 6

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


Taille 10 :

algorithme échanges comparaisons

tri par insertion 45 45

tri par sélection 10 45

c. Il est multiplié par 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.

Chapitre 6 – Trier et chercher dans les tableaux 19


Pour le premier `c' dans une chaîne de caractères : la chaîne n’étant pas triée on n’a pas le choix et doit
utiliser la recherche linéaire. Par contre si les caractères dans la chaîne étaient croissants: ex :
`aaabcddz' alors on pourrait utiliser la recherche dichotomique puisque on peut accéder efficacement à
n’importe quelle position de la chaîne par son indice.

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)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# Test:
t = ['0601010101','0620202020','0601010101','0601010104','0601010104']
t0 = t

compte_distinct(t) # devrait afficher 3


print(t0 is t) # t ne doit pas avoit changé

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)

Chapitre 6 – Trier et chercher dans les tableaux 20


# Test:
t = ['0601010101','0620202020','0601010101','0601010104','0601010104']
t0 = t
t1 = t.copy()
compte_par_numero(t) # devrait afficher:
# 0601010101 2
# 0601010104 2
# 0620202020 1
print(t0 is t and t1 == t) # t ne doit pas avoit changé

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


d’inversions dans 𝑡. Cf exercice 13.

13 a. (4,2) (7,2) et (7,5)


b.
def affiche_inversions(t):
for i in range(len(t)-1):
for j in range(i+1,len(t)):
if t[i]>t[j]:
print(f'indices {i} et {j}: {t[i]},{t[j]}')

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

Chapitre 6 – Trier et chercher dans les tableaux 21


d. Lors d’un échange sur un tableau dont toutes les valeurs sont distinctes, le nombre d’inversions peut
:
• diminuer de 1 (échange t[i]>t[i+1])

• augmenter de 1 (échange t[i]<t[i+1])

• rester le même (ex: échange de 8 et 3 dans 1 8 3 4)

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


où les plus petits descendent à fauche pour faciliter la comparaison avec le tri par sélection vu en
cours). Ce tri a une complexité quadratique, par exemple sur t=[n,n-1, n-2…,1].
Le tri bulle, assez universellement décrié à en croire wikipédia, ne sert à peu près à rien en pratique car
il est inefficace. Le seul intérêt de cet exercice est donc pédagogique : faire réfléchir l’élève à « ce qui
se passe » quand on effectue de manière répétée ces échanges entres voisins, en prolongeant l’exercice
« Algo mystère », qui, lui, correspond à un des exercices de l’épreuve-type mise à disposition sur le
site Eduscol.

15 a. On identifierait la position à laquelle insérer chaque élément en ⌊log 2 (𝑖) + 1⌋ comparaisons.


b. Il faudrait quand même décaler les i-idx cases vers la droite, donc cela fait i-idx échanges.
c. On diminue le nombre de comparaisons à effectuer (dans le pire cas), mais pas le nombre
d’échanges. Comme on insère 𝑛 éléments et que ⌊log 2 (𝑖) ≤ log 2 (𝑛), le coût total pour trouver les
positions auxquelles insérer les éléments serait de l’ordre de 𝑛log𝑛 comparaisons tout au long de
l’algorithme. Le coût total des insertions en comptant les échanges, par contre, resterait quadratique :
𝑛(𝑛 − 1)/2, le nombre d’échange restant le même.
Au vu des arguments ci-dessus, il est difficile de trancher. On peut affirmer qu’en terme de complexité
on n’a pas gagné grand chose avec l’optimisation suggérée: on divise le nombre d’opérations du pire
cas par 2 puisqu’on a remplacé 𝑛(𝑛 − 1)/2 échanges et comparaisons par 𝑛(𝑛 − 1)/2 échanges.
Comme on a rendu l’algorithme plus compliqué pour un gain incertain, la version du cours est
préférable.
En fait, il y a plusieurs autres raisons pour lesquelles l’algorithme du cours est plutôt meilleur :

Chapitre 6 – Trier et chercher dans les tableaux 22


• comme on va de toute manière échanger les valeurs tôt ou tard pour les décaler, les comparer
avant ne coûte pas grand chose, et cela permet d’accéder aux valeurs en traversant le tableau de
droite à gauche sans faire des « sauts » dans la mémoire, contrairement à la recherche
dichotomique. Quitte à parcourir une fois cette partie du tableau, qu’on fasse une ou deux
opérations sur la case (décalage seulement, ou comparaison et décalage) n’a quasiment pas
d’impact sur la performance ;

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

• surtout, il n’y a pas que le pire cas à prendre en compte.

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 (linéaire) sur un tableau déjà trié ;

• qu’il est simple ;

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


17 a. Oui car on insère chaque élément i (par i croissant) dans la position la plus à droite possible
parmi celles qui conviennent : on n’échange t[j] avec t[j-1] que si t[j] est strictement plus grand. En cas
d’ex aequo on maintient donc leur ordonnancement.
b. Le tri par insertion resterait correct (même invariant), mais ne serait plus stable . Par exemple sur
[(0,'a'),(1,'b'),(1,'c')] et avec la fonction f qui à toute paire (x,y) renvoie son premier élément x (ce qui veut
dire qu’on trie les paires sur le premier élément), le tri par insertion modifié renverrait
[(0,'a'),(1,'c'),(1,'b')].

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

Chapitre 6 – Trier et chercher dans les tableaux 23


for j in range(0, i):#or range(i-1,-1,-1)
# j_m = indice du maximum parmi t[i],...,t[j]
if t[j] > t[j_m]:
j_m = j
if j_m != i: # on echange les cases i et j_m
t[i], t[j_m] = t[j_m], t[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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


if i < j:
t[i], t[j] = t[j], t[i]
return t

#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

Chapitre 6 – Trier et chercher dans les tableaux 24


# Version 1: sur place
for i in range(nb_b):
t[i] = 'bleu'
for i in range(nb_b,len(t)):
t[i] = 'red'
# Version 2: si on voulait renvoyer une copie triee:
return [ 'bleu' if i < nb_b else 'rouge' for i in range(len(t)) ]

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]

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


if t[j] < t[j_min]:
j_min = j
for j in range (j_min,len(t)):
if t[j] == t[j_min]:
t[i], t[j] = t[j], t[i]
i = i+1

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.

Chapitre 6 – Trier et chercher dans les tableaux 25


def est_impair(n):
"""renvoie 0 pour un impair, 1 pour un pair"""
return (n+1)%2

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def tri_entiers_non_distincts(t,n):
"""
Trie le tableau d'entiers t.
Suppose que t ne contient que des entiers entre 0 et n
"""
casiers = [0]*n
for x in t:
casiers[x] = casiers[x] + 1
res = [None]*len(t)
i=0
for val, compteur in enumerate(casiers):
for j in range(compteur):
res[i] = val
i = i+1
return res

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.

Chapitre 6 – Trier et chercher dans les tableaux 26


23 a.
def produit_scalaire(t1,t2):
res = 0
for i in range(len(t1)):
res += t1[i]*t2[i]
return res

# 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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


et `idx_right.

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:

Chapitre 6 – Trier et chercher dans les tableaux 27


v[i] = True
angle_max = h[i]/d[i]
return v

# 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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# Test :
print(fusion([2,3,7],[1,3])==[1, 2, 3, 3, 7])

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

Chapitre 6 – Trier et chercher dans les tableaux 28


if t == []:
return t
i = find_min(t)
t1 = [None]*(i+1)
t2 = [None]*(len(t)-i-1)
for j in range(i+1):
t1[j] = t[i-j]
for j in range(i+1,len(t)):
t2[j-i-1] = t[j]
return fusion(t1,t2)

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


Noter aussi que l’algorithme utilisé est aussi très similaire à celui permettant de fusionner deux listes
triées, voir exercice 26 « Fusion de tableaux triés » ; dans les deux cas on avance alternativement sur
les deux listes, de manière à parcourir les éléments de l’union des deux listes dans l’ordre.
def intersecte(t1, t2):
"""
Entree: deux tableaux t1, t2.
Les elements dans t1 sont tous distincts.
Les elements dans t2 aussi sont tous distincts.
(donc t1 et t2 representent des ensembles au sens mathematique)
Renvoie: un tableau res qui represente l'intersection des 2 ensembles.
C'est a dire que res contient les elements qui sont simultanement dans t1 et dans t2.
Pour que le code soit efficace on s'autorise a utiliser la fonction t.append(v) qui ajoute la valeur v a la fin du tableau t.
"""
tb = sorted(t1)
t2b = sorted(t2)
if t1 == []:
return t2
if t2 == []:
return t1
res = []
i1 = 0
i2 = 0
while i1 < len(tb) and i2 < len(t2b):
if tb[i1] == t2b[i2]:
res.append(tb[i1])
i1 = i1+1

Chapitre 6 – Trier et chercher dans les tableaux 29


i2 = i2+1
elif tb[i1] < t2b[i2]:
i1 = i1+1
else:
i2 = i2+1
return res

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

30 Il s’agit d’utiliser une forme de recherche dichotomique. Le problème est double :


• on n’a pas le tableau des distances avec p mais uniquement le tableau des luminosités ;

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


def evalue_la_pente(pix,i, t):
"""
suppose i>0 et i <len(t)-1
- renvoie -1 si t[i] est moins proche de pix que t[i+1]
- renvoie 1 si t[i] est moins proche de pix que t[i-1]
- renvoie 0 sinon (t[i] est plus proche de pix que ses 2 voisins).
"""
assert(i>0 and i<len(t)-1)
if abs(pix-t[i+1]) < abs(pix-t[i]):
return -1
elif abs(pix-t[i-1]) < abs(pix-t[i]):
return 1
else:
return 0
# Test :
print(evalue_la_pente(.4,1,[.2,.31,.54,.57,.8])==0)
print(evalue_la_pente(.6,1,[.2,.31,.54,.57,.8])==-1)
print(evalue_la_pente(.19,3,[.2,.31,.54,.57,.8])==1)

def best_color_dicho(pixel, t):


"""
Entree:
- pixel: float,
- palette: tableau de float,
par ordre strictement croissant.
Renvoie le flottant le plus proche
de pixel dans palette
Cette solution parcourt tout le tableau, ce qui n'est pas ideal

Chapitre 6 – Trier et chercher dans les tableaux 30


"""
c=0
if abs(pixel-t[0]) <= abs(pixel-t[1]):
return t[0]
elif abs(pixel-t[len(t)-1]) <= abs(pixel-t[len(t)-2]):
return t[len(t)-1]
l=1
r = len(t)-2
while l <= r:
m = (l + r) // 2
if evalue_la_pente(pixel,m,t) == 0:
return t[m]
elif evalue_la_pente(pixel,m,t) < 0:
l = m +1
else: # evalue_la_pente(pixel,m,t) > 0
r = m -1
return -1 # ne devrait jamais arriver

def pour_tester_best_color_lineaire(pixel, palette):


"""
Entree:
- pixel: float,
- palette: tableau de float,
par ordre strictement croissant.
Renvoie le flottant le plus proche
de pixel dans palette
Cette solution parcourt tout le tableau, ce qui n'est pas ideal
"""
c = palette[0]
for x in palette:
if abs(pixel-x) < abs(pixel-c):
c=x
return c

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# Test :
print(pour_tester_best_color_lineaire(.4,[.2,.31,.54,.57,.8])==.31)
print(best_color_dicho(.4,[.2,.31,.54,.57,.8])==.31)

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

# on pourrait aussi utiliser la distance au carre :


# def dist_carre(p1):
# return p1[0]**2+p1[1]**2

def tri_distance(t):
""" Entree: un tableau t de points (paire de nombres).

Chapitre 6 – Trier et chercher dans les tableaux 31


Renvoie une copie de t triee par distance croissante a zero
"""
return sorted(t, key=distance_a_zero)

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# Donc pas de risque que position_min vaille encore None.
liste_visites[i+1] = position_min
deja_visite[position_min] = True
return liste_visites

# 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 valeur_en_degre(deg, min, sec):


"""
Entrée: un angle décrit par des degrés, minutes et seconde d'arc.
Renvoie l'angle sous forme d'un nombre flottant degré
"""
return deg+min/60+sec/3600

def geodistance_deg(lat1, lon1, lat2, lon2):


""" Entree : latitude et longitude nord/ouest de deux points
Renvoie leur distance, en utilisant implicitement la fonction haversine
"""
lat1r = lat1*pi/180
lat2r = lat2*pi/180
lon1r = lon1*pi/180
lon2r = lon2*pi/180
return 2 * 6371 * asin( sqrt( sin((lat2r-lat1r)/2.)**2 +

Chapitre 6 – Trier et chercher dans les tableaux 32


sin((lon2r-lon1r)/2.)**2 * cos(lat1r) * cos(lat2r)))

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

tableau_de_coordonnees = [notre_dame, mont_saint_michel, tour_eiffel, madrid, saint_petersburg, seattle, le_cap, bueno


s_aires, canberra]

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


# Affichage des distances, à exécuter tel quel (rien à modifier) :
coordonnees_triees = tri_distance(tableau_de_coordonnees)
print(coordonnees_triees)

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

# En déduire la liste triée des noms de lieux à partir de coordonnees_triees


lieux_tries = [d_inv[x] for x in coordonnees_triees]

# Test :
print(lieux_tries[0]=='notre_dame' and lieux_tries[2]=='mont_saint_michel')

Chapitre 6 – Trier et chercher dans les tableaux 33


32
• L’accès dans un dictionnaire est encore plus rapide que la recherche dichotomique en moyenne
(le pire cas y est linéaire, donc inefficace, mais est assez improbable sur une entrée « courante »).

• Le dictionnaire permet d’insérer facilement de nouvelles valeurs ou d’en supprimer. On ne peut


pas efficacement supprimer ou insérer de nouvelles valeurs dans un tableau trié.

• 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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


d’appels à log 2 (2 ∗ (𝑛 − 1)) + log 2 (𝑛/2), ce qui fait à peu près 2log 2 𝑛. Le nombre d’appel est donc
logarithmique.
Ce n’était pas demandé, mais on donne ci-dessous des codes comparant la version ci-dessus avec la
version optimisée.
#Pour tester, on peut evaluer la fonction comme suit:
def arbitre(i):
nombre_cache = 1000
if i < nombre_cache:
return 'nombre inferieur a n'
elif i == nombre_cache:
return 'nombre egal a n'
else:
return 'nombre superieur a n'

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

Chapitre 6 – Trier et chercher dans les tableaux 34


def rech_amelioree():
i=1
nb_iter = 0
while arbitre(i) == 'nombre inferieur a n':
i = i*2
nb_iter = nb_iter+1
# dichotomie:
idx_left = i//2+1
idx_right = i+1
while idx_left <= idx_right:
nb_iter = nb_iter+1
idx_mid = (idx_left + idx_right) // 2
if arbitre(idx_mid) == 'nombre egal a n':
print(f"nombre d'appels a l'arbitre; version amelioree: {nb_iter}")
return idx_mid
elif arbitre(idx_mid) == 'nombre inferieur a n':
idx_left = idx_mid +1
else: # arbitre(idx_mid) indique idx_mid > val
idx_right = idx_mid -1
return -1 # ne devrait jamais arriver

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def v(m,g,co,t):
""" Entrée:
- poids m (en kg)
- durée t (en s)
- paramètres g,co
Renvoie la vitesse (en m/s)
atteinte par le parachutiste après t secondes de chute libre.
"""
vf = sqrt(2*m*g/co)
return vf * tanh(g*t/vf)

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

Chapitre 6 – Trier et chercher dans les tableaux 35


plt.show()

# bisection(f,1,200,.001) avec la fonction de l'activite


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

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


if b<0:
return 'g',b+1
elif a >= len(t):
return 'd',a
else:
return 's',b+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)

Chapitre 6 – Trier et chercher dans les tableaux 36


g=0
d = len(t)-1
i = len(t)-1
while g < d:
res[i] = s[g]
res[i-1] = s[d]
i = i-2
g = g+1
d = d-1
if g==d:
assert(i==0)
res[0] = s[g]
return res

# Une autre solution, non demandée dans l'énoncé.


# Avantage: évite de définir 3 variables g,d,i: on n'a plus que i.
# Inconvénient (discutable): il faut plus de calcul sur les indices, c'est pourquoi on a privilégié la solution précédente.

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 :

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


print(montagne_russe_par_la_fin([2,1,5,3,8,6])==[5, 3, 6, 2, 8, 1])
print(montagne_russe_par_la_fin_bis([2,1,5,3,8,6])==[5, 3, 6, 2, 8, 1])

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

Chapitre 6 – Trier et chercher dans les tableaux 37


print(s[i_min])
while i_min > 0:
i_min = i_min - 1
i_max = i_max + 1
print(s[i_max])
print(s[i_min])

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

[(x[1],x[2]) for x in sorted([(m[i][j],i,j) for i in range(len(m)) for j in range(len(m[0]))])]


#[(1, 1), (0, 0), (1, 0), (0, 1)]

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

• Cas où V1 < E1:

– si V2 != E1 alors (V3,E1) et il suffit de comparer V1,E2 ;

– si V2=E1 il suffit de comparer V1,E2.

b. Non. On ne cherchera pas à le prouver.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


40 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

# 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).

Chapitre 6 – Trier et chercher dans les tableaux 38


d.
def argmin(t):
""" renvoie l'indice du (premier) minimum du tableau t.
Complexite: O(n)
"""
return min(enumerate(t),key=f)[0]

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Exercices Titan
43 a.
def mystere(a,x):
lo = 0
hi = len(a)
while lo < hi:
mid = (lo + hi) // 2
if key(a[mid]) < x:
lo = mid + 1
else:
hi = mid
return hi

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

Chapitre 6 – Trier et chercher dans les tableaux 39


first = ++it; # avance la ref d'une case
count -= step + 1;
}
else
count = step;
}
return first
"""

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)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


print(position_moyenne(20.,0,4,1.,40.)==1)
print(position_moyenne(20.,0,8,0.,40.)==4)
print(position_moyenne(20.,1900,1908,0.,40.)==1904)
print(position_moyenne(85.,1900,1920,80.,90.)==1910)

def recherche_interpolation(t, x):


""" entrée: tableau t trié par ordre croissant, valeur x
résultat: indice i tel que t[i] == x, ou -1 si x not in t.
"""
i_min = 0
i_max = len(t)-1
nb_etapes = 0
while (i_max >= i_min and x >= t[i_min] and x <= t[i_max]):
nb_etapes += 1
mid = position_moyenne(x,i_min,i_max,t[i_min], t[i_max])
#print(f"recherche de {x}, étape {nb_etapes} : i_min={i_min}\ti_max={i_max}\tmid={mid}")
if t[mid] == x:
print(f"Nombre d'étapes pour la recherche par interpolation : {nb_etapes}")
return mid
elif t[mid] < x:
i_min = mid+1
else:
i_max = mid-1
print(f"Nombre d'étapes pour la recherche par interpolation : {nb_etapes}")
return -1 # instruction atteinte ssi x n'est pas dans t

b.

Chapitre 6 – Trier et chercher dans les tableaux 40


def recherche_dichotomique(t, x):
""" entrée: tableau t trié par ordre croissant, valeur x
résultat: indice i tel que t[i] == x, ou -1 si x not in t.
"""
i_min = 0
i_max = len(t)-1
nb_etapes = 0
while (i_max >= i_min):
nb_etapes += 1
mid = (i_min + i_max)//2
if t[mid] == x:
print(nb_etapes)
return mid
elif t[mid] < x:
i_min = mid+1
else:
i_max = mid-1
print(f"Nombre d'étapes pour la recherche dicho : {nb_etapes}")
return -1 # instruction atteinte ssi x n'est pas dans t

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

c. i_max - i_min est un variant de boucle.


Petit point culturel : on peut aussi utiliser l’interpolation afin d’accélérer la méthode dichotomique
pour chercher la racine d’une fonction. Cette technique un peu plus compliquée s’appelle la « méthode
de la fausse position » (regula falsi), et est connue depuis l’antiquité (babyloniens, égyptiens, grecs,
chinois…).
Autre point culturel : on peut prouver (mais ce n’est pas facile, et pas du tout au programme) que la
recherche par interpolation a une complexité en moyenne de l’ordre de loglog𝑛 itérations sur une

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


entrée uniformément distribuée de taille n. Encore plus efficace, donc, que la recherche dichotomique.
Pour aller plus loin : sauriez-vous construire une entrée (pas une entrée aléatoire de distribution
uniforme),sur laquelle la recherche dichotomique est beaucoup plus efficace que la recherche par
interpolation ?
Réponse à la question `pour aller plus loin'' : Pour construire un cas défavorable à l’interpolation, on peut chercher à
avoir une croissance beaucoup plus forte à droite de la valeur cherchée pour que `mid augmente seulement de 1
chaque itération. On peut ainsi chercher 6 dans un tableau de n-2 5 suivi de 6, suivi d’un élément
immense.
# un exemple défavorable à la recherche par interpolation:
recherche_dichotomique([5]*998+[6,10**9],6) # 9
recherche_interpolation([5]*998+[6,10**9],6) # 999

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)

Chapitre 6 – Trier et chercher dans les tableaux 41


if vx < 0:
x_min = x_mid+1
elif vx > 0:
x_max = x_mid-1
else: # vx == 0
x_min = x_mid
x_max = x_mid
if vy < 0:
y_min = y_mid+1
elif vy > 0:
y_max = y_mid-1
else: # vy == 0
y_min = y_mid
y_max = y_mid
return x_min, y_max

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


On aurait pu généraliser cet exercice en 3 dimensions ou plus.

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

print(f"L'angle de tir est de {bisection(d,0,45,3500)} degrés")

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.

Chapitre 6 – Trier et chercher dans les tableaux 42


Preuve : Soit A(t) un algorithme qui renvoie pour tout tableau t la valeur minimale dans t. Soit t un
tableau de taille n et val la valeur minimale dans t. Tant qu’il reste un indice j tel que A(t) n’a pas évalué
t[j], A ne peut pas renvoyer de valeur x sans prendre le risque de se tromper. En effet A ne peut pas
distinguer le tableau t du même tableau dans lequel on remplacerait t[j] par x-1 (ou plus exactement une
valeur plus petite que x`car on n’a pas supposé que `t était un tableau de nombres). Donc A doit bien évaluer
les n cases du tableau t avant de renvoyer un résultat.
b. On pourrait recopier toute la preuve en remplaçant minimal par maximal, x-1 par x+1… Mais c’est
inélégant. Il est beaucoup mieux de montrer que le problème se ramène au problème précédent
(l’énoncé nous y invite implicitement).
Version courte : le problème de calculer (en utilisant des comparaisons) le maximum d’un tableau et le
problème de calculer son minimum sont équivalents : il suffit de changer le sens des comparaisons (>
devient <, etc.). Donc la complexité des 2 problèmes est la même.
Version longue, un peu plus précise, illustrant la notion -hors programme- de réduction : Si on a un
algorithme A capable de calculer le maximum de tout tableau t à n éléments en f(n) opérations, alors on
en déduit un algorithme B capable de calculer le maximum de tout tableau en f(n) opérations.
L’algorithme B exécute l’algorithme A en inversant le résultat de toutes les comparaisons, ce qui
signifie qu’on inverse l’ordre. Le maximum pour l’ordre initial devient un minimum pour l’ordre
inversé, que l’on peut donc calculer avec B.
Point culturel : attention au sens de la « réduction » : pour montrer une borne inférieure sur B il faut
bien montrer que B est au moins aussi dur que A, donc qu’on peut transformer tous les problèmes A en
problème B, comme ici. À l’inverse, si on avait voulu montrer une borne supérieure sur B, il aurait fallu
montrer que B est au plus aussi dur que A, donc qu’on peut transformer tous les problèmes B en
problème A.
On notera qu’ici, on a une réduction dans les deux sens : le problème de calculer un minimum et celui
de calculer un maximum sont équivalents dans le sens où ils ont exactement la même complexité.
Cet exercice vous fait découvrir sur un exemple la notion de « réduction » qui consiste à ramener un

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


problème à un autre problème. Les réductions sont un outil-clé pour prouver la complexité de
problèmes, mais ne sont pas au programme du baccalauréat, pas plus que la théorie de la complexité
de toute façon.

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

Chapitre 6 – Trier et chercher dans les tableaux 43


Représentation des mouvements et positions : - un mouvement de gauche à droite des personnes 1 et 5
(c’est-à-dire des personnes qui individuellement mettraient 1min et 5min) sera représenté par le triplet
(1,5,'gd'). Un mouvement de droite à gauche par un triplet (1,5,'dg'). Quand la personne i traverse seule,
on répète i, ex: (5,5,'gd'). - un dictionnaire position va permettre d’associer à chaque personne sa position
`g' ou `d' (sur la rive gauche ou la rive droite) tout au long de l’algorithme.
def valide(durees,mvts):
"""
Entree:
- durees est le tableau des personnes=durees :
durees[i] est la duree mise pour traverser par personne i quand elle est seule.
- mvt est un tableau de triplets (i,j,s) avec
i et j dans duree et s qui vaut 'dg' ou 'gd'
=========
Calcul:
position[x] indique la rive sur laquelle se trouve x.
au depart tous sont rive gauche (g).
a l'arrivee tous doivent etre sur la rive droite
=========
Renvoie:
- True ssi l'enchainement des mouvements est faisable avec une seule torche, et que toutes les personnes se retrouvent riv
e droite a la fin.
- False sinon
"""
position = {x: 'g' for x in durees}
for t in mvts:
x1, x2, mv = t
if mv[0] != position[x1] or mv[0] != position[x2]:
return False
position[x1] = mv[1]
position[x2] = mv[1]

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


for p in position.values():
if p == 'g':
return False
return True

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

Chapitre 6 – Trier et chercher dans les tableaux 44


x1, x2, mv = t
duree_totale += max(x1,x2)
return duree_totale

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

• tous les voyageurs avec ti > 2*t1 - t0 sont considérés lents ;

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


• tant qu’il reste deux voyageurs lents: \{ On commence par envoyer t0 à droite par l’opération: (0
et 1 traversent à droite) (0 ramène la torche à gauche) #1 attend donc à droite, prêt à ramener la
torche Puis: (les deux voyageurs les plus lents à gauche traversent ensembles) (1 ramène la
torche à gauche) } ;

• 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) } ;

• enfin : (0 et l’éventuel voyageur restant traversent à droite).

Écrire un programme qui prend en entrée un tableau de durées t et :


• affiche dans l’ordre les paires de voyageurs qui traversent, en indiquant dans quel sens ;

• renvoie la durée totale.


def solution(durees):
t = sorted(durees)
mvts = []
if len(t)==1:
mvts = [(t[0],t[0],'gd')]
else:
i = len(t)-1
while t[i-1] > 2*t[1]-t[0]: #en particulier i>=3
mvts.append((t[0],t[1],'gd'))
mvts.append((t[0],t[0],'dg'))
mvts.append((t[i],t[i-1],'gd'))
mvts.append((t[1],t[1],'dg'))

Chapitre 6 – Trier et chercher dans les tableaux 45


i = i-2
while i > 1:
mvts.append((t[0],t[i],'gd'))
mvts.append((t[0],t[0],'dg'))
i = i-1
mvts.append((t[0],t[1],'gd'))
return mvts

# Test :
print(solution([1,2,5,8]))
print(duree_totale(solution([1,2,5,8]))==15)

Point culturel : anecdotes historiques autour de cette énigme.


Ce problème s’inscrit dans la tradition des problèmes de traversée de rivière, dont on trouve les
premières traces dans un manuscript attribué au clerc Alcuin d’York, un des principaux érudits et
enseignants à la cour de Charlemagne vers 780 puis abbé de Marmoutiers près de Tours.
Des anecdotes amusantes circulent à propos de ce problème (dans sa version simple avec des durées
de 1,2,5,10 minutes et un temps total de 17minutes pour la torche) : il se raconte que Microsoft
utilisait la question dans des entretiens d’embauche autour des années 2000, le candidat ayant 5
minutes pour trouver la solution. Une variante humoristique de l’anecdote raconte qu’un candidat
aurait trouvé en 3 minutes, qu’un autre aurait résolu le problème en 37 minutes en écrivant un
programme testant toutes les combinaisons, tandis qu’un groupe de 50 ingénieurs d’une grande
compagnie de télécom américaine n’aurait même pas trouvé la solution.
La solution de la version plus générale avec n durées au lieu de 4 n’a été prouvée formellement
qu’assez récemment, même si les fans d’énigmes avaient l’intuition de la solution depuis plus
longtemps. Les intuitions amenant à cette solution restent sans doute compréhensible pour un lycéen
curieux. Une discussion plus détaillée peut être trouvée dans l’ouvrage ``Mathématiques pour le
plaisir'' de J.P Delahaye, et accessible en ligne: https://www.pourlascience.fr/sd/logique/la-traversee-
du-pont-1276.php On trouve en ligne un article de recherche en anglais discutant la preuve, mais ce

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


n’est pas une lecture très pertinente au niveau du lycée :
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.6.8855&rep=rep1&type=pdf).

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)

Chapitre 6 – Trier et chercher dans les tableaux 46


def vache(portail):
"""
Entrée:
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:
les mouvements que fait la vache si elle suit l'algorithme jusqu'à trouver le portail.

A vous d'inférer l'algorithme à partir des tests ci-dessous.


"""
mvts = []
if portail == 0:
mvts = [(O,O)]
else:
direction = 1
dist = 1
while 0 > direction*portail or direction*portail > dist:
# la boucle s'arrête si portail est à distance moins de dist dans la direction.
mvts.append((0,dist*direction))
mvts.append((dist*direction,0))
direction = -1*direction
dist = 2*dist
mvts.append((0,portail))
return mvts

# 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):

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


"""
Entrée:
un tableau de paires (coordonnée de départ, coordonnée d'arrivée)
représentant les mouvements que fait la vache.
Renvoie:
la longueur totale du trajet.
"""
total = 0
for trajet in mouvements:
total += max(trajet)-min(trajet)
return total

# Test :
print(longueur([(0,1),(1,0),(0,-1)])==3)
print(longueur([(0,1),(1,0),(0,-2),(-2,0),(0,3)])==9)

print("trajet de la vache :", vache(4))


print("longueur du trajet :",longueur(vache(65)))

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

Chapitre 6 – Trier et chercher dans les tableaux 47


parcourir une grande distance dans l’autre direction avant de revenir et atteindre le portail. C’est le cas
où 𝑑 = 2𝑘−2 + 1. Donc 9𝑑 = 𝑑 + 8(2𝑘−2 + 1) est plus grand que la distance parcourue 2𝑘+1 − 1 +
𝑑.
Remarque : le problème existe aussi dans une version où la vache se trouver perdue au croisement
entre 𝑘 chemins et doit rentrer à l’étable, qui se trouve au bout d’un des chemins.
Dans ce cas, la borne est (2𝑘 − 1) × 𝑑 quand on connaît la distance 𝑑, et est plus compliquée lorsque
l’on ne connaît pas la distance (la longueur des recherches sur les différentes branches forme une sorte
de spirale).
Voir : https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.22.270&rep=rep1&type=pdf

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re

Chapitre 6 – Trier et chercher dans les tableaux 48


Chapitre 7 – Données en tables

• Notes sur le chapitre


Caractères accentués : Dans les exemples du Manuel papier, on a choisi de n’utiliser aucun caractère
accentué dans les valeurs de colonne qui sont des chaînes de caractères. Dans tous les corrigés
électroniques (activité, TP, exercices non donnés dans le manuel papier), on utilisera des caractères
accentués comme dans les situations réelles. Dans les noms de colonne en revanche on n’utilisera
jamais de caractères accentués (principalement pour des raisons de tradition dans les bases de
données.)
Boucles : Dans toutes les sections de la version papier du manuel, y compris les corrigés des exercices
en fin de manuel, on a utilisé
for i in range(len(t)):
... t[i] ...

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Casse des noms de colonne : Les exemples et corrigés utiliseront en général des majuscules pour des
raisons de lisibilité (et de tradition en vue des bases de données abordées en Terminale), mais pas
toujours. Les récents sujets du baccalauréat utilisent les deux, voire les combinent.

• Notes sur l’activité


Si le cours présente tous les concepts, il ne présente pas toutes les manipulations des représentations
des tables, en particulier la création et l’affichage. L’activité a été conçue pour le faire, sur des
exemples les plus simples possibles. Ainsi, faire faire l’activité avant ou subséquemment au cours peut
être une bonne mise en pratique des concepts principaux du cours.
Fonction d’affichage : On introduit dans l’activité (dans le fichier à télécharger) une fonction
d’affichage très sommaire mais très simple. Elle a l’avantage d’expliciter et d’insister clairement sur la
structure de tableau de dictionnaires ; de plus elle ne dépend pas du schéma de la table ; enfin elle
n’utilise pas de matériel Python un peu plus fin comme des chaînes formatées ou des alignements par
exemple.
On a choisi de l’utiliser dans tous les corrigés de ce chapitre, pour se concentrer sur la structure de
table et sa représentation en Python par un tableau de dictionnaires. Bien sûr ce choix n’influe pas sur
le matériel présenté dans ce chapitre, et toute autre fonction d’affichage plus sophistiquée d’un tableau
de dictionnaires conviendrait.

Chapitre 7 – Données en tables 1


• Notes sur le TP
L’esprit adopté dans le TP est que les élèves sont devenus relativement autonomes sur le matériel de
ce chapitre, et on propose un TP plutôt proche de la vie réelle, mais sans introduire de nouveau
matériel (tout a été vu dans les exercices), sauf l’insertion d’un nouvel achat dans un fichier.
Il n’est pas demandé explicitement dans l’énoncé d’écrire l’expression d’interrogation pour chaque
fonction, mais dans le deuxième exercice c’est implicite dans la demande de matérialiser les résultats
intermédiaires. C’est probablement utile de le faire aussi dans les trois exercices suivants, cela prend
très peu de temps et donne probablement du recul.
Les fichiers CSV fournis en téléchargement utilisant des noms de colonne en minuscule, il est
nécessaire de s’y conformer. Du coup dans les expressions d’interrogation on écrira les tables elles-
même en minuscules, par souci d’homogénéité.
On pourra bien sûr ajouter des ligne aux fichiers CSV fournis. Toutefois dans ce contexte il est utile de
pouvoir continuer à visualiser les contenus, donc sans trop de lignes, pour pouvoir vérifier
efficacement la validité des codes écrits.
Le corrigé du TP est dans le dossier TP-vente, sous forme d’un notebook Jupyter et de fichiers Python.
tp-vente.py contient l’essentiel du code, et est importé par tp-vente-text.py et tp-vente-graph.py, qui sont les
deux fichiers à exécuter pour utiliser respectivement l’interface textuelle et l’interface graphique.

• Notes sur les exercices


Comme pour les autres chapitres, des notebooks Jupyter et des fichiers Python rassemblant l’ensemble
des corrigés de chaque catégorie d’exercices sont fournis. De plus, et contrairement aux autres
chapitres, des fichiers Python individuels par exercice sont fournis dans le dossier c07-exercices-corrige. Il
en résulte une duplication de certains fichiers (notamment les fichiers CSV) entre ce dossier et le
dossier du chapitre, car ces fichiers doivent être dans le même dossier que le code qui les référence.
Accès à la représentation Python des tables : Dans les corrigés de la section Newbie fournis dans le

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Manuel papier, on a choisi, de manière similaire au Cours, d’afficher la représentation Python d’une
table en explicitant l’accès à chaque dictionnaire élément du tableau par son indice, puis à chaque
valeur du dictionnaire par sa clé, afin d’insister sur la structure de tableau de dictionnaires. Cette
approche est à but didactique, et vraiment très sommaire, et on ne l’utilisera plus ailleurs.
Progression des exercices : À l’intérieur des sections Newbie et Initié respectivement, les exercices
sont à peu près par difficulté croissante, et combinent progressivement du matériel abordé dans les
exercices précédents.
Commentaires dans les corrigés : On a commenté assez largement dans les premiers exercices, puis
moins, et sans répéter des commentaires identiques. On a essayé de renvoyer aux corrigés antérieurs.
Parfois un commentaire est fait dans un certain exercice, même s’il aurait pu apparaître déjà dans un
exercice antérieur (cette apparition décalée n’est pas signifiante).
Définition des opérateurs : Les définitions choisies n’imposent pas d’ordre sur le résultat. En
conséquence, la valeur d’une expression d’interrogation, par exemple une projection, n’est bien sûr
pas unique au sens strict, mais elle l’est à une permutation près de l’ordre des lignes. Dans tout ce
chapitre c’est bien dans ce sens qu’on entend l’unicité de la valeur d’une expression d’interrogation.
(Le programme officiel imposant la définition d’une table comme étant une liste, la définition que
nous avons choisie dans ce chapitre nous a paru présenter le bon équilibre entre les directives du
programme officiel, et le concept de table dans les bases de données, qui est un ensemble dans la
théorie, et un multi-ensemble dans les systèmes.) Cet « inconvénient » mineur (unicité à l’ordre des
lignes près de la valeur d’une expression d’interrogation) disparaîtra en Terminale avec la définition
canonique d’une table comme un ensemble.

Chapitre 7 – Données en tables 2


Remarque : La représentation en Python d’une table est unique. Toutefois comme la table valeur
d’une expression n’est donc unique qu’à l’ordre des lignes près, lorsque l’on parlera de « la
représentation en Python d’une expression d’interrogation », cette représentation ne sera en général
pas unique (sauf si elle est vide ou ne contient qu’une ligne). On continuera toutefois à entendre cette
unicité comme à l’ordre des lignes près.
Vocabulaire : On utilisera de manière équivalente : « expression d’interrogation », « expression »
(comme indiqué dans le Cours), « interrogation » (et exceptionnellement « recherche »).
Propriétés et approfondissements : On intégrera parfois dans les corrigés de certains exercices des
commentaires complémentaires signalant ou fournissant une généralisation, une propriété, ou un
approfondissement du Cours, pour la plupart hors programme.

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


électroniques on codera l’affichage comme décrit plus haut, avec le même code extrêmement basique.
Une version de cette fonction est fournie dans le fichier affichage.py qui est importé dans à peu près tous
les corrigés à partir de la section Initié.

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 ]

Activité : Un module d’interrogation de tables


Représentation et création des tables dans le programme Python

Concernant l’utilisation de fonctions : On choisit pour des raisons de lisibilité et d’organisation du


code Python de coder chaque question et sous-question par une fonction. Un codage sans aucune
fonction peut aussi être choisi : le code serait plus court et plus simple, mais cette approche ne
conviendrait plus pour de plus gros programmes. Toutefois un tel code sans fonction est tout à fait
adapté ici s’il est utile à l’approche de l’enseignant.e. En effet, les fonctions seront progressivement
introduites à partir des exercices Initiés.
La quasi-totalité du corrigé de la première question est fournie dans le fichier clients.py à télécharger
(indiqué par le pictogramme dans la version papier). Le but de ce fichier est aussi d’introduire par
l’exemple l’organisation en fonctions, la méthode append pour construire une table résultat (par
ailleurs introduite et motivée dans le Cours), et de fournir la fonction d’affichage.

Chapitre 7 – Données en tables 3


def creer_les_clients():
table = []
table.append({'IDC': 27, 'NOM': 'Rita', 'AGE': 16, 'ACTIVITE': 'kitesurf'})
table.append({'IDC': 19, 'NOM': 'Riton', 'AGE': 16, 'ACTIVITE': 'tango'})
table.append({'IDC': 11, 'NOM': 'Jules', 'AGE': 17, 'ACTIVITE': 'theatre'})
table.append({'IDC': 18, 'NOM': 'Dédé', 'AGE': 17, 'ACTIVITE': 'kitesurf'})
table.append({'IDC': 12, 'NOM': 'Jim', 'AGE': 16, 'ACTIVITE': 'cinema'})
table.append({'IDC': 17, 'NOM': 'Lulu', 'AGE': 17, 'ACTIVITE': 'MOMA'})
return table

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)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Calcul intuitif à la main sur l’exemple

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

Sélection d’une table selon une condition


# La condition est : VILLE = Cayeux

v = 'Cayeux'

def selection_sur_ville(table, valeur):


# table doit posséder une colonne VILLE
u = []
for ligne in table:
if ligne['VILLE'] == valeur:
u.append(ligne)
return u

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)

Chapitre 7 – Données en tables 4


Jointure de deux tables sur une colonne
def jointure_sur_activite(table1, table2):
# table1 et table2 doivent toutes deux posséder une colonne ACTIVITE
u = []
for ligne1 in table1:
for ligne2 in table2:
if ligne1['ACTIVITE'] == ligne2['ACTIVITE']:
u.append({'IDC': ligne1['IDC'], 'NOM': ligne1['NOM'],
'AGE': ligne1['AGE'],
'ACTIVITE': ligne1['ACTIVITE'],
'IDS': ligne2['IDS'], 'VILLE': ligne2['VILLE']})
return u

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)

Projection d’une table sur certaines de ses colonnes


def projection_sur_nom_ids(table):
# table doit posséder les colonnes NOM et IDS
u = []
for ligne in table:
u.append({'NOM': ligne['NOM'], 'IDS': ligne['IDS']})
return u

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 :')

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


afficher_table(table_resultat_final)

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

TP : Un module de vente de produits


Importation des tables (et affichage)
1.
from csv import DictReader

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'])

Chapitre 7 – Données en tables 5


u.append(dict)
return u

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

afficher(les_produits, 'Table des produits :')

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


afficher(les_clients, 'Table des clients :')
afficher(les_achats, 'Table des achats :')

Menu dirigeant (interrogations)


1.
# proj[idp,idc](sel[expedie=oui](achat))
def dirigeant_interro1(table):
# table achat
u1 = []
for ligne in table:
if ligne['expedie'] == 'oui':
u1.append(ligne)
u2 = []
for ligne in u1:
u2.append({'idp': ligne['idp'], 'idc': ligne['idc']})
return u2

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'],

Chapitre 7 – Données en tables 6


'idc': ligne1['idc'],
'expedie': ligne1['expedie'],
'nom': ligne2['nom'],
'age': ligne2['age']})
u2 = []
for ligne in u1:
u2.append({'idp': ligne['idp'], 'age': ligne['age']})
return u2

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


dirigeant_interro3(les_achats, les_clients)

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.

# fonction pour le tri par prix


def cle_prix(ligne):
return ligne['prix']

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

Chapitre 7 – Données en tables 7


for ligne2 in u2:
if ligne1['idc'] == ligne2['idc']:
u3.append({'idc': ligne1['idc'],
'nom': ligne1['nom'],
'age': ligne1['age'],
'prix': ligne2['prix']})
u4 = []
for ligne in u3:
u4.append({'nom': ligne['nom'], 'prix': ligne['prix']})
u4.sort(key=cle_prix, reverse=True)
return [{'nom': u4[0]['nom']}]

dirigeant_interro4(les_achats, les_clients, les_produits)

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 = []

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


for ligne in u1:
u2.append({'idp': ligne['idp']})
u3 = []
for ligne1 in table3:
for ligne2 in u2:
if ligne1['idp'] == ligne2['idp']:
u3.append({'idp': ligne1['idp'],
'nom': ligne1['nom'],
'prix': ligne1['prix']})
u4 = []
for ligne in u3:
u4.append({'nom': ligne['nom']})
return u4

dirigeant_interro5(les_achats, les_clients, les_produits, 'Rita')

Menu dirigeant (vérifications de tables)


1.
def dirigeant_verif1(table):
# table produit
for ligne in table:
if not ligne['prix'] > 0:
return False
return True

dirigeant_verif1(les_produits)

2.

Chapitre 7 – Données en tables 8


def dirigeant_verif2(table):
# table produit
u = []
for ligne in table:
u.append({'idp': ligne['idp']})
# On utilise l'algorithme du Cours 3.2. D'autres algorithmes
# meilleurs sont présentés dans les exercices.
for i in range(len(table)):
for j in range(len(table)):
if table[i]['idp'] == table[j]['idp'] and i != j:
return False
return True

dirigeant_verif2(les_produits)

Menu client (interrogations)


1.
# proj[age,idc](sel[nom=n](client))
def client_interro1(table, n):
# table client
# n = nom de client, par exemple'Rita'
u = []
for ligne in table:
if ligne['nom'] == n:
u.append({'age': ligne['age'], 'idc': ligne['idc']})
return u

client_interro1(les_clients, 'Rita')

2.
# proj[nom,prix](produit)
def client_interro2(table):

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


# table produit
u = []
for ligne in table:
u.append({'nom': ligne['nom'], 'prix': ligne['prix']})
return u

client_interro2(les_produits)

Menu client (insertion)


def insertion_achat(ic, ip):
# On construit la chaîne qui est la représentation au format
# CSV de la ligne à insérer dans la table. str traduit un
# entier en chaîne. Il faut terminer la chaîne par un "retour
# charriot" pour assurer que la prochaine insertion sera bien
# sur une nouvelle ligne pour respecter le format CSV.
ligne = str(ip)+','+str(ic)+',non\n'
# Le code suivant fait l'hypothèse que le fichier achats.csv
# contient comme dernier caractère le caractère "retour charriot"
# (touche "entrée" à droite du clavier. Accédez au fichier avec
# un éditeur de texte si besoin avant le premier appel à cette
# fonction.
with open('achats.csv', 'a') as f:
f.write(ligne)
return importer_achats()

insertion_achat(25, 92)

Application interactive
1.

Chapitre 7 – Données en tables 9


def menu():
# On pré-importe pour ne pas être obligé de commencer par cela :
les_produits = importer_produits()
les_clients = importer_clients()
les_achats = importer_achats()
while True:
print("""\
-------------------------------------------------------------------------------
Bienvenue chez LCER

Entrez un entier :

0 : Sortir du menu et du programme

Importation des tables (et affichage) :


1 : Réimporter les tables (1.1)
2 : Afficher les tables (1.2)

Menu dirigeant.e (interrogations) :


3 : Les idp, idc des achats déjà expédiés (2.1)
4 : Les idp, age des achats et clients (2.2)
5 : Les idp achetés par des clients non majeurs (2.3)
6 : Le client ayant acheté le produit le plus cher (2.4)
7 : Le nom des produits du client donné au clavier (2.5)

Menu dirigeant.e (vérification de tables) :


8 : Le test de cohérence prix > 0 pour la table produit (3.1)
9 : Le test de clé primaire sur idp de la table produit (3.2)

Menu client.e (interrogations) :


10 : Les age et idc du client donné par son nom au clavier (4.1)
11 : Les nom et prix de tous les produits (4.2)

Menu client.e (insertion) :

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


12 : Insertion d'un achat (5)
-------------------------------------------------------------------------------
""")
n = int(input('Entrez votre choix : '))
if n == 0:
return
elif n == 1:
les_produits = importer_produits()
les_clients = importer_clients()
les_achats = importer_achats()
elif n == 2:
afficher(les_produits,
'Représentation Python de la table PRODUIT :')
afficher(les_clients,
'Représentation Python de la table CLIENT :')
afficher(les_achats,
'Représentation Python de la table ACHAT :')
elif n == 3:
t = dirigeant_interro1(les_achats)
afficher(t, 'Les idp, idc des achats déjà expédiés :')
elif n == 4:
t = dirigeant_interro2(les_achats, les_clients)
afficher(t, 'Les idp, age des achats et clients :')
elif n == 5:
t = dirigeant_interro3(les_achats, les_clients)
afficher(t, 'Les idp achetés par des clients non majeurs :')
elif n == 6:
t = dirigeant_interro4(les_achats, les_clients, les_produits)
afficher(t, 'Le client ayant acheté le produit le plus cher :')

Chapitre 7 – Données en tables 10


elif n == 7:
client = input('Entrez un nom de client : ')
t = dirigeant_interro5(les_achats, les_clients, les_produits, client)
afficher(t, 'Le nom des produits du client donné au clavier :')
elif n == 8:
v = dirigeant_verif1(les_produits)
print('Le test de cohérence prix > 0 pour la table produit '
'est : ', v)
# Le fichier CSV contient un produit de prix 0. On le retire
# et on reteste.
print(les_produits.pop(4))
afficher(les_produits, 'Les produits sans le caramel :')
v = dirigeant_verif1(les_produits)
print('Le test de cohérence prix > 0 pour la table produit '
'est : ', v)
elif n == 9:
v = dirigeant_verif2(les_produits)
print('Le test de clé primaire sur idp de la table produit '
'est : ', v)
# On ajoute un doublons et on reteste.
les_produits.append(
{'idp': les_produits[2]['idp'], 'nom': 'chapeau', 'prix': 100})
afficher(les_produits, 'Les produits avec deux idp identiques :')
v = dirigeant_verif2(les_produits)
print('Le test de clé primaire sur idp de la table produit '
'est : ', v)
elif n == 10:
client = input('Entrez un nom de client : ')
t = client_interro1(les_clients, client)
afficher(t, 'Les age et idc du client donné par son nom au '
'clavier :')
elif n == 11:
t = client_interro2(les_produits)
afficher(t, 'Les nom et prix de tous les produits :')

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


elif n == 12:
# Pour se concentrer sur les tables on ne vérifiera pas
# que les valeurs entrées sont des entiers :
les_achats = insertion_achat(input('Entrez un idc : '),
input('Entrez un idp : '))
afficher(les_achats,
'Représentation Python de la table ACHAT après '
'insertion :')
else:
print('Choix non disponible')

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.

Chapitre 7 – Données en tables 11


2 a. On s’inspire du code de création de la table ABONNE dans le cours, qui est sans fonction.
Dans les cas où l’activité aurait été faite auparavant, on peut aussi s’inspirer du fichier clients.py à
télécharger dans la question 1 de l’Activité, en adaptant le code en retirant les fonctions, et qui en
outre introduit en particulier l’affichage de la représentation d’une table.
L’indication de ne pas utiliser la méthode items() dans l’affichage a pour but simplement de guider vers
l’utilisation d’uneboucle de parcours d’un tableau de dictionnaires très simple, comme par exemple
celle introduite dans l’Activité.
Remarquer enfin que dans ce corrigé on utilise des caractères accentués et des apostrophes dans les
valeurs de colonne dans les tables qui sont des chaînes de caractères.
les_livres = []
les_livres.append({'IDL': 162, 'TITRE': 'Fondation', 'AUTEUR': 'Asimov'})
les_livres.append({'IDL': 261, 'TITRE': 'Poèmes', 'AUTEUR': 'Bonnefoy'})
les_livres.append({'IDL': 179, 'TITRE': 'Kyoto', 'AUTEUR': 'Kawabata'})
les_livres.append({'IDL': 284, 'TITRE': 'Planches courbes', 'AUTEUR': 'Bonnefoy'})
les_livres.append({'IDL': 283, 'TITRE': 'Trilogie', 'AUTEUR': 'Mahfouz'})
# On rajoute un livre par rapport à l'exemple du cours, afin
# d'avoir deux lignes dans la table résultat de l'exercice 3 :
les_livres.append({'IDL': 180, 'TITRE': 'Pays de neige', 'AUTEUR': 'Kawabata'})

print('La représentation Python de la table LIVRE :')


for dict in les_livres:
print(dict)
print()

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


les_abonnes.append({'IDA': 33, 'NOM': 'Jules', 'AGE': 17, 'NBE': 20})

print('La représentation Python de la table ABONNE :')


for dict in les_abonnes:
print(dict)
print()

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

print('La représentation Python de la table EMPRUNT :')


for dict in les_emprunts:
print(dict)
print()

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.

Chapitre 7 – Données en tables 12


Remarquer que l’affichage de la représentation de la table LIVRE ne peut être évité lors de l’import,
mais après tout c’est utile de visualiser la table d’origine sur laquelle on va effectuer des manipulation
(ici le tri), afin de vérifier le résultat en comparant visuellement avec la table d’origine.
Dans les exercices 3 et 5 on importera seulement la représentation les_livres de la table LIVRE. Dans
l’exercice 4 on importera aussi la représentation de la table EMPRUNT comme suit :
from exo02 import les_livres, les_emprunts

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

print('table valeur sel[AUTEUR=Kawabata](LIVRE) :')


for i in range(len(table_resultat)):
print(table_resultat[i]['IDL'],
table_resultat[i]['TITRE'],

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


table_resultat[i]['AUTEUR'])

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']})

print('table valeur de join(LIVRE,EMPRUNT) :')


for i in range(len(table_resultat)):
print(table_resultat[i]['IDL'],
table_resultat[i]['TITRE'],
table_resultat[i]['AUTEUR'],
table_resultat[i]['IDA'],
table_resultat[i]['ANNEE'])

Chapitre 7 – Données en tables 13


5 a.
proj[TITRE](sel[AUTEUR=Kawabata](LIVRE))

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']})

print('table valeur de ' +


'proj[TITRE](sel[AUTEUR=Kawabata](LIVRE)) :')
for i in range(len(table_resultat)):
print(table_resultat[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']})

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


def cle_de_tri(dict):
return dict['AUTEUR']

u.sort(key=cle_de_tri)

# Bien sûr on affiche ensuite cette table, ne serait-ce que pour vérifier :

print('La représentation Python de la table des auteurs',


'et de leurs titres, triée par auteur :')
for dict in u:
print(dict)
print()

Exercices Initié

7 La réponse est Riton.


Cet exercice est une application de la section 2.5 du cours, sur le triptyque :
• question en français dont la réponse est une table ;

• expression d’interrogation dont la valeur est la table répondant à cette question ;

• code Python calculant la représentation de la table valeur de cette expression d’interrogation.

Chapitre 7 – Données en tables 14


Remarque : Les contenus des représentations des tables dans cet exercice ne sont pas totalement
réalistes puisque la durée d’un voyage vers Florence n’est pas fournie.
On peut étendre cet exercice dans l’esprit de l’exercice 8 en demandant la question (en français) dont
ce code Python calcule la table réponse, ainsi qu’une expression d’interrogation correspondant (il peut
y en avoir en général plusieurs). Cette question est donc : Donner les noms des personnes dont le
voyage dure au moins 2 heures. Une expression d’interrogation la calculant est (on suppose que les
tables s’appellent respectivement VOYAGE et DUREE) :
proj[NOM](sel[NBHEURES>=2](join(VOYAGE,DUREE)))

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

8 a. Cet exercice porte sur le matériel de la section 2.1 du Cours.


Remarque 1 : L’énoncé contient un abus de langage mineur : une expression d’interrogation ne «
calcule » pas une table, elle « vaut » une table, elle « a pour valeur » une table.
Remarque 2 : Cet exercice est indiqué comme hors ligne mais la question a.2 est du code Python et
peut donc se faire en ligne.
La réponse à la première partie de la question est : «Donner les emprunts de l’abonné d’identifiant
27».
Pour la deuxième partie de la question, on suit exceptionnellement l’approche de codage Python suivie
dans le code fourni dans la question b. Plus précisément on code une boucle par itération au moyen de
l’indice du tableau et non par itérateur direct (comme dans la fonction d’affichage par exemple). En
outre on ne place pas ce code dans une fonction.
from affichage import afficher
from emprunts import les_emprunts

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


afficher(les_emprunts,
'Représentation Python de la table EMPRUNT :')

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 :")

b. Expression d’interrogation : sel[AGE>15](ABONNE).


Question : « Donner les abonnés de plus de 15 ans ».
Code correspondant :
# Le fichier abonnes.py definit la variable Python les_abonnes
# qui contient la représentation Python de la table ABONNE :
from abonnes import les_abonnes
afficher(les_abonnes,
'Représentation Python de la table ABONNE :')

table_resultat = []
for i in range(len(les_abonnes)):
if les_abonnes[i]['AGE'] > 15:
table_resultat.append(les_abonnes[i])
afficher(table_resultat,

Chapitre 7 – Données en tables 15


'Représentation Python de la table réponse à la question : \
Donner les abonnés de plus de 15 ans :')

9 a. Cet exercice porte sur le matériel de la section 1.2 du Cours.


Le corrigé est le fichier livres.csv ci-joint. En fait on a ajouté quelques lignes en plus de l’exemple du
Cours, afin que les réponses des interrogations des exercices ne soient pas vides.
Contenu du fichier livres.csv :
IDL,TITRE,AUTEUR
162,Fondation,Asimov
261,Poèmes,Bonnefoy
179,Kyoto,Kawabata
284,Planches courbes,Bonnefoy
283,Trilogie,Mahfouz
180,Pays de neige,Kawabata
285,Le champ,Elbaz
286,L'alchimiste,Coelho
287,Nuée d'oiseaux blancs,Kawabata

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Remarque : Un Dictreader renvoie en fait un dictionnaire pour chaque ligne du fichier CSV. Il suffit
donc ensuite de transformer en entier les valeurs des colonnes concernées. (Dans le Cours page 127,
section 1.2, la ligne 5 du code d’importation était nécessaire en utilisant dans une version antérieure la
fonction reader au lieu de DictReader, mais elle est donc inutile avec DictReader.)
Note : Un tel appel à DictReader utilise la première ligne du fichier CSV pour déterminer les clés des
dictionnaires créés pour les lignes suivantes.

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'])

Chapitre 7 – Données en tables 16


dict['AGE'] = int(dict['AGE'])
dict['NBE'] = int(dict['NBE'])
u.append(dict)
return u

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 :')

10 a. Cet exercice porte sur le matériel de la section 2.2 du cours.


Réponse : proj[TITRE](LIVRE)

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)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


En effet, bien que la question implique des données provenant des deux tables LIVRE et EMPRUNT,
il se trouve que ces données sont dans EMPRUNT elle-même par construction.

c.
from affichage import afficher

# Le fichier livres.py definit la variable Python les_livres


# qui contient la représentation Python de la table LIVRE :
from livres import les_livres
afficher(les_livres,
'Représentation Python de la table LIVRE :')

# Le fichier emprunts.py definit la variable Python les_emprunts


# qui contient la représentation Python de la table EMPRUNT :
from emprunts import les_emprunts
afficher(les_emprunts,
'Représentation Python de la table EMPRUNT :')

# Remarque : la fonction ci-dessous est prévue pour prendre


# en paramètre exactement la représentation de la table LIVRE.
# On fait donc l'hypothèse que son paramètre a exactement les
# colonnes de LIVRE. On fera la même hypothèse de principe pour
# tous les exercices suivants, sauf indication contraire.
def exo10a(table):
# Le paramètre est en fait la représentation d'une table
u = []
for ligne in table:
u.append({'TITRE': ligne['TITRE']})

Chapitre 7 – Données en tables 17


return u

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

11 a. Cet exercice porte sur le matériel de la section 2.4 du cours.


La réponse est ren[AUTEUR\NOM](LIVRE).

b.
from affichage import afficher

# Dans cet exercice on choisit, comme on indiqué dans le corrigé


# de l'exercice 10, d'utiliser les tables importées depuis les
# fichiers CSV. Rappelons que l'exercice 9 affiche aussi les
# représentations des tables, comme on l'a commenté dans les
# corrigés des exercices précédents.
from exo09 import les_livres

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


def exo11(table):
# Le paramètre est en fait la représentation d'une table
u = []
for ligne in table:
u.append({'IDL': ligne['IDL'], 'TITRE': ligne['TITRE'],
'NOM': ligne['AUTEUR']})
return u

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

from affichage import afficher

Chapitre 7 – Données en tables 18


from exo09 import les_livres

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


from exo09 import les_emprunts

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

from affichage import afficher


from exo09 import les_livres, les_emprunts

Chapitre 7 – Données en tables 19


def exo14(table1, table2):
# Rappel : table1, table2 sont des tableaux.
# Rappel : ligne1, ligne2, ligne des dictionnaires.
u1 = []
for ligne1 in table1:
for ligne2 in table2:
if ligne1['IDL'] == ligne2['IDL']:
u1.append({'IDL': ligne1['IDL'], 'TITRE': ligne1['TITRE'],
'AUTEUR': ligne1['AUTEUR'],
'IDA': ligne2['IDA'], 'ANNEE': ligne2['ANNEE']})
u2 = []
for ligne in u1:
u2.append({'TITRE': ligne['TITRE'], 'ANNEE': ligne['ANNEE']})
return u2

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

Le code de l’exercice 5 calcule aussi la représentation de cette expression d’interrogation.


Remarquons qu’on a deux codes calculant la même valeur, mais c’est une situation habituelle, par
exemple les différents algorithmes de tri du chapitre 6 renvoient tous le même résultat. On a cité dans
la section 2.1 du Cours le concept de « déclarativité » ; ce concept s’étend aux expressions
d’interrogation, et les codes Python des exercices 5 et 15.a en sont une illustration. C’est sur cette
propriété que s’appuiera l’optimisation dans les systèmes de gestion de bases de données (SGBD) qui
seront abordés en Terminale.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


b.
# Il s'agit du calcul au vol de l'expression d'interrogation
# de l'exercice 14.

from affichage import afficher


from exo09 import les_livres, les_emprunts
def exo15(table1, table2):
# Rappel : table1, table2 sont des tableaux.
# Rappel : ligne1, ligne2 des dictionnaires.
u = []
for ligne1 in table1:
for ligne2 in table2:
if ligne1['IDL'] == ligne2['IDL']:
u.append({'TITRE': ligne1['TITRE'], 'ANNEE': ligne2['ANNEE']})
return u

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

Chapitre 7 – Données en tables 20


Remarque : la sous-expression e1 = ren[AUTEUR\NOM](LIVRE) est l’expression qui répondait à l’exercice
11.

b. On choisit pour illustrer le renommage de matérialiser e1 et de calculer e2 = e =


au vol, afin d’une part d’illustrer cette possibilité, et d’autre part d’insister sur
proj[IDA](join(e1,ABONNE))
l’opérateur de renommage.
from affichage import afficher
from exo09 import les_livres, les_abonnes

def exo16(table1, table2):


# Rappel : table1, table2 sont des tableaux.
# Rappel : ligne1, ligne2 des dictionnaires.
u1 = []
for ligne in table1:
u1.append({'IDL': ligne['IDL'], 'TITRE': ligne['TITRE'],
'NOM': ligne['AUTEUR']})
u2 = []
for ligne1 in u1:
for ligne2 in table2:
if ligne1['NOM'] == ligne2['NOM']:
u2.append({'IDA': ligne2['IDA']})
return u2

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


et coder des algorithmes déjà très intéressants, par exemple 24, 37, 38, 39.)

17 Cet exercice porte sur la section 2.5 du cours.


Les exercices 17 à 20 sont une construction progressive aboutissant au 21. Ils peuvent toutefois être
faits séparément avec un peu plus de difficulté.
Réponse à la question : Il y a en général plusieurs expressions d’interrogation ayant pour valeur la
table réponse à une question en français donnée. Par exemple ici c’est le cas pour les deux expressions
suivantes :
e1 = proj[IDA](join(sel[AUTEUR=Kawabata](LIVRE),EMPRUNT))
e2 = proj[IDA](sel[AUTEUR=Kawabata](join(LIVRE,EMPRUNT)))

La preuve n’en est pas au programme.


La formulation de l’énoncé « Donner et coder en Python l’expression d’interrogation… » est donc
malheureuse, inexacte pour être rigoureux. Dans les autres énoncés on demande bien « une »
expression d’interrogation (sauf dans les 18, 19, 20, 21 dans lesquels figure la même coquille).
Par ailleurs on peut remarquer que, si on adopte l’approche de matérialiser les résultats intermédiaires
des sous-expressions, en général le calcul de e1 sera plus rapide que celui de e2 car la sélection interne
va diminuer significativement la taille de la jointure. C’est sur ce type de propriété et d’« équivalence
entre expressions » que se basent les optimiseurs des SGBDs. Toutefois dans le codage en Python des
exercices on ignorera cette problématique, afin de se concentrer sur la maîtrise du codage d’une part
par matérialisation des résultats intermédiaires, et d’autre part au vol.

Chapitre 7 – Données en tables 21


Dans le code ci-dessous on choisit d’illustrer le concept de calcul au vol, qu’on a peu illustré jusqu’ici.
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

def exo17(table1, table2):


u = []
for ligne1 in table1:
for ligne2 in table2:
if ligne1['IDL'] == ligne2['IDL'] \
and ligne1['AUTEUR'] == 'Kawabata':
u.append({'IDA': ligne2['IDA']})
return u

t = exo17(les_livres,les_emprunts)
afficher(t, 'Représentation Python de la table : \
proj[IDA](join(sel[AUTEUR=Kawabata](LIVRE),EMPRUNT)) :')

18 Cet exercice porte sur la section 2.5 du cours.


Les exercices 17 à 20 sont une construction progressive aboutissant au 21. Ils peuvent toutefois être
faits séparément avec un peu plus de difficulté.
La réponse est :
e = proj[IDA](join(sel[AUTEUR=Kawabata](LIVRE),sel[ANNEE>=2020](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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


def exo18(table1, table2):
u = []
for ligne1 in table1:
for ligne2 in table2:
if ligne1['IDL'] == ligne2['IDL'] \
and ligne1['AUTEUR'] == 'Kawabata' \
and ligne2['ANNEE'] >= 2020:
u.append({'IDA': ligne2['IDA']})
return u

t = exo18(les_livres,les_emprunts)
afficher(t, 'Représentation Python de la table : \
proj[IDA](join(sel[AUTEUR=Kawabata](LIVRE),EMPRUNT)) :')

19 Cet exercice porte sur la section 2.5 du cours.


Les exercices 17 à 20 sont une construction progressive aboutissant au 21. Ils peuvent toutefois être
faits séparément avec un peu plus de difficulté.
La réponse est :
e = proj[TITRE,NOM](join(join(LIVRE,EMPRUNT),ABONNE))

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.

Chapitre 7 – Données en tables 22


from affichage import afficher
from exo09 import les_livres, les_emprunts, les_abonnes

def exo19(table1, table2, table3):


u = []
for ligne1 in table1:
for ligne2 in table2:
for ligne3 in table3:
if ligne1['IDL'] == ligne2['IDL'] \
and ligne2['IDA'] == ligne3['IDA']:
u.append({'TITRE': ligne1['TITRE'],'NOM': ligne3['NOM']})
return u

t = exo19(les_livres, les_emprunts, les_abonnes)


afficher(t, 'Représentation Python de la table : \
proj[TITRE,NOM](join(join(LIVRE,EMPRUNT),ABONNE)) :')

20 Cet exercice porte sur la section 2.5 du cours.


Les exercices 17 à 20 sont une construction progressive aboutissant au 21. Ils peuvent toutefois être
faits séparément avec un peu plus de difficulté.
La réponse est :
proj[TITRE](
join(join(sel[AUTEUR=Kawabata](LIVRE),
sel[ANNEE>=2020](EMPRUNT)),
sel[NOM=Rita](ABONNE)))

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


from affichage import afficher
from exo09 import les_livres, les_emprunts, les_abonnes

def exo20(table1, table2, table3):


u = []
for ligne1 in table1:
for ligne2 in table2:
for ligne3 in table3:
if ligne1['IDL'] == ligne2['IDL'] \
and ligne2['IDA'] == ligne3['IDA'] \
and ligne1['AUTEUR'] == 'Kawabata' \
and ligne2['ANNEE'] >= 2020 \
and ligne3['NOM'] == 'Rita':
u.append({'TITRE': ligne1['TITRE']})
return u

t = exo20(les_livres, les_emprunts, les_abonnes)


afficher(t, 'Représentation Python de la table : \
proj[TITRE](join(join(sel[AUTEUR=Kawabata](LIVRE),sel[ANNEE>=2020](EMPRUNT)),\
sel[NOM=Rita](ABONNE))) :')

21
proj[NOM,TITRE](sel[NOM=AUTEUR](join(
proj[NOM,IDL](join(sel[AGE<18 et NBE=500](ABONNE),
sel[ANNEE=2021](EMPRUNT))),LIVRE)))

Pour illustrer les deux manières de calculer on code « au vol » la sous-expression e1 :

Chapitre 7 – Données en tables 23


proj[NOM,IDL](join(sel[AGE<18 et NBE=500](ABONNE), sel[ANNEE=2021](EMPRUNT)))

et on « matérialise » les autres.


from affichage import afficher
from exo09 import les_livres, les_emprunts, les_abonnes

def exercice21(les_livres, les_abonnes, les_emprunts):


# calcul de e1 :
t1 = []
for i in range(len(les_abonnes)):
for j in range(len(les_emprunts)):
if les_abonnes[i]['IDA'] == \
les_emprunts[j]['IDA'] and \
les_abonnes[i]['AGE'] < 18 and \
les_abonnes[i]['NBE'] == 500 and \
les_emprunts[j]['ANNEE'] == 2021:
t1.append({
'NOM': les_abonnes[i]['NOM'],
'IDL': les_emprunts[j]['IDL']})
# join(T1,LIVRE) :
t2 = []
for i in range(len(t1)):
for j in range(len(les_livres)):
if t1[i]['IDL'] == les_livres[j]['IDL']:
t2.append({
'NOM': t1[i]['NOM'],
'IDL': t1[i]['IDL'],
'TITRE': les_livres[j]['TITRE'],
'AUTEUR': les_livres[j]['AUTEUR']})
# sel[NOM=AUTEUR](T2) :
t3 = []
for i in range(len(t2)):
if t2[i]['NOM'] == t2[i]['AUTEUR']:

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


t3.append(t2[i])
# proj[NOM,TITRE](T3) :
table_resultat = []
for i in range(len(t3)):
table_resultat.append({
'NOM': t3[i]['NOM'],
'TITRE': t3[i]['TITRE']})
return table_resultat

t = exercice21(les_livres, les_abonnes, les_emprunts)


afficher(t, '')

22 a. Cet exercice porte sur la section 2.5 du cours.


Cet exercice fait référence aux exercices 18 à 21, mais il est en fait indépendant et peut être fait avant
ou après ces derniers.
Cet exercice est un exercice Initié, mais il présente d’une part un ou deux points techniques Python, et
d’autre part il nécessite peut-être un peu plus d’indications que dans la version papier du Manuel, afin
que les élèves comprennent bien la question a.
Dans l’énoncé à la deuxième ligne il faut lire « noms de colonne pour le renommage, la projection et
la jointure » (c’est une malheureuse coquille).
Enfin, cet exercice, une fois codé (voire par l’enseignant), peut servir à s’entraîner sur la partie algèbre
relationnelle sans avoir à coder de Python. Toutefois ce n’est sans doute pas la priorité du programme
officiel de 1ère.

Chapitre 7 – Données en tables 24


Les en-têtes attendus des fonctions demandées sont ceux codé ci-dessous, il paraît utile de le dire en
indication, en donnant par exemple aussi des exemples d’appel comme ci-dessous pour fixer les idées.
from affichage import afficher
from exo09 import les_livres, les_abonnes, les_emprunts

def proj(colonnes, table):


u = []
for ligne in table:
u.append({col: ligne[col] for col in colonnes})
return u

# test hors questions :


t = proj(['IDL', 'TITRE'], les_livres)
afficher(t,'proj[IDL,TITRE](LIVRE)')

def ren(col1, col2, table):


if table == []:
return []
else:
# On copie la liste des noms de colonne en remplaçant celui à
# renommer :
# L'expression Python x if C else y renvoie x si C est vraie et y
# sinon.
colonnes_result = [s if s != col1 else col2 for s in list(table[0])]
u = []
for ligne in table:
# On recopie les couples clé-valeur en gérant à part la colonne
# renommée :
valeur_col1 = ligne[col1]
u.append({col: ligne[col] if col != col2 else valeur_col1
for col in colonnes_result})
return u

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


# test hors questions :
t = ren('AUTEUR', 'NOM', les_livres)
afficher(t,r'ren[AUTEUR\NOM](LIVRE)')

def join(col_join, table1, table2):


# Pour simplifier on fournit le nom de la colonne de jointure en paramètre
# sinon on peut le trouver en faisant l'intersection des deux listes
# de noms de colonnes des deux tables.
if table1 == [] or table2 == []:
return []
else:
cols_non_join_table1 = list(table1[0])
cols_non_join_table1.remove(col_join)
colonnes_result = cols_non_join_table1 + list(table2[0])
u = []
for ligne1 in table1:
for ligne2 in table2:
if ligne1[col_join] == ligne2[col_join]:
u.append(
{col: ligne1[col] if cols_non_join_table1.count(col) == 1
else ligne2[col]
for col in colonnes_result})
return u

# test hors questions :


t = join('IDL', les_livres, les_emprunts)
afficher(t,'join(LIVRE,EMPRUNT)')

Chapitre 7 – Données en tables 25


def sel(cond_bool, table):
# cond_bool est une fonction booléenne prenant en paramètre un
# dictionnaire qui sera la ligne courante.
# Elle est passée sous la forme :
# lambda ligne: ligne['AUTEUR'] == 'Kawabata'
# où lambda est un mot-clé, ligne le paramètre, et ce qui suit une
# expression booléenne quelconque.
u = []
for ligne in table:
if cond_bool(ligne):
u.append(ligne)
return u

# test hors questions :


t = sel(lambda ligne: ligne['AUTEUR'] == 'Kawabata', les_livres)
afficher(t,'sel[AUTEUR=Kawabata](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)))

def exo22b_18(table1, table2, table3):


u = proj(['IDA'],
join('IDL',
sel(lambda ligne: ligne['AUTEUR'] == 'Kawabata', table1),
sel(lambda ligne: ligne['ANNEE'] >= 2020, table2)))
return u

t = exo22b_18(les_livres, les_emprunts, les_abonnes)


afficher(t, 'proj[IDA](join(sel[AUTEUR=Kawabata](LIVRE), \
sel[ANNEE>=2020](EMPRUNT))) :')

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


# exercice 19 :
# proj[TITRE,NOM](join(join(LIVRE,EMPRUNT),ABONNE))

def exo22b_19(table1, table2, table3):


u = proj(['TITRE','NOM'],
join('IDA',
join('IDL', table1, table2),
table3))
return u

t = exo22b_19(les_livres, les_emprunts, les_abonnes)


afficher(t,'proj[TITRE,NOM](join(join(LIVRE,EMPRUNT),ABONNE)) :')

# exercice 20 :
# proj[TITRE](
# join(join(sel[AUTEUR=Kawabata](LIVRE),
# sel[ANNEE>=2020](EMPRUNT)),
# sel[NOM=Rita](ABONNE)))

def exo22b_20(table1, table2, table3):


u = proj(['TITRE'],
join('IDA',
join('IDL',
sel(lambda ligne: ligne['AUTEUR'] == 'Kawabata',
table1),
sel(lambda ligne: ligne['ANNEE'] >= 2020, table2)),
sel(lambda ligne: ligne['NOM'] == 'Rita', table3)))
return u

Chapitre 7 – Données en tables 26


t = exo22b_20(les_livres, les_emprunts, les_abonnes)
afficher(t,'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 exo22b_21(table1, table2, table3):


u = proj(['NOM','TITRE'],
sel(lambda ligne: ligne['NOM']==ligne['AUTEUR'],
join('IDL',
proj(['NOM','IDL'],
join('IDA',
sel(lambda ligne:
ligne['AGE']<18 and ligne['NBE']==500,
table3),
sel(lambda ligne: ligne['ANNEE']==2021,
table2))),
table1)))
return u

t = exo22b_21(les_livres, les_emprunts, les_abonnes)


afficher(t,'proj[NOM,TITRE](sel[NOM=AUTEUR](join(proj[NOM,IDL](\
join(sel[AGE<18 et NBE=500](ABONNE),sel[ANNEE=2021](EMPRUNT))),LIVRE))) :')

23 LIVRE : pas de condition.


ABONNE : 0 < NBE < 1000 ; cela fait plus de deux livres par jour mais NBE doit être borné pour
empêcher les valeurs absurdes comme 1 000 000.
EMPRUNT : 1892 < ANNEE < 2022 ; ainsi quelqu’un peut emprunter toute sa vie. Pour simplifier on

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


ne vérifie pas que les noms par exemple ne contiennent pas de caractères absurdes comme « ; » ou
« ».
Enfin il n’est pas nécessaire d’imposer qu’un identifiant soit positif.
from exo02 import les_abonnes, les_emprunts

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

Chapitre 7 – Données en tables 27


24 a.
def elim(t):
u = []
for i in range(len(t)):
selection = []
for j in range(len(u)):
if u[j]['a'] == t[i]['a'] and \
u[j]['b'] == t[i]['b']:
selection.append(u[j])
if len(selection) == 0:
u.append(t[i])
return u

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

afficher(s, 'Table s sans doublons :')

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


t = creer_doublons(s)
afficher(t, 'Table t avec doublons :')
afficher(elim(t), 'Table t après élimination des doublons :')

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

from affichage import afficher


from exo09 import les_abonnes, les_emprunts

def exo25a(table):
for i in range(len(table)):
for j in range(i): # au lieu de : for j in range(len(table))

Chapitre 7 – Données en tables 28


if table[i]['IDA'] == table[j]['IDA']:
return False
return True

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


valeur = exo25b(les_emprunts, les_abonnes)
print('Le test de clé étrangère de EMPRUNT(IDA) vers ABONNE(IDA) est : ', valeur)
# On ajoute des IDA non dans ABONNE et on reteste :
les_emprunts.append({'IDL': 285, 'IDA': 22, 'ANNEE': 2023})
afficher(les_emprunts, 'Les emprunts, dont une référence folle dans IDA :')
valeur = exo25b(les_emprunts, les_abonnes)
print('Le test de clé étrangère de EMPRUNT(IDA) vers ABONNE(IDA) est : ', valeur)

26 a. On adapte le code du cours du chapitre 6.


from affichage import afficher
from exo09 import les_abonnes

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, '')

Chapitre 7 – Données en tables 29


27 Cet exercice porte sur la section 4 du cours. Il est très similaire à l’exercice 26.
from affichage import afficher
from exo09 import les_abonnes

# On adapte le code du Cours section 3.2 du Chapitre 6.


def exercice27a(t):
for i in range(len(t)-1):
j_max = i
for j in range(i+1, len(t)):
# j_max = indice du maximum parmi t[i], ..., t[j-1]
if t[j]['NBE'] > t[j_max]['NBE']:
j_max = j
if j_max != i: # on échange les cases i et j_min
t[i], t[j_max] = t[j_max], t[i]
# t[0] >= ... >= t[i] sont les plus grands éléments de t

afficher(les_abonnes,'Représentation de la table ABONNE :')


exercice27a(les_abonnes)
afficher(les_abonnes,'Représentation de la table ABONNE après tri à la main :')

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'])

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


dict['age'] = int(dict['age'])
u.append(dict)
return u

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

from affichage import afficher


afficher(les_clients,
'Représentation Python de la table CLIENT :')

Chapitre 7 – Données en tables 30


afficher(les_sejours,
'Représentation Python de la table SEJOUR :')
afficher(les_reservations,
'Représentation Python de la table RESERVATION :')

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

def exo28c(table1, table2):


# tables client, reservation
u = []
for ligne1 in table1:
for ligne2 in table2:
if ligne1['idc'] == ligne2['idp'] \

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


and ligne2['jour'] == 365:
u.append({'nom': ligne1['nom']})
return u

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.

Chapitre 7 – Données en tables 31


def exo28e(table1, table2):
# tables sejour, reservation
u = []
for ligne1 in table1:
for ligne2 in table2:
if ligne1['ids'] == ligne2['idv'] \
and ligne1['activite'] == 'jeux video' \
and ligne2['jour'] == 33:
u.append({'ville': ligne1['ville']})
return u

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


t = exo28f(les_clients, les_reservations, les_sejours)
afficher(t, """Représentation Python de la table :
proj[nom,age,jour](
sel[hobby=activite et nom=ville](
join(client,
join(ren[idv\ids](ren[idp\idc](
sel[60<=jour et jour<=90](reservation))),
sejour)))) :
""")

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:

Chapitre 7 – Données en tables 32


if ligne1['idc'] == ligne2['idp'] \
and ligne2['idv'] == ligne3['ids'] \
and ligne1['age'] > 40 \
and ligne1['hobby'] == 'tango' \
and 32 <= ligne2['jour'] \
and ligne2['jour'] <= 59 \
and ligne3['activite'] == 'tango':
u.append({'nom': ligne1['nom'], 'ville': ligne3['ville'],
'jour': ligne2['jour']})
return u

t = exo28g(les_clients, les_reservations, les_sejours)


afficher(t, """Représentation Python de la table :
proj[nom,ville,jour](
join(sel[age>40 et hobby=tango](client),
join(ren[idv\ids](ren[idp\idc](
sel[32<=jour et jour<=59](reservation))),
sel[activite=tango](sejour)))) :
""")

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


def exercice29a(les_emprunts, les_livres):
t1 = []
for i in range(len(les_emprunts)):
for j in range(len(les_livres)):
if les_emprunts[i]['IDL'] == les_livres[j]['IDL'] and \
les_livres[j]['TITRE'] == 'Kyoto':
t1.append({'IDA': les_emprunts[i]['IDA']})
t2 = []
for i in range(len(les_emprunts)):
for j in range(len(les_livres)):
if les_emprunts[i]['IDL'] == les_livres[j]['IDL'] and \
les_livres[j]['TITRE'] == 'Poemes':
t2.append({'IDA': les_emprunts[i]['IDA']})
table_resultat = []
for i in range(len(t1)):
for j in range(len(t2)):
if t1[i]['IDA'] == t2[j]['IDA']:
table_resultat.append(t1[i])
break
return table_resultat

t = exercice29a(les_emprunts, les_livres)
afficher(t, '')

b.
def exercice29b(les_emprunts, les_livres):
t1 = []
for i in range(len(les_livres)):

Chapitre 7 – Données en tables 33


if les_livres[i]['TITRE'] == 'Kyoto':
t1.append(les_livres[i])
t2 = []
for i in range(len(les_livres)):
if les_livres[i]['TITRE'] == 'Poemes':
t2.append(les_livres[i])
t3 = []
for i in range(len(t1)):
t3.append(t1[i])
for i in range(len(t2)):
t3.append(t2[i])
table_resultat = []
for i in range(len(les_emprunts)):
for j in range(len(t3)):
if les_emprunts[i]['IDL'] == t3[j]['IDL']:
table_resultat.append({'IDA':
les_emprunts[i]['IDA']})
return table_resultat

t = exercice29b(les_emprunts, les_livres)
afficher(t, '')

30 a. Cet exercice porte sur les sections 2.1 et 4 du cours.

On a vu dans la section 2 du chapitre 6 que la recherche dichotomique est significativement plus


rapide que la recherche linéaire de l’algorithme naïf du cours du présent chapitre 7 pour la sélection.

b.
from affichage import afficher
from exo09 import les_abonnes
def cle_exercice30(ligne):
return ligne['NOM']
les_abonnes.sort(key=cle_exercice30)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


afficher(les_abonnes,
'Représentation de la table ABONNE après tri par sort sur NOM :')

# On adapte le code du Cours section 2.3 du chapitre 6 :


def exercice30(t, x):
i_min = 0
i_max = len(t)-1
while i_max >= i_min:
mid = (i_min + i_max) // 2
if t[mid]['NOM'] == x:
return mid
elif t[mid]['NOM'] < x:
i_min = mid+1
else:
i_max = mid-1
return -1

valeur = input("Entrez le nom de l'abonné.e à chercher : ")


print('Position de ', valeur , 'dans ABONNE triée',
exercice30(les_abonnes, valeur))

31 Cet exercice porte sur la section 1.1 du cours.


Voici une solution. Chaque ligne de la table est repésentée par un tableau. La table est représentée par
un tableau de tels tableaux, et sa première ligne représente les noms de colonne. On précise les autres
conventions dans la question 31.b ci-dessous.
tab_de_tab_livres = [
['IDL','TITRE','AUTEUR'],

Chapitre 7 – Données en tables 34


[162,'Fondation','Asimov'],
[261,'Poèmes','Bonnefoy'],
[179,'Kyoto','Kawabata'],
[284,'Planches courbes','Bonnefoy'],
[283,'Trilogie','Mahfouz'],
[180,'Pays de neige','Kawabata'],
[285,'Le champ','Elbaz'],
[286,"L'alchimiste",'Coelho'],
[287,"Nuée d'oiseaux blancs",'Kawabata']]

def afficher31(tableau, message):


print(message)
# Les noms de colonnes s'affichent automatiquement au début :
for tab in tableau:
print(tab)
print()

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


En résumé une conclusion peut être que la représentation par tableau de dictionnaires évite toute
convention, et tout calcul laborieux, et c’est pour cela qu’elle a été choisie dans ce Manuel. D’autres
approches pourraient privilégier d’autres critères, et choisir une autre représentation. En conclusion de
cet exercice, gardons à l’esprit que les types structurés, et en particulier ceux de Python, permettent en
général plusieurs possibilités pour représenter une entité du monde réel ou mathématique, mais que
chacune présentera des avantages et des inconvénients dans différentes situations, et qu’une analyse
préalable assez détaillée est en général nécessaire pour faire le meilleur choix dans une situation
donnée.
def exo31(tableau):
auteur = tableau[0].index('AUTEUR')
u = [tableau[0]]
for i in range(1, len(tableau)):
if tableau[i][auteur] == 'Kawabata':
u.append(tableau[i])
return u

t = exo31(tab_de_tab_livres)
afficher31(t, """
Représentation Python par tableau de tableaux de la table :
sel[AUTEUR=Kawabata(LIVRE) :
""")

Chapitre 7 – Données en tables 35


32 Cet exercice porte sur le matériel de la section 1.2 du cours, plus précisément sur une variante, qui
apparaît dans le programme officiel. Il fait suite à l’exercice 9 sur l’importation de tables représentées
par du texte au format CSV dans des fichiers.
Compléments à l’énoncé de la version papier du manuel : On va utiliser en fait comme dans l’exercice
9 le module csv. Il semble qu’il n’y ait pas de moyen simple de faire cela dans la bibliothèque
standard Python. La seule différence par rapport à un fichier CSV est donc le séparateur. La structure
du code sera exactement celle du cours et de l’exercice 9. La seule différence sera qu’il faut indiquer à
la fonction DictReader que le séparateur est le caractère de tabulation et pas la virgule. Une manière
simple de faire cela est d’ajouter un ``paramètre nommé'' (comme dans la méthode sort) indiquant que
le séparateur est le caractère de tabulation : DictReader(f,delimiter=’), où ’‘ est la valeur chaîne
représentant le caractère de tabulation. L’extension suggérée dans le nom du fichier est .tsv (.csv
conviendrait mais prêterait à confusion dans le présent chapitre).
Le corrigé est le fichier livres.tsv ci-joint. On considère seulement la table LIVRE, les deux autres tables
se traitant de manière absolument identique. Remarquer que ce sont bien des caractères de tabulation
qui séparent les valeurs des colonnes de la table 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,

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


'Représentation Python de la table 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))))

Chapitre 7 – Données en tables 36


a. À partir de e1, une jointure sur IDL avec sel[TITRE=Kyoto](LIVRE) va donner les lignes de e1 dont l’un
des livres est Kyoto, et une deuxième jointure sur IDL2 avec sel[TITRE=Poèmes](ren[IDL\IDL2](LIVRE)) va
donner la réponse.
Une expression réponse est alors :
proj[IDA](
join(
join(
sel[TITRE=Kyoto](LIVRE),
join(EMPRUNT,ren[IDL\IDL2](ren[ANNEE\ANNEE2](EMPRUNT)))),
sel[TITRE=Poèmes](ren[IDL\IDL2](LIVRE))))
Ci-dessous on projette e1 sur les colonnes nécessaires pour simplifier.

from affichage import afficher


from exo09 import les_livres, les_emprunts, les_abonnes

def exo33a(table1, table2):


# proj[IDA,IDL,IDL2](join(EMPRUNT,
# ren[IDL\IDL2](ren[ANNEE\ANNEE2](EMPRUNT)))) :
table3 = table2
u1 = []
for ligne1 in table2:
for ligne2 in table3:
if ligne1['IDA'] == ligne2['IDA']:
u1.append({'IDA': ligne1['IDA'],
'IDL': ligne1['IDL'], 'IDL2': ligne2['IDL']})
# join(sel[TITRE=Kyoto](LIVRE),e1) :
u2 = []
for ligne1 in table1:
for ligne2 in u1:
if ligne1['IDL'] == ligne2['IDL'] \
and ligne1['TITRE'] == 'Kyoto':
u2.append({'IDA': ligne2['IDA'], 'IDL2': ligne2['IDL2']})

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


# join(e2,sel[TITRE=Poèmes](ren[IDL\IDL2](LIVRE))))) :
u3 = []
for ligne1 in u2:
for ligne2 in table1:
if ligne1['IDL2'] == ligne2['IDL'] \
and ligne2['TITRE'] == 'Poèmes':
u3.append({'IDA': ligne1['IDA']})
return u3

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

def exo33b(table1, table2, table3):


# proj[ida](ABONNE) :

Chapitre 7 – Données en tables 37


u1 = []
for ligne in table3:
u1.append({'IDA': ligne['IDA']})
# proj[IDA](join(EMPRUNT,sel[TITRE=Kyoto](LIVRE))) :
u2 = []
for ligne1 in table1:
for ligne2 in table2:
if ligne1['IDL'] == ligne2['IDL'] \
and ligne1['TITRE'] == 'Kyoto':
u2.append({'IDA': ligne2['IDA']})
# diff (cet algorithme est peu efficace mais il est simple) :
u3 = []
for ligne1 in u1:
present = False
for ligne2 in u2:
if ligne1['IDA'] == ligne2['IDA']:
present = True
if present == False:
u3.append(ligne1)
return u3

t = exo33b(les_livres, les_emprunts, les_abonnes)


afficher(t, 'Représentation Python de la table : \
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 :

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


proj[IDA](sel[IDL!=IDL2](join(EMPRUNT,
ren[IDL\IDL2](ren[ANNEE\ANNEE2](EMPRUNT)))))

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))))) :')

d. Indication 1 : les bons = tous - les méchants.


Indication 2 : donner les couples (IDA,IDL) possibles au moyen du produit cartésien.
Indication 3 : donner les couples (IDA,IDL) n’apparaissant pas dans EMPRUNT.
Les couples manquants = tous les couples possibles - les couples existants.
Les bons = tous les abonnés - ceux pour qui il manque un couple (IDA,IDL) dans EMPRUNT.
Une expression réponse est :

Chapitre 7 – Données en tables 38


diff(proj[IDA](ABONNE),
proj[IDA](diff(cart(proj[IDA](ABONNE),proj[IDL](LIVRE)),
proj[IDA,IDL](EMPRUNT))))

def exo33d(table1, table2, table3):


# e1 = cart(proj[IDA](ABONNE),proj[IDL](LIVRE)) :
u1 = []
for ligne1 in table3:
for ligne2 in table1:
u1.append({'IDA': ligne1['IDA'], 'IDL': ligne2['IDL']})

# 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

t = exo33d(les_livres, les_emprunts, les_abonnes)


afficher(t, 'Représentation Python de la table : \

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


diff(proj[IDA](ABONNE),proj[IDA](\
diff(cart(proj[IDA](ABONNE),proj[IDL](LIVRE)),proj[IDA,IDL](EMPRUNT)))) :')

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

def exo34(abonne, emprunt):


for ligne1 in abonne:
for ligne2 in emprunt:
if ligne1['IDA'] == ligne2['IDA']:
annee = ligne2['ANNEE']

Chapitre 7 – Données en tables 39


cpt = 0
for ligne3 in emprunt:
if ligne3['IDA'] == ligne1['IDA'] \
and ligne3['ANNEE'] == annee:
cpt = cpt+1
if cpt > ligne1['NBE']:
print("L'abonné ", ligne1['IDA'],' a ', cpt, ' emprunts en ',
annee)
return False
return True

valeur = exo34(les_abonnes, les_emprunts)


print('Le test de contrainte sur les NBE est : ', valeur)

# On ajoute des emprunts et on reteste :


les_emprunts.append({'IDL': 285, 'IDA': 27, 'ANNEE': 2021})
les_emprunts.append({'IDL': 285, 'IDA': 27, 'ANNEE': 2021})
les_emprunts.append({'IDL': 285, 'IDA': 27, 'ANNEE': 2021})
les_emprunts.append({'IDL': 285, 'IDA': 27, 'ANNEE': 2021})
les_emprunts.append({'IDL': 285, 'IDA': 27, 'ANNEE': 2021})
afficher(les_emprunts, 'Les emprunts, avec 11 en 2021 pour Rita :')

valeur = exo34(les_abonnes, les_emprunts)


print('Le test de contrainte sur les NBE est : ', valeur)

35 Cet exercice porte sur la section 2.5 du cours.


On commentera peu ou pas, les corrigés sur la Bibliothèque ayant été commentés en détail, en
particulier celui de l’exercice 33, qui traite du même matériel sur un ensemble de tables différents, et
auquel on renvoie. Le présent exercice peut être fait avant ou après l’exercice 33. S’il est fait avant, il
sera aussi utile de consulter les indications de l’exercice 33, qu’on ne redonne pas ici. Le présent
exercice utilise le matériel de l’exercice 29.
Cet exercice est assez dur. Il peut être profitable de fournir plus d’indications que l’énoncé de la

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


version papier du Manuel. On trouvera de telles indications dans l’exercice 33.
from affichage import afficher
from exo28a import les_clients, les_sejours, les_reservations

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

def exo35a(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']})
# u2 = proj[idp](join(reservation,
# ren[ids\idp](sel[ville=Cayeux](sejour))))
u2 = []
for ligne1 in reservation:
for ligne2 in sejour:

Chapitre 7 – Données en tables 40


if ligne1['idv'] == ligne2['ids'] \
and ligne2['ville'] == 'Cayeux':
u2.append({'idp': ligne1['idp']})
# u3 = inter(u1, u2)
u3 = []
for ligne1 in u1:
for ligne2 in u2:
if ligne1['idp'] == ligne2['idp']:
u3.append(ligne1)
break # nécessaire pour la définition de l'intersection
# u4 = proj[nom](join(client, ren[idp\idc](u3)))
u4 = []
for ligne1 in client:
for ligne2 in u3:
if ligne1['idc'] == ligne2['idp']:
u4.append({'nom': ligne1['nom']})
return u4

t = exo35a(les_clients, les_reservations, les_sejours)


afficher(t, """Représentation Python de la table :
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](

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


join(proj[idp](join(reservation,
ren[ids\idv](sel[ville=New York](sejour)))),
proj[idp](join(reservation,
ren[ids\idv](sel[ville=Cayeux](sejour)))))))

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']})

Chapitre 7 – Données en tables 41


# u2 = proj[idp](join(reservation,
# ren[ids\idp](sel[ville=Cayeux](sejour))))
u2 = []
for ligne1 in reservation:
for ligne2 in sejour:
if ligne1['idv'] == ligne2['ids'] \
and ligne2['ville'] == 'Cayeux':
u2.append({'idp': ligne1['idp']})
# u3 = join(u1, u2)
u3 = []
for ligne1 in u1:
for ligne2 in u2:
if ligne1['idp'] == ligne2['idp']:
u3.append(ligne1)
# break : pas de break pour la définition de la jointure
# u4 = proj[nom](join(client, ren[idp\idc](u3)))
u4 = []
for ligne1 in client:
for ligne2 in u3:
if ligne1['idc'] == ligne2['idp']:
u4.append({'nom': ligne1['nom']})
return u4

t = exo35b(les_clients, les_reservations, les_sejours)


afficher(t, """Représentation Python de la table :
proj[nom](
join(client,
ren[idp\idc](
join(proj[idp](join(reservation,
ren[ids\idv](sel[ville=New York](sejour)))),
proj[idp](join(reservation,
ren[ids\idv](sel[ville=Cayeux](sejour))))))) :
""")

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


c. Les bons = tous - les méchants. On renvoie à l’exercice 33 pour plus de commentaires.
proj[nom](
join(
client,
diff(proj[idc](client),
proj[idc](
ren[idp\idc](
join(ren[idv\ids](reservation),
sel[activite=kitesurf](sejour)))))))

def exo35c(client, reservation, sejour):


# u1 = proj[idc](
# ren[idp\idc](
# join(ren[idv\ids](reservation),
# sel[activite=kitesurf](sejour)))))))
u1 = []
for ligne1 in reservation:
for ligne2 in sejour:
if ligne1['idv'] == ligne2['ids'] \
and ligne2['activite'] == 'kitesurf':
u1.append({'idc': ligne1['idp']})
# u2 = diff(proj[idc](client), u1)
u2 = []
for ligne1 in client:
present = False
for ligne2 in u1:
if ligne1['idc'] == ligne2['idc']:
present = True

Chapitre 7 – Données en tables 42


break
if present == False:
u2.append(ligne1)
# u3 = proj[nom](join(client, u2)
u3 = []
for ligne1 in client:
for ligne2 in u2:
if ligne1['idc'] == ligne2['idc']:
u3.append({'nom': ligne1['nom']})
return u3

t = exo35c(les_clients, les_reservations, les_sejours)


afficher(t, """Représentation Python de la table :
proj[nom](
join(
client,
diff(proj[idc](client),
proj[idc](
ren[idp\idc](
join(ren[idv\ids](reservation),
sel[activite=kitesurf](sejour)))))))
""")

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


def exo35d(client, reservation, sejour):
# u1 = proj[idp,activite](join(reservation,ren[ids\idv](sejour)))
u1 = []
for ligne1 in reservation:
for ligne2 in sejour:
if ligne1['idv'] == ligne2['ids']:
u1.append({'idp': ligne1['idp'], 'activite': ligne2['activite']})
# u2 = ren[ipd\idc](sel[activite!=activite2](
# join(u1,ren[activite\activite2](u1))))
# note : le renommage de activite en activite2 n'est pas nécessaire
# dans le code. Il apparait dans l'expression pour deux raisons :
# - assurer que la jointure se fasse sur idp
# - distinguer les deux tables dans l'opération de sélection
u2 = []
for ligne1 in u1:
for ligne2 in u1:
if ligne1['idp'] == ligne2['idp'] \
and ligne1['activite'] != ligne2['activite']:
u2.append({'idc': ligne1['idp']})
# u3 = proj[nom,age](join(client,u2))
u3 = []
for ligne1 in client:
for ligne2 in u2:
if ligne1['idc'] == ligne2['idc']:
u3.append({'nom': ligne1['nom'], 'age': ligne1['age']})
return u3

t = exo35d(les_clients, les_reservations, les_sejours)

Chapitre 7 – Données en tables 43


afficher(t, """Représentation Python de la table :
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))))))))

def exo35e(client, reservation, sejour):


# u1 = cart(proj[idc](client),proj[ids](sejour))
u1 = []
for ligne1 in client:
for ligne2 in sejour:
u1.append({'idc': ligne1['idc'], 'ids': ligne2['ids']})

# u2 = proj[idc](u1,
# ren[idp\idc](ren[idv\ids](proj[idp,idv](reservation))))
u2 = []
for ligne1 in u1:
present = False

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


for ligne2 in reservation:
if ligne1['idc'] == ligne2['idp'] \
and ligne1['ids'] == ligne2['idv']:
present = True
break
if present == False:
u2.append({'idc': ligne1['idc']})

# 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

t = exo35e(les_clients, les_reservations, les_sejours)


afficher(t, """Représentation Python de la table :

Chapitre 7 – Données en tables 44


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

36 Cet exercice porte sur la section 4 du cours.


from affichage import afficher
from exo28a import les_clients

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 :
""")

37 Cet exercice porte sur les sections 3.2 et 4 du cours.


from affichage import afficher
from exo28a import les_clients

# ces deux fonctions sont celles de l'exercice 36,


# renommées pour plus de lisibilité
def cle_hobby_nom(dict):
return dict['hobby'], dict['nom']

def trier_hobby_nom(t):

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


t.sort(key=cle_hobby_nom, reverse=True)

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 :
""")

Chapitre 7 – Données en tables 45


38 Cet exercice porte sur la section 2.3 du cours.
Remarque : La notation de la jointure dans la section 2.3 du Cours est join(LIVRE,EMPRUNT), et
elle ne fait pas apparaître le nom de colonne unique en commun sur lequel se fait la jointure. Il serait
possible dans les notations de l’expliciter mais on n’a pas fait ce choix dans ce chapitre. La notation
join[IDL](LIVRE,EMPRUNT) n’est donc pas cohérente avec le reste du chapitre, mais cette
malheureuse coquille est mineure.
from affichage import afficher
from exo09 import les_livres, les_emprunts

def cle_de_tri38(dict):
return dict['IDL']

def exo38(livre, emprunt):


livre.sort(key=cle_de_tri38)
emprunt.sort(key=cle_de_tri38)
afficher(livre, 'Représentation Python de la table LIVRE triée sur IDL :')
afficher(emprunt, 'Représentation Python de la table EMPRUNT triée sur 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) \

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


and livre[i]['IDL'] == emprunt[j]['IDL']:
j = j+1
j_haut = j-1
while i < len(livre) \
and livre[i]['IDL'] == emprunt[j_haut]['IDL']:
i = i+1
i_haut = i-1
for i in range(i_bas, i_haut+1):
for j in range(j_bas, j_haut+1):
u.append({'IDL': livre[i]['IDL'], 'TITRE': livre[i]['TITRE'],
'AUTEUR': livre[i]['AUTEUR'],
'IDA': emprunt[j]['IDA'], 'ANNEE': emprunt[j]['ANNEE']})
i = i_haut+1
j = j_haut+1
return u

t = exo38(les_livres, les_emprunts)
afficher(t, 'Représentation Python de la table join(LIVRE,EMPRUNT) :')

39 Cet exercice porte sur la section 2.1 du cours.


L’énoncé évoque un tableau de 26 entrées pour représenter l’index. Dans le code ci-dessous on utilise
un dictionnaire Python.
from affichage import afficher
from exo09 import les_abonnes

def cle_de_tri39(dict):
return dict['NOM']

Chapitre 7 – Données en tables 46


def creer_index(table, cle):
index = {}
# initialiser l'index avec comme valeur -1 pour chaque lettre
for lettre in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
index[lettre] = -1
#
for i in range(len(table)):
ligne = table[i]
initiale = ligne[cle][0]
if index[initiale] < 0:
index[initiale] = i
return index

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

def exo39(abonne, index, nom):


u = []
i = index[nom[0]]
if i > -1:
print("Les noms d'initiale", nom[0], 'commencent à la position ', i)
else:
print("Il n'existe pas de noms en", nom[0], 'dans ABONNE')
while i < len(abonne) and abonne[i]['NOM'] != nom \
and abonne[i]['NOM'][0] == nom[0]:
print('La position', i, 'ne contient pas', nom)
i = i+1
if abonne[i]['NOM'] == nom:
u.append(abonne[i])
else:

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


print(nom, "n'existe pas dans ABONNE")
return u

les_abonnes.sort(key=cle_de_tri39)
afficher(les_abonnes, 'Représentation Python de la table ABONNE triée sur NOM :')

index = creer_index(les_abonnes, 'NOM')


afficher_index(index)

t = exo39(les_abonnes, index, input('Entrez un nom : '))


afficher(t, 'Représentation Python de la table sel[NOM=xxx](ABONNE) :')

Chapitre 7 – Données en tables 47


Chapitre 8 – Spécifier et tester

Notes sur le chapitre


Section 1 - Spécifier avant de code
Ce chapitre consacré à la spécification et au test introduit un langage informel de spécification que
nous appelons « pseudo-code » destiné à structurer et décrire un algorithme à différents niveaux de
détail. Nous avons choisi un pseudo-code suffisamment proche de Python pour faciliter la traduction
vers le code exécutable, mais permettant aussi d’exprimer des traitements en français.
Dans tout le manuel, le pseudo-code est sur fond orange pour le distinguer du code Python. Il n’est pas
indispensable de l’utiliser de manière aussi rigoureuse que nous avons essayé de le faire : le but est de
permettre d’exprimer les étapes d’un traitement et la structuration d’un programme, d’abord à haut
niveau, puis progressivement de manière plus précise.
Nous avons voulu insister sur l’importance de déclarer les variables et les paramètres ainsi que leurs
types. Ces déclarations n’existent pas en Python mais elles font partie de nombreux langages. Surtout,
elles permettent de spécifier clairement et précisément les données et les fonctions utilisées.

Section 1.3 - Du pseudo-code au code


Dans la précondition de trier(t) on utilise la fonction Python type(t) et l’expression type(t) is list. La
fonction type(v) n’a pas été introduite dans le cours. Elle retourne le type de v et est utilisée en général
avec l’opérateur is pour tester si une valeur est un entier (int), un nombre flottant (float), une chaîne de
caractères (str), un booléen (bool), un tuple (tuple), un tableau ou une liste (list), ou un dictionnaire (dict).

Section 2 - Documenter pour expliquer


Cet aspect très important de la programmation n’est pas explicitement traité dans les exercices. Une
façon de sensibiliser les élèves à l’importance de la documentation consiste à leur fournir un
programme sans commentaires et à leur demander de déterminer ce que fait le programme. On peut à

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


cet effet piocher dans les corrigés des exercices (de ce chapitre ou d’un autre). Une variante consiste à
donner un programme dans lequel on a introduit une erreur, que les élèves doivent identifier et
corriger, ou à fournir à un groupe d’élèves un programme réalisé par un autre groupe. (Cet exercice
peut se faire en débranché).
Alternativement, on peut donner le même programme à une partie des élèves avec les commentaires et
à l’autre sans les commentaires, puis ensuite inverser les rôles avec un second programme.

Section 3 - Tester pour valider


L’utilisation de pytest nous parait intéressante car elle permet de systématiser les tests et de faciliter
leur exécution. Les avantages de pytest (ou sa version Jupyter ipytest) sur l’exécution directe de tests
dans Python sont les suivants :
• pytestne s’arrête pas à la première erreur si un programme contient plusieurs tests avec des
erreurs ;

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

Chapitre 8 – Spécifier et tester 1


Section 4 - Utiliser des bibliothèques logicielles
Cette capacité fait partie du programme et est illustrée dans les exercices par l’utilisation de la
bibliothèque PIL. Il existe un très grand nombre de bibliothèques Python, mais la plupart font appel à
des concepts de Python qui ne font pas partie du programme, en particulier les classes et les objets,
mais aussi les paramètres mots-clés ou les tuples nommés. De plus la plupart des bibliothèques ne sont
documentées qu’en anglais, et les documentations sont souvent spartiates.
Si l’on choisit de faire découvrir une bibliothèque autre que PIL aux élèves, il faut s’assurer qu’elle
leur est abordable. Ainsi, la bibliothèque matplotlib, que nous utilisons régulièrement dans les exercices
pour afficher les résultats, semble un bon candidat. Mais c’est une bibliothèque très riche, et qui de
plus est souvent utilisée avec la bibliothèque numpy pour la gestion de grandes tables de données,
bibliothèque qui est explicitement en dehors du programme… Il faut donc sérieusement baliser la
partie de la bibliothèque que l’on utilise en se focalisant sur les fonctions (par opposition aux
méthodes de classes).

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 lancement depuis la ligne de commande :

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


– test_life.py : lancer avec Python pour exécuter les tests ;

– pytest_life.py : lancer avec pytest pytest_life.py pour exécuter les tests ;

– life.py : programme final à lancer par python life.py;

• Pour Jupyter :

– test_life.ipynb : notebook pour lancer les tests ;

– pytest_life.ipynb : notebook qui utilise ipytest pour lancer les tests ;

– life.py : notebook avec le programme final;

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.

Chapitre 8 – Spécifier et tester 2


Activité : Tournoi
Spécifier le programme
var gagnant : Equipe

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

fonction gerer_tour(equipes : Tournoi) → Tournoi


var gagnants : Tournoi # Liste des équipes du prochain tour
pour chaque paire d’équipes A et B faire
ajouter jouer_match(A, B) à gagnants
fin
retourner gagnants

fonction gerer_tournoi(equipes : Tournoi) → Equipe


tantque il y a au moins 2 équipes faire
equipes <- gerer_tour(equipes)
fin
retourner equipes[0] # Equipe gagnante

Ajouter des pré- et post-conditions

1.
fonction gerer_tournoi(equipes : Tournoi) → Equipe
pre : le nombre d’équipes est une puissance de 2

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


… corps de la fonction …
post : il reste une seule équipe

Implémenter et tester le programme


def jouer_match(equipeA, equipeB):
""" Demande à l'utilisateur quel est le gagnant du match A-B """
while True:
rep = input(f'Qui gagne entre {equipeA} et {equipeB} ? ')
if rep == equipeA:
return equipeA
if rep == equipeB:
return equipeB
print("Répondre le nom de l'équipe gagnante")

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

Chapitre 8 – Spécifier et tester 3


print(f'Tour {tour} : {", ".join(equipes)}')
equipes = gerer_tour(equipes)
tour = tour + 1
assert len(equipes) == 1 # il y a une équipe gagnante
return equipes[0]

print('le gagnant est', gerer_tournoi(equipes))

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

europe = ['Danemark', 'Hongrie', 'Espagne', 'Grece', 'Monténégro', 'Norvège',


'France', 'Lituanie', 'Croatie', 'Jordanie', 'Belgique', 'Pologne',
'Kazakhstan', 'Italie', 'Ouzbekistan', 'Allemagne']
shuffle(europe)

def test_tournoi(equipes, gagnant):


""" Tester le tournoi en vérifiant le gagnant """
assert gerer_tournoi(equipes) == gagnant
print()
print('Le gagnant du tournoi est', gagnant)

test_tournoi(europe, 'Allemagne')

# BONUS : si on fait gagner la première équipe de chaque match,


# c'est la première équipe de la liste qui gagne

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def jouer_match(eq1, eq2):
""" La première équipe dans l'ordre alphabétique gagne """
print(f' {eq1} joue contre {eq2} : {eq1} gagne')
return eq1

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

fonction generation(cellules: Cellules) → Cellules


pour chaque cellule c <- cellules[i][j] faire
var nvoisins: [0,8] <- voisins(cellules, i, j)
cellules[i][j] <- nouvel_etat(cellules[i][j], nvoisins)
fin
retourner cellules

Chapitre 8 – Spécifier et tester 4


fin

fonction nvoisins(cellules : Cellules, i : [1,n], j : [1, n]) → [0-8]


Calculer le nombre de cellules voisines (entre i-1, j-1 et i+1, j+1) vivantes
fin

fonction nouvel_etat(etat : Booléen, voisins : [0-8]) → Booléen


Utiliser les règles pour calculer le nouvel état de la cellule
fin

cellules = init(config)
afficher(cellules)
tantque non fini faire
cellules <- generation(cellules)
afficher(cellules)
fin

Tester au fur et à mesure que l’on programme


def test_nouvel_etat():
# Tester pour un nombre de voisins entre 0 et 8
for voisins in range(9):
# Tester le nouvel etat dans le cas où l'état courant est vivant
etat = nouvel_etat(True, voisins)
if voisins == 2 or voisins == 3:
assert etat == True
else:
assert etat == False

# Tester le nouvel etat dans le cas où l'état courant est mort


etat = nouvel_etat(False, voisins)
if voisins == 3:
assert etat == True
else:
assert etat == False

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


print("OK")

def nouvel_etat(etat, voisins):


assert voisins >= 0 and voisins <= 8

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

# cette version de nvoisins a une erreur


def nvoisins(cellules, i, j):
assert i > 0 and i < len(cellules) -1
assert j > 0 and j < len(cellules[i]) -1

Chapitre 8 – Spécifier et tester 5


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

assert n >= 0 and n <= 8


return n

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


return n

test_4voisins()
test_8voisins()

def init(taille):
return [([False] * taille) for i in range(taille)]

# première version : erreur


def generation(cellules):
for i in range(len(cellules)):
for j in range(len(cellules[i])):
nv = nvoisins(cellules, i, j)
cellules[i][j] = nouvel_etat(cellules[i][j], nv)
return cellules

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

Chapitre 8 – Spécifier et tester 6


assert generation(avant) == apres
print("OK")

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


Le programme principal
# importer les fonctions d'interface :
# grille = creer_grille(cellules)
# crée une grille graphique affichant l'état du tableau cellules
# l'utilisateur peut changer l'état d'une cellule en cliquant dessus
#
# afficher(cellules, grille)
# met à jour la grille avec le contenu du tableau cellules
#
# start_stop_button(step)
# crée un bouton "start" qui permet de lancer/arrêter la simulation
# appelle la fonction step chaque seconde lorsque la simulation est active
#
from life_ui import creer_grille, afficher, start_stop_button

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)

Chapitre 8 – Spécifier et tester 7


Exercices

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


return (q, r)

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.

Chapitre 8 – Spécifier et tester 8


fonction rechercher_tableau(x: , t: Tableau ) → Booléen
pour chaque élément t[i] faire
assertion x n’est pas parmi les éléments t[0] à t[-1]
si t[i] == x alors
post x est dans le tableau
retourner vrai
fin
fin
post x n’est pas dans le tableau
retourner faux
fin

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

# Pour compter le nombre de comparaisons, on retourne un tuple (trouve, nbcomp)


def recherche_tableau(x, t):
nbcomp = 0
for y in t:
assert x not in tranche(t, 0, nbcomp), "erreur invariant"
nbcomp += 1
if x == y:
assert x in t, "erreur post-condition"
return (True, nbcomp)
assert x not in t, "erreur post-condition"
return (False, nbcomp)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# cas le meilleur
assert recherche_tableau('c', ['c', 'b', 'a']) == (True, 1)

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

Chapitre 8 – Spécifier et tester 9


def est_trie(t):
""" Retourne True si le tableau t est trié """
for i in range(len(t)-1):
if t[i] > t[i+1]:
return False
return True

def dicho(t, val):


""" Recherche dichotomique de t dans val.
Retourne un tuple (indice, nombre de tours de boucle)
"""
assert est_trie(t)
gauche, droite = 0, len(t)-1
compteur = 0 # compteur de boucle
while gauche <= droite:
compteur = compteur + 1
variant = droite - gauche # variant de boucle
milieu = (gauche + droite) // 2
if t[milieu] == val:
assert val in t and t[milieu] == val
return (milieu, compteur)
if t[milieu] < val:
gauche = milieu + 1
else:
droite = milieu -1
assert droite - gauche < variant
assert val not in t
return (-1, compteur)

def test_dicho_meilleur():
""" Teste le cas le meilleur : val au milieu de t """
assert dicho([1, 2, 3, 5, 7], 3) == (2, 1)

from math import floor, log2

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def test_dicho_pire():
""" teste le cas le pire : val pas dans tableau,
entre deux éléments d'incides consécutifs """
n_pire = floor(log2(5)+1)
assert dicho([1, 2, 3, 5, 7], 6) == (-1, n_pire)

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

fonction carte_gagnante(c1: Carte, c2: Carte) → Nombre[-1..1]


var v1: Entier <- valeur de c1
var v2: Entier <- valeur de c2
retourner -1, 0 ou 1 selon que v1 est plus petit, égale ou supérieur à v2
fin

fonction jouer_bataille(cartes: Paquet) → Tuple(Nombre, Nombre)


# retourne le score de chaque joueur
var score1: Entier <- 0
var score2: Entier <- 0
var c1: Carte
var c2: Carte
var gain: Entier <- 1
tantque il reste des cartes faire
tirer deux cartes c1 et c2

Chapitre 8 – Spécifier et tester 10


var r: Entier <- carte_gagnante(c1, c2)
si r == 0 alors
gain <- gain + 1
sinon
si r == -1 alors
score1 <- score1 + gain
sinon
score2 <- score2 + gain
fin
gain <- 1
fin
fin
retourner (score1, score2)

b. La partie se termine et les points de cette dernière bataille ne comptent pas.


c.
# Note : les cartes sont retirées du "dessus" du paquet, c'est-à-dire de la fin
def test_1():
# pas de bataille
cartes = [(3, "Pique"), (2, "Trèfle"), (4, "Cœur"),
(10, "Carreau"), (12, "Cœur"), (14, "Pique")]
assert jouer_bataille(cartes) == (1, 2)

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"),

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


(4, "Carreau"), (12, "Cœur"), (14, "Pique")]
assert jouer_bataille(cartes) == (1, 1)

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:

Chapitre 8 – Spécifier et tester 11


score2 += gain
print(c1, c2, ': joueur 1 gagne', gain, 'point(s)')
gain = 1
print('score final', score1, score2)
return (score1, score2)

test_1()
test_2()
test_3()

5 a.
from math import gcd

assert pgcd(10, 4) == gcd(10, 4)


assert pgcd(10, 1) == gcd(10, 1)
assert pgcd(10, 10) == gcd(10, 10)

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


fin
fin

def pgcd(a, b):


if a == 0:
return b
if b == 0:
return a

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

assert pgcd(10, -4) == gcd(10, -4)


assert pgcd(-10, 1) == gcd(-10, 1)
assert pgcd(10, 0) == gcd(10, 0)

Chapitre 8 – Spécifier et tester 12


6
t_trie = [i for i in range(10)] # tableau trié

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

from random import shuffle


def test_tri_aleatoire():
""" tri d'un tableau en ordre aléatoire """
t = t_trie.copy()
shuffle(t)
assert trier(t) == t_trie

# Le programme original échoue sur les trois tests


try:
test_tri_trie()
except:
print('test_tri_trie a échoué')

try:
test_tri_inverse()
except:
print('test_tri_inverse a échoué')

try:
test_tri_aleatoire()
except:
print('test_tri_aleatoire a échoué')

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


On observe tout d’abord que le programme échoue avec une erreur de dépassement de tableau. Celle-
ci est due à l’instruction j = i+1 alors que i va jusqu’à l’indice du dernier élément du tableau.
On peut être tenté de résoudre ce problème en change range(n) en range(n-1. Ceci élimine l’erreur, mais
c’est maintenant l’assertion qui échoue : le tableau n’est pas trié.
Si l’on ajoute une trace permettant de voir ce que fait le programme, par exemple en ajoutant à la fin
de la boucle :
print(f'echange {i} et {j}')
print(t)

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

Chapitre 8 – Spécifier et tester 13


min = t[k]
if i != j: # Inutile d'échanger un élément avec lui-même
t[i], t[j] = t[j], t[i]
print(f'echange {i} et {j}') # trace pour voir ce qui se passe
print(t)
return t

# On vérifie que les trois tests passent avec succès


test_tri_trie()
print()
test_tri_inverse()
print()
test_tri_aleatoire()

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


retourner somme

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

test(10000, 0.75/100, 10)

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

Chapitre 8 – Spécifier et tester 14


def test2(s, t, n):
S = interets2(s, t, n)
T = round(s * (1+t) ** n, 2)
assert S == T, f'Erreur test2: {S} au lieu de {T}'

test2(10000, 0.75/100, 10)

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

def test3(s, t, n):


S = interets3(s, t, n)
T = round(s * (1+t) ** n, 2)
assert S == T, f'Erreur test3: {S} au lieu de {T}'

test3(10000, 0.75/100, 10)

9 a.
type Jeu = tableau de Entier

fonction coupe(paquet : Jeu) → Jeu


pre: le nombre de cartes est pair
var n : Entier <- nombre de cartes
var battu : Jeu
ajouter paquet[n // 2] à paquet[n - 1] à battu
ajouter paquet[0] à paquet[n//2 - 1] à battu

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


retourner battu
fin

fonction battage(paquet : Jeu, n : Nombre) → Jeu


pre: le nombre de cartes est multiple de n
var taille : Entier <- nombre de cartes // n
var battu : Jeu
pour paquet allant de n-1 à 0 faire
var debut <- paquet * taille
var fin <- debut + taille - 1
ajouter paquet[debut] à paquet[fin] à battu
fin
retourner battu
fin

fonction americain(paquet : Jeu) → Jeu


pre: le nombre de cartes est pair
var n : Entier <- nombre de cartes
var battu : Jeu
pour i allant de 0 à n//2 faire
ajouter paquet[i] et paquet[n//2 + i] à battu
fin
fin

Note : On peut observer que la coupe est identique au battage avec 2 sous-paquets.
b.

Chapitre 8 – Spécifier et tester 15


paquet = [i for i in range(52)]
def test_coupe():
""" Teste que deux coupes = ordre initial """
assert coupe(coupe(paquet)) == paquet

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)

def battage(paquet, n):


""" Sépare les cartes en n paquets égaux et les bat
en mettant les paquets en ordre inverse """
assert len(paquet) % n == 0, 'tailles inégales'
taille = len(paquet) // n # taille d'un paquet
battu = []
for sous_paquet in range(n-1,-1,-1):
# indice de la première carte du sous_paquet
debut = sous_paquet * taille
for i in range(taille):
battu.append(paquet[debut + i])
return battu

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def americain(paquet):
""" Bat les cartes en alternant deux sous-paquets """
assert len(paquet) % 2 == 0, 'taille impaire'
milieu = len(paquet) // 2
battu = []
for i in range(milieu):
battu.append(paquet[i])
battu.append(paquet[milieu+i])
return battu

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:

Chapitre 8 – Spécifier et tester 16


battu = americain(battu)
cycle += 1
return cycle

for n in range(20, 53, 2):


paquet = [i for i in range(n)]
print(f'taille = {n}, cycle = {cycle_americain(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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


t <- []
i1 <- 0
i2 <- 0
tantque i1 < n1 ou i2 < n2 faire
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
fin
retourner t
fin

On ajoute les pre/post-conditions et les variants/invariants :


fonction tri_fusion(t1 : tableau[n1] de Nombre,
t2 : tableau[n2] de Nombre)
→ tableau[n1+n2] de Nombre
var t : Tab

pre: t1 et t2 sont triés

Chapitre 8 – Spécifier et tester 17


post: t est trié

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


c.
def est_trie(t):
""" Retourne """
# Note : ici on utilise une fonction prédédinie de Python.
# On peut aussi utiliser la version explicite définie dans l'exercice 3
return t == sorted(t)

def trifusion(t1, t2):


""" Retourne le résultat de la fusion des deux tableaux triés t1 et t2 """
assert est_trie(t1) and est_trie(t2)

t = []
i1 = 0
i2 = 0

while i1 < len(t1) or i2 < len(t2):


i1prec = i1
i2prec = i2

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

Chapitre 8 – Spécifier et tester 18


else:
t.append(t2[i2])
i2 += 1

assert i1 > i1prec or i2 > i2prec


assert est_trie(t) and len(t) == i1+i2

assert est_trie(t)
return t

def test_trifusion(t1, t2, t):


print('t1 =', t1)
print('t2 =', t2)
print('t = ', t)
assert t == trifusion(t1, t2)
print()

# t1 = [nombres pairs], t2 = [nombres impairs]


pairs = [(i+1)*2 for i in range(10)] # nombres pairs
impairs = [i*2+1 for i in range(10)] # nombres impairs
t20 = [i+1 for i in range(20)]
test_trifusion(pairs, impairs, t20)

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# t1 = [1..5, 11..15], t2 = [6..10, 16..20]
t1 = [i+1 for i in range(5)] + [i+11 for i in range(5)]
t2 = [i+6 for i in range(5)] + [i+16 for i in range(5)]
test_trifusion(t1, t2, t20)

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

pre: t1 et t2 sont triés


post: t est trié

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

si t1[n1] < t2[n2] alors


ajouter t1[i1] à t
i1 <- i1 + 1
sinon

Chapitre 8 – Spécifier et tester 19


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

# Cette assertion montre qu’un seule des deux boucles suivantes est exécutée
assertion: i1 == n1 ou i2 == n2

tantque i1 < n1 faire


ajouter t1[i1] à t
i1 <- i1 + 1
assertion: t est trié et a i1+i2 éléments
fin

tantque i2 < n2 faire


ajouter t2[i2] à t
i2 <- i2 + 1
assertion: t est trié et a i1+i2 éléments
fin

assertion: i1 == n1 et i2 == n2

retourner t
fin

def trifusion2(t1, t2):


""" Retourne le résultat de la fusion des deux tableaux triés t1 et t2 """

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


assert est_trie(t1) and est_trie(t2)

t = []
i1 = 0
i2 = 0

# Tant qu'il y a des éléments dans les deux tableaux : fusion


while i1 < len(t1) and i2 < len(t2):
i1prec = i1
i2prec = i2

if t1[i1] < t2[i2]:


t.append(t1[i1])
i1 += 1
else:
t.append(t2[i2])
i2 += 1

assert i1 > i1prec or i2 > i2prec


assert est_trie(t) and len(t) == i1+i2

assert i1 == len(t1) or i2 == len(t2)

# Ajouter le reste du tableau qui n'était pas épuisé


while i1 < len(t1):
t.append(t1[i1])
i1 += 1
while i2 < len(t2):
t.append(t2[i2])

Chapitre 8 – Spécifier et tester 20


i2 += 1

assert i1 == len(t1) and i2 == len(t2)


assert est_trie(t)
return t

test_trifusion(pairs, impairs, t20)


test_trifusion(t10, t10.copy(), t)
test_trifusion(t10, [], t10)
test_trifusion([], t10, t10)
test_trifusion(t1, t2, t20)

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


b.
def est_premier(n):
""" Retourne vrai si n est premier """
for i in range(2, n):
if n % i == 0:
return False
return True

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.

Chapitre 8 – Spécifier et tester 21


from math import sqrt, floor

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

def recherche_dichotomique(val, t):


""" Retourne le nombre de comparaisons pour chercher x dans t (trié) """
nbcomp = 0
idx_left = 0
idx_right = len(t)-1
while idx_left <= idx_right:
nbcomp += 1
idx_mid = (idx_left + idx_right) // 2

nbcomp += 1
if t[idx_mid] == val:
return nbcomp

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


nbcomp += 1
if t[idx_mid] < val:
idx_left = idx_mid +1
else:
idx_right = idx_mid -1
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]

pour p fois faire


créer deux tableaux aléatoires identiques t1 et t2
val <- valeur aléatoire entre 0 et n-1
L <- L + recherche_lineaire(val, t1)
D <- D + recherche_dichotomique(val, t2)
fin

retourner (L/p, D/p)


fin

from random import shuffle, randint

Chapitre 8 – Spécifier et tester 22


def moyennes(n, p):
""" Calculer les moyennes, sur p essais, du nombre de comparaisons
d'une recherche linéaire et dichotomique dans un tableau de n éléments"""
L = 0 # nombre de comparaisons en recherche linéaire
D = 0 # nombre de comparaisons en recherche dichotomique

for i in range(p): # p essais


# créer deux tableaux aléatoires identiques t1 et t2
t1 = [i for i in range(n)] # un tableau [0, 1, ..., n-1]
shuffle(t1) # le mélanger
t2 = t1.copy() # le copier

# tirer une valeur à chercher au hasard


val = randint(0, n-1)

# accumuler nombre de comparaisons:


L += recherche_lineaire(val, t1)
D += recherche_dichotomique(val, t2)

# retourner la moyenne sur p essais


return (L/p, D/p)

c.
from matplotlib.pyplot import plot, show

# le tableau des valeurs de n


t_n = [10, 50, 100, 200, 300, 400, 500]
# le tableau des moyennes que l'on va calculer
t_LD = []
# le nombre d'essais pour chaque valeur de n
p = 100

# calculer les moyennes pour chaque valeur de n

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


for n in t_n:
t_LD.append(moyennes(n, p))
# les afficher
print(t_n)
print(t_LD)
plot(t_n, t_LD)
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

fonction lissage(t: Tableau[n], f: Entier) → Tableau[n]


var res: Tableau[n]
pour i de 0 à n-1 faire
res[i] <- moyenne_fenetre(t, i, f)
fin
retourner res
fin

b.

Chapitre 8 – Spécifier et tester 23


# cas normal: une moyenne glissante d'un tableau constant devrait être constant
t = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
assert moyenne_glissante(t, 3) == t

# cas normal: calcul du résultat à la main dans un cas simple


t = [1, 2, 3, 4, 5, 6, 7, 8]
res = [(1+2+3)/3, (1+2+3+4)/4, (1+2+3+4+5)/5, (2+3+4+5+6)/5,
(3+4+5+6+7)/5, (4+5+6+7+8)/5, (5+6+7+8)/4, (6+7+8)/3]
moyenne_glissante(t, 2) == res

# cas limite: moyenne glissante d'un tableau vide ne cause pas d'erreur
assert moyenne_glissante([], 3) == []

# cas limite: moyenne glissante avec fenêtre de taille 0 = tableau initial


t = [1, 3, 2, 5, 4, 6, 5, 7]
assert moyenne_glissante(t, 0) == t

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

for j in range(i-f, i+f+1):


# ne considérer que les éléments du tableau
if j >= 0 and j < len(t):
s += t[j]
n += 1

return s/n

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def moyenne_glissante(t, f):
""" Retourne une version lissée de t avec une fenêtre de taille 2f+1 """
res = []
for i in range(len(t)):
res.append(moyenne_fenetre(t, i, f))
return res

# solution alternative, sans utiliser append


def moyenne_glissante(t, f):
""" Retourne une version lissée de t avec une fenêtre de taille 2f+1 """
res = [0] * len(t)
for i in range(len(t)):
res[i] = moyenne_fenetre(t, i, f)
return res

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)

from matplotlib.pyplot import plot, show


plot(hospitalisations)
plot(lissage)
show()

Chapitre 8 – Spécifier et tester 24


d.
fonction seuil(t: Tableau[n], debut: Entier, fin: Entier) → Tableau[n]
var s: Tableau[n]
var alerte: Boolean <- False

pour chaque élément t[i] faire


si on est en alerte et t[i] est sous le seuil de fin alors
mettre alerte à faux
sinon si on n’est pas en alerte et t[i] est sous le seuil de fin alors
mettre alerte à faux
fin

ajouter 100 à s si alerte est vrai, 0 sinon


fin

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


retourner s
fin

def seuil(t, debut, fin):


assert fin <= debut, "le seuil de début doit être supérieur au seuil de fin"
s = []
alerte = False

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

# solution alternative, sans utiliser append


def seuil(t, debut, fin):
assert fin <= debut, "le seuil de début doit être supérieur au seuil de fin"
s = [0] * len(t) # remplir le tableau de 0
alerte = False

Chapitre 8 – Spécifier et tester 25


for i in range(len(t)):
e = t[i]
if alerte and e <= fin:
alerte = False
elif not alerte and e >= debut:
alerte = True

if alerte:
s[i] = 100

return s

# test d'un cas normal


t = [1, 10, 120, 110, 90, 20, 10]
res = [0, 0, 100, 100, 100, 0, 0]
assert seuil(t, 100, 80) == res

# test d'un cas limite : on commence en alerte


t = [120, 110, 90, 20, 10]
res = [100, 100, 100, 0, 0]
assert seuil(t, 100, 80) == res

# test d'un cas limite : egalite des deux seuils


t = [1, 10, 120, 110, 90, 20, 10]
res = [0, 0, 100, 100, 0, 0, 0]
assert seuil(t, 100, 100) == res

alertes = seuil(lissage, 2000, 1900)


# pour mieux afficher les alertes, on remplace 100 par le seuil
alertes = [i * 20 for i in alertes]

from matplotlib.pyplot import plot, show


plot(lissage)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


plot(alertes)
show()

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

Chapitre 8 – Spécifier et tester 26


fin
retourner histo
fin

from PIL import Image

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

# charger une image


image = Image.open('poisson.jpg')
# la convertir en niveaux de gris et l'afficher
image = image.convert('L')
image.show()
# afficher l'histogramme
from matplotlib.pyplot import plot, show
plot(histogramme(image))
show()

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


Exercices Titan
16 a.
fonction americain(cartes : Jeu) → Jeu
var n : Entier <- nombre de cartes
var a, b, battu : Jeu
var coupe : Entier <- getcut(n)

a <- coupe premières cartes de cartes


b <- n - coupe dernières cartes de cartes
battu <- []
tantque il reste des cartes dans a et b faire
si random() < 0.5 alors
retirer une carte de a et l’ajouter à battu
sinon
retirer une carte de b et l’ajouter à battu
fin
fin

Chapitre 8 – Spécifier et tester 27


si il reste des cartes dans a alors les ajouter à battu fin
si il reste des cartes dans b alors les ajouter à battu fin

post: battu contient les même cartes que cartes


retourner battu
fin

from random import random

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 = []

while len(a) > 0 and len(b) > 0:


# Prendre une carte de l'un des sous-paquets
ncartes = len(a) + len(b) # nombre de cartes restantes
if random() < len(a) / ncartes:
# retirer la première carte de a et l'ajouter à battu
battu.append(a.pop(0))
else:
# retirer la première carte de b et l'ajouter à battu
battu.append(b.pop(0))

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# Finir le sous-paquet qui n'est pas vide
while len(a) > 0:
battu.append(a.pop(0))
while len(b) > 0:
battu.append(b.pop(0))

assert sorted(battu) == sorted(paquet)


return battu

cartes = [i+1 for i in range(52)]


print(americain_reel(cartes))

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 = []

# on remplace la sélection aléatoire par une sélection


# qui alterne entre le sous-paquet a et le sous-paquet b
de_a = True
while len(a) > 0 and len(b) > 0:

Chapitre 8 – Spécifier et tester 28


if de_a:
# retirer la première carte de a et l'ajouter à battu
battu.append(a.pop(0))
else:
# retirer la première carte de b et l'ajouter à battu
battu.append(b.pop(0))
de_a = not de_a

# Finir le sous-paquet qui n'est pas vide


while len(a) > 0:
battu.append(a.pop(0))
while len(b) > 0:
battu.append(b.pop(0))

assert sorted(battu) == sorted(cartes), 'mélange incorrect'


return battu

# Fonction de test de l'exercice 9


paquet = [i+1 for i in range(52)]
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 == cartes

test_americain()

c.
# fonction de mélange aléatoire
def randshuffle(t):
shuffle(t)
return t

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# fonction de mélange américain itéré n fois
def americainN(paquet, n):
for k in range(n):
paquet = americain_reel(paquet)
return paquet

def americain2(paquet):
return americainN(paquet, 2)

def americain3(paquet):
return americainN(paquet, 3)

def americain7(paquet):
return americainN(paquet, 7)

# comparer mélange américain itéré 1, 2, 3, 7 fois, et mélange aléatoire


print('americain 1 fois :', mesure(americain_reel))
print('americain 2 fois :', mesure(americain2))
print('americain 3 fois :', mesure(americain3))
print('americain 7 fois :', mesure(americain7))
print('mélange aléatoire :', mesure(randshuffle))

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.

Chapitre 8 – Spécifier et tester 29


BONUS : La fonction ci-dessous affiche les histogrammes pour les différents mélanges. On observe la
forme en V renversé de l’histogramme d’un mélange parfait (mélange purement aléatoire). On observe
également que dès 2 mélanges successifs, on est déjà proche du mélange parfait.
from matplotlib.pyplot import plot, show

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

# comparer mélange américain itéré 1, 2, 3, 7 fois, et mélange aléatoire


stats(americain_reel)
stats(americain2)
stats(americain3)
stats(americain7)
stats(randshuffle)

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


cherche, il n’est pas utile d’essayer : on retourne -1 immédiatement.
fonction rechercher(r: Chaîne, t: Chaîne) → Entier
si len(r) > len(t) alors retourner -1 fin

pour i de 0 à len(t) - len(r) faire


pour chaque caractère r[j] de r faire
si r[j] ≠ t[i+j] alors passer au i suivant fin
fin
si on a parcouru toute la chaîne r alors retourner i fin
fin

retourner -1
fin

b.
# test cas normal : chaîne au milieu du texte
assert rechercher('fait', 'il fait beau') == 3

# test cas limite : chaîne en fin de texte


assert rechercher('beau', 'il fait beau') == 8

# test cas normal : chaîne absente du texte


assert rechercher('faisait', 'il fait beau') == -1

# test cas limite : chaîne dont le début est en fin de texte


assert rechercher('beaucoup' ,'il fait beau') == -1

# test cas limite : chaîne dont le début se répète

Chapitre 8 – Spécifier et tester 30


assert rechercher('très beau' ,'il fait très très beau') == 13

# test cas limite : chaîne trop longue


assert rechercher('beaucoup', 'beau') == -1

# test cas limite : chaîne vide


assert rechercher('', 'il fait beau') == 0

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

for i in range(0, len(t) - len(r) + 1):


# On ne peut pas utiliser une boucle `for`
# car on doit pouvoir sortir avant la fin
# On utilise donc une boucle `while`
j=0
ok = True
while ok and j < len(r):
if r[j] != t[i+j]:
ok = False # on sort de la boucle
else:
j = j+1 # on passe au caractère suivant de r
if ok:
return i

return -1

Tester cette fonction en exécutant la cellule de la question b. après celle-ci

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


d.
fonction rechercher_tout(r: Chaîne, t: Chaîne) → Tableau de Entier
var occurrences: Tableau de Entier <- []

si len(r) > len(t) alors retourner [] fin

pour i de 0 à len(t) - len(r) faire


pour chaque caractère r[j] de r faire
si r[j] ≠ t[i+j] alors passer au i suivant fin
fin
si on a parcouru toute la chaîne r alors ajouter i à occurrences fin
fin

retourner occurrences
fin

def rechercher_tout(r, t):


""" Recherche toutes les occurrences de la chaîne r dans le texte t.
Retourne le tableau des indices, ou [] si non trouvé.
"""
if len(r) > len(t):
return []

occurrences = []
for i in range(0, len(t) - len(r) + 1):
# On ne peut pas utiliser une boucle `for`

Chapitre 8 – Spécifier et tester 31


# car on doit pouvoir sortir avant la fin
# On utilise donc une boucle `while`
j=0
ok = True
while ok and j < len(r):
if r[j] != t[i+j]:
ok = False # on sort de la boucle
else:
j = j+1 # on passe au caractère suivant de r
if ok:
occurrences.append(i)

return occurrences

# cas normal : pas d'occurrence


assert rechercher_tout('faisait', 'il fait beau') == []

# cas normal : deux occurrences séparées


assert rechercher_tout('pata', 'et patati et patata') == [3, 13]

# cas limite : deux occurrences qui se chevauchent


assert rechercher_tout('toto', 'tototo') == [0, 2]

# cas limite : recherche d'une chaine vide


assert rechercher_tout('', 'toto') == [0, 1, 2, 3, 4]

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


Dans les deux cas, la recherche continue à partir de la fin de la chaîne qui a été comparée, c’est-à-dire
à l’indice i+j.
fonction remplacer_tout(r: Chaîne, s: Chaîne, t: Chaîne) → Tableau de Entier
var res: Chaîne <- ""
var tmp: Chaîne

si len(r) > len(t) alors retourner t fin

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

si on a parcouru toute la chaîne r alors


res <- res + s # remplacement
sinon
res <- res + tmp # pas de remplacement
fin
i <- i+j
fin

copier la fin de t dans res

Chapitre 8 – Spécifier et tester 32


retourner res
fin

def remplacer_tout(r, s, t):


""" Retourne une chaîne où toutes les occurrences de la chaîne r
dans le texte t ont été remplacées par s.
"""
if len(r) > len(t):
return t

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

if ok: # chaîne trouvée : ajouter s au résultat


res = res + s
else: # chaîne non trouvée : ajouter la partie de t
res = res + tmp
i = i+j

for i in range(i, len(t)):


res = res + t[i]

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


return res

# cas normal : plusieurs occurrences séparées


assert remplacer_tout('abc', 'ZZ', 'abcdabcdefabcdefg') == 'ZZdZZdefZZdefg'

# cas limite : début d'occurrence à la fin du texte


assert remplacer_tout('abc', 'ZZ', 'abcab') == 'ZZab'

# cas limite : occurrences qui se chevauchent


assert remplacer_tout('aaa', 'Z', 'aaaaa') == 'Zaa'

remplacer_tout('tomato', 'potato', 'tomatomatomatomato')

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

pour chaque caractère c de r faire


v <- valeur entière de c
vsuiv <- valeur entière du caractère suivant de c

Chapitre 8 – Spécifier et tester 33


si v < vsuiv alors
valeur <- valeur - v
sinon
valeur <- valeur + v
fin
fin
retourner valeur
fin

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

# mettre à jour la valeur


if v < vsuiv:
valeur -= v

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


else:
valeur += v

return valeur

# test simple : ajout des valeur


assert romain_vers_entier('CXXIII') == 123

# test avec retrait d'une valeur


assert romain_vers_entier('IX') == 9

# test avec une plusieurs ajouts et retraits de valeurs


assert romain_vers_entier('MCMLXXXIV') == 1984

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.

Chapitre 8 – Spécifier et tester 34


type Chiffre = Chaîne('I', 'V', 'X', 'L', 'C', 'D', 'M')

fonction chiffre_romain(v: Entier[0..9], unité, cinq, dix: Chiffre) → Nombre


retourner la représentation en chiffres romains de v
en utilisant les 3 chiffres unité, cinq, dix
fin

fonction entier_vers_romain(v: Entier) → Chaîne


décomposer v en chiffres des milliers, centaines, dizaines, unités

var romain: Chaine <- 'M' * milliers


ajouter à romain la conversion chiffre_romain(centaines, ‘C’, ‘D’, ‘M’)
ajouter à romain la conversion chiffre_romain(dizaines, ‘X’, ‘L’, ‘C’)
ajouter à romain la conversion chiffre_romain(unités, ‘I’, ‘V’, ‘X’)

retourner romain
fin

def chiffre_romain(v, unite, cinq, dix):


""" Retourne le conversion de v, 0 ≤ v ≤ 9,
en chiffre romain utilisant les symboles
unite, cinq, dix
"""
if v < 4:
return unite * v
if v == 4:
return unite + cinq
if v < 9:
return cinq + unite * (v-5)
if v == 9:
return unite + dix

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def entier_vers_romain(v):
milliers = v // 1000
v = v % 1000
centaines = v // 100
v = v % 100
dizaines = v // 10
unités = v % 10

romain = 'M' * milliers


romain += chiffre_romain(centaines, 'C', 'D', 'M')
romain += chiffre_romain(dizaines, 'X', 'L', 'C')
romain += chiffre_romain(unités, 'I', 'V', 'X')

return romain

# test simple : ajout des valeur


assert entier_vers_romain(123) == 'CXXIII'

# test avec retrait d'une valeur


assert entier_vers_romain(9) == 'IX'

# test avec une plusieurs ajouts et retraits de valeurs


assert entier_vers_romain(1984) == 'MCMLXXXIV'

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.

Chapitre 8 – Spécifier et tester 35


def romain_valide(r):
return entier_vers_romain(romain_vers_entier(r)) == r

print('MCMLXXXIV est valide :', romain_valide('MCMLXXXIV'))


print('IIX est valide :', romain_valide('IIX'))

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re

Chapitre 8 – Spécifier et tester 36


Chapitre 9 – Des 0 et des 1

Notes sur le chapitre


Habituellement, le chapitre sur le codage des types de base suit (voire précède) le chapitre sur
l’architecture de von Neumann, car les deux sont fortement liés. Cependant, nous avons choisi de
commencer le manuel par le chapitre sur l’architecture de von Neumann afin de comprendre le
principe de fonctionnement des ordinateurs, puis ensuite d’aborder directement les concepts de la
programmation de haut niveau. Cela permet d’aborder le chapitre sur le codage avec suffisamment de
notion de programmation pour écrire du code permettant de mettre en œuvre les principes du codage
binaire.
Comme indiqué dans les notes du Chapitre 1, si on préfère ne pas commencer par l’architecture de von
Neumann, il est tout à fait possible d’attendre le présent chapitre pour la traiter soit avant, soit après
lui.

Section 1.3 - Les expressions logiques : évaluation « court-circuit »


Nous avons choisi de traiter la question des évaluations « court-circuit » dans ce chapitre, comme
indiqué dans le programme, au risque de créer de la confusion pour les élèves entre les portes et et ou
et les opérateurs booléens de mêmes noms.
Il conviendra donc de bien distinguer :
• Les opérateurs logiques, définis par leurs tables de vérité et indépendant du matériel comme du
logiciel (ou du langage) ;

• 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 » ;

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


• Les opérateurs binaires, qui ne sont pas abordé dans ce cours et permettent des opérations
binaires bit à bit, telles que le et, le ou et le non, mais aussi les décalages vers la droite et vers la
gauche. En Python, ces opérateurs sont notés respectivement &, |, ~, >> et <<. Nous avons fait le
choix de ne pas les introduire.

Section 2 - La représentation des nombres entiers


Les notations 0o123 pour l’octal et 0x123 et l’hexadécimal sont standard dans de nombreux langages, y
compris Python. Elles ne sont pas explicitées dans le cours mais elles apparaissent dans quelques
exercices, de même que la notation 0b101010 pour le binaire, qui est moins usuelle.

Section 4.3 - Python et UTF-8


Le codage UTF-8, bien que désormais largement répandu, n’est pas encore universel. Ainsi, les
versions de Python antérieures à la version 3 ne reconnaissent le codage UTF-8 d’un fichier contenant
du code Python seulement s’il commence par une ligne « magique » :
# coding: utf-8

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.

Chapitre 9 – Des 0 et des 1 1


Activité : Calculer avec un boulier
Le boulier chinois

Entrée du premier nombre :

Résultat après ajout du deuxième nombre :

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


Le boulier japonais
Question : Utiliser le https:soroban.html[simulateur en ligne] pour représenter des …
Entrée du premier nombre :

Résultat après ajout du deuxième nombre :

Chapitre 9 – Des 0 et des 1 2


Activité : Le codage binaire
Représentation binaire
def binaire(n):
""" retourne la représentation binaire du nombre positif n """
assert n >= 0

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

print('42 en binaire =', binaire(42), '=', bin(42))


print('212 en binaire = ', binaire(212), '=', bin(212))

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


Octal et Hexadécimal
chiffres_octal = '01234567'
def octal(n):
""" retourne la représentation octale du nombre positif n """
assert n >= 0

r = '' # le résultat
# Extraire les chiffres un par un
while n != 0:
r = chiffres_octal[n%8] + r
n = n // 8

return r

print('42 en octal =', octal(42), '=', oct(42))


print('212 en octal =', octal(212), '=', oct(212))

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

Chapitre 9 – Des 0 et des 1 3


return r

print('42 en hexa =', hexa(42), '=', hex(42))


print('212 en hexa =', hexa(212), '=', hex(212))

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 chiffre(c, chiffres):


""" Retourne l'indice de c dans le tableau chiffre """
for i in range(len(chiffres)):
if c == chiffres[i]:
return i
# provoquer une erreur si le chiffre n'est pas dans le tableau
assert False, 'chiffre inconnu !'

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def decode_hexa(h):
""" Retourne le nombre représenté en hexadécimal par la chaîne h """
# Parcourir les chiffres de gauche à droite pour calculer la valeur
n=0
for i in range(len(h)):
c = chiffre(h[i], chiffres_hexa)
n = n*16 + c

return n

print(decode_binaire('101010'), '=', int('101010', 2))


print(decode_binaire('11010100'), '=', int('11010100', 2))

print(decode_octal('52'), '=', int('52', 8))


print(decode_octal('324'), '=', int('324', 8))

print(decode_hexa('2A'), '=', int('2A', 16))


print(decode_hexa('D4'), '=', int('D4', 16))

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

def vers_base(n, base):


""" retourne la représentation en base `base` du nombre positif n """

Chapitre 9 – Des 0 et des 1 4


assert n >= 0
assert base < 36

r = '' # le résultat
# Extraire les chiffres un par un
while n != 0:
r = chiffres_base[n%base] + r
n = n // base

return r

def depuis_base(x, base):


""" Retourne le nombre représenté la chaîne x dans la base `base` """
# Parcourir les chiffres de gauche à droite pour calculer la valeur
n=0
for i in range(len(x)):
c = chiffre(x[i], chiffres_base)
assert c < base, f'{c}: chiffre interdit en base {base}'
n = n*base + c

return n

print(42, vers_base(42, 2), vers_base(42, 8), vers_base(42, 16))


print(212, vers_base(212, 2), vers_base(212, 8), vers_base(212, 16))

print(depuis_base('101010', 2), depuis_base('52', 8), depuis_base('2A', 16))


print(depuis_base('11010100', 2), depuis_base('324', 8), depuis_base('D4', 16))

Capacité de représentation
from math import log2

print(round(log2(1000)+0.5))
print(round(log2(1000000)+0.5))

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


# On peut aussi convertir le nombre en binaire et compter les bits :
print(len(binaire(1000)))
print(len(binaire(1000000)))

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

Vrai Faux Vrai

Faux Vrai Vrai

Faux Faux Faux

Chapitre 9 – Des 0 et des 1 5


b. R = A or B and not (A and B)

A B A or B A and B not A and B R


Vrai Vrai Vrai Vrai Faux Faux

Vrai Faux Vrai Faux Vrai Vrai

Faux Vrai Vrai Faux Vrai Vrai

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

Vrai Faux Faux Vrai Faux Vrai Vrai

Faux Vrai Faux Vrai Vrai Faux Vrai

Faux Faux Faux Vrai Vrai Vrai Vrai

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


A B A ou B non (A ou B) non A non B non A et non B
Vrai Vrai Vrai Faux Faux Faux Faux

Vrai Faux Vrai Faux Faux Vrai Faux

Faux Vrai Vrai Faux Vrai Faux Faux

Faux Faux Faux Vrai Vrai Vrai Vrai

3
A B somme retenue A xor B A and B
Faux = 0 Faux = 0 0 0 Faux = 0 Faux = 0

Faux = 0 Vrai = 1 1 0 Vrai = 1 Faux = 0

Vrai = 1 Faux = 0 1 0 Vrai = 1 Faux = 0

Vrai = 1 Vrai = 1 0 1 Faux = 0 Vrai = 1

Chapitre 9 – Des 0 et des 1 6


4
x >= 0 and sqrt(x) < v
i >= 0 and i < len(t) and t[i] == 0
x > 0 and y < 0 or x < 0 and y > 0
xmax != xmin and (x - xmin)/(xmax - xmin) < 0.5
i == -1 or (i >= 0 and i < len(t))
i >= -1 and i < len(t)

Le code ci-dessous permet de tester les expressions :


from math import sqrt

def t1(x, v):


return x >= 0 and sqrt(x) < v

assert t1(100, 11) == True # x positif et inférieur à v**2


assert t1(-1, 11) == False # racine d'un nombre négatif
assert t1(100, 9) == False # x trop grand

def t2(t, i):


return i >= 0 and i < len(t) and t[i] == 0

assert t2([1,3,0,2], 2) == True # t[2] == 0


assert t2([1,3,0,2], 1) == False # t[1] != 0
assert t2([1,3,0,2], -1) == False # t[-1] n'existe pas
assert t2([1,3,0,2], 10) == False # t[10] n'existe pas

def t3(x, y):


return x > 0 and y < 0 or x < 0 and y > 0

assert t3(5, 10) == False # même signe


assert t3(5, -10) == True # signes différents
assert t3(-5, 10) == True # signes différents
assert t3(-5, -10) == False # même signe

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


def t4(x, xmin, xmax):
return xmax != xmin and (x - xmin)/(xmax - xmin) < 0.5

assert t4(12, 10, 20) == True # dans la première moitié de l'intervalle


assert t4(17, 10, 20) == False # dans la seconde moitié de l'intervalle
assert t4(10, 10, 10) == False # intervalle vide
assert t4(12, 20, 10) == False # intervalle inversé
assert t4(17, 20, 10) == True # intervalle inversé

def t5(t, i):


return i == -1 or (i >= 0 and i < len(t))
# autre formulation :
# return i >= -1 and i < len(t)

assert t5([1,2,3], -2) == False # indice négatif


assert t5([1,2,3], -1) == True # indice 'spécial' -1
assert t5([1,2,3], 2) == True # indice dans tableau
assert t5([1,2,3], 3) == False # indice trop grand

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 :

Chapitre 9 – Des 0 et des 1 7


print(f'Sur 16 bits: de -{2**15} à {2**15-1}')
print(f'Sur 32 bits: de -{2**31} à {2**31-1}')
print(f'Sur 64 bits: de -{2**63} à {2**63-1}')

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


passe à 0 (avec une retenue à 1 pour le deuxième bit) lorsque l’on ajoute 1.
Si un nombre binaire positif se termine par deux zéros, il est divisible par 4. Son complément à deux
se termine également par deux zéros. En effet les deux derniers bits sont inversés et deviennent 11,
puis 00 (avec une retenue à 1 pour le troisième bit) lorsque l’on ajoute 1.
Les deux propriétés sont donc aussi valables pour des nombres négatifs codés en complément à deux.

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

On ajoute à 42 la valeur -56 : ```pseudo


0 0 1 0 1 0 1 0 42
+ 1 1 0 0 1 0 0 0 + (-56)
--------------------------
= 11110010

Complément à deux du résultat :


0 0 0 0 1 1 1 0 14

Donc 42 - 56 = -14.

Chapitre 9 – Des 0 et des 1 8


9 a. De la mème façon que l’on ignore les 0 en tête d’un nombre décimal (0000123 = 123), on peut
ignorer les 0 en tête d’un nombre binaire car ils ne changent pas sa valeur. Les autres bits (ou chiffres)
sont donc « significatifs ».
Lorsque l’on code un nombre binaire négatif en complément à deux, le nombre ne change pas si l’on
ajoute des 1 en tête (de la même façon que des 0 pour un nombre positif). Ainsi le nombre 11110xxx
est identique à 1111111111110xxx. Les seuls 4 derniers bits déterminent sa valeur et sont donc «
significatifs ».
b. Si l’on considère d’abord que n et m sont positifs et ont respectivement i et j bits significatifs, leur
valeur maximale est 2𝑖 − 1 et 2𝑗 − 1. Leur produit est donc au maximum (2𝑖 − 1) × (2𝑗 − 1), soit
2𝑖+𝑗 − 2𝑖 − 2𝑗 + 1, qui est inférieur à 2𝑖+𝑗 − 1. Le nombre de bits significatifs du produit 𝑛 × 𝑚 est
donc au maximum 𝑖 + 𝑗.
La situation est la même si n et/ou m sont négatifs. En effet on peut se ramener au produit des valeurs
absolues, et changer le signe du résultat s’ils sont de signes opposés. Cela n’affecte pas la valeur
absolue du résultat, et donc le nombre de bits significatifs.

10 a. Ce code affiche le poids de chaque bit de la partie fractionnaire :


for n in range(1, 5):
print('bit', n, 1/2**n)

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}')

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


Une autre solution est de convertir les nombres de 0 à 15 en binaire et de calculer la valeur
fractionnaire correspondante. Cette solution a l’avantage d’être plus facile à généraliser à un nombre
de bits quelconque :
frac = [0] * 16
for n in range(16):
valeur = 0 # valeur fractionnaire
binaire = '' # représentation binaire
nombre = n
for i in range(4):
if n % 2: # le bit de poids faible est un 1
binaire = '1'+binaire
valeur += 1/2**(4-i)
else: # le bit de poids faible est un 0
binaire = '0'+binaire
n = n // 2 # on passe au prochain bit
frac[nombre] = valeur
print(f'0.{binaire} = {valeur}')

b.
# On utilise le tableau `frac` du code précédent
for x in range(10):
v = x / 10 # la valeur à approcher

# On cherche d'abord le plus petit indice tel que frac[i] >= v


i=0
while i < len(frac) and frac[i] < v:

Chapitre 9 – Des 0 et des 1 9


i=i+1

# On regarde si le nombre suivant est plus proche


proche = frac[i]
if i < len(frac)-1 and v - proche > frac[i+1] - v:
proche = frac[i+1]

# On calcule l'erreur relative entre v et proche


erreur = 0
if v != 0:
erreur = round(abs(v - proche)/v*100, 2)

print(f'La valeur la plus proche de {v} est {proche}, erreur = {erreur}%')

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re

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)

Chapitre 9 – Des 0 et des 1 10


12 a.
Code décimal Caractère ASCII
0 à 15 caractères non imprimables, par exemple retour à la ligne (0x0D)

16 à 47 caractères spéciaux : espace ! " # $ % & ’ ( ) * + , - . /

48 à 64 chiffres de 0 à 9 et 6 caractères spéciaux (: ; < = > ? @)

65 à 90 lettres majuscules de A à Z

91 à 96 6 caractères spéciaux ([ ] ^ _ `)

97 à 121 lettres minuscules de a à z

122 à 127 4 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():

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


while True:
rep = input('Code décimal ASCII ? ')
if rep.isdigit() and 0 <= int(rep) <= 127:
code = int(rep)
# Chercher l'entrée dans le tableau des catégories :
for min, max, descr in categories:
if min <= code <= max:
# BONUS : on utilise la fonction chr(code)
# qui affiche la valeur du code (voir exercice 26)
print(f'{code} est {descr} : {chr(code)}')
return
else:
print('Entrer un nombre entre 0 et 127')

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_π)

Chapitre 9 – Des 0 et des 1 11


élève = "Dominique"

# plusieurs façons de mettre un caractère UTF-8 dans une chaîne


print("π =", π)
print("Deux fois \u03C0 =", deux_π)
print("Carré de \N{Greek small letter pi} =", carré(π))

print("Emoji = \N{smiling face with smiling eyes}")


print("Smile = \U0001F60A")
print("Sourire = 😊")

smile_😊 = "sourire" # provoque une erreur 'caractère invalide'

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 :

non (non A ou non


A B A et B non A non B non A ou non B B)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Vrai Vrai Vrai Faux Faux Faux Vrai

Vrai Faux Faux Faux Vrai Vrai Faux

Faux Vrai Faux Vrai Faux Vrai Faux

Faux Faux Faux Vrai Vrai Vrai Faux

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

A B A ou B non A non B non A et non B non (non A et non B)


Vrai Vrai Vrai Faux Faux Faux Vrai

Vrai Faux Vrai Faux Vrai Faux Vrai

Faux Vrai Vrai Vrai Faux Faux Vrai

Chapitre 9 – Des 0 et des 1 12


Faux Faux Faux Vrai Vrai Vrai Faux

15 a.
A non A B A⇒B
Vrai Faux Vrai Vrai

Vrai Faux Faux Faux

Faux Vrai Vrai Vrai

Faux Vrai Faux 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 A⇒B B⇒A AB


Vrai Vrai Vrai Vrai Vrai

Vrai Faux Faux Vrai Faux

Faux Vrai Vrai Faux Faux

Faux Faux Vrai Vrai Vrai

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


16 a.
retenue retenue
A+B entrante somme sortante
0+0 +0 =0 0

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.

Chapitre 9 – Des 0 et des 1 13


17 a.
• Lorsque l’entrée S passe à 1, la sortie Q passe à 1, et reste à 1 lorsque S repasse à 0.

• Lorsque l’entrée R passe à 1, la sortie Q passe à 0, et reste à 0 lorsque R repasse à 0.

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.

18 a. Le programme retire de t les éléments jusqu’au premier 0 non inclus.


b. Le programme donne une erreur si t est un tableau sans élément valant 0.
c.
t = [1, 9, 5, 3, 0, 2, 6, 4, 8, 7]

while len(t) > 0 and t.pop(0) != 0:


pass # ne rien faire

print(t)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


d.
def premier_zero(t):
i=0
while i < len(t) and t[i] != 0:
i = i+1
if i == len(t):
return -1
return i

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

Chapitre 9 – Des 0 et des 1 14


n-ième bit de la représentation binaire d’un nombre 𝑁1 vers la droite de façon à ce que le n-ième bit de
𝑁1 devienne le bit de poids faible du nombre 𝑁2 obtenu après la division, on a la propriété suivante :
• si 𝑁2 est pair, alors le bit de poids faible de 𝑁2 est égal à 0, et donc le n-ième bit de 𝑁1 est égal à
0;

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Algorithme : CreerSequenceGray(𝑛)

• Si 𝑛 vaut 1, on retourne la séquence [0, 1] ;

• Sinon, on retourne la concaténation des deux séquences suivantes :

– la séquence CreerSequenceGray(𝑛 − 1), en ajoutant 0 devant chaque nombre ;

– la séquence CreerSequenceGray(𝑛 − 1) prise à l’envers, en ajoutant 1 devant chaque


nombre.

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

999958 représente donc -42.


b. 256 - 190 - codage en complément à 10 : -190 → 999809 + 1 = 999810 - addition et résultat : 256 +
999810 = (1)000 066 = 66
128 - 792 - codage en complément à 10 : -792 → 999207 + 1 = 999208 - addition : 128 + 999208 =
999 336 - résultat : 999336 → 000663 + 1 = -664

Chapitre 9 – Des 0 et des 1 15


22
def binaire(n):
""" Retourne la représentation binaire en complément à 2 du nombre n """
assert n >= -128 and n <= 127

r = ''
# Se ramener à un nombre positif
negatif = False
if n < 0:
n = -n
negatif = True

# Extraire les bits un par un


while n != 0:
if n % 2 == 0:
r = '0' + r
else:
r = '1' + r
n = n // 2

# Compléter par des zéros à gauche


while len(r) < 8:
r = '0' + r

# 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):

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


if b[i] == '1':
c = c + '0'
else:
c = c + '1'

# ajouter 1
return somme(c, '00000001')

# Ce dictionnaire donne la somme s et la retenue sortante rs,


# résultats de l'addition de trois bits (a, b et retenue entrante re)
addition = {
# a b 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', '0', '1'): ('0', '1'),
('1', '1', '0'): ('0', '1'),
('1', '1', '1'): ('1', '1')
}
def somme(b1, b2):
""" Retourne le résultat de l’addition des nombres binaires b1 et b2 """
total = ''
r = '0' # retenue

# Ajouter les bits de gauche à droite


for i in range(7, -1, -1): # accéder les bits de droite à gauche
s, r = addition[(b1[i], b2[i], r)]

Chapitre 9 – Des 0 et des 1 16


total = s + total

# NOTE : on ne contrôle pas le débordement !!


return total

def difference(b1, b2):


""" Retourne le résultat de la soustraction des nombres binaires b1 et b2 """
return somme(b1, complement(b2))

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

# Parcourir les bits de gauche à droite pour calculer la valeur


n=0
for i in range(8):
n = n*2
if b[i] == '1':
n += 1

# Retourner le résultat
if negatif:
return -n
return n

b42 = binaire(42)
print(b42, base10(b42))

bm42 = binaire(-42)
print(bm42, base10(bm42))

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


s = somme(binaire(11), binaire(21))
print(f'11+21 = {base10(s)}, {binaire(11)} + {binaire(21)} = {s}')

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

Chapitre 9 – Des 0 et des 1 17


1 0 1 0 1

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

24 a. Afin de calculer la représentation binaire de la partie fractionnaire d’un nombre 𝑁 sur 𝑛 = 8

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


bits, on propose l’algorithme suivant :
Algorithme : PartieDecimaleEnBinaire(𝑁, 𝑛) - Soit 𝐵 un tableau de bits vide - Soit 𝐷 la partie
décimale de 𝑁 (sous la forme d’un nombre flottant compris entre 0 et 1) - Pour 𝑖 allant de 1 à 𝑛, faire :
1 1
- Si 𝐷 ≥ 𝑛 , alors faire : - Ajouter 1 au tableau de bits 𝐵 - Modifier 𝐷 avec 𝐷 ← 𝐷 − 𝑛 - Sinon faire :
2 2
- Ajouter 0 au tableau de bits 𝐵 - Retourner 𝐵, qui contient les bits de la représentation de la partie
décimale de 𝑁 sur 𝑛 bits
On en propose une implémentation en Python ci-dessous :
from math import floor

def bits_partie_fractionnaire(nombre, nb_bits):


""" Retourne un tableau de bits représentant la partie fractionnaire
de `nombre` en binaire sur `nb_bits`.
"""
bits = []
# on retranche la partie entière du nombre à lui-même
partie_frac = nombre - floor(nombre)

# On calcule les bits un à un à chaque itération de la boucle.


for i in range(1, nb_bits + 1):
puissance_inverse = 2**(-i)

# print(f"{i}-ème bit : {partie_frac}, {puissance_inverse}")

if partie_frac >= puissance_inverse:


bits.append(1)

Chapitre 9 – Des 0 et des 1 18


partie_frac = partie_frac - puissance_inverse
else:
bits.append(0)

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 :

# tous les bits doivent valoir 0


print(bits_vers_chaine(bits_partie_fractionnaire(1.0, 8)))

# tous les bits doivent valoir 1


print(bits_vers_chaine(bits_partie_fractionnaire(0.99999999, 8)))

# 01001100 (cf. « La représentation des nombres fractionnaires » dans le cours)


print(bits_vers_chaine(bits_partie_fractionnaire(0.3, 8)))

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


# On calcule les bits un à un à chaque itération de la boucle.
# Attention : contrairement à l'alogirthme pour la partie fractionnaire,
# on va cette fois-ci de 2^(n-1) à 2^0 !
for i in range(0, nb_bits):
puissance = 2**(nb_bits - i - 1)

# print(f"{i}-ème bit : {partie_entiere}, {puissance}")

if partie_entiere >= puissance:


bits.append(1)
partie_entiere = partie_entiere - puissance
else:
bits.append(0)

return bits

# Quelques tests :

# tous les bits doivent valoir 0


print(bits_vers_chaine(bits_partie_entiere(0.0, 8)))

# tous les bits doivent valoir 0 sauf le dernier


print(bits_vers_chaine(bits_partie_entiere(1.0, 8)))

# tous les bits doivent valoir 0 sauf le dernier


print(bits_vers_chaine(bits_partie_entiere(1.99999999, 8)))

# 00101010
print(bits_vers_chaine(bits_partie_entiere(42, 8)))

Chapitre 9 – Des 0 et des 1 19


# tous les bits doivent valoir 1
print(bits_vers_chaine(bits_partie_entiere(255, 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é

- On retourne le résultat obtenu sous forme d’un 2-uplet (𝐸, 𝐷)


On propose une implémentation en Python ci-dessous :
# On commence par définir quelques fonctions pour créer et afficher des
# nombres binaires en utilisant la représentation à virgule fixe de cet exercice.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


NB_BITS = 8

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

# On itère sur les bits de la partie entière


# ET de la partie décimale dans une seule boucle.
# À chaque itération, on ajoute la « contribution » des bits

Chapitre 9 – Des 0 et des 1 20


# à l'indice courant au résultat
# (nulle si le bit vaut 0, ou une certaine puissance de 2 sinon).
for i in range(NB_BITS):
puissance = NB_BITS - 1 - i
# puissance de 2 * i-ième bit de la partie entière
nombre += 2**(NB_BITS - 1 - i) * e[i]
# puissance inverse de 2 * i-ième bit de la partie décimale
nombre += 2**(-i - 1) * d[i]

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}")

# On définit l'addition de tableaux de bits, ainsi que


# l'addition de deux nombres binaires dans notre représentation en virgule fixe.

def additionner_bits(bits_1, bits_2):


""" Retourne un 2-uplet (bits, retenue) contenant un tableau de bits
représentant la somme des deux tableaux de bits passés en paramètre
et une retenue égale à 1 s'il y a un dépassement de capacité (ou 0 sinon).
"""
bits_somme = []
retenue = 0

# On additionne les bits de bits_1 et bits_2


# en commençant par les bits de poids faible
# (i.e. de la fin des tableaux de bits).

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


for i in range(NB_BITS):
bit_1 = bits_1[NB_BITS - 1 - i]
bit_2 = bits_2[NB_BITS - 1 - i]

if bit_1 + bit_2 + retenue == 3:


bits_somme.append(1)
retenue = 1
elif bit_1 + bit_2 + retenue == 2:
bits_somme.append(0)
retenue = 1
elif bit_1 + bit_2 + retenue == 1:
bits_somme.append(1)
retenue = 0
else: # bit_1, bit_2 et retenue valent tous 0
bits_somme.append(0)
retenue = 0

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

def additionner_nombres_binaires(nombre_binaire_1, nombre_binaire_2):


""" Retourne un 2-uplet représentant la somme
des deux nombres binaires fournis.
"""

Chapitre 9 – Des 0 et des 1 21


e1, d1 = nombre_binaire_1
e2, d2 = nombre_binaire_2

# On additione les parties décimales des deux nombres.


bits_somme_1, retenue_1 = additionner_bits(d1, d2)

# On écrit la retenue sous la forme d'un tableau de NB_BITS bits.


bits_retenue_1 = [0] * NB_BITS
bits_retenue_1[NB_BITS - 1] = retenue_1

# On additionne les parties entières des deux nombres et la retenue.


bits_somme_2, retenue_2 = additionner_bits(e1, e2)
bits_somme_3, retenue_3 = additionner_bits(bits_somme_2, bits_retenue_1)

return (bits_somme_2, bits_somme_1)

# Quelques tests :

def test_somme(nombre_1, nombre_2):


print(f"Somme de {nombre_1} et {nombre_2} :")

nombre_binaire_1 = creer_nombre_binaire(nombre_1)
nombre_binaire_2 = creer_nombre_binaire(nombre_2)

somme = additionner_nombres_binaires(nombre_binaire_1, nombre_binaire_2)

afficher_nombre_binaire(nombre_binaire_1)
print(" +")
afficher_nombre_binaire(nombre_binaire_2)
print(" =")
afficher_nombre_binaire(somme)
print("")

test_somme(25, 75)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


test_somme(1.6, 3.4)
test_somme(1.00000001, 8.99999999)

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

Chapitre 9 – Des 0 et des 1 22


test(10.5)
test(1.23456)

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 = []

# On convertit chaque caractère en une chaîne de caractères


# comprenant deux chiffres du code du caractère en hexadécimal.
for caractere in chaine:
code_hexa = caractere_vers_hexa(caractere)
codes_hexa.append(code_hexa[2:])

return " ".join(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.
"""

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


# le second argument de int() sert à spécifier la base
return chr(int(code_hexa, 16))

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 = []

# On transforme chaque code héxadécimal (des chaînes de longueur 2)


# sous forme de caractère, que l'on transforme en majuscule,
# puis re-transforme sous forme de chaîne de caractères
# comprenant deux chiffres du code du (nouveau) caractère en hexadécimal.
for code_hexa in codes_hexa:
caractere = code_hexa_vers_caractere(code_hexa)
code_hexa_majuscule = caractere_vers_hexa(caractere.upper())
codes_hexa_majuscules.append(code_hexa_majuscule[2:])

return " ".join(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,

Chapitre 9 – Des 0 et des 1 23


qui contient les codes héxadécimaux des caractères séparés par des espaces.
"""
codes_hexa = chaine_hexa.split(" ")
caracteres = []

# On transforme chaque code héxadécimal (des chaînes de longueur 2)


# sous forme de caractère.
for code_hexa in codes_hexa:
caractere = code_hexa_vers_caractere(code_hexa)
caracteres.append(caractere)

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

for car in texte:


if car == '\n': # fin de ligne de type Linux
if type != 'inconnu' and type != 'Linux':
return 'incohérent'
type = 'Linux'

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


elif car == '\r': # fin de ligne de type MacOS
if type != 'inconnu' and type != 'MacOS Classic':
return 'incohérent'
type = 'MacOS Classic'

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

for car in texte:


if car == '\n':
if precedent == '\r': # on a une séquence \r\n
if type != 'inconnu' and type != 'Windows':
return 'incohérent'
type = 'Windows'

else: # c'est un \n seul


if type != 'inconnu' and type != 'Linux':
return 'incohérent'
type = 'Linux'

elif precedent == '\r': # on avait un \r non suivi d'un \n

Chapitre 9 – Des 0 et des 1 24


if type != 'inconnu' and type != 'MacOS Classic':
return 'incohérent'
type = 'MacOS Classic'

precedent = car

# Attention : le dernier caractère peut être un \r !


if car == '\r':
if type != 'inconnu' and type != 'MacOS Classic':
return 'incohérent'
return 'MacOS Classic'

return type

linux = "il fait beau\nmais\nil va pleuvoir\n"


windows = "il fait beau\r\nmais\r\nil va pleuvoir\r\n"
macos = "il fait beau\rmais\ril va pleuvoir\r"
macos2 = "il fait beau mais il va pleuvoir\r"
inconnu = "il fait beau mais il va pleuvoir"
incoherent = "il fait beau\nmais\r\nil va pleuvoir\n"

assert fdl(linux) == 'Linux'


assert fdl(windows) == 'Windows'
assert fdl(macos) == 'MacOS Classic'
assert fdl(macos2) == 'MacOS Classic'
assert fdl(inconnu) == 'inconnu'
assert fdl(incoherent) == 'incohérent'

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='')

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


texte = f.read()
print(nom, fdl(texte))

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

Chapitre 9 – Des 0 et des 1 25


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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


I F I I

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

Chapitre 9 – Des 0 et des 1 26


Montrons d’abord que la propriété (1) est préservée : Dans le cas où 𝑁 ≥ 0, la représentation binaire
de 𝑁 est identique à la représentation canonique d’un nombre en base 2 : on se réfère donc à l’énoncé
pour affirmer que décaler les bits à gauche multiplie la valeur encodée par 2.

Dans le cas où 𝑁 < 0, 𝑁 étant représenté en binaire par complément à deux, on a 𝑁 = 2𝑛 −


(2𝑛−1 𝑏𝑛 + 2𝑛−2 𝑏𝑛−1 + ⋯ + 20 𝑏1 ) et 𝑏𝑛 = 1. On pose alors :

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 :

Dans le cas où 𝑁 ≥ 0, et donc 𝑏𝑛 = 0 :

- Si 𝑁 < 2𝑛−2 , et donc 𝑏𝑛−1 = 0, alors le 𝑛-ième bit demeurera à 0 lors du décalage à gauche.

- Si 𝑁 ≥ 2𝑛−2 , et donc 𝑏𝑛−1 = 1, alors 2𝑁 ≥ 2𝑛−1 : lors du décalage à gauche, il y aura un


dépassement de capacité (car la plus grande valeur positive que l’on peut ici représenter est
2𝑛 − 1), et le 𝑛-ième bit passera à 1 (le dépassement fera apparaître 2𝑁 comme un nombre
négatif).
Dans le cas où 𝑁 < 0, et donc 𝑏𝑛 = 1 :

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


- Si 𝑁 > −2𝑛−2 − 1, et donc 𝑏𝑛−1 = 1, alors le 𝑛-ième bit demeurera à 1 lors du décalage à
gauche.

- Si 𝑁 ≤ −2𝑛−2 − 1, et donc 𝑏𝑛−1 = 0, alors 2𝑁 ≤ 2𝑛−1 − 1 : lors du décalage à gauche, il y


aura un dépassement de capacité (car la plus petite valeur négative que l’on peut ici
représenter est −2𝑛 ), et le 𝑛-ième bit passera à 0 (le dépassement fera apparaître 2𝑁 comme
un nombre positif).
Dans le cas d’un décalage à droite (division par deux) :
Note : de par la façon dont fonctionne un décalage à droite, l’opération réalisée est plus exactement
une division par deux arrondie à l’entier inférieur. Dans le cas d’un nombre pair, cela ne change rien
; dans le cas d’un nombre impair, cela signifie que la partie décimale du résultat est ignorée (étant
donné que la représentation ici utilisée ne pouvant coder que des nombres entiers, et que
l’information portée par le bit sortant est perdue).
Dans le cas d’un décalage à droite, il n’y a pas de risque de dépassement. En revanche, le bit de poids
fort (𝑏𝑛 ) étant décalé vers la droite et remplacé par le bit entrant, il faut s’assurer que celui-ci soit
identique au bit 𝑏𝑛 avant le décalage, afin de préserver le signe de 𝑁. Il s’agit précisément de la
condition énoncée dans la question : si l’on s’assure que le bit entrant est le même que 𝑏𝑛 , alors le
signe de 𝑁 demeure inchangé par le décalage.
On montre ci-dessous que la valeur absolue de 𝑁 est bien divisée par deux par un décalage à droite :

Chapitre 9 – Des 0 et des 1 27


Dans le cas où 𝑁 ≥ 0, la représentation binaire de 𝑁 est identique à la représentation canonique d’un
nombre en base 2 : on se réfère donc à l’énoncé pour affirmer que décaler les bits à droite en insérant
un 0 à gauche divise la valeur encodée par 2 (étant donné que si 𝑁 ≥ 0, alors 𝑏𝑛 = 0).

Dans le cas où 𝑁 < 0, 𝑁 étant représenté en binaire par complément à deux, on a 𝑁 = 2𝑛 −


(2𝑛−1 𝑏𝑛 + 2𝑛−2 𝑏𝑛−1 + ⋯ + 20 𝑏1 ). On pose alors :

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 :

- Si 𝑁 ≥ 0, et donc 𝑏𝑛 = 0, et 𝑏𝑛−1 = 1 (c’est-à-dire si 𝑁 ≥ 2𝑛−2 ) ;

- Si 𝑁 < 0, et donc 𝑏𝑛 = 1, et 𝑏𝑛−1 = 0 (c’est-à-dire si 𝑁 ≤ −2𝑛−2 − 1).

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


30 a. Pour réaliser la multiplication de deux nombres représentés dans une base donnée, on procède de
la même manière que pour multiplier deux nombres décimaux, en multipliant le premier nombre par
chacun des chiffres du second :
1 fonction multiplier(A, B, base)
2 résultat <- 0
3 tantque B != 0 faire
4 d <- B % base # reste de la division par la base = dernier chiffre de B
5 résultat <- résultat * base + A * d
6 B <- B // base # quotient de la division par la base = décaler B à droite
7 A <- A * base # multiplier par la base = décaler A vers la gauche
8 fin
9 retourner résultat

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

Chapitre 9 – Des 0 et des 1 28


3 tantque B != 0 faire
4 si B % 2 == 1 alors
5 résultat <- résultat + A
6 fin
7 décaler B à droite # diviser B par 2
8 décaler A à gauche # multiplier A par 2
9 fin
10 retourner résultat

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]

# On reprend la table de l'exercice 22


addition = {
('0', '0', '0'): ('0', '0'),
('0', '0', '1'): ('1', '0'),
('0', '1', '0'): ('1', '0'),

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


('0', '1', '1'): ('0', '1'),
('1', '0', '0'): ('1', '0'),
('1', '0', '1'): ('0', '1'),
('1', '1', '0'): ('0', '1'),
('1', '1', '1'): ('1', '1')
}
# La fonction est modifiée car dans l'exercice 22
# les nombres ont toujours 8 bits, alors qu'ici ils
# n'ont pas forcément le même nombre de bits
def somme(A, B):
""" Additionne les représentations binaires
(sous forme de chaînes) de A et B
"""
total = ''
r = '0' # retenue
while A != '' or B != '':
a = bit_poids_faible(A)
b = bit_poids_faible(B)
s, r = addition[(a, b, r)]
total = s + total
A = decalage_droite(A)
B = decalage_droite(B)

if r == '1':
total = r + total
return total

def multiplier(A, B):


""" Multiplie les représentations binaires

Chapitre 9 – Des 0 et des 1 29


(sous forme de chaînes) de A par B
"""
resultat = ''
while B != '':
if bit_poids_faible(B) == '1':
resultat = somme(resultat, A)
B = decalage_droite(B)
A = decalage_gauche(A)
return resultat

print(0b0110, 0b0101, 0b011110)


print(multiplier('0110', '0101'))

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


def add_petits_nombres():
return 12345 + 67890

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

# On estime le temps d'exécution moyen des fonctions définies ci-dessus.


# On retranche le temps d'exécution de la fonction vide aux autres mesures.

temps_add_petits_nombres = chrono(add_petits_nombres, nb_repetitions)


temps_add_grands_nombres = chrono(add_grands_nombres, nb_repetitions)

temps_mul_petits_nombres = chrono(mul_petits_nombres, nb_repetitions)


temps_mul_grands_nombres = chrono(mul_grands_nombres, nb_repetitions)

temps_vide = chrono(fonction_vide, nb_repetitions)

# On affiche les résultats (donnés en secondes).

print(f"=== Temps d'exécutions pour {nb_repetitions} répétitions ===")

Chapitre 9 – Des 0 et des 1 30


print(f"Fonction vide : {temps_vide} secondes")

print(f"Addition (petits nombres) : ")


print(f"{temps_add_petits_nombres} secondes")
print(f"{temps_add_petits_nombres - temps_vide} secondes hors appel de fonction")

print(f"Addition (grands nombres) : ")


print(f"{temps_add_grands_nombres} secondes")
print(f"{temps_add_grands_nombres - temps_vide} secondes hors appel de fonction")

print(f"Multiplication (petits nombres) : ")


print(f"{temps_mul_petits_nombres} secondes")
print(f"{temps_mul_petits_nombres - temps_vide} secondes hors appel de fonction")

print(f"Multiplication (grands nombres) : ")


print(f"{temps_mul_grands_nombres} secondes")
print(f"{temps_mul_grands_nombres - temps_vide} secondes hors appel de fonction")

c.
# On commence par écrire quelques fonctions utilitaires.

# Note : dans la suite, on parlera des "parties" d'un grand nombre


# pour désigner les nombres d'au plus trois chiffres
# contenus dans le tableau qui représente le grand nombre.

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]

# Sinon, on construit le grand nombre.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


resultat = []
n_restant = n
numero_partie = 1

# On "découpe" le nombre fourni par parties d'au plus trois chiffres


# (une par itération).
while n_restant > 0:
puissance = (10**(3 * numero_partie)) # 10^3, puis 10^6, 10^9, etc.
puissance_inf = (10**(3 * (numero_partie - 1))) # 1, puis 10^3, 10^6, etc.

# On récupère la partie du nombre qui nous intéresse (max. 3 chiffres).


partie = n_restant % puissance // puissance_inf
resultat.insert(0, partie)

# On met à jour le nombre qu'il reste à "découper" en parties


# et le numéro de la prochaine partie.
n_restant -= partie * puissance_inf
numero_partie += 1

return resultat

def grand_nombre_vers_chaine(grand_n):
"""Retourne une chaîne de caractère représentant le grand nombre fourni."""
parties_chaine = []

# On crée la chaîne partie par partie.


for partie in grand_n:
# On ajoute des zéros à gauche de la chaîne représentant la partie

Chapitre 9 – Des 0 et des 1 31


# de manière à avoir une chaîne de 3 caractères.
zeros = "0" * (3 - len(str(partie)))
partie_chaine = zeros + str(partie)
parties_chaine.append(partie_chaine)

# On remplace la chaîne représentant la première partie


# par une version sans zéro inutile.
parties_chaine[0] = str(grand_n[0])

return "".join(parties_chaine)

# On écrit ensuite une fonction qui additionne deux grands nombres :

def egalise_nb_parties_avec_zeros(grand_n1, grand_n2):


"""
Retourne deux grands nombres égaux aux deux grands nombres fournis
mais assurés de contenir le même nombre de parties
(en ajoutant des parties de poids fort à zéro si nécessaire).
"""
gn1 = grand_n1.copy()
gn2 = grand_n2.copy()

nb_parties_gn1 = len(gn1)
nb_parties_gn2 = len(gn2)

if nb_parties_gn1 > nb_parties_gn2:


for _ in range(nb_parties_gn1 - nb_parties_gn2):
gn2.insert(0, 0)
elif nb_parties_gn2 > nb_parties_gn1:
for _ in range(nb_parties_gn2 - nb_parties_gn1):
gn1.insert(0, 0)

return gn1, gn2

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


def big_add(grand_n1, grand_n2):
""" Additionne les deux grands nombres et
retourne le résultat sous forme d'un grand nombre.
"""
resultat = []
retenue = 0
numero_partie = 1

grand_n1, grand_n2 = egalise_nb_parties_avec_zeros(grand_n1, grand_n2)

# On ajoute les parties de la chaîne entre elles,


# avec une éventuelle retenue passée à l'itération suivante.
longueur = len(grand_n1)
for i in range(longueur):
partie_grand_n1 = grand_n1[longueur -1 -i]
partie_grand_n2 = grand_n2[longueur -1 -i]

somme_parties = partie_grand_n1 + partie_grand_n2 + retenue

# Si la somme ne tient pas sur trois chiffres, on a une retenue.


if (somme_parties > 999):
retenue = 1
somme_parties = somme_parties % 10**3
else:
retenue = 0

resultat.insert(0, somme_parties)
numero_partie += 1

Chapitre 9 – Des 0 et des 1 32


# S'il reste une retenue, on ajoute une dernière partie.
if retenue > 0:
resultat.insert(0, retenue)

return resultat

# Quelques tests de création de grands nombres

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

# On reproduit l'addition donnée en exemple dans la question,


# en comparant avec Python :

grand_n4 = creer_grand_nombre(34666524)
grand_n5 = creer_grand_nombre(32397179)

somme_1 = big_add(grand_n4, grand_n5)

print(f"Somme : {grand_nombre_vers_chaine(somme_1)}")
print(f" {34666524 + 32397179} (version Python)")

# On teste un calcul avec des nombres beaucoup plus grands :

grand_n6 = creer_grand_nombre(10**100 + 1)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


grand_n7 = creer_grand_nombre(10**100)

somme_2 = big_add(grand_n6, grand_n7)

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 = []

# On itère sur les parties des deux nombres.


# Pour chaque combinaison (ordonnée) de parties, on en fait le produit,
# lui-même multiplié par la bonne puissance de 1000 (un "sous-produit").
long1 = len(grand_n1)
long2 = len(grand_n2)
for i in range(long1):
partie_n1 = grand_n1[long1 -1 -i]
for j in range(long2):
partie_n2 = grand_n2[long2 -1 -j]
produit = creer_grand_nombre(partie_n1 * partie_n2)

# Afin de prendre en compte la position des parties dans


# les deux nombres, on insère autant de zéros (= produits par 1000)
# que la somme des indices des deux parties.

Chapitre 9 – Des 0 et des 1 33


for _ in range(i + j):
produit.append(0)

sous_produits.append(produit)

# Afin de calculer le résulat du produit des deux grands nombres,


# on additionne tous les sous-produits en lesquels on l'a décomposé.
resultat = creer_grand_nombre(0)

while len(sous_produits) > 0:


facteur = sous_produits.pop()
resultat = big_add(resultat, facteur)

return resultat

# Quelques tests :

grand_n1 = creer_grand_nombre(6)
grand_n2 = creer_grand_nombre(7)

produit = big_mul(grand_n1, grand_n2)

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)

produit = big_mul(grand_n3, grand_n4)

print(f"Produit : {grand_nombre_vers_chaine(produit)}")
print(f" {10**123 * 10**321} (version Python)")

# On compare notre multiplication de grands nombres avec celle de Python.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


nb_repetitions = 10**2

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

temps_exec_produit_exercice = chrono(produit_exercice, nb_repetitions)


temps_exec_produit_python = chrono(produit_python, nb_repetitions)

print(f"=== Temps d'exécutions pour {nb_repetitions} répétitions ===")

print(f"Produit (version exercice) : {temps_exec_produit_exercice} secondes")


print(f"Produit (version Python) : {temps_exec_produit_python} secondes")

rapport_vitesses = temps_exec_produit_exercice / temps_exec_produit_python


print(f"La version Python est {rapport_vitesses} fois plus rapide")

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

Chapitre 9 – Des 0 et des 1 34


32 a.
print(ord('π'))
print(chr(960))
print(ord('😊'))
print(chr(128522))

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


d.
b'\xc3\x80 c\xc3\xb4t\xc3\xa9'.decode()

Chapitre 9 – Des 0 et des 1 35


Chapitre 10 – Réseau
Prérequis -TEST
1 c ; 2 b ; 3 c ; 4 b ; 5 c ; 6 a ; 7 c.

Activité : Envoi d’une donnée par paquets


Installation de liens

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

0 Inoccupé Inoccupé Inoccupé Inoccupé

1 Donnée X Réservé Inoccupé Inoccupé

2 Réservé Donnée X Inoccupé Inoccupé

Approche 2

© Hachette Livre 2021 – Guide pédogique Numérique et Sciences informatiques 1 re


Unités de temps Lien A-B Lien B-C Lien C-D Lien D-A

0 Inoccupé Inoccupé Inoccupé Inoccupé

1 Donnée X Inoccupé Inoccupé Inoccupé

2 Inoccupé Donnée X Inoccupé Inoccupé

Envois simultanés
1.a.

Donnée Liens réservés

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

0 Inoccupé Inoccupé Inoccupé Inoccupé

1 Donnée X Réservé Inoccupé Inoccupé

2 Réservé Donnée X Inoccupé Inoccupé

3 Donnée Y Inoccupé Inoccupé Inoccupé

4 Inoccupé Donnée Z Réservé Inoccupé

5 Inoccupé Réservé Donnée Z Inoccupé

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


2.

Unités de temps Lien A-B Lien B-C Lien C-D Lien D-A

0 Inoccupé Inoccupé Inoccupé Inoccupé

1 Donnée X Donnée Z Inoccupé Inoccupé

2 Donnée Y Donnée X Donnée Z Inoccupé

Autre configuration

Unités de temps Lien E-F Lien H-F Lien F-G

0 Inoccupé Inoccupé Inoccupé

1 Donnée X Donnée Y Inoccupé

2 Inoccupé Inoccupé Donnée X

3 Inoccupé Inoccupé Donnée Y

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

0 Inoccupé Inoccupé Inoccupé Vide

1 Paquet X1 Paquet Y1 Inoccupé Vide

2 Paquet X2 Paquet Y2 Paquet X1 Paquet Y1

3 Inoccupé Inoccupé Paquet Y1 Paquet X2, Paquet Y2

4 Inoccupé Inoccupé Paquet X2 Paquet Y2

5 Inoccupé Inoccupé Paquet Y2 Vide

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


QCM (CHECKPOINT)
1 b ; 2 a et c ; 3 a ; 4 a et b ; 5 a, b et c ; 6 a ; 7 b ; 8 b ; 9 c ; 10 a ; 11 b ; 12 c ; 13 a.

TP : Protocole du bit alterné sur UDP


Note : Les codes Python des programmes du TP sont dans le dossier TP_reseau_corrige. Chaque étape a
deux fichiers MachineA1.py et MachineB1.py pour l’étape 1, MachineA2.py et MachineB2.py pour l’étape 2, etc.
Il faut lancer les programmes Machine A et Machine B dans deux fenêtres Terminal différentes, en
commençant par Machine B (récepteur). En effet on utilise le protocole UDP selon lequel un message
envoyé est perdu s’il n’y a pas de récepteur pour le lire.

Réception de la machine B (étape 1)


import socket

# créer le récepteur
receveur = socket.socket(type = socket.SOCK_DGRAM)
receveur.bind(('localhost', 56789))

# attendre la réception d'un message et l'afficher


donnee, adresse = receveur.recvfrom(1500)
print (donnee, adresse)

Émission de la machine A (étape 1)


import socket

# créer l'émetteur
emetteur = socket.socket(type = socket.SOCK_DGRAM)

# envoyer un message
emetteur.sendto(b"Hello world", ('localhost',56789))

Échange entre plusieurs machines


À partir de cette étape, la communication peut se faire entre deux machines différentes, comme
indiqué dans l’énoncé, après avoir identifié l’adresse IP de chaque machine et modifié les adresses
dans les programmes.

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

Ajout du bit de contrôle (étape 2)


Note : À partir de cette étape nous utilisons la fonction suivante qui peut être fournie aux élèves pour
simplifier la programmation. En effet cette fonction utilise les « slices » de Python, qui ne sont pas au
programme. On peut remplacer remplacer son utilisation par une boucle qui recopie la partie souhaitée
du message, mais cela est fastidieux avec des chaînes d’octets, et peu efficace. Le code de la fonction
n’est pas explicitement listé dans les codes des machines A et B ci-dessous par souci de concision,
mais il est inclus dans les version Python des programmes dans le dossier TP_reseau_corrige.
# Fonction à fournir aux élèves
def decoderPaquet(donnee):
""" Extrait de `donnee` le bit de contrôle et le contenu du message """
# on utilise une facilité de Python appelée "tranches" (slices)
# pour "découper" le message en bit de contrôle (dernier octet : donnee[-1])
# et reste du message (donnee[:1] = tout le tableau sans le dernier octet)
return (donnee[-1], donnee[:-1])

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Machine A
import socket

def envoieAvecBit(donnee, bit, emetteur, machine, port):


""" Envoi d'un message avec un bit de contrôle """
x = bytearray(donnee) # transforme la donnée (chaîne) en tableau d'octets
x.append(bit) # ajoute le bit de contrôle
emetteur.sendto(x, (machine,port))

# créer l'émetteur
emetteur = socket.socket(type = socket.SOCK_DGRAM)

# envoyer le message avec le bit de contrôle


envoieAvecBit(b"Hello world", 1, emetteur, 'localhost', 56789)

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

# attendre de recevoir un message et l'afficher


donnee, bit, adresse = recoitAvecBit(receveur)
print(donnee, bit, adresse)

Renvoi du bit de contrôle (étape 3)


Machine A
import socket

def envoieAvecBit(donnee, bit, emetteur, machine, port):


""" Envoi d'un message avec un bit de contrôle """
x = bytearray(donnee) # transforme la donnée (chaîne) en tableau d'octets
x.append(bit) # ajoute le bit de contrôle

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

# envoyer le message avec le bit de contrôle


envoieAvecBit(b"Hello world", 1, emetteur, 'localhost', 56789)

# attendre de recevoir le message de confirmation


donnee, bit, adresse = recoitAvecBit(receveur)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


print(donnee, bit, adresse)

Machine B
import socket

def envoieAvecBit(donnee, bit, emetteur, machine, port):


""" Envoi d'un message avec un bit de contrôle """
x = bytearray(donnee) # transforme la donnée (chaîne) en tableau d'octets
x.append(bit) # ajoute le bit de contrôle
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 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)

# attendre de recevoir un message et l'afficher


donnee, bit, adresse = recoitAvecBit(receveur)
print(donnee, bit, adresse)

# envoyer l'accusé de réception avec le même bit


envoieAvecBit(b"ok", bit, emetteur, adresse[0], 56790)

Envois multiples (étape 4)


Machine A
import socket

def envoieAvecBit(donnee, bit, emetteur, machine, port):


""" Envoi d'un message avec un bit de contrôle """
x = bytearray(donnee) # transforme la donnée (chaîne) en tableau d'octets
x.append(bit) # ajoute le bit de contrôle
emetteur.sendto(x, (machine,port))

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)

# créer l'émetteur et le récepteur


emetteur = socket.socket(type = socket.SOCK_DGRAM)
receveur = socket.socket(type = socket.SOCK_DGRAM)
receveur.bind(('localhost', 56790))

# tableau des messages à envoyer


donnees = [b"Hello world", b"How are you", b"ok"]
# bit de contrôle
bit = 1
for d in donnees:
# boucle d'envoi
renvoi = True
print('envoi de', d)
while renvoi:

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


# envoi
envoieAvecBit(d, bit, emetteur, 'localhost', 56789)
# attente de confirmation
recu, bit_recu, adresse = recoitAvecBit(receveur)
# fin de la boucle d'envoi si le bit est celui attendu
if bit_recu == bit:
renvoi = False
else:
print('bit de contrôle erronné : renvoi')
# inverser le bit de contrôle
if bit == 1:
bit = 0
else:
bit = 1

Machine B
import socket
import random

def envoieAvecBit(donnee, bit, emetteur, machine, port):


""" Envoi d'un message avec un bit de contrôle """
x = bytearray(donnee) # transforme la donnée (chaîne) en tableau d'octets
x.append(bit) # ajoute le bit de contrôle
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 le récepteur et l'émetteur


receveur = socket.socket(type = socket.SOCK_DGRAM)
receveur.bind(('localhost', 56789))
emetteur = socket.socket(type = socket.SOCK_DGRAM)

# boucle infinie de réception des messages


# (arrêter le programme avec Control-C)
while True:
# attendre la réception d'un message
donnee, bit, adresse = recoitAvecBit(receveur)
print (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)

Simuler les pertes de réseaux (étape 5)


Machine A : identique à la version précédente
Machine B : modifier la boucle de lecture comme suit :
# boucle infinie de réception des messages
# (arrêter le programme avec Control-C)
while True:
# attendre la réception d'un message
donnee, bit, adresse = recoitAvecBit(receveur)
print (donnee, bit, adresse)

# simuler une erreur (inversion du bit de contrôle) 1 message sur 3


if random.randint(1, 3) == 3:
if bit == 1:
bit = 0
else:
bit = 1
envoieAvecBit(b"", bit, emetteur, adresse[0], 56790)

Pour aller plus loin (étape 6)


On crée une bibliothèque commune comme_tcp.py qui contient les fonctions demarrer et envoyer :
Note : La bibliothèque comme_tcp.py commence par deux fonctions qui utilisent les « slices » Python
pour décoder un paquet et pour extraire une tranche d’un message. Ces fonctions peuvent être fournies
aux élèves pour faciliter la programmation. (Veuillez noter que la fonction decoderPaquet est différente
de celle utilisée dans les étapes précédentes car ici l’octet contenant le numéro de paquet est au début
du message, alors que précédemment le bit de contrôle était à la fin de celui-ci).

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


import socket

# Fonction à fournir aux élèves


def tranche(donnee, debut, fin):
""" Retourne la partie de `donnee` entre les indices `debut` et `fin -1`"""
# on utilise une facilité de Python appelée "tranches" (slices)
return donnee[debut:fin]

# Fonction à fournir aux élèves


def decoderPaquet(donnee):
""" Retourne un tuple (numéro de séquence, contenu du message) """
# on utilise une facilité de Python appelée "tranches" (slices)
# pour "découper" le message en numéro de séquence (premier octet : donnee[0])
# et reste du message (donnee[1:] = tout le tableau sans le premier octet)
return (donnee[0], donnee[1:])

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

def envoyer(donnee, machine, port, taille):


""" Envoie un message selon un protocole similaire à TCP.
Le message est la chaîne de caractères `données.
Le destinataire est `machine: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""

while octetAEnvoyer < len(donnee) or len(recu) > 1 :

# calculer le rang du dernier octet à envoyer dans ce paquet


fin = octetAEnvoyer + taille
if fin >= len(donnee):
fin = len(donnee)

# x est le message à envoyer,


# il commence par le numéro du dernier octet reçu
x = bytearray(1)
## Attention, ne fonctionne que pour des tailles de données <256
x[0] = octetRecu

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


# ajouter le contenu du paquet
if octetAEnvoyer < len(donnee):
x += tranche(donnee, octetAEnvoyer, fin)

# envoyer le message et attendre la réponse


emetteur.sendto(x, (machine,port))
recu, adresse = receveur.recvfrom(1500)

# mettre à jour le numéro du prochain octet à envoyer


# en fonction de l'information du message reçu,
# et extraire le corps du message recu
octetAEnvoyer, messageRecu = decoderPaquet(recu)

# mettre à jour le numéro du dernier octet reçu


if len(recu) > 1 :
octetRecu += len(recu) - 1

print()
print("Envoye : ", x)
print("Recu : ", messageRecu, len(recu))
print("Octet recu : ", octetRecu)
print("Octet a envoyer", octetAEnvoyer)

# Afficher le message reçu lorsqu'il est complet


messageComplet += messageRecu
if len(recu) == 1 and len(messageComplet) > 0:
print("===> Message complet : ", messageComplet)
messageComplet = b""

# Permet d'envoyer un dernier message à l'autre machine si elle est en attente


x = bytearray(1)
x[0] = octetRecu
emetteur.sendto(x, (machine,port))

print("Fini")

Les codes des deux machines sont alors les suivants.


Machine A
import comme_tcp as tcp

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


- d’un entête qui contient les informations nécessaires au transport de ce paquet jusqu’à sa destination.
Il contient en particulier une adresse source, une adresse destination, une taille de paquet, un
checksum, sa taille, le protocole de niveau 4 utilisé, etc.

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

2 a. Un entête IPv4 est composé d’au moins 20 octets.


b. Un entête IPv6 est composé d’au moins 40 octets.
c. Il n’y a pas de taille minimum pour une zone de données, elle peut être égale à 0.

3 a. Une adresse IPv4 est codée sur 4 octets.


b. Une adresse IPv6 est codée sur 16 octets.
c. Une adresse Ethernet est composée de 6 octets.

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

5 a. Le protocole du bit alterné est un protocole de fiabilisation.


b. Une adresse source peut être une adresse Ethernet mais aussi une adresse IP.

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

decoupagePaquets('D’amour mourir me font, belle Marquise, vos beaux yeux', 10)

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


return r

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

2001:660:1:1::1 2001:660:1:1::11 2001:660:2:1::11 2001:660:1:2::22 2001:660:1:2::3

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


13 a. B doit renvoyer le bit 1 pour confirmer qu’il a bien reçu la donnée.
b. A doit renvoyer la donnée déjà envoyée s’il reçoit 0 comme bit de contrôle, indiquant que la donnée
n’a pas été reçue.
c. S’il reçoit le bit de contrôle 1, la donnée a été reçue, il doit renvoyer la donnée suivante : 67890.

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

15 a. A envoie 123 et 1 comme bit de contrôle


B renvoie 1 comme bit de contrôle
A envoie 456 et 0 comme bit de contrôle
B renvoie 0 comme bit de contrôle
A envoie 789 et 1 comme bit de contrôle
B renvoie 1 comme bit de contrôle

b. A envoie 123 et 1 comme bit de contrôle


B renvoie 1 comme bit de contrôle
A envoie 456 et 0 comme bit de contrôle
B renvoie 1 comme bit de contrôle
A envoie 456 et 0 comme bit de contrôle
B renvoie 1 comme bit de contrôle
A envoie 456 et 0 comme bit de contrôle
B renvoie 0 comme bit de contrôle
A envoie 789 et 1 comme bit de contrôle
B renvoie 1 comme bit de contrôle

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


18
def testPrefixe(adresse, prefixe):
""" teste si l'adresse est dans le préfixe """
# p est le préfixe et longueur son nombre de bits
p, longueur = prefixe
i=0
for a in adresse :
if longueur >= 8 :
# l'octet a de l'adresse est dans le préfixe :
# vérifier que l'octet du préfixe correspond
if a != p[i] :
return False
# passer au prochain octet
i = i+1
longueur = longueur - 8
else:
# seuls les `longueur` premiers bits de l'adresse doivent
# correspondre à ceux du préfixe.
# On construit un masque avec `longueur` bits à 1 à gauche
# en utilisant les opérateurs de décalage à droite (>>)
# et à gauche (<<)
mask = (0xFF >> (8-longueur)) << (8-longueur)
# l'opérateur & réalise un "ou" binaire (bit à bit)
# avec le masque, donc permet de mettre les bits
# de droite de l'adresse à 0. On compare ainsi les
# bits de gauche de l'adresse avec ceux du préfixe
# et on vérifie s'ils correspondent
return a & mask == p[i] & mask
# l'adresse et le préfixe correspondent (cas où longueur == 32)
return true

# exemples du cours (p 182)


print(testPrefixe((192,168,211,0), ((192,168,250,0), 24)))
print(testPrefixe((192,168,250,12), ((192,168,250,0), 24)))

print(testPrefixe((192,168,251,170), ((192,168,250,0), 23)))


print(testPrefixe((192,168,248,0), ((192,168,250,0), 24)))

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Source : ac:ed:12:34:56:78
Destination : ac:ed:78:9a:bc:10
4 30

10.0.2.221
10.0.45.23

21 M2 doit envoyer l’octet 508 et il s’attend à recevoir l’octet 1025.


22 a. M1 envoie les octets 71 à 75.
b. M1 envoie les octets 71 à 75.

c. M1 renvoie les octets 51 à 60.

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


c. Les 2 paquets Ethernet qui transportent le paquet IP entre M1 et M2 sont les suivants.

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


while len(donnee) > 0 :
if donneeAEnvoyer == None :
donneeAEnvoyer = donnee.pop(0)
else:
print('renvoi')
retour = envoiePaquet(donneeAEnvoyer, bitDeControle) # Envoie la donnee
print("Retour : ", retour)
if retour == bitDeControle :
bitDeControle = 1 - bitDeControle # Alterne la valeur du bit de controle
donneeAEnvoyer = None

b.
def envoiePaquet(a,b):
return b

envoieAvecBitAlterne([123, 456, 789])

# ATTENTION - ce code boucle car la fonction envoieAvecBitAlterne


# renvoie le paquet indéfiniment

def envoiePaquet(a, b):


return 1

envoieAvecBitAlterne([123, 456, 789])

import random
def envoiePaquet(a, b):
return random.randint(0, 1)

envoieAvecBitAlterne([123, 456, 789])

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


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

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1 re


A→B 51 3 43 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

29 a. Les adresses IP nécessaires sont :


Machine Nombre d’adresses IP

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.

A vers F P1(1500), P2(1500), P3(1500), P4(1500), P5(1000)

B vers A P6(1500), P7(1500), P8(1500), P9(1500),P10(1500), P11(1500), P12(1500), P13(1500)

CàA P14(1500), P15(1500), P16(100)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Temps A B C D E F

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

11 P10 P11,P12,P13 P16

12 P12,P13 P11

13 P11 P13 P12

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

Notes sur le chapitre


Ce chapitre est un défi car il nécessite d’introduire plusieurs langages et syntaxes :
• la syntaxe des URLs, qui est déjà probablement plus ou moins connue ;

• la syntaxe et le langage HTML, qui a normalement été introduit en seconde ;

• la syntaxe et le langage CSS, qui est probablement nouveau ;

• 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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Nous ne sommes pas revenus en détail sur l’architecture générale du Web, qui a dû être vue en
seconde. Il peut être utile cependant de faire un rappel.
GET et POST
Le programme insiste sur la distinction entre requêtes GET et POST. Dans la pratique la différence
sémantique entre ces requêtes est rarement respectée : une requête GET n’est pas censée changer l’état
du serveur Web, par exemple en ajoutant ou modifant du contenu, tandis qu’une requête POST est
censée être réservée à cet usage. Par voie de conséquence, la même requête GET émise plusieurs fois de
suite doit donner le même résultat (idempotence), comme par exemple une requête à un moteur de
recherche, tandis que l’envoi de la même requête POST plusieurs fois de suite peut avoir un effet
différent, comme par exemple ajouter plusieurs fois le même commentaire.
Cependant, la distinction est ténue entre les deux situations : par exemple, on accède à un moteur de
recherche par des requêtes GET, mais celui-ci utilise les données récoltées pour affecter les recherches
futures, de telle sorte qu’une même requête peut donner des résultats différents.
D’autre part, l’utilisation de GET ou POST affecte le mécanisme d’historique des navigateurs, puisque les
paramètres font partie de l’URL dans une requête GET, mais pas dans une requête POST. Une
conséquence est que les requêtes GET laissent plus de traces, non seulement dans l’historique mais
aussi dans les fichiers de log des serveurs. C’est pourquoi les requêtes POST sont censées mieux
respecter la confidentialité, et sont souvent utilisées à la place des requêtes GET.
Codes d’erreur
Il peut être utile de faire analyser par les élèves les différents codes d’erreur que peut retourner un
serveur Web, par exemple à partir de la liste disponible sur Wikipedia :
https://fr.wikipedia.org/wiki/Liste_des_codes_HTTP.

Chapitre 11 – Interaction sur le Web 1


2 Les langages du Web
Dans ce chapitre nous tentons de faire une introduction la plus succinte possible aux trois langages du
Web : HTML, CSS et JavaScript.
Concernant HTML, le point important au-delà de ce qui a dû être vu en seconde est l’importance des
attributs id et class qui servent à faire le lien avec les sélecteurs CSS et avec le code JavaScript, et
l’attribut name qui, dans les formulaires, détermine les paramètres de la requête GET ou le contenu de la
requête POST envoyée au serveur.
Concernant CSS, le point important est la syntaxe des sélecteurs, en particulier le préfixe # pour
sélectionner un élément par son identifiant (attribut id) et le préfixe . pour sélectionner des éléments
par leur classe (attribut class).
Enfin, concernant JavaScript, le but n’est pas de rendre les élèves aptes à programmer en JavaScript,
mais à être capable de comprendre du code JavaScript et éventuellement y apporter des modifications
mineures. À cet effet, la liste suivante de différences avec Python pourra s’avérer utile :
• Booléens : true et false au lieu de True et False.

• 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}.

• Opérateurs arithmétiques : Les opérateurs + et ne fonctionnent pas avec les tableaux. * ne


fonctionne pas avec les chaînes mais + concatène les chaînes, comme en Python. De plus,
JavaScript est plus permissif que Python et convertit les valeurs lorsqu’il le juge nécessaire, par
exemple, si a vaut 10, la chaîne "a = "+a vaut "a = 10" : la valeur de a a été convertie en une chaîne
de caractères (en Python il faut la convertir explicitement avec str(a)). Ces conversions
automatiques peuvent cependant être piégeuses : 3 * "2" vaut 6 car JavaScript convertit la chaîne 2
en nombre, mais 3 + "2" vaut la chaîne "32" car il convertit 3 en chaîne de caractères !

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


• Opérateurs logiques : Les opérateurs logiques sont notés ! (non), && (et) et || (ou) au lieu de not,
and et or.

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

• Blocs : Les accolades délimitent les blocs, et pas l’indentation.

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

Chapitre 11 – Interaction sur le Web 2


• Variables globales : C’est probablement la différence la plus « piégeuse » : en JavaScript, il faut
déclarer les variables locales d’une fonction, sinon elles sont considérées comme variables
globales, ce qui est l’inverse de Python.

3 Interactions sur le Web


L’interaction sur le Web met en jeu de nombreuses conventions et notations différentes qui rendent
l’exercice difficile.
Pour les formulaires, l’important est de comprendre le passage de paramètres via l’attribut name des
éléments input (côté client), et la réception par le serveur de ces paramètres dans le dictionnaire
retourné par la fonction parse_qs de la bibliothèque urllib.parse, qui s’occupe de nombreux détails tels
que l’encodage / décodage des caractères spéciaux.
Une technique très utile et importante à comprendre est l’utilisation d’éléments input de type hidden
pour transmettre de l’information sans qu’elle soit visible de l’utilisateur. En effet chaque requête
HTTP est indépendante, et il n’existe pas dans le protocole de notion de « session ». Pourtant, du point
de vue de l’utilisateur, cette notion de session est naturelle : il ou elle va sur un site Web de e-
commerce, navigue d’une page à l’autre, remplit son panier d’achat, et retrouve le contenu de celui-ci
lorsqu’il est prêt à payer.
Les champs cachés sont le moyen de créer artificiellement une session, en échangeant un numéro de
session entre client et serveur : le serveur crée la session et envoie son numéro (ou autre identifiant) au
client avec la réponse à la requête ; le client récupère cet identifiant de session et le met dans un
champ caché du formulaire afin qu’il soit transmis lors de la prochaine requête et que le serveur puisse
savoir de quelle session il s’agit.
Cette technique est illustrée dans la partie Bonus de l’activité d’introduction avec la notion de
conversation, mais aussi dans plusieurs exercices tels que le panier d’achat, et, de manière peut-être
moins évidente, dans le TP sur l’explorateur de fichiers, où le chemin du dossier courant est transmis
avec chaque requête et chaque réponse.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


L’utilisation de l’API du DOM et de ses événements ne fait pas clairement partie du programme.
Cependant il nous semble important de les aborder pour comprendre comment fonctionnent les sites
Web actuels. En effet, ces APIs sont utilisées quasiment systématiquement aujourd’hui afin de réagir
aux actions de l’utilisateur en modifiant le contenu de la page Web, sans requête au serveur, en
particulier pour valider les entrées dans un formulaire avant de l’envoyer au serveur.
On pourra par ailleurs faire le lien avec le Chapitre 3 sur l’interaction graphique : les éléments du
DOM qui sont des champs de saisie sont similaires aux interacteurs tandis que le mécanisme des
événements et des fonctions de rappel est quasiment le même dans les deux contextes.
Ces APIs sont aussi au cœur des applications Web, dans lesquelles les requêtes au serveur ne
remplacent pas le contenu de la page par celui de la réponse envoyée par le serveur, mais sont utilisées
pour récupérer des données qui sont ensuite utilisées pour modifier le contenu de la page (on parle de
SPA pour « Single-Page Application », c’est-à-dire une application Web constituée d’une seule page).
Cet aspect n’est pas abordé dans le cours, mais fait l’objet de la dernière partie du TP Explorateur de
fichiers, avec l’introduction de la bibliothèque jQuery qui facilite la manipulation du DOM et des
requêtes AJAX qui permettent d’interroger un serveur et d’obtenir une réponse sans remplacer la page
courante.

Prérequis – TEST
1 b et d ; 2 b et c ; 3 c ; 4 b ; 5 b.

Chapitre 11 – Interaction sur le Web 3


Activité : Un service Web
Lancer un serveur Web écrit en Python

Le navigateur affiche un message d’erreur ``404, page non trouvée: /message''.

Créer une page dynamique

Répondre à un formulaire

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


1. Lorsque l’on clique « Envoyer », le message est ajouté en haut de la liste en-dessous du formulaire.
Si l’on ouvre plusieurs onglets sur la même page, il faut recharger la page pour voir les messages
envoyés par les autres onglets.
2. Dans dynserveur.py, on remplace la ligne
conversation.insert(0, message)

par
conversation.append(message)

afin que les nouveaux messages s’ajoutent à la fin.


Dans conversation.html, on déplace le formulaire après la liste de messages :
<h2>Conversation</h2>
<ul>
{% for msg in messages %}
<li>{{msg}}</li>
{% endfor %}
</ul>
<form action="/message" method="POST">
Message : <input type="text" name="msg">
<input type="submit" value="Envoyer">
</form>

L’ensemble du code solution est dans le dossier activite_web_corrige, dont le contenu est le suivant :

Chapitre 11 – Interaction sur le Web 4


• serveur.py : la bibliothèque du serveur ;

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

• dynserveur2.py et conversation2.html : versions correspondant à la dernière question. Lancer python


dynserveur2.py et ouvrir la page http://localhost:8000/conversation.html pour tester l’ajout de messages
et vérifier qu’ils s’affichent dans l’ordre chronologique.

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

• La fonction page_conversation récupère le nom de la conversation afin d’afficher son contenu ;

• De même, la fonction nouveau_message récupère le nom de la conversation afin d’ajouter le

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


message à la bonne conversation ;

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

Chapitre 11 – Interaction sur le Web 5


TP : Explorateur de fichiers
Lancer le serveur

Exemple d’images d’écrans. Si l’on saisit un nom de dossier invalide, on obtient une erreur 404 «
ressource non trouvée ».

2. Il suffit d’ajouter la ligne


fichiers.sort()

juste après la ligne 29 où la variable fichiers est initialisée avec la liste des noms de fichiers.

Naviguer dans les fichiers et afficher leur contenu

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


de plus la variable contenu qui représente le contenu du fichier texte. Le patron image.html utilise la
variable dossier qui contient le chemin vers le dossier contenant l’image.
2. Le code à ajouter dans dynserveur.py est le suivant. Dans les trois cas, le nom du fichier est passé dans
vars['nom'] et le nom du dossier qui le contient dans vars['dossier']. La fonction verifie_nom_dossier, fournie
dans le fichier élève, permet de vérifier que ces deux variables sont présentes et retournent une
réponse d’erreur si ce n’est pas le cas.
Les trois requêtes dynamiques pour les trois type de fichiers sont similaires : elles utilisent le patron
dont le nom correspond au type de fichier. Pour un fichier texte, on doit d’abord récupérer son contenu
pour le passer dans la vars['contenu']. Pour les deux autres types, on applique directement le patron.
Note : On pourra le cas échéant donner aux élèves l’une des trois fonctions afin de faciliter la
rédaction des deux autres.
def fichier_texte(url, vars):
""" Retourne une page dynamique pour présenter le fichier texte
dont le nom est dans la variable `nom`
"""
# vérifier que l'on a bien le dossier et le nom
erreur = verifie_nom_dossier(vars)
if erreur is not None:
return erreur
# calculer le chemin du fichier
chemin = vars['dossier']+ '/' + vars['nom']
# lire son contenu
file = open(chemin, 'r')
vars['contenu'] = file.read()

Chapitre 11 – Interaction sur le Web 6


file.close()
# afficher le patron "texte"
template = get_template('texte.html')
return OK(render(template, vars))

pageDynamique('/texte', fichier_texte)

def fichier_image(url, vars):


""" Retourne une page dynamique pour présenter le fichier image
dont le nom est dans la variable `nom`
"""
# vérifier que l'on a bien le dossier et le nom
erreur = verifie_nom_dossier(vars)
if erreur is not None:
return erreur
# afficher le patron "image"
template = get_template('image.html')
return OK(render(template, vars))

pageDynamique('/image', fichier_image)

def fichier_autre(url, vars):


""" Retourne une page dynamique pour présenter le fichier
dont le nom est dans la variable `nom`
"""
# vérifier que l'on a bien le dossier et le nom
erreur = verifie_nom_dossier(vars)
if erreur is not None:
return erreur
# afficher le patron "autre"
template = get_template('autre.html')
return OK(render(template, vars))

pageDynamique('/autre', fichier_autre)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Le patron liste.html est modifié de la façon suivante : la variable liste contient une liste de dictionnaires
de la forme {'nom': f.txt, 'type': 'texte'}. On teste le type du fichier :
• si c’est un dossier, on crée un lien vers la page liste en lui passant le nom du dossier (et on ajoute
un / à la fin du nom pour montrer qu’il s’agit d’un dossier) ;

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

Chapitre 11 – Interaction sur le Web 7


Note : On notera que les contenus des fichiers images ne s’affichent pas si l’on est dans un dossier au-
dessus du dossier de départ. Cela est dû à la bibliothèque http.server utilisée dans serveur.py, qui, pour
des raisons de sécurité, ne retourne pas le contenu de fichiers qui sont à l’extérieur du dossier de
départ (et de ses sous-dossiers).

Créer un explorateur de fichiers

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>

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Enfin, il faut ajouter une page dynamique pour répondre à la requête /fichier utilisée par explorateur.html
pour obtenir le code HTML présentant le contenu d’un fichier :
def fichier(url, vars):
""" Appelle l'une des 3 fonctions précédentes
en fonction du type de fichier
"""
type = type_fichier(vars['nom'])
vars['type'] = type
if type == 'texte':
return fichier_texte(url, vars)
if type == 'image':
return fichier_image(url, vars)
if type == 'autre':
return fichier_autre(url, vars)
return NotFound(f)

pageDynamique('/fichier', fichier)

2. La fonction selectionner est complétée comme suit :


function selectionner(e) {
// Retirer la classe `selection` de tous les éléments de liste
$('li').removeClass('selection')
// Ajouter la classe `selection` à l'élément e
$(e).addClass('selection')
}

Chapitre 11 – Interaction sur le Web 8


3. Les images sont dans le dossier images, en deux tailles : 32 pixels pour la liste de fichiers, et 256
pixels pour l’affichage dans la partie droite.
On ajoute les liens vers ces images dans les patrons des trois types de fichiers ainsi :
• texte3.html

<h2><img src="/images/texte-256.png">Fichier {{nom}}</h2>


<hr>
<pre>
{{contenu}}
</pre>

• image3.html

<h2><img src="/images/image-256.png">Fichier {{nom}}</h2>


<hr>
<img src="{{dossier}}/{{nom}}">

• autre3.html

<h2><img src="/images/autre-256.png">Fichier {{nom}}</h2>


<hr>
<p>Le contenu de ce fichier ne peut pas être affiché.</p>

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>

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


{% else %}
<li onclick="charger_fichier('{{dossier}}', '{{f.nom}}', this)">
<img src="/images/{{f.type}}-32.png">{{f.nom}}
</li>
{% endif %}
{% endfor %}
</ul>

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

2 https ici /la.com/ailleurs

3 https 123.456.78.90 /12.345.678.90

Chapitre 11 – Interaction sur le Web 9


4 http x.yz / xyz

5 http x.yz / abc#xyz

6 http base.fr /question/ x=1?y=2

7 http base.fr /question/ x=1&y=2

8 http base.fr /question/ reponse?x=1&y=2

9 http base.fr /question/ reponse x=1&y=2

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.

• Les URLs 6 à 9 sont toutes valides mais certaines sont trompeuses :

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


– Dans l’URL 6, le paramètre est x=1?y=2, qui sera interprété comme le paramètre x ayant la
valeur 1?y=2 ;

– 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'))

Chapitre 11 – Interaction sur le Web 10


2 a.
http://un.serveur.fr/cherche/?nom=world-wide-web&lang=fr

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Voici la version correcte :
<ul>
<li>HTML est le <i>langage du <b>Web</b></i>.</li>
<li><p>C'est un descendant de XML.</p></li>
</ul>

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.

Chapitre 11 – Interaction sur le Web 11


<html>
<head>
<title>Exercice 6</title>
<style type="text/css">
body {
background: #eee;
}

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;

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


}
</style>
</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 class="highlight">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>

La page HTML est dans le fichier exo6-corrige.html.

7 a.
<html>
<head>
<title>Exercice 7</title>
</head>
<body>
<form action="https://qwant.com/" method="GET">
Rechercher : <input type="text" name="q">

Chapitre 11 – Interaction sur le Web 12


<input type="hidden" name="t" value="web"><br>
<input type="submit">
</form>
</body>
</html>

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


</style>

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

La page HTML est dans le fichier exo8-corrige.html.

Chapitre 11 – Interaction sur le Web 13


Exercices Initié
9 a. Les deux onglets chargent la même page, mais dans le second, la page est positionnée sur la
section Histoire.
b. Non, la requête envoyée au serveur n’inclut pas l’ancre #Histoire.
c. Oui, si l’on cherche #Histoire dans le contenu retourné, on trouve <a href="#Histoire"> au début de la
section correspondante.
d. L’ancre n’est pas transmise au serveur, elle est gardée en mémoire par le navigateur. Lorsqu’il
reçoit la réponse du serveur, le navigateur cherche dans le contenu qu’il a reçu une balise a avec un
attribut href dont la valeur est le nom de l’ancre qu’il a gardé en mémoire. S’il la trouve il fait défiler le
document jusque-là.

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


11 a.
response = requests.get("https://fr.wikipedia.org/wiki/Spécial:Page_au_hasard")
print(response.url)

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)

print("URL de la requête (response.url) : " + response.url)


print("URL de redirection (reponse.headers['Location']) : " + response.headers["Location"])

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

Chapitre 11 – Interaction sur le Web 14


b.

c.
ul:hover {
border: 1px solid red;
}

ul:hover li {
border: 1px solid blue;
}

Le fichier exo12-bordures-corrige.html contient le code pour la question c) ci-dessus (voir le commentaire


dans le fichier pour la question a).

13 a.
<style>
#marge {
margin: 10px;
padding: 0;
}
#pad {
margin: 0;
padding: 10px;
}
</style

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


b.
<style>
#marge {
margin: 10px;
padding: 0;
background-color: lightblue;
}
#pad {
margin: 0;
padding: 10px;
background-color: yellow;
}
</style>

Chapitre 11 – Interaction sur le Web 15


c.
<style>
p{
border: 1px solid red;
}
</style>

Le fichier exo13-marges-corrige.html contient le code ci-dessus.

14 a.
<html>
<head>
<meta charset="utf-8">
<title>Une conversation</title>
</head>
<body>
<p class="moi">Bonjour</p>

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


<p class="autre">Salut</p>
<p class="moi">Comment va ?</p>
<p class="autre">Ca roule et toi</p>
<p class="moi">Faut que je finisse mes exos</p>
<p class="autre">Courage !</p>
</body>
</html>

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;

Chapitre 11 – Interaction sur le Web 16


text-align: left;
}
</style>

Le fichier exo14-conversation-corrige.html contient le code HTML complet.

15 a. Ajouter le code suivant à la fin du fichier HTML (avant </body>) :


<input type="button" onclick="precedent()" value="<<">
<input type="button" onclick="suivant()" value=">>">

et le code suivant à la fin de l’élément <script> :


var etape = 1

// Change l'étape courante


function change(nouvelle_etape) {
normal(etape)
etape = nouvelle_etape
en_evidence(etape)
}
// Bouton suivant
function suivant() {
if (etape < 5)
change(etape+1)
}
// Bouton précédent
function precedent() {
if (etape > 1)
change(etape-1)
}

b. Ajouter la règle suivante à la fin de l’élément <style> :

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


<style>
...
li { color: white; }
</style>

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

Le fichier exo14-conversation-corrige.html contient le code HTML complet.

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>

Chapitre 11 – Interaction sur le Web 17


Afin que la valeur en degrés Farenheit corresponde à la valeur en degrés Celsius (sélectionnée avec la
tirette), celle-ci doit être mise à jour à deux occasions :
• lorsque la page est chargée (en accord avec la valeur initiale de l’élément <input> ;

• à chaque fois que la valeur de l’élément <input> est modifiée.

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()">

Lorsque le sens de la conversion change, plusieurs choses doivent être modifiées :


• les termes Celsius et Fahrenheit doivent être inversés (près de la tirette et dans le paragraphe
contenant le résultat de la conversion) ;

• 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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


éléments <span>, dont le contenu sera mis à jour pour afficher les bonnes unités :
<input type="range" id="tirette" oninput="mettreTemperatureAJour()"> <span id="temperature">0</span> degrés <span i
d="unite-avant-conversion"></span>
<p>
Température en degrés <span id="unite-apres-conversion"></span> : <span id="temperature-convertie"></span>
</p>

On modifie enfin le code JavaScript :


• on y ajoute une variable contenant le sens actuel de la conversion ;

• on modifie la fonction mettreTemperatureAJour afin d’utiliser la bonne formule de conversion en


fonction du sens actuel ;

• 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();

Chapitre 11 – Interaction sur le Web 18


modifierTexte('temperature', temperature);

// On choisit la bonne formule de conversion en fonction du sens de la conversion.


if (celsiusVersFarenheit) {
modifierTexte('temperature-convertie', (temperature - 32) * (5/9));
}
else {
modifierTexte('temperature-convertie', (temperature / (5/9)) + 32);
}
}

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;

// On inverse les deux unités et recalcule la température convertie.


mettreUnitesAJour();
mettreTemperatureAJour();
}

function initialiser() {
mettreTemperatureAJour();
mettreUnitesAJour();

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


}

Le fichier exo15-conversion-corrige.html contient le code HTML complet.

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)

Chapitre 11 – Interaction sur le Web 19


secrets = {}

def secret(url, vars):


# extraire les valeurs du formulaire
nom = vars['nom']
motdepasse = vars['motdepasse']
secret = vars['secret']
msg = ''

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

Les fichiers exo17-secret-corrige.py et exo17-secret-corrige.html contiennent le code Python et HTML de la


solution. La bibliothèque serveur.py (fournie) est également nécessaire. Lancer le fichier Python puis
ouvrir l’URL http://localhost:8000/secret dans un navigateur.
Attention : le nom du patron HTML (avant-dernière ligne du code ci-dessus) doit correspondre au nom
du fichier HTML qui contient ce patron.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Exercices Titan
18 a. On ajoute les 16 boutons (<input> de type button) séparés par des sauts de ligne (<br>) à l’intérieur
de l’élément <body> :
<body>
<input type="text" id="resultat" size="15">
<br>

<input type="button" value="7">


<input type="button" value="8">
<input type="button" value="9">
<input type="button" value="/">
<br>

<input type="button" value="4">


<input type="button" value="5">
<input type="button" value="6">
<input type="button" value="*">
<br>

<input type="button" value="1">


<input type="button" value="2">
<input type="button" value="3">
<input type="button" value="+">
<br>

Chapitre 11 – Interaction sur le Web 20


<input type="button" value="C">
<input type="button" value="0">
<input type="button" value="=">
<input type="button" value="-">
<br>
</body>

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>

<input type="button" onclick="chiffre(4)" value="4">


<input type="button" onclick="chiffre(5)" value="5">
<input type="button" onclick="chiffre(6)" value="6">
<input type="button" onclick="operation('*')" value="*">
<br>

<input type="button" onclick="chiffre(1)" value="1">


<input type="button" onclick="chiffre(2)" value="2">
<input type="button" onclick="chiffre(3)" value="3">
<input type="button" onclick="operation('+')" value="+">
<br>

<input type="button" onclick="efface()" value="C">


<input type="button" onclick="chiffre(0)" value="0">
<input type="button" onclick="egale()" value="=">
<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à

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


définies et la valeur affichée :
• operandeGauche doit contenir la valeur entrée dans la calculette avant qu’une opération soit
sélectionnée ;

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

// On traite la valeur actuellement entrée comme l'opérande gauche

Chapitre 11 – Interaction sur le Web 21


// de la future opération, et on remet l'opérande droite à zéro.
operandeGauche = operandeDroite;
operandeDroite = 0;
}

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


operandeGauche = resultat;
operandeDroite = 0;

modifierResultat(resultat)
}

Le fichier exo18-calculette-corrige.html contient le code HTML complet.

19 a. Le contenu de la réponse, une fois transformé en JSON, est le suivant :


[{
"word": "bonjour",
"phonetics": [
{}
],
"meanings": [
{
"partOfSpeech": "nom masculin",
"definitions": [
{
"antonyms": [],
"definition": "Souhait de bonne journée (adressé en arrivant, en rencontrant).",
"synonyms": [
"salut"
]
}
]
}

Chapitre 11 – Interaction sur le Web 22


]
}]

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

• phonetics : un tableau (contenant un dictionnaire vide) ;

• meanings : un tableau, contenant un autre dictionnaire qui possède les clés suivantes :

– partOfSpeech : une chaîne de caractères ;

– definitions : un tableau, contenant un autre dictionnaire qui possède les clés suivantes :

• antonyms : un tableau (vide, probablement de chaînes de caractères) ;

• definition : une chaîne de caractères ;

• synonyms : un tableau de chaînes de caractères.

b. On ajoute une page dynamique dans dicoserveur.py :


def page_dictionnaire(url, variables):
"""Génère une réponse pour une requête HTTP du chemin /dictionnaire."""

# Si la langue ou le mot ne sont pas définis dans les paramètres de l'URL,


# on définit une langue et/ou un mot par défaut.
if "langue" not in variables:
variables["langue"] = "fr"
if "mot" not in variables:
variables["mot"] = "bonjour"

langue = variables["langue"]

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


mot = variables["mot"]

# On construit l'URL à partir des variables et effectue la requête.


# Si elle échoue on retourne une erreur indiquant que le mot n'a pas été trouvé
url_requete = f"{url_dictionnaire}/{langue}/{mot}"
reponse = requests.get(url_requete)

if reponse.status_code != 200:
return NotFound(f"Le mot \"{mot}\" n'a pas été trouvé.")

# On utilise la réponse pour générer la page HTML et retourner un succès.


# On crée pour cela un dictionnaire de variables destiné au patron.
reponse_json = reponse.json()[0] # premier et unique dictionnaire du tableau
variables_patron = {
"langue": langue,
"reponse": reponse_json
}

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

Chapitre 11 – Interaction sur le Web 23


<title>Dictionnaire</title>
</head>
<body>
<!-- On affiche le résultat d'une requête (s'il y en a un). -->
{% if reponse %}
<p class="mot">{{ reponse.word }}</p>
<!-- On affiche chaque sens du mot. -->
{% for m in reponse.meanings %}
<p class="sens">{{ m.partOfSpeech }}</p>
<!-- Pour chaque sens, on affiche toutes ses définitions
(sous forme de liste ordonnée). -->
<ol>
{% for d in m.definitions %}
<li>
{{ d.definition }}

<!-- S'il y a au moins un synonyme, on les affiche tous -->


{% if d.synonyms %}
<p class="synonymes">
Synonymes :
{% for s in d.synonyms %}
{{ s }}
{% endfor %}
</p>
{% endif %}
<!-- S'il y a au moins un antonyme, on les affiche tous -->
{% if d.antonyms %}
<p class="antonymes">
Antonymes :
{% for a in d.antonyms %}
{{ a }}
{% endfor %}
</p>
{% endif %}

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


</li>
{% endfor %}
</ol>
{% endfor %}
{% endif %}
</body>
</html>

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>

Chapitre 11 – Interaction sur le Web 24


<label><input type="radio" name="langue" value="de"
{% if langue == "de" %} checked {% endif %}> Allemand</label>
<label><input type="radio" name="langue" value="en_US"
{% if langue == "en_US" %} checked {% endif %}> Anglais (US)</label>
<label><input type="radio" name="langue" value="ru"
{% if langue == "ru" %} checked {% endif %}> Russe</label>
</form>

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

Les fichiers exo19-dictionnaire-corrige.html et exo19-dictionnaire-corrige.py contiennent le code HTML et


Python complet. Le fichier serveur.py doit également être présent dans le dossier. Lancer le fichier
Python puis ouvrir l’URL http://localhost:8000/dictionnaire dans un navigateur.

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 à

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


ajouter au panier côté serveur.
{% for id in articles %}
<li>
<form action="/ajouter_article" method="POST">
<input type="submit" value="Ajouter au panier">
{{articles[id][0]}}, {{articles[id][1]}}€
<input type="hidden" name="article_id" value="{{ id }}">
</form>
</li>
{% endfor %}

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"])

Chapitre 11 – Interaction sur le Web 25


return page_articles(url, variables)

pageDynamique('/ajouter_article', page_ajouter_article)

c. On commence par remplacer le tableau global articles_ids_dans_panier par un dictionnaire de tableaux


d’identifiants d’articles ajoutés à un panier indexé par des numéros de session.
contenu_panier_par_session = {}

On ajoute ensuite quelques fonctions utilitaires pour :


• tester si une session avec un numéro donné existe ;

• 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

return session_id in 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".
"""

# On vérifie si le numéro de session existe dans les variables.


if "session_id" not in variables:
return "absent"

# On tente de le convertir en nombre et de trouver un panier portant ce numéro,


# que l'on retourne le cas échéant.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


try:
session_id = int(variables["session_id"])
except ValueError:
return "invalide"

if session_existe(session_id):
return session_id
return "invalide"

On modifie ensuite les fonctions page_articles, page_panier, page_vider_panier, et page_ajouter_article, afin


d’adapter la façon dont le contenu du panier est récupéré/modifié. Si le numéro de session présent
dans variables est invalide/inconnu, on retourne systématiquement une erreur.
Dans le cas de page_articles et page_panier, on assigne article_ids_dans_panier autrement.
global contenu_panier_par_session

# On tente d'extraire un numéro de session des variables :


# - s'il est spécifié et correspond à un panier, on recupère son contenu ;
# - s'il est spécifié mais inconnu, on retourne une erreur ;
# - s'il n'est pas spécifié (dans le cas d'un visiteur n'ayant ajouté aucun article),
# alors le panier ne contient aucun article.
article_ids_dans_panier = None
session_id = extraire_session_id(variables)

if session_id == "invalide":
return NotFound("Numéro de session inconnu")
elif session_id == "absent":
article_ids_dans_panier = []

Chapitre 11 – Interaction sur le Web 26


else:
article_ids_dans_panier = contenu_panier_par_session[session_id]

Dans le cas de page_vider_panier, on accède au tableau à vider autrement.


global article_ids_dans_panier

# On tente d'extraire un numéro de session des variables :


# - s'il est défini et correspond à un panier, on vide celui-ci ;
# - s'il est défini mais ne correspond à aucun panier, on retourne une erreur.
session_id = extraire_session_id(variables)

if session_id == "invalide":
return NotFound("Numéro de session inconnu")
elif session_id != "absent":
contenu_panier_par_session[session_id] = []

Dans le cas de page_ajouter_article, on


• accède au tableau auquel ajouter l’article autrement ;

• 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

# On tente d'extraire un numéro de session des variables :


# - s'il est défini et correspond à un panier, on ajoute un article à celui-ci ;
# - s'il est défini mais ne correspond à aucun panier, on retourne une erreur ;
# - s'il n'est pas défini, on crée une nouvelle session/un nouveau panier,
# on y ajoute l'article, et on ajoute le numéro de session aux variables
# (afin que celui-ci soit présent dans les futures pages HTML générées).
session_id = extraire_session_id(variables)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


if session_id == "invalide":
return NotFound("Numéro de session inconnu")
elif session_id == "absent":
session_id = nouvelle_session()
contenu_panier_par_session[session_id] = []
variables["session_id"] = session_id

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.

{% if session_id %}<input type="hidden" name="session_id" value="{{ session_id }}">{% endif %}

Les fichiers exo20-panier-corrige.py, exo20-panier-corrige.html et exo20-articles-corrige.html contiennent le code


HTML et Python complet. Le fichier serveur.py doit également être présent dans le dossier. Lancer le
fichier Python puis ouvrir l’URL http://localhost:8000/articles dans un navigateur.

Chapitre 11 – Interaction sur le Web 27


Chapitre 12 – Systèmes et périphériques

Notes sur le chapitre


Dans ce chapiter, nous avons fait le choix d’utiliser les commandes du Terminal du système Linux,
qui sont les plus répandues. En effet elles sont aussi utilisées sur MacOS et elles sont disponibles sur
Windows avec l’extension Cygwin que nous suggérons fortement d’installer sur les machines
accessibles aux élèves. Ainsi, les connaissances acquises seront applicables sur les trois principaux
systèmes d’exploitation des ordinateurs de bureau.
Il est à noter que ce chapitre peut être abordé plus tôt dans le cours. En effet, il est utile d’avoir une
maîtrise minimale des commandes du Terminal, notamment pour gérer ses fichiers, en complément de
l’explorateur de fichiers. Il est aussi parfois utile de pouvoir lancer plusieurs commandes en parallèle
dans plusieurs fenêtres Terminal, ce qui n’est pas possible depuis un environnement comme
EduPython. Enfin les quelques programmes Python utilisés dans les exerices du chapitre nécessitent
peu de connaissances du langage : celles des chapitres 2 et 4.
Note : L’invite du Terminal (le « prompt ») est souvent notée $ dans la littérature et est souvent le
défaut. Dans ce manuel nous avons choisi le caractère % pour éviter la connotation de la monnaie
américaine. Dans le manuel et dans les corrigés, nous mettons systématiquement l’invite devant les
commandes afin de facilement distinguer les commandes des réponses. Bien entendu, l’invite ne doit
pas être tapée au clavier, elle est affichée par le Terminal lorsqu’il attend une commande.

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Cette étape est nécessaire pour créer la hiérarchie de fichiers et de répertoires souhaitée, avec les droits
d’accès corrects. Les élèves doivent s’abstenir de lire le contenu du script init.sh car cela leur
permettrait de découvrir où sont les fichiers à trouver dans l’activité. (On peut ajouter une ligne à la fin
du script pour qu’il s’auto-détruise, mais cela a paru un peu dangereux…) L’autre solution, qui
consisterait à créer une archive contenant les fichiers après exécution du script, ne fonctionnera pas
correctement car zip ne stocke ni ne restaure les droits d’accès des fichiers.
Note : Les sources des fichiers utilisés dans l’activité sont les suivantes :
• mystere.txt :
https://fr.wikipedia.org/wiki/Liste_des_villes_de_l%27Union_européenne_par_population

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

Chapitre 12 – Systèmes et périphériques 1


Activité : Découvrir le terminal
Chasse au trésor

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

(La commande cat permet de voir le contenu du fichier).


Pour trouver le fichier, on aura probablement parcouru l’arborescence ainsi (la commande ls -F permet
de reconnaître les noms de sous-répertoires par leur suffixe / et ainsi de savoir où il est possible de
naviguer) :
% cd parici
% ls -F
% cd Est
% ls -F
% cd ..
% cd Nord
% ls -F
% cd ..
% cd Ouest
% ls -F
% cd ..
% cd Sud
% ls -F
% cd ../..

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


% cd parla
% ls -F
% cd lycee
...

2.

Chemins d’accès

Le fichier mystere.txt est dans activite/parici/Ouest. Le répertoire courant est activite/parla/lycee/premiere/NSI.

Chapitre 12 – Systèmes et périphériques 2


Il faut donc « remonter » de quatre niveaux (jusqu’à activité) pour « redescendre » dans parici/Ouest :
% ls ../../../../parici/Ouest/mystere.txt
% cd ../../../../parici/Ouest
% cat mystere.txt

Manipuler les fichiers

Tout d’abord on remonte dans le répertoire activite :


% cd ../..

Ensuit on crée le répertoire travail et on y copie les deux fichiers.


% mkdir travail
% cp parla/lycee/premiere/NSI/tresor.txt travail/tresor.txt
% cp parici/Ouest/mystere.txt travail/mystere.txt

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

Note : On peut éviter les deux cd en renommant directement :

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


% mv travail/mystere.txt travail/trouve.txt
% 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

On obtient la sortie suivante :


62 134 1011 tresor.txt
49 106 794 tmp1
111 240 1805 total

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.

Chapitre 12 – Systèmes et périphériques 3


Bonus : Si l’on veut connaître la liste des doublons, on peut utiliser la commande diff f1 f2 qui compare
les contenus de deux fichiers. Comme la comparaison se fait ligne à ligne, il faut comparer la version
triée de tresor.txt avec la version triée et dé-doublonnée. On utilise donc un second fichier temporaire :
% sort tresor.txt > tmp2
% diff tmp2 tmp1

On obtient la sortie suivante :


9d8
< Cape d'invisibilité
12d10
< Carte du maraudeur
15d12
< Choixpeau magique
18,19d14
< Coupe de feu
< Déluminateur
29d23
< Horcruxes
35d28
< Magicobus
38d30
< Miroir du risèd
42d33
< Pensine
45,46d35
< Pierre philosophale
< Portoloin
50d38
< Poudlard Express
55d42
< Retourneur de temps
63d49

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


< Épée de Gryffondor

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

On obtient une liste de 42 villes qui commence ainsi :


Aix-en-Provence France 143097
Amiens France 133891
Angers France 154508
Annecy France 128199
Argenteuil France 110213
Besançon France 116775
Bordeaux France 257068
...

Chapitre 12 – Systèmes et périphériques 4


Note : On peut compter le nombre de villes avec la commande
% grep France trouve.txt | sort | wc

Droits d’accès

1. Voici la trace de l’exécution des commandes demandées :


% cd ../droits
% ls -l
total 24
-r--r--r--@ 1 mbl staff 26 Jun 18 14:47 protege.txt
--w------- 1 mbl staff 119 Jun 18 14:41 secret.txt
-rw-r--r--@ 1 mbl staff 3594 Jun 18 13:16 texte.txt
% cat secret.txt
cat: secret.txt: Permission denied
% cp texte.txt protege.txt
cp: protege.txt: Permission denied
%

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.

• nécessite de lire le contenu de texte.txt et d’écrire dans protege.txt. On peut


cp texte.txt protege.txt
vérifier avec la commande cat texte.txt que l’on a le droit de lecture pour texte.txt, l’erreur provient
donc de protege.txt.

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


• -r- pour protege.txt indique que l’on peut le lire mais pas y écrire ;

• --w pour secret.txt indique que l’on peut y écrire mais pas le lire ;

• -rw pour texte.txt indique que l’on peut le lire et y écrire.

Cela est compatible avec les messages d’erreurs observés.

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

On peut vérifier les nouveaux droits avec ls -l :


% ls -l
total 24
-r--r--r--@ 1 mbl staff 3594 Jul 4 14:34 protege.txt
-rw-------@ 1 mbl staff 119 Jun 18 14:41 secret.txt
-rw-r--r--@ 1 mbl staff 3594 Jun 18 13:16 texte.txt

Chapitre 12 – Systèmes et périphériques 5


Gérer les processus

1. Voici un exemple d’affichage de la commande ps a sur MacOS :


498 s002 S 0:00.02 -bash
5115 s002 S+ 5:21.11 /Users/mbl/opt/anaconda3/bin/python /Users/mbl/opt/anaconda3/bin/jupyter-notebook
516 s003 Ss 0:00.02 login -pfl mbl /bin/bash -c exec -la bash /bin/bash
723 s004 Ss+ 0:00.01 /bin/bash -l
67113 s005 Ss 0:00.09 login -pf mbl
...

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


...
97275 s000 S+ 0:02.76 python boucle.py
...
97275 s000 R+ 0:04.29 python boucle.py
...
97275 s000 S+ 0:05.80 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
...

3. Le numéro de processus est la première colonne de la commande ps :


...
97361 ttys000 0:01.10 python boucle.py
...

On « tue » donc le processus avec la commande

Chapitre 12 – Systèmes et périphériques 6


% kill 97361

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


1
• Une application est constituée d’un ou plusieurs programmes qui fonctionnent ensemble.

• Les programmes sont constitués de fichiers stockés dans la mémoire de masse.

• Un processus est un programme qui est en train de s’exécuter.

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.

• Classement SimilarWeb de Juin 2020 : Ubuntu, RedHat, Linux Mint, ArchLinux.

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.

4 a. Un soixiantième de seconde = 1/60 = 0,01666.., soit 1,66.. centièmes de secondes.


b. Le programme est disponible dans le fichier temps.py, utilisé également dans l’exercice 14 (avec une
valeur de n différente).

Chapitre 12 – Systèmes et périphériques 7


from timeit import timeit

# ajuster la valeur de n pour que l'exécution du programme


# prenne environ 0.01666 secondes
n = 250000 # valeur pour un ordinateur Macintosh de 2018

def f():
""" calculer la somme des n premiers nombres """
s=0
for i in range(n):
s = s+i

# afficher le temps d'exécution


print(timeit(f, number=1))

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

6 a. Solution 1, sans changer de répertoire courant :

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


% cp ../f1 ../a/d
% ls ../a/d

Solution 2, en remontant au répertoire parent :


% cd ..
% cp f1 a/d
% ls a/d

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

Pour la seconde interprétation, la séquence est la suivante :

Chapitre 12 – Systèmes et périphériques 8


% mkdir b/h
% mv c/f/f6 b/h
% mv c/g/f7 b/h
% mv c/g/f8 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) :

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Dans la seconde, il est le suivant :

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 :

Chapitre 12 – Systèmes et périphériques 9


% rm -f a

9
• cat f1 : permis car f1 a le droit de lecture ;

• cp f3 f2 : interdit car on peut lire f3 mais on ne peut pas écrire dans f2 ;

• 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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


% echo F2 > f2
% chmod a-w f2
% echo F3 > f3
% chmod a+x f3
% mkdir d1
% chmod a-x d1
% mkdir d2
% chmod a-w d2

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 ;

• rm d/f1 : droit de lecture et d’écriture sur d, pas de contrainte sur f1 ;

• 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).

Chapitre 12 – Systèmes et périphériques 10


Exercices Initié
12 a. Tout d’abord les logiciels libres sont gratuits, alors que la tendance actuelle est de faire payer les
utilisateurs via des licences annuelles, en particulier pour les logiciels à usage professionnel.
Cependant, les systèmes d’exploitation propriétaires sont souvent gratuits car intégrés à la machine
lors de l’achat, et les mises à jour sont également souvent gratuites.
Ensuite, le code d’un logiciel libre étant disponible, il est plus probable que la communauté des
développeurs détecte et corrige des failles de sécurité rapidement. Cela est particulièrement important
pour un système d’exploitation, qui a accès à toutes les ressources de la machine et est donc une cible
de choix pour les hackers.
Par ailleurs, les logiciels libres peuvent être modifiés et adaptés par tout programmeur pour répondre à
des besoins spécifiques. Il existe ainsi des dizaines de versions de Linux, qui a été porté / adapté /
étendu / restreint à de multiples cas d’utilisations spécifiques. Même si la modification d’un système
d’exploitation n’est pas à la portée de tout programmeur, un logiciel libre permet à une communauté
de programmeurs de s’organiser pour fournir l’effort nécessaire.
Enfin les logiciels libres ne sont pas à la merci des décisions d’obsolescence programmée des éditeurs
de logiciels, qui imposent souvent d’installer les nouvelles versions et arrêtent la maintenance
d’anciennes versions. A l’inverse, on peut par exemple installer des versions antérieures de Linux sur
des machines anciennes.
b. Les logiciels libres peuvent être modifiés de manière malveillante pour y introduire des virus ou des
moyens de surveillance. Aussi est-il important de s’assurer de l’origine du logiciel lorsqu’on le
télécharge, particulièrement lorsqu’il s’agit d’un système d’exploitation, pour les raisons évoquées ci-

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


dessus : une faille de sécurité peut avoir des conséquences beaucoup plus importantes qu’avec une
application. À cet effet, un système de signature électronique permet de s’assurer que la version
téléchargée est bien la version officielle.
Le fait que de telles modifications soient possibles peut sembler être une faiblesse par rapport à des
systèmes propriétaires que seul l’éditeur du logiciel peut modifier. Cependant, l’expérience montre
que ce n’est pas le cas, et que Linux, par exemple, est beaucoup plus sûr que des systèmes
propriétaires comme Windows.
D’autre part, un logiciel libre n’offre pas de garantie ni de support technique en cas de problème, ou
bien ce support est payant. Il faut donc compter sur la communauté des développeurs et des
utilisateurs du logiciel pour fournir de l’aide. Dans le cas de Linux, cette communauté est très active,
mais elle s’adresse principalement a des utilisateurs avertis, et moins au grand public.
Enfin, comme un logiciel libre dépend de la communauté que le développe et l’utilise, il peut être
abandonné par ses développeurs, qui n’ont pas d’obligation de le maintenir. Le risque existe aussi que
des sous-communautés développent des versions parallèles (des « forks ») car elles ne sont pas
d’accord avec l’évolution du logiciel. Cette fragmentation devient source de confusion et de difficultés
pour les utilisateurs. Unix, puis Linux, ont tous les deux connus ces problèmes, qui perdurent d’une
certaine façon à travers les principales versions de Linux (Ubuntu, RedHat, Debian, …) qui sont plus
ou moins en concurrence.

13 a. Le programme peut s’écrire ainsi :

Chapitre 12 – Systèmes et périphériques 11


input('taper entrée')
while True:
print('je boucle')

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

n = 120000000 # ajusté pour un Macbook Pro de 2018

def f():
""" calculer la somme des n premiers nombres """
s=0
for i in range(n):
s = s+i

# afficher le temps d'exécution


print(timeit(f, number=1))

b. Sur l’ordinateur utilisé pour rédiger ces réponses (Macbook Pro de 2018), la réponse est

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


curieusement 12, alors que le processeur (Intel Core i9 à 2,9 GHz) a 6 cœurs !
c. Sur un Macbook Pro de 2018 on obtient :
% python temps.py
5.132301388
% python temps.py & python temps.py
[1] 459
5.453960791
5.459891707

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

• 6 lancements : 6,71s en moyenne

• 8 lancements : 9,36s en moyenne

• 10 lancements : 11,85s en moyenne

• 12 lancements : 14,53s en moyenne

Affichons la courbe de ces résultats :

Chapitre 12 – Systèmes et périphériques 12


from matplotlib.pyplot import plot, show, savefig
plot([1,2,4,6,8,10,12], [5.13, 5.45, 5.88, 6.71, 9.36, 11.85, 14.35])
show()

On observe que jusqu’à 6 lancements en parallèle (correspondant au nombre de cœurs), le temps


augmente légèrement, puis de manière beaucoup plus importante au-delà de 6 cœurs.
Par ailleurs, dans tous les cas tous les programmes se terminent en même temps. Lorsqu’il y a plus de
programmes que de cœurs pouvant les exécuter, cela implique nécessairement que le système alloue
des tranches de temps à chacun d’eux, à tour de rôle, afin qu’ils progressent dans leur exécution de
manière similaire. Sinon les 6 premiers programmes termineraient d’abord, et les suivants plus tard.

15 a.
m0 = taille_memoire()
n = 1000000
t = [i for i in range(n)]
m1 = taille_memoire()
mega = 1000000

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


print(f'début={m0/mega:.4}Mo, augmentation={(m1-m0)/mega:.4}Mo')

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

Note : Le fichier memoire.py contient le code ci-dessus.


d. Note : L’exécution des programmes ci-dessus dans Jupyter donne des résultats très différents de
l’exécution depuis le terminal. La raison est qu’un notebook Jupyter est connecté à un programme
Python qui s’exécute pendant tout le temps où le notebook est ouvert, pour exécuter l’ensemble des
programmes Python du notebook. Sa taille mémoire est donc en général bien plus importante que lors
d’une exécution « simple » depuis le terminal. De plus, la gestion sophistiquée de la mémoire par
Python peut donner des résultats surprenants, comme la diminution de la mémoire lorsque l’on crée un

Chapitre 12 – Systèmes et périphériques 13


grand tableau. Cela est dû au « ramasse-miettes » de Python, qui récupère la mémoire inutilisée (ce
point est abordé au Chapitre 14).
Exemples de réponses aux questions précédentes :
• a. début=11.1Mo, augmentation=37.0Mo.

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

16 a. Ce programme est disponible dans le fichier memoire2.py.


import psutil

# fonction fournie dans l'énoncé


process = psutil.Process()
def taille_memoire():
return process.memory_info().rss #vms

m0 = taille_memoire()

# créer un tableau d'1 million d'éléments

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


n = 1000000
t = [i for i in range(n)]
m1 = taille_memoire()

# réinitialiser le tableau au tableau vide


t = []
m2 = taille_memoire()

# afficher les résultats


mega = 1000000
print(f'début : {m0/mega:.4}Mo')
print(f'après initialisation : {m1/mega:.4}Mo')
print(f'après t = [] : {m2/mega:.4}Mo')

b. L’exécution du programme précédent depuis le terminal donne les résultats suivants :


• début : 11.33Mo

• après initialisation : 54.17Mo

• 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

Chapitre 12 – Systèmes et périphériques 14


17
% head -5 eleves.txt | tail -1
% tail -2 eleves.txt | head -1

18 a. Le fichier apparait dans l’explorateur de fichiers.


b. Le nouveau fichier apparait et l’ancien disparait.
c. Oui, le fichier est bien modifié.
On peut pousser l’expérience en modifiant le contenu du fichier (avec cat > f.txt) pendant qu’il est
ouvert dans l’éditeur de texte. Selon l’éditeur utilisé, on observera plusieurs comportements, par
exemple :
• Le dernier à enregistrer le contenu du fichier gagne : si on enregistre depuis l’éditeur de texte, les
modifications faites avec cat seront perdues ;

• 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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


le propriétaire :
% chmod a-rwx protege.txt
% chmod u+rw protege.txt

b. Il suffit de retirer le droit d’exécution sur le dossier :


% chmod a-x sauvegarde

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

Chapitre 12 – Systèmes et périphériques 15


On notera qu’il faut taper ./protege et non pas protege pour invoquer le script, car le système cherche les
noms de commandes dans un ensemble de répertoires prédéfinis. Si le nom de commande est un
chemin absolu ou relatif, cette recherche n’a pas lieu et le système exécute le fichier référencé.
b. Le script crée le répertoire sauvegarde, y copie le fichier et affiche le message :
% cat > sauve
mkdir -f sauvegarde
cp $1 sauvegarde
echo fichier $1 sauvegardé
% chmod a+x sauve
% ./sauve f.txt
fichier f.txt sauvegardé

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Si l’on change la ligne cp du script sauve ainsi :
cp $1 sauvegarder/`date +%Y-%m-%d@%H:%M:%S`-$1

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

n = 0.0 # nombre d'appels


total = 0.0 # temps total
prec = None # temps précédent

def mesure(x, y):


""" mesurer le temps entre appels successifs """
global n, prec, total
if prec is None:
# premier appel : initialiser prec

Chapitre 12 – Systèmes et périphériques 16


prec = time()
else:
# calculer l'écart de temps et l'ajouter au total
t = time()
total += t - prec
prec = t
n += 1

def affiche(x, y):


""" afficher le résultat """
if n == 0.0 or total == 0.0:
return
print(f'temps moyen entre appels = {total/n}s, fréquence = {n/total} appels/seconde')

# appeler `mesure` à chaque déplacement,


# et `affiche` à la fin
ondrag(mesure)
onrelease(affiche)

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.

23 a. La commande wc affiche le nombre de lignes de chaque fichier (première colonne). Lorsqu’on


lui passe plusieurs fichiers en paramètres, elle affiche en plus le total. Il suffit donc d’exécuter ces
deux commandes, et de comparer le total de la première avec le résultat de la seconde :
% wc g1.txt g2.txt
20 40 309 g1.txt
20 40 322 g2.txt
40 80 631 total
% wc eleves.txt
39 78 615 eleves.txt

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


On en déduit qu’il y a un élève en plus dans les deux groupes que dans la classe.
b. Pour comparer les deux fichiers, on va concaténer les deux fichiers g1.txt et g2.txt, trier le résultat, et
le comparer, à l’aide de la commande diff avec une version triée du fichier eleves.txt. En effet, la
commande diff travaille ligne à ligne, et si deux fichiers ont les mêmes lignes dans un ordre différent,
diff indiquera qu’ils sont différents :

% cat g1.txt g2.txt | sort > f1.txt


% sort eleves.txt > f2.txt
% diff f1.txt f2.txt
% rm f1.txt f2.txt

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

Cet affichage s’interprète comme suit :


• 7d6 indique qu’il faut détruire la ligne 7 du premier fichier pour être identique au second ;

• < Amadis Buisson indique que c’est la ligne qui est dans le premier fichier mais pas le second ;

Chapitre 12 – Systèmes et périphériques 17


• les deux lignes suivantes sont similaires : Florent Franck 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 ;

• > Séverin Frey indique la ligne qui doit être ajoutée

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.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Enfin Séverin Frey est dans le fichier de la classe mais dans aucun des deux groupes.
Cela fait donc trois erreurs à corriger.

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

Chapitre 12 – Systèmes et périphériques 18


Charline-Chantal Brousse 6
Salomé Martineau 5

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

25 a. Ce programme est disponible dans le fichier sauvegarde.py.


from os import listdir, system

# liste des fichiers du répertoire courant


liste = listdir('.')

# demander le nom de fichier à sauvegarder


fichier = input('Fichier à sauvegarder : ')

# vérifier qu'il existe

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


if fichier not in liste:
print('Fichier inconnu')
quit()

# chercher un nom libre


trouve = False
n=1
while not trouve:
copie = fichier + '.' + str(n)
if copie not in liste:
trouve = True
else:
n += 1

# 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

Chapitre 12 – Systèmes et périphériques 19


Cette solution est préférable à la solution précédente car elle permet de garder trace de la date à
laquelle a été modifiée chaque version sauvegardée du fichier. Un certain nombre de programmes
utilisent les dates de dernières modification, par exemple pour faire des sauvegardes sur support
externe.

26 a. Ce programme est disponible dans le fichier dernier.py.


from sys import argv
from os import listdir

# retirer le premier élément du tableau argv,


# qui est le nom du programme Python
argv.pop(0)

# liste des fichiers du répertoire courant


liste = listdir('.')

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

for fichier in argv:


sauvegarde = dernier(fichier)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


if sauvegarde is None:
print(f'{fichier}: pas de sauvegarde')
else:
print(f'{fichier}: dernière sauvegarde {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

# retirer le premier élément du tableau argv,


# qui est le nom du programme Python
argv.pop(0)

# liste des fichiers du répertoire courant


liste = listdir('.')

for fichier in argv:


# vérifier qu'il existe
if fichier not in liste:
print('Fichier inconnu')
quit()

# chercher un nom libre

Chapitre 12 – Systèmes et périphériques 20


trouve = False
n=1
while not trouve:
copie = fichier + '.' + str(n)
if copie not in liste:
trouve = True
else:
n += 1

# 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

# remplissage d'un tableau d'1 million d'entrées


n = 1000000
t = [0]*n

debut = time()
for i in range(n):
t[i] = i
fin = time()
temps_tableau = fin - debut

print(f'Remplissage tableau : {temps_tableau:.4}')

b.
# écriture d'un million d'entiers dans un fichier
debut = time()
f = open('test.txt', 'w')

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


for i in range(n):
f.write(str(i))
f.close()
fin = time()
temps_fichier = fin - debut
# détruire le fichier
system('rm test.txt')

print(f'Remplissage fichier : {temps_fichier:.4}')

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.

Chapitre 12 – Systèmes et périphériques 21


Chapitre 13 – Algorithmes avancés

Notes sur le chapitre


Le but de ce chapitre est de présenter les algorithmes les plus "avancés" du programme de première.
Donc la méthode gloutonne, et un algorithme d’apprentissage : les k plus proches voisins.

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


"l’algorithme glouton reste toujours devant" : pour i croissant on montre que l’algorithme glouton
obtient à l’étape i une solution partielle dont la valeur totale est au moins aussi bonne que n’importe
quelle autre stratégie à l’étape i - soit on utilise une propriété d’échange : on suppose qu’une autre
stratégie fait mieux que la stratégie gloutonne. Prenons la meilleure des telles stratégies. On aboutit à
une contradiction en prouvant qu’on peut encore l’améliorer en échangeant 2 éléments (généralement
l’échange revient à suivre le principe glouton).

ACTIVITE, Prédire la classe d’un objet avec la méthode KNN


Au cas où il y aurait un doute, dans l’image de gauche le point noir représente la fleur 1. Dans l’image
du milieu et celle de droite, le point noir représente la fleur 85. La figure de droite est un zoom de la
figure du milieu, le zoom étant centré sur la fleur 85.

ACTIVITE, le problème du sac à dos


Preuve d’optimalité de l’algorithme glouton2frac : soit 𝑥 = 𝑥1 , … , 𝑥𝑛 (0 ≤ 𝑥𝑖 ≤ 1) la solution de
glouton3. Supposons par l’absurde que la valeur optimale soit supérieure et soit donc obtenue par une
autre solution 𝑥′ = 𝑥1 ′, … , 𝑥𝑛 ′. Soit 𝑗 le premier indice pour lequel 𝑥𝑗 ! = 𝑦𝑗 . Alors par définition de
glouton2frac, 𝑥𝑗 > 𝑥𝑗 ′. Comme 𝑥′ a une valeur supérieure à 𝑥, il existe donc un autre indice 𝑘 > 𝑗 tel
que 𝑥𝑘 < 𝑥𝑘 ′. Mais alors, on pourrait encore augmenter la valeur de 𝑥′ en prenant un peu moins
d’objet 𝑘 pour rajouter un peu d’objet 𝑗 puisque l’objet 𝑗 a une plus grande valeur volumique. Ceci
contredit la supposition que 𝑥′ était optimale, ce qui conclut la preuve par l’absurde : il ne peut pas y
avoir de stratégie meilleure que glouton3.
NB : De manière surprenante peut être l’activité met en oeuvre un algorithme glouton compliqué.
C’est plutôt du à une contrainte d’espace dans le manuel : comme nous disposons de 2 pages pour
l’activité, nous avons choisi d’expliquer avec le plus en détail le problème le plus complexe parmi
ceux suggérés au programme. On peut envisager de commencer par présenter à l’élève un algorithme

Chapitre 13 – Algorithmes avancés 1


glouton plus simple pour commencer le chapitre, avant de démarrer l’activité. Une solution serait
d’illustrer le principe de l’approche gloutonne en présentant un algorithme en langage naturel, sans
mettre en oeuvre de code. Les exemples les plus simples d’algorithmes gloutons nous semblent : - le
rendu de monnaie (simple et au programme), cf TP. - à peine plus compliqué : l’exercice 27 ou 26,
que l’on peut aussi présenter sous la forme "refaire le plein le moins souvent possible long d’une
autoroute" (il semble naturel que si on veut faire le moins d’arrêts possible il faut refaire le plein le
plus loin possible).

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 18 : appliquer kNN à des données.


Cet exercice utilise La librairie sklearn. Il suppose donc que la partie "Utiliser l’implémentation de
knn par la librairie sklearn" de l’activité a été réalisée, ou bien sinon que l’élève ira chercher dans la

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


doc de sklearn les paramètres des fonctions fit, predict, score…
Cet exercice, ne demande en l’état pas beaucoup d’investissement de la part de l’élève : à part
quelques trous dans le code, il s’agit principalement d’exécuter le code tel quel et d’observer, sans
avoir particulièrement à prendre de recul.

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 25 : Le loueur de skis


On a donné les entrées dans un format qui simplifie l’écriture de l’algorithme (hauteur avant le nom).
Sous cette forme simple, le problème devrait être faisable sans même donner une ébauche du code à
compléter.
Une version "sans code" de cet exercice classique se trouve dans le concours castor 2016, exercice
"différence de taille" https://concours.castor-informatique.fr/?

Chapitre 13 – Algorithmes avancés 2


Exercice 26 La grenouille
La formulation la plus courante de ce problème consiste à minimiser le nombre d’arrêts à des stations
essences le long d’une autoroute. Plus réaliste (pour l’hypothèse de ligne droite) mais moins
champêtre - développement durable.
Comme beaucoup d’exercices de ce chapitre, le code a trou n’aide pas beaucoup, pour ne pas
"donner/imposer la solution", le but étant avant tout que l’élève découvre par lui-même le critère
glouton à appliquer. Ceci fait que l’exercice, tel qu’il est donné, est difficile (il pourrait bien sûr être
simplifié en donnant un code à trou plus complet une fois que les élèves ont trouvé l’algorithme).

Exercice supplémentaire

Point de vue/Exercice complémentaire kNN : k-NN comme une estimation de densité de la


distribution.
Pour les élèves ayant un fort goût pour l’abstraction et qui connaitraient le théorème de Bayes (qui
n’est a priori vu qu’en terminale spé math), il peut être éclairant de justifier la méthode des k-NN sous
l’angle d’une estimation de la densité de la distribution, en vulgarisant l’approche du livre de Bishop
cité ci-dessous. Mais pour tout public autre, cette approche semble plutôt à éviter.

Exercice supplémentaire : qcm niveau initié


Quels risques identifiez-vous et quelle solution proposer si - il y a 1000 fois plus d’éléments de classe
a que de classe b dans l’échantillon - les éléments ont 2 attributs : l’attribut x est un nombre flottant
entre 10 et 100, et y est un nombre flottant entre 20 et 20000.

Exercice supplémentaire : glouton - The gift


On trouve un exercice simple et original sur : https://www.codingame.com/training/medium/the-gift n
personnes souhaitent créer une cagnotte pour offrir un cadeau de valeur v. Chaque personne a un
budget maximal qu’elle ne peut pas dépasser. Le but est de trouver comment répartir le prix a payer
pour respecter les contraintes, de sorte que la contribution maximale soit la plus faible possible (et
ensuite s’il y a plusieurs solutions, que la 2nde plus grande contribution soit la plus faible possible,
etc: bref, on veut répartir de la manière la plus "juste" possible les contributions).

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


On a en entrée la valeur v ainsi qu’une liste t de n valeurs indiquant le budget maximal de chacune des
n personnes. Renvoyer le budget que doit payer chaque personne.
Solution : on utilise un algorithme glouton. On trie les personnes par budget croissant. A chaque étape
on divise le montant restant à payer par le nombre de personnes restantes (chacune est donc censée
payer au moins ce montant si elle peut). La personne va payer le minimum entre son budget et ce
montant (ce min est soustrait du montant restant). S’il reste un montant non nul après la dernière
personne, il n’y a pas de solution satisfaisant les contraintes de budget.

Exercice supplémentaire : glouton - Couper la corde


Couper une corde de longueur entière 𝑚 en fragments de longueur entière 𝑚1 , 𝑚2 de façon à
maximiser le produit des longueurs. En d’autre termes, trouver comment des entiers (𝑚𝑖 )𝑖≥1 tels que
𝑚 = ∑𝑖 𝑚𝑖 qui maximisent stem:\prod_i m_i]. Indication : la partition optimale pour 6 est 3 + 3,
pour 7 est 3 + 2 + 2, pour 8 : 3 + 3 + 2, pour 20 : 3 + ⋯ + 3 + 2.
Solution : On prend autant de 3 que possible, et complète avec des 2.

Exercice supplémentaire : glouton - des intervalles


Soit 𝑡 une tableau d’intervalles donnés sous la forme de paire de nombres (min,max). Donner un
algorithme glouton qui calcule un ensemble 𝐸 de nombres, de taille minimale, tel que 𝐸 contient un
point de chaque intervalle.
NB : ce problème est une des variantes du problème appelé en anglais "interval stabbing". Il est plutôt
plus simple à coder que celui de la grenouille/gardien de musée. On le trouve parfois mis en scène en

Chapitre 13 – Algorithmes avancés 3


ligne (par exemple des cordes alignées que l’on souhaite couper avec le minimum de coups de
ciseaux, etc…)
Solution : Parcourir les intervalles par fin (valeur max) croissante. Quand un intervalle n’est pas
couvert par le dernier point ajouté à 𝐸, on ajoute la fin de cet intervalle à E.
def stab(t):
s = sorted(t,key=lambda x:x[1])
e = [s[0]]
for intervalle in s:
if intervalle[0]>e[-1][1] :
e.append(intervalle)
return e

stab([(1.2,3.4),(1.3,3.7),(1.4,3.3),(4,6),(3.3,5),(5.5,7)]) # [(1.4, 3.3), (4, 6)]

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 :

– avec un 1 dans la partie réservée aux 0

– avec un 2 dans la partie réservée aux 0

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


– avec un 1 dans la partie réservée aux 2?

Procéder de même pour les autres nombres.


Faire le maximum d’échanges de ce type possibles. Une fois qu’il n’y a plus d’échanges de ce genre,
terminer avec une stratégie raisonnable.
(cet exercice était aux olympiades d’informatique en 1996, sans indication bien sûr).
Réponse et ébauche de solution
Une fois qu’un élément se trouve dans sa partie, on n’a aucune raison de l’échanger.
Un échange va dans le meilleur des cas placer 2 éléments "au bon emplacement". On va donc faire au
maximum des échanges d’éléments qui placent les 2 éléments échangés dans leur emplacement final.
Une fois qu’il n’y a plus d’échanges optimaux de ce type, il peut rester des éléments à permuter. On
peut le faire par exemple en échangeant tous les 0 ne se trouvant pas dans leur emplacement final avec
les éléments se trouvant dans la partie réservée aux 0 (on pourra noter qu’il y en a forcément). Puis on
échangera les 1 et les 2.

Exercice glouton 49 du chapitre 6


Le problème 49 du chapitre 6 (traversée de pont avec une torche) vu au chapitre 6 met en oeuvre une
approche gloutonne. Mais nous ne l’avons pas présenté sous cet angle car l’algorithme optimal est un
peu compliqué vu qu’il combine 2 critères gloutons, ce qui est plutôt atypique. Nous avons donc
préféré donner l’algorithme et donner pour exercice de l’implémenter dans un programme Python.

Chapitre 13 – Algorithmes avancés 4


Exercice supplémentaire glouton en terminale
On pourra envisager d’ajouter des exercices gloutons sur les arbres et graphes (compris graphes
implicites dans calculs de voisinages sur matrice).
On pourra aussi envisager des algorithmes gloutons récursifs. Par exemple, l’algorithme des fractions
égyptiennes est très facile à définir de façon récursive (on pourrait même éventuellement le faire en
itératif dès la première mais la formulation récursive semble plus simple).

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)

• savoir identifier la famille d’algorithme (apprentissage, glouton…) d’un des algorithmes du


programme, par ex: le rendu de monnaie (10G3), les kNN (sujet-type 0 question 42)

• complexité d’une boucle imbriquée (1G2, 1G4, 1G5)

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

Références : Statistiques et apprentissage automatique.


L’élève intéressé par l’histoire des statistiques pourra être intéressé par les nombreuses anecdotes sur

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


cette frise (apparemment réalisée par l’american statistical association et la royal statistical society) :
http://www.statslife.org.uk/images/pdf/timeline-of-statistics.pdf
La frise est en anglais et a fait la part belle à des sujets anglo-américains, mais cela traduit sans doute
un intérêt particulier des états unis pour les statistiques. Le sujet de l’apprentissage automatique (et du
lien entre individu identifiés et statistiques dans la collecte de données massives) mène facilement à
des questions de sociétés.
https://www.independent.co.uk/voices/commentators/rupert-cornwell/rupert-cornwell-why-9-out-10-
americans-love-statistics-2102408.html
https://www.newyorker.com/magazine/2019/09/09/what-statistics-can-and-cant-tell-us-about-
ourselves
Rapport Villani sur l’IA disponible sur : https://www.aiforhumanity.fr/
Livre Blanc de l’Inria sur l’IA : https://www.inria.fr/sites/default/files/2019-10/AI_livre-
blanc_n01%20%281%29.pdf

Source pour les biais des algorithmes :


https://www.reuters.com/article/us-amazon-com-jobs-automation-insight/amazon-scraps-secret-ai-
recruiting-tool-that-showed-bias-against-women-idUSKCN1MK08G?
Reconnaissance faciale: https://www.nature.com/articles/d41586-020-03186-4

Chapitre 13 – Algorithmes avancés 5


Références sur KNN:
https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm
Google recommande: https://techdevguide.withgoogle.com/resources/topics/k-nearest-neighbor/?no-
filter=true https://machinelearningmastery.com/tutorial-to-implement-k-nearest-neighbors-in-python-
from-scratch/
Eduscol:
https://cache.media.eduscol.education.fr/file/NSI/76/6/RA_Lycee_G_NSI_algo_knn_1170766.pdf
Quelques cours: https://www.cs.toronto.edu/~urtasun/courses/CSC411_Fall16/05_nn.pdf
https://www.cs.cornell.edu/courses/cs4780/2017sp/lectures/lecturenote02_kNN.html
https://www.cs.cornell.edu/courses/cs5670/2018sp/lectures/lec20_image_classification.pdf
Lycée Blaise Pascal : https://info.blaisepascal.fr/nsi-les-k-plus-proches-voisins
Exemple en dimension 2 : une visualisation intéressante où l’on peut modifier de façon interactive le
nombre de classes et la valeur de k.
Stanford : https://cs231n.github.io/classification/
https://scikit-learn.org/stable/tutorial/statistical_inference/supervised_learning.html et en particulier
https://scikit-learn.org/stable/auto_examples/neighbors/plot_classification.html#sphx-glr-auto-
examples-neighbors-plot-classification-py
https://scikit-learn.org/stable/auto_examples/neighbors/plot_classification.html#sphx-glr-auto-
examples-neighbors-plot-classification-py
https://scikit-
learn.org/stable/auto_examples/exercises/plot_digits_classification_exercise.html?highlight=knn
Bishop’s Pattern Recognition and Machine Learning, Chap 2.5.2 Nearest neighbour methods p124
(pas grand chose, et aborde uniquement le sujet du point de vue density estimation).

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Leskovec, Ullman: Mining of Massive Datasets.
http://www.mmds.org/ chap 12.4 Large-Scale Machine Learning.

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

Activité : Prédire la classe d’un objet avec la méthode des k plus


proches voisins
Découverte du jeu de données

2.
def dataset_petale():
""" Cree a partir de iris une table (tableau de tableaux) de 150 lignes avec 3 colonnes.

Chapitre 13 – Algorithmes avancés 6


Chaque ligne represente (longueur du petale, largeur du petale, nom de l'espece) pour une fleur du jeu de donnees.

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

Donc pour chaque i dans 0,1,...,149:


- iris['data'][i][2] donne la longueur du petale de la fleur i.
- especedict[iris['target'][i]] est l'espece de la fleur

REMARQUE: les programmes d'apprentissages vont assez souvent distinguer


- le tableau des donnes (on le note souvent X dans sklearn)
- le tableau des valeurs que l'on veut predire, donc ici l'espece (on le note souvent y)
Nous avons choisi d'inclure les donnees et valeurs a predire dans un meme tableau,
mais ce choix n'est pas forcement le plus courant.
"""
# On utilise le tableau ci-dessous comme un dictionnaire,
# pour remplacer le code de l'espece (0,1,2) par le nom correspondant.
especedict = ['setosa', 'versicolor', 'virginica']
return [
[iris['data'][i][2],
iris['data'][i][3],
especedict[iris['target'][i]]]
for i in range(150) ]

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)

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


# - y est le tableau des annotations (target)

#donc une autre manière d'écrire le code ci-dessus est:


def dataset_petale2():
especedict = ['setosa', 'versicolor', 'virginica']
X,y = datasets.load_iris(return_X_y=True)
print(X,y)
return [[X[i][2],X[i][3],especedict[y[i]]] for i in range(150)]

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.

Prédire la classe de fleurs à partir d’un échantillon

2. L’algorithme va prédire la classe virginica pour la fleur 85 puisque c’est la classe de son plus
proche voisin.

Chapitre 13 – Algorithmes avancés 7


4.
Evaluer l’efficacité des prédictions
def taux_reussite(training, points, k):
"""
Entrée:
- un ensemble de points annotés utilisés pour entraîner le modèle; l'échantillon 'training'
- les points que l'on veut classifier, dont la dernière coordonnée est l'annotation (utilisée pour vérifier mais pas pour
classifier)
- la valeur du paramètre k, le nombre de voisins à prendre en compte dans le modèle.
Renvoie: le pourcentage de prédictions correctes sur l'ensemble 'points'.
"""
d = len(points[0])-1
n = len(points)
predictions = [None]*n
nb_reussites = 0
for i in range(n):
# on incremente nb_reussites si la classe predite pour points[i] est correcte
predictions[i] = prediction(training, points[i], k)
if predictions[i] == points[i][d]:
nb_reussites += 1
return nb_reussites/n

training = [dataset_petale()[i] for i in range(150) if i%2==0]


points = [dataset_petale()[i] for i in range(150) if i%2==1]
print([taux_reussite(training, points, i) for i in range(1,5)])

Utiliser l’implémentation de knn par la librairie sklearn


La librairie sklearn implémente la méthode des k plus proches voisins. https://scikit-
learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html#sklearn.neighbors.K
NeighborsClassifier.predict
Pour entraîner un modèle de classification, on utilise le code suivant, dans lequel

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


• X est un tableau des caractéristiques de chaque objet, et

• 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')

from sklearn import neighbors


"""
On veut apprendre une fonction qui predit y a partir de X.
"""

Chapitre 13 – Algorithmes avancés 8


k=5
clf = neighbors.KNeighborsClassifier(k)
clf.fit(X,y)

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]

prediction_1_et_85 = clf.predict([x_fleur1, x_fleur85])


score_global_sur_X = clf.score(X, y)

#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).

Visualiser les frontières des zones menant à chaque décision

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re

k-NN pour des points de dimension 4


La fonction predictions est conçue pour classifier des données d’une dimension quelconque : elle
calcule le nombre de caractéristiques en prenant la longueur de chaque entrée moins un, puis fait
ensuite appel à plus_proches_voisins et distance_euclidienne qui ont un paramètre renseignant la dimension
des points.
Donc tout ce que l’on a à faire est de redéfinir le dataset pour que chaque entrée soit un tableau de
longueur 5 : les 4 caractéristiques de l’espèce, suivi du nom de l’espèce. Puis appeler prediction sur
ces données.
La fonction `taux_reussite`aussi s’adapte à la dimension de l’entrée.

Chapitre 13 – Algorithmes avancés 9


def dataset_petale_4D():
""" Cree a partir de iris une table (tableau de tableaux) de 150 lignes avec 5 colonnes.
Chaque ligne represente (longueur du sepale, largeur du sepale, longueur du petale, largeur du petale, nom de l'espece) p
our une fleur du jeu de donnees.

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

Donc pour chaque i dans 0,1,...,149:


- iris['data'][i][2] donne la longueur du petale de la fleur i.
- especedict[iris['target'][i]] est l'espece de la fleur i

On a choisi de normaliser les longueurs/largeurs (les diviser par la valeur maximale).


C'est generalement une bonne pratique de normaliser toutes les dimensions pour l'algorithme k-NN,
car cela permet d'equilibrer l'influence des dimensions.
Cela aurait ete une bonne idee de le faire aussi pour 2 dimensions,
mais nous ne l'avions pas fait pour simplifier.
"""
# On utilise le tableau ci-dessous comme un dictionnaire,
# pour remplacer le code de l'espece (0,1,2) par le nom correspondant.
especedict = ['setosa', 'versicolor', 'virginica']
max_val = [max([x[i] for x in iris['data']]) for i in range(4)]
return [
[
iris['data'][i][0]/max_val[0],
iris['data'][i][1]/max_val[1],
iris['data'][i][2]/max_val[2],
iris['data'][i][3]/max_val[3],
especedict[iris['target'][i]]]
for i in range(150) ]

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


dataset_p_4D = dataset_petale_4D()
print(dataset_p_4D)
fleur_a_classifier_4D = dataset_petale_4D()[85].copy()
# On peut effacer la classe dans la fleur a classifier :
# fleur_a_classifier[-1] = 'inconnu'
# Mais ce n'est pas indispensable car notre algorithme ne "triche pas":
# il n'utilise pas cette information meme si elle est presente

print("Prédiction k-nn (k=5) pour la fleur 85 :", prediction(dataset_p_4D, fleur_a_classifier_4D, 5))

training = [dataset_petale_4D()[i] for i in range(150) if i%2==0]


points = [dataset_petale_4D()[i] for i in range(150) if i%2==1]
print([taux_reussite(training, points, i) for i in range(1,15)])

Activité : Résoudre un problème d’optimisation avec un algorithme


glouton
Le problème du sac à dos

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.

Chapitre 13 – Algorithmes avancés 10


import matplotlib.pyplot as plt
from math import sqrt

vol = [2,5,5,4,9]

prix = [10,11,23,12,30]

#paquets = [{'id':i,'vol':v,'prix':p} for i,(v,p) in enumerate(zip(vol,prix))]

def meilleur_candidat(contenu, vol, prix, w_restant):


"""
Entree:
- tableau vol de taille n: vol[i] = volume du paquet i
- tableau prix de taille n: prix[i] = prix du paquet i
- w_restant: volume qui reste disponible dans le sac a dos
(les prix et volumes sont supposes positifs)
Renvoie l'indice de l'objet le plus cher qui rentre dans le sac a dos.
Renvoie -1 si aucun objet ne rentre dans le sac à dos.
Complexite: lineaire O(n)
"""
candidat = -1
prix_candidat = -1
for i in range(len(vol)):
if contenu[i] == 0 and vol[i] <= w_restant and prix[i] > prix_candidat:
candidat = i
prix_candidat = prix[i]
return candidat

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


def glouton1(vol,prix, w):
"""
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: au plus n appels a meilleur_candidat, donc O(n*n).
"""
n = len(vol)
contenu = [0]*n
w_restant = w
prochain_paquet = meilleur_candidat(contenu, vol, prix, w_restant)
while prochain_paquet != -1:
print(f'On ajoute le paquet:{prochain_paquet}. Vol:{vol[prochain_paquet]}, Prix:{prix[prochain_paquet]}, volume resta
nt:{w_restant}')
contenu[prochain_paquet] = 1
w_restant = w_restant - vol[prochain_paquet]
prochain_paquet = meilleur_candidat(contenu, vol, prix, w_restant)
return contenu, sum(prix[i] * contenu[i] for i in range(n))

# Test:
print(glouton1(vol,prix,20)==([1, 0, 1, 1, 1], 75))

Chapitre 13 – Algorithmes avancés 11


def glouton1_optimise(vol,prix,w):
"""
Idee: trier par valeur decroissante 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] = 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_prix_desc = sorted([(prix[i], i, vol[i]) for i in range(n)], reverse=True)
contenu = [0]*n
w_restant = w
for p,i,v in paquets_prix_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))

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


4.
def glouton2_optimise(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] = 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))

Chapitre 13 – Algorithmes avancés 12


# Test :
print(glouton2_optimise(vol,prix,20)==([1, 0, 1, 1, 1], 75))

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


w_restant = w
for r,p,i,v in paquets_r_desc:
if v <= w_restant:
contenu[i] = 1
w_restant = w_restant - v
else:
contenu[i] = w_restant/v
return contenu, sum(prix[j] * contenu[j] for j in range(n))
return contenu, sum(prix[j] * contenu[j] for j in range(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))

Pour aller plus loin


Un algorithme optimal pour le problème du sac à dos (hors programme)
Cet algorithme met en oeuvre la programmation dynamique, qui est au programme de terminale, mais
va bien au delà de ce qui est exigible d’un élève de première.
def optimal_programmation_dynamique(vol,prix, W):
""" Programme qui calcule une solution optimale, mais en utilisant
la technique de programmation dynamique (au lieu de l'approche gloutonne).
La programmation dynamique ne sera etudiee qu'en terminale.
Complexite: O(n*W)
"""
n = len(vol)
vols = [0]+vol
prixs = [0]+prix

Chapitre 13 – Algorithmes avancés 13


valeur = [ [0]*(n+1) for i in range(W+1) ]
# valeur[i][j] = meilleure valeur qu'on puisse obtenir en utilisant uniquement
# des objets d'indice <= j pour un poids total <= i (1<=i,j<=n).
for i in range(W+1):
for j in range(1,n+1):
if vols[j] > i:
valeur[i][j] = valeur[i][j-1]
else:
valeur[i][j] = max(valeur[i][j-1], prixs[j] + valeur[i-vols[j]][j-1])
return valeur[W][n]

optimal_programmation_dynamique(vol,prix,20)

Illustration représentant la performance des différentes stratégies


import matplotlib.pyplot as plt
plt.clf()
fig, ax = plt.subplots()

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_color('none')
plt.title('Valeur des solutions de remplissage obtenues',y=-.16)
plt.margins(0.0)
plt.show()

Chapitre 13 – Algorithmes avancés 14


TP A : Problème du rendu de monnaie
1. [1,1,1,0,0,0,0,0] qui représente un ensemble comportant une pièce de 1,2, et 5 centimes d’euros.
2.
def glouton(s,v):
n=len(s)
restant = v
paiement = [0]*n
for i in range(n):
paiement[n-1-i] = restant//s[n-1-i]
restant = restant%s[n-1-i]
return paiement

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


def canonique3(s):
q = s[2]//s[1]
r = s[2]-s[1]
return r == 0 or r >= s[1]-q

# 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).

Chapitre 13 – Algorithmes avancés 15


"""
nb_bascule = 0
for j in range(len(l)):
if l[i][0] <= l[j][0] < l[i][1] or l[j][0] <= l[i][0] < l[j][1]:
if b[j]:
b[j] = False
nb_bascule += 1
return nb_bascule

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


def glouton(l):
"""
Entrée: une liste de tâches l
Renvoie: les tâches sélectionnées, sous forme d'une liste de tâches (et non d'un tableau de booléens)
"""
n = len(l)
nb_taches_dispos = n
dispos = [True]*n
taches = sorted(l,key=lambda t:t[1])
taches_selectionnees = []
while nb_taches_dispos > 0:
for i in range(n):
if dispos[i]:
taches_selectionnees.append(taches[i])
nb_taches_dispos -= conflit(taches,dispos,i)
return taches_selectionnees

# 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)])

Chapitre 13 – Algorithmes avancés 16


5. Prendre la stratégie gloutonne qui sélectionne à chaque fois l’activité disponible qui a la dernière
date de début.

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


implémentation de l’algorithme peut tout à fait décider d’utiliser l’ordre des données pour effectuer
ces choix. Mais l’ordre ne joue pas de rôle fondamental dans l’algorithme en tant que tel.
b. Même réponse que ci-dessus. Pour l’algorithme, chaque élément peut être vu comme un
dictionnaire plutôt qu’un tableau ; à chaque caractéristique on associe une valeur. L’algorithme ne
prend pas en compte l’ordre dans lequel sont données les caractéristiques et traite toutes les
caractéristiques de façon identique.
c. Par contre, les doublons ont une influence : un élément qui apparaît deux fois ``compte double''. Il
comptera pour 2 voisins. Donc éliminer les doublons de l’échantillon modifie le résultat de k-NN.
Noter que dans le cas particulier de 1-NN, par contre, les doublons ne jouent aucun rôle et peuvent
être éliminés.

4
☑ La méthode peut s’appliquer à des données ayant un nombre quelconque de dimensions
(caractéristiques); 1,2,3 ou plus : Vrai.

☐ Cet algorithme d’apprentissage automatique demande un entraînement coûteux : Faux: il est


coûteux au moment de la classification, mais il n’y a pas d’entraînement à proprement parler sauf si on
indexe l’échantillon. La partie ``entraînement'' a donc un coût à peu près nul.

☑ 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 de classification simple : Vrai.

Chapitre 13 – Algorithmes avancés 17


☑ C’est un algorithme de régression simple : Vrai.

☑ 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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


k = 3: 1 k = 4: 1
k = 5: 1 ou 2 k = 6 ou 7: 1
k = 8: 1 ou 2 k > 8: 2
b. Oui c’est possible, mais uniquement en jouant sur les ex-aequos (car si on utilise la distance
euclidienne, ce qui est implicitement supposé, tout point a au moins 3 points de classe 2 plus proches
de lui que le point de classe 3 le plus éloigné). En jouant sur les ex-aequos, un point se trouvant très
haut aura le choix entre les 3 classes 2, 3 et 4. On peut aussi placer un point suffisamment loin en bas à
gauche pour avoir le choix entre les 3 classes 1, 2 et 3.

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:

Chapitre 13 – Algorithmes avancés 18


# X0,y0=datasets.load_digits(return_X_y=True)

# Comme on veut des listes et pas un tableau numpy:


X = [[x[i] for i in range(64)] for x in X0]
y = [v for v in y0]

On peut visualiser les images comme ci-dessous.


import numpy as np
import matplotlib.pyplot as plt
plt.figure(figsize=(30,4))
for i in range(10):
image = X0[i]
label = y0[i]
plt.subplot(1, 10, i + 1)
plt.imshow(np.reshape(image, (8,8)), cmap=plt.cm.gray)
plt.title(f'Training: {label}\n', fontsize = 20);

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

X_train = [X[i] for i in range(900)]


y_train = [y[i] for i in range(900)]
X_valid = [X[i] for i in range(900,1797)]
y_valid = [y[i] for i in range(900,1797)]

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


# Test :
print(X_train[0]==[0.0,0.0,5.0,13.0,9.0,1.0,0.0,0.0,0.0,0.0,13.0,15.0,10.0,15.0,5.0,0.0,0.0,3.0,15.0,2.0,0.0,11.0,8.0,0.0,0.0,4.0
,12.0,0.0,0.0,8.0,8.0,0.0,0.0,5.0,8.0,0.0,0.0,9.0,8.0,0.0,0.0,4.0,11.0,0.0,1.0,12.0,7.0,0.0,0.0,2.0,14.0,5.0,10.0,12.0,0.0,0.0,0.0,
0.0,6.0,13.0,10.0,0.0,0.0,0.0])
print(y_train[0]==0)
print(y_valid[0]==4)

# Entraînement du modèle :
from sklearn import neighbors

clf = neighbors.KNeighborsClassifier(5)
clf.fit(X_train,y_train)

taux_de_prediction = clf.score(X_valid, y_valid)

# 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);

Chapitre 13 – Algorithmes avancés 19


Exercices Titan
30 a. Les jeux 1 et 2 ex-aequos, puis, le jeu 3, puis le jeu 4. Avec k = 5, en prenant un point sur 2 dans
l’échantillon, nous avons obtenu un taux de succès de 0.97 pour les 2 premiers (donc 3 % d’erreurs),
0.73 pour le jeu 3 (27 % d’erreurs), et 0.2 (80 % d’erreurs) pour le jeu 4.
Le jeu 1 est en fait le jeu Iris utilisé pour l’activité. Nous avons créé le jeu 2 en recopiant le jeu 1 et en
ajoutant une copie des données (en permutant les classes) dans une autre portion de l’espace de sorte
qu’il n’y ait pas de risque de confusions entre les groupes (on parle de ``clusters'' en anglais). Il n’y a
donc ni plus ni moins de possibilités d’erreurs dans le jeu 2 que dans le jeu 1.
On voit que le jeu 3 est un peu plus mélangé, il y a donc une plus forte probabilité d’erreurs de
prédictions.
Quant au jeu 4, il est en fait généré de manière aléatoire. La prédiction n’a donc aucun sens et ne fait
pas mieux qu’une prédiction au hasard parmi les 4 classes disponibles (en fait, .2 c’est même
légèrement pire car en prédisant n’importe quelle classe au hasard on devrait indiquer la bonne dans
25 % des cas).
b. Il est vrai que cela va accélérer la classification puisqu’au lieu de calculer les plus proches voisins

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


parmi tous les points de l’échantillon initial, il suffit de calculer les voisins parmi l’échantillon résumé.
Par contre, c’est une mauvaise idée de ne garder qu’un point par classe.
D’une part, il faut impérativement garder k=5 points de chaque classe (à moins d’adapter l’algorithme
en pondérant). Ensuite, même si k=1, pour les classes violettes et rouges qui sont formées d’un groupe
homogène, cela ne pose pas de problème de ne garder qu’un point, mais par contre pour les classes
turquoises et vertes il faut a priori au moins 2 points; 1 par groupe de points'' faute de quoi il risque d’y avoir
trop de confusions entre les 2 classes vu que chacune de ces classes est formée de 2 groupes'' (clusters). Pour avoir
une bonne précision, il faudra même pour ces 2 classes un assez grand nombre de points et pas juste 2
points car on voit qu’elles sont légèrement imbriquées au niveau de leur frontière, donc il faudra
garder des échantillons suffisamment nombreux pour identifier assez précisément la frontière entre ces
2 classes.
c. Non, il est tout à fait possible et même possiblement judicieux de créer des échantillons virtuels,
c’est-à-dire prendre comme représentants de chaque classe des points qui ne fassent pas partie de
l’échantillon. Un bon candidat par défaut dans le cas de clusters homogènes (comme les classes rouge
et violette) serait le barycentre de chaque ``cluster'' (pour délimiter la frontière des classes verte et
turquoise, on peut supposer qu’il est judicieux de garder aussi des points plus proches de la frontière).

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

c. 50 × 50 × 50 = 125000 points. Et en dimension 𝑑, 50𝑑 points. Dès que la dimension devient


importante, il faut un nombre astronomique de points pour que la méthode des plus proches voisins
garantisse un résultat fiable. En fait, le problème est le même pour beaucoup de méthodes

Chapitre 13 – Algorithmes avancés 20


d’apprentissage automatique. On applique donc souvent une technique consistant à projeter'' les points sur
un nombre de dimensions restreintes, ce qu’on appelle la réduction de dimensionalité''.

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

8 ❏ La méthode gloutonne est une approche permettant de concevoir des algorithmes

❏ 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).

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


10 Voir le cours. La méthode gloutonne donne un algorithme optimal pour le système de monnaie
``euro'' (mais pas pour d’autres systèmes).

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)

Chapitre 13 – Algorithmes avancés 21


n = len(v)//2
total_1 = 0
total_2 = 0
objets_1 = [None]*n
objets_2 = [None]*n
i_debut = 0
i_fin = len(v)-1
for i in range(n):
# On effectue 2 etapes d'un coup: celle de 1 et de 2
# Etape de 1:
if v[i_debut] > v[i_fin]:
objets_1[i] = v[i_debut]
i_debut = i_debut + 1
else:
objets_1[i] = v[i_fin]
i_fin = i_fin - 1
# Etape de 2:
if v[i_debut] > v[i_fin]:
objets_2[i] = v[i_debut]
i_debut = i_debut + 1
else:
objets_2[i] = v[i_fin]
i_fin = i_fin - 1
print(f'R1 va obtenir {sum(objets_1)} avec les objets {objets_1}')
print(f'R2 va obtenir {sum(objets_2)} avec les objets {objets_2}')
return objets_1,objets_2

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


(en supposant que R2 choisit aussi cette stratégie)
"""
assert(len(v)%2 == 0)
n = len(v)//2
total_pair = 0 # total des valeurs a des indices pairs
total_impair = 0 # total des valeurs a des indices impairs
objets_pairs = [None]*n
objets_impairs = [None]*n
for i in range(n): # on traite 2 par 2 les cases de v
total_pair = total_pair + v[2*i]
objets_pairs[i] = v[2*i]
total_impair = v[2*i+1]
objets_impairs = v[2*i+1]
if total_pair > total_impair :
return objets_pairs, total_pair
else:
return objets_impairs, total_impair

print("objets et total qu'obtient R1 en jouant la strategie pair/impair :",valeur_R1([1.2,4.2,80,5]))

# 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.
"""

Chapitre 13 – Algorithmes avancés 22


assert(len(v)%2==0)
total_pair = 0
total_impair = 0
for i in range(len(v)):
if i%2==0:
total_pair = total_pair + v[i]
else:
total_impair = total_impair + v[i]
if total_pair > total_impair:
return [ v[i] for i in range(len(v)) if i%2==0 ], total_pair
else:
return [ v[i] for i in range(len(v)) if i%2==1 ], total_impair

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


def decompose(t):
"""
Entree: t est un tableau de proprietes, données dans l'ordre le long d'une rue.
========
On suppose que chaque propriete est un dictionnaire:
{'nom': ..., 'valeur': ...}
On suppose aussi (nécessairement) que la valeur totale de l'ensemble des propriétés le long de la rue est positive,
sinon il n'y a pas de solution.
========
Renvoie: la liste des lots (tableau de tableau de proprietes).
"""
assert(sum([x['valeur'] for x in t])>=0)
res = []
lot = [] # proprietes depuis le lot precedent
total = 0 # valeur totale depuis le lot precedent
for j in range(len(t)):
lot = lot + [t[j]]
total = total + t[j]['valeur']
if total >= 0:
res = res + [lot]
lot = []
total = 0
if total < 0: # les éléments dans le dernier lot ne forment pas un lot valide seuls.
j = len(res)-1
while total < 0:
for x in res[j]:
total = total + x['valeur']
lot = res[j]+lot
j = j-1

Chapitre 13 – Algorithmes avancés 23


return [res[i] for i in range(0,j+1)]+[lot]
return res

# 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")

21 a. La stratégie optimale trie par ordre croissant d’heure de début.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


b.
def optimise_salle(l):
"""
On suppose chaque cours est represente par un dictionnaire {'debut': ..., 'fin': ...}
On suppose qu'il y a au moins un cours.
Renvoie un planning qui minimise le nb de salles.
Le planning est un tableau de salles.
Chaque salle est un tableaux de seances de cours
"""
def f(c):
""" renvoie le debut du cours c"""
return c['debut']
seances = sorted(l, key=f)
salles = [[seances[0]]]
for i in range(1,len(seances)):
j=0
while j < len(salles) and seances[i]['debut'] < salles[j][-1]['fin']:
j = j+1
if j == len(salles):
salles.append([seances[i]])
else:
salles[j].append(seances[i])
return salles

# Test :
liste_cours = [{'debut': 1, 'fin': 2},
{'debut': 1, 'fin': 3},
{'debut': 3, 'fin': 6},

Chapitre 13 – Algorithmes avancés 24


{'debut': 2, 'fin': 7}]

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. considérer les n*2 paires d’entiers x,y. Mettre x avant y si xy<yx.

• 1. commencer par répéter les chiffres de chaque nombre jusqu’à ce que tous les nombres aient la

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


même longueur, puis trier les nombres.

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

f(['5', '534', '52', '46', '39']) # 5534524639

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

Chapitre 13 – Algorithmes avancés 25


- h est un tableau de paires (nombre, chaine): hauteur et nom des skieurs
- on suppose que l et h ont meme longueur
"Calcule" la strategie qui minimise l'ecart de taille entre un skieur et son ski.
Affiche pour chaque skieur : son nom et le ski que la strategie lui attribue.
Renvoie le total des ecarts pour la strategie.
Complexite:
... (en tout cas moins que quadratique)
"""
skieurs = sorted(h)
skis = sorted(l)
total = 0
for i in range(len(skis)):
total += abs(skieurs[i][0]-skis[i])
print(f"skieur: {skieurs[i][1]} aura les skis: {skis[i]}")
return total

# Test :
skieurs0 = [(175,"Sofia"),(168,"Sarah"),(172,"Maxime"),(180,"Ali")]
skis0 = [195,168,180,174]
print(f(skis0,skieurs0)==22)

26 Approche gloutonne : la grenouille va sélectionner le nénuphar le plus éloigné qu’elle puisse


atteindre a chaque étape. Il est assez facile de comprendre (mais on ne va pas le prouver formellement)
que cette solution est optimale. Pour savoir si un nénuphar est le plus éloigné, avant de prendre la
décision au nénuphar i il faut regarder la distance du nénuphar i+1 (cela reste un algorithme glouton).
# Attention aux comparaisons de flottants
# Par exemple on risque en Pyton d'avoir '(11.2+1.1>=12.3) == False'.
# On veut qu'une grenouille puisse toujours sauter sur un nénuphar à distance 1.1, et tolérera donc que la grenouille puisse s
auter sur un nénuphar tant
# nénuphar à distance très légèrement (ex: .00001) plus grande que 1.1.
def grenouille_sauteuse(l):
"""
Entree: tableau indiquant la distance (en m) des nenuphars au point de depart (ils sont alignes).

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Chaque nenuphar est caracterise par une distance 0<x<20, un décimal ayant au plus 2 chiffres après la virgule (représenté
en Python par un flottant)
L'arrivee est situee a 20m.
Chaque saut doit être strictement inférieur à 1.1.
Renvoie : le tableau des nenuphars choisis (identifies par leur distance)
ou une erreur s'il n'y a pas de solution.
"""
d = 0# distance_parcourue
nenuphars = [0]+sorted(l)+[20] # petite astuce: on considere le depart et l'arrivee comme des nenuphars pour simplifier le
code.
solution = []
i=0
while i < len(nenuphars)-1:
if nenuphars[i+1] <= d + 1.1 + 0.00001:
i = i+1
elif d == nenuphars[i]: # pas possible de progresser depuis d
raise Exception(f'Impossible de progresser au-dela de: {d}')
else:
# on a progresse mais atteint la limite pour cette etape.
d = nenuphars[i]
solution.append(nenuphars[i])
if i == len(nenuphars)-1:
return solution # on a atteint le dernier nenuphar = l'autre rive

# 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])

Chapitre 13 – Algorithmes avancés 26


nenuphars_avec_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.3,13.2,14.25,15.3,16.35,
17.4,18.45,19.5,20]
print(grenouille_sauteuse(nenuphars_avec_distance_de_unvirgule1)==[1,2,3.05,4.1,5.15,6.2,7.1,8.15,9.2,10.2,11.2,12.3,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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


10). Coût total de 19.
b. Un code python est donné ci-dessous. L’idée est de maintenir dans une queue de priorité la taille
des blocs par ordre croissant (en Python, on peut utiliser heapq pour cela).
Tant que la queue contient au moins 2 éléments, on assemble ses deux plus petits éléments (on les
enlève donc de la queue, et insère le nouvel élément formé dans la queue, à la position correspondant à
son rang pour que la queue reste triée).
from heapq import heappush, heappop

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)

Chapitre 13 – Algorithmes avancés 27


29 a. On part de Paris et sélectionne à chaque fois la ville la plus proche de la position courante (parmi
celles qui n’ont pas été encore visitées).
b. On peut imaginer un ensemble de villes réparties à intervalle régulier le long d’un cercle (en partant
de la ville représentée par un carré orange ci-dessous). vers la moitié du cercle on place une ville en
dehors du cercle (losange vert ci-dessous) dont la distance aux points du cercle est légèrement plus
grande que celle entre 2 points successifs sur le cercle.
L’algorithme glouton va d’abord parcourir le cercle en entier et devra faire un grand détour pour
visiter la ville à l’écart: il visite en dernier cette ville, lorsqu’il n’y aura plus d’autres villes proches à
visiter, alors qu’il aurait été préférable de faire un petit détour vers la moitié du cercle lorsque le
parcours passait relativement proche de cette ville.
from matplotlib import pyplot as plt
from math import cos,sin,pi

theta = [2*pi*i/20 for i in range(20)]


x = [cos(a) for a in theta]+[-2,1]
y = [sin(a) for a in theta]+[0,0]
x2 = [cos(theta[i]) for i in range(10)]+[-2] + [cos(theta[i]) for i in range(10,20)] + [1]
y2 = [sin(theta[i]) for i in range(10)]+[0] + [sin(theta[i]) for i in range(10,20)] + [0]

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


plt.show()

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

Chapitre 13 – Algorithmes avancés 28


33 Le robot peut sélectionner à chaque fois son voisin de valeur la plus élevée. Sur l’exemple donné
(en numérotant (0,0) la case en haut à gauche, et (3,0) celle en bas à gauche…), le parcours du robot
sera (0,0), (0,1), (0,2) (1,2), et finalement (1,3).
Remarque : cette heuristique est très utilisée en optimisation sous des formes un peu plus complexes :
on parle alors de descente de gradient.
def voisins(pos,n):
"""
Entrée:
- une paire pos=(i,j) correspondant à une position dans une matrice.
- la "dimension" n de la matrice (la matrice a n*n cases)
Renvoie:
les 4 voisins de pos si elle en a 4. Elle en aura moins (2 ou 3) si elle est sur un bord
"""
v = []
if pos[0]>0:
v.append((pos[0]-1,pos[1]))
if pos[0]+1<n:
v.append((pos[0]+1,pos[1]))
if pos[1]>0:
v.append((pos[0],pos[1]-1))
if pos[1]+1<n:
v.append((pos[0],pos[1]+1))
return v

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


===
On suppose naturellement que la case supérieure gauche est celle d'indice 0,0.
On suppose que le robot est initialement placé dans la case supérieure gauche.
===
Renvoie le parcours du robot jusqu'à atteindre un maximum local, en adoptant la stratégie gloutonne.
"""
pos = (0,0)
n = len(m)
prochaine_pos = pos
chemin = [pos]
for p in voisins(pos,n):
if m[p[0]][p[1]] > m[prochaine_pos[0]][prochaine_pos[1]]:
prochaine_pos = p
while pos != prochaine_pos:
pos = prochaine_pos
chemin.append(pos)
for p in voisins(pos,n):
if m[p[0]][p[1]] > m[prochaine_pos[0]][prochaine_pos[1]]:
prochaine_pos = p
return chemin

# 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)])

Chapitre 13 – Algorithmes avancés 29


Chapitre 14 – Au-delà de Python

Notes sur le chapitre


JavaScript et Ruby dans un notebook Jupyter
Pour rappel, il est possible d’exécuter du code JavaScript ou Ruby dans une cellule d’un notebook
Jupyter, il faut pour cela faire commencer la cellule par %%js ou %%javascript pour JavaScript et par
%%ruby pour Ruby.

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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Notes sur les exercices
Array.push() - La fonction push permettant d’ajouter un élément à la fin d’un tableau en JavaScript est
introduite à l’exercice 14 :

(push est l’équivalent de append en Python.)


Cependant son usage est également nécessaire à l’exercice 11.

Prérequis – TEST
1 c ; 2 c ; 3 b ; 4 b ; 5 b.

Chapitre 14 – Au-delà de Python 1


Activité : Mêmes algorithmes, différentes syntaxes
Mots-clés
1. Réponses en colonne 1 de la table :

Définir une Déclarer une Lever une Importer une


Langage Afficher du texte
fonction variable exception bibliothèque

Pas de mot-
son type printf(…); Pas d’exception #include
C clé

JavaScript function let/var/const console.log(…); throw import

Caml let let/value print_string …;; raise open

Ruby def Pas de mot-clé3 puts … raise require

Python def Pas de mot-clé3 print(…) raise import

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);).

Délimitation des instructions

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


Réponses dans la colonne 2 de la table :

Retour-ligne Python, Ruby Visual Basic, Fortran, MATLAB, …

Point-virgule ; C, JavaScript C++, Pascal, Perl, Ada, Java, Rust, …

Double point-virgule ;; Caml OCaml, …

Virgule , ou point . Erlang, Prolog, SmallTalk…

Délimitation des blocs


Langage Début de bloc Fin de bloc

Python : Retour-ligne au même niveau d'indentation

JavaScript, C { }

Ruby do ou { end ou }

Caml ne dispose pas de blocs à proprement parler.

Chapitre 14 – Au-delà de Python 2


Commentaires

C++, Java, C, JavaScript... Perl, R, Python, Ruby... TeX, MATLAB, Erlang...

// Sur une ligne

/* Sur plusieurs # Sur une ligne % Sur une ligne

lignes */

Ruby... OCaml, Caml... HTML, XML...

=begin Sur plusieu (* Sur plusieur <!-- Sur plusieu


rs s rs

lignes =end lignes *) 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

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


(cela en change également le type) ; 15 b ; 16 b ; 17 b.

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.

Chapitre 14 – Au-delà de Python 3


7 Le code est valide en JavaScript, il produit la chaîne "Le parrain 2". Il est également valide en C, il
produit la chaîne " parrain" (ici le +2 est interprété comme un décalage du début de la chaîne initiale). Il
n’est pas valide en Python.

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

10 a. La console JavaScript affiche true dans les deux cas.


b. Contrairement à Python, JavaScript évalue d’abord la première comparaison, puis effectue la
seconde comparaison entre le résultat de la première (un booléen) et le troisième opérande. Par
ailleurs, en JavasScript le nombre 0 est équivalent à false, et tout autre nombre à true ; à l’inverse, true
utilisé comme un entier vaut 1.
Ainsi 1 < 3 < 5 équivaut à (1 < 3) < 5, soit true < 5, et donc 1 < 5, qui est vrai.
1 > 3 == 0 équivaut à (1 > 3) == 0, soit false == 0, qui est vrai (en JavaScript).

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


c.
• print((1<3)<5)

• print((1>3)==0)

11 a.
function inverse_tableau(t) {
let inverse = [];

// t.length donne le nombre d'éléments d'un tableau `t` en JavaScript


for (let i=t.length-1 ; i >= 0 ; i-=1) {
inverse.push( t[i] );
}
return inverse;
}

console.log( inverse_tableau([1, 2, 3, 4, 5, "table"]) );

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

Chapitre 14 – Au-delà de Python 4


13
joueur1 = rand(1..6)
joueur2 = rand(1..6)
puts "#{joueur1} vs #{joueur2}"
puts joueur1 > joueur2 ? "joueur 1 gagne" : joueur2 > joueur1 ? "joueur 2 gagne" : "égalité"

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

console.log(diviseurs(51)); // Array [ 17, 3 ]

16 a. En Ruby : notez l’usage de puts et unless, ou encore l’usage de do-then-else pour délimiter les
blocs.

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re


b. Elle applique le « crible d’Ératosthène », introduit dans les exercices du Chapitre 8, et affiche les
résultats.
c. Solution en Python
def c_e(val_max):
if val_max < 2:
print("aucun")
valeurs = [True] * (val_max+1)
for i in range(2, val_max):
if valeurs[i]:
for j in range(2*i, val_max, i):
valeurs[j] = False
print(i)

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]) {

Chapitre 14 – Au-delà de Python 5


for (let j = 2*i ; j < val_max ; j += i) {
valeurs[j] = false;
}
console.log(i);
}
}
}

c_e(40);

© Hachette Livre 2021 – Guide pédagogique Numérique et Sciences informatiques 1re

Chapitre 14 – Au-delà de Python 6

Vous aimerez peut-être aussi