Cours d`informatique pour tous

publicité
Cours d’informatique pour tous (2013-2014)
Stéphane Flon
Table des matières
Introduction
5
partie A. Algorithmique et programmation
7
Chapitre I. Programmation
1. Principes généraux de programmation
2. Le langage Python
3. Auto-documentation
4. Types
5. Les identificateurs
6. Structures de contrôle
7. Programmation fonctionnelle
8. Quelques approfondissements en Python
9. Présentation de quelques traits de programmation
9
9
11
12
12
16
17
19
20
25
Chapitre II. Algorithmique
1. Algorithmes : principes généraux
2. Algorithmes de recherche
3. Que peut-on espérer d’un algorithme ?
4. Complexité d’un algorithme
29
29
30
31
31
partie B. Architecture des ordinateurs et représentation des nombres
Chapitre III. Représentation des nombres
1. Introduction : représentation humaine des nombres entiers naturels
2. Représentation de l’information dans un ordinateur
3. La représentation des nombres entiers en informatique
4. La représentation des nombres réels
partie C. Feuilles de TD
35
37
37
39
39
40
43
Feuille de TD 1. Premiers pas en Python
45
Feuille de TD 2. Représentation des nombres
1. Représentation des entiers naturels
2. Représentation des réels
49
49
50
Feuille de TD 3. Arithmétique
1. Problèmes élémentaires
2. Problème classiques
3. Tests de primalité et méthodes de factorisation
4. Cryptographie
51
51
51
52
52
3
Introduction
5
Première partie
Algorithmique et programmation
CHAPITRE I
Programmation
1. Principes généraux de programmation
1.1. Caractéristiques d’un langage de programmation
Un algorithme consiste en la donnée d’une démarche organisée afin d’effectuer une tâche précise. Par exemple,
cette recette de key lime pie 1, peut être considérée comme un algorithme.
Temps de p r é p a r a t i o n : 20 minutes
Temps de c u i s s o n : 25 minutes
I n g r é d i e n t s ( pour 6 p e r s o n n e s ) :
− 200 g de p e t i t s −b e u r r e s
− 5 citrons verts
− 75 g de b e u r r e
− 1 b o i t e de l a i t c o n c e n t ré s u c r é
− 3 oeufs
− sel
P ré p a r a t i o n de l a r e c e t t e :
P r é c h a u f f e r l e f o u r à 180C ( t h e r m o s t a t 6 ) .
É m i e t t e r l e s p e t i t s b e u r r e p u i s a j o u t e r l e b e u r r e fondu e t mélanger .
D i s p o s e r c e t t e p r é p a r a t i o n dans un p l a t .
F a i r e c u i r e e n v i r o n 10 minutes à 180C ( t h e r m o s t a t 6 )
j u s q u ’ à c e que l a c r o û t e a i t une c o u l e u r d o ré e .
Pendant c e temps , p ré p a r e r l a g a r n i t u r e : b a t t r e l e s j a u n e s d ’ o e u f s avec
l e l a i t c o n c e n t ré s u c ré , a j o u t e r l e j u s de c i t r o n e t mélanger
a f i n d ’ o b t e n i r une c o n s i s t a n c e crémeuse .
V e r s e r l a g a r n i t u r e s u r l a p r é p a r a t i o n de b i s c u i t s e t l a i s s e r c u i r e e n v i r o n
20 minutes à 180C ( t h e r m o s t a t 6 ) j u s q u ’ à c e que l a crème de c i t r o n s o i t b i e n p r i s e .
B a t t r e l e s b l a n c s d ’ o e u f s avec une p i n cé e de s e l e t l e s u c r e ,
j u s q u ’ à c e qu ’ i l s s o i e n t b i e n f e r m e s e t b r i l l a n t s .
É t a l e r s u r l e g â t e a u e t r e m e t t r e au f o u r à 180C ( t h e r m o s t a t 6 )
e n v i r o n 5 minutes pour que l a meringue s o i t d o ré e .
Servir bien f r a i s .
On peut définir un programme comme un texte ou un fichier (que l’on appelle souvent code) exprimant
un algorithme, dans une version intelligible par un langage de programmation : ce langage va alors analyser le
fichier reçu, et le compiler (i.e. le réécrire une fois pour toutes d’une façon adaptée au langage machine), ou
l’interpréter (le réécrire temporairement pour l’occasion en langage machine).
La plupart du temps, les langages compilés sont plus rapides que les langages interprétés.
1. piquée chez Marmiton et à peine remaniée
9
1. PRINCIPES GÉNÉRAUX DE PROGRAMMATION
CHAPITRE I. PROGRAMMATION
Transcrire un algorithme –décrit en langue naturelle par un pseudo-code– en un programme dépendra
évidemment beaucoup du langage choisi. Certains langages sauront digérer un léger remaniement de l’algorithme
initial : ils sont dits de haut niveau. D’autres exigeront une version beaucoup plus proche du langage machine,
et nécessiteront donc un effort de conversion de la part du programmeur : ils sont dits de bas niveau.
Par exemple, étant donné l’algorithme minimaliste
A f f i c h e r ” H e l l o World ”
une version Python peut être donnée par
print ( ’ H e l l o World ’ )
alors qu’une version en assembleur (fournie par Wikipedia) pourrait être
. model s m a l l
. s t a c k 100h
. data
bonjour
db
” H e l l o world ! $ ”
. code
main
proc
mov AX, @data
mov DS , AX
mov DX, o f f s e t b o n j o u r
mov AX, 0 9 0 0 h
i n t 21h
mov AX, 4 C00h
i n t 21h
main endp
end main
$
tandis qu’en langage machine, on aurait, en binaire
10111010
00000001
00001001
00100001
11100100
00010110
00000000
11001101
01001000
01101100
01101111
01010111
01110010
01100100
00100100
00010000
10110100
11001101
00110000
11001101
10111000
01001100
00100001
01100101
01101100
00100000
01101111
01101100
00100001
( he )
( ll )
(o )
(wo)
( rl )
(d ! )
($)$
On constate donc que, pour cet exemple au moins, Python est de haut niveau, et on se félicite déjà du choix
de ce langage pour apprendre la programmation.
1.2. Expressions et instructions
Une instruction est un ordre donné à l’ordinateur (par exemple, assigner une certaine valeur à un certain
identificateur, exécuter telle fonction en tel point, etc.). Une expression est une phrase (une chaı̂ne de caractères)
définissant une valeur.
10
Stéphane FLON
CHAPITRE I. PROGRAMMATION
2. LE LANGAGE PYTHON
1.3. Variables, identificateurs et fonctions
Une variable est un objet que l’ordinateur doit stocker en mémoire, comme un nombre, une liste, ou même
une fonction. Cela peut se faire de manière explicite, lorsqu’on déclare une variable en la liant à un identificateur :
x = 456
Ici, la variable est 456, et son identificateur est x. Nous avons affecté (ou assigné) la valeur 456 à l’identificateur x.
Cela peut aussi se faire de manière implicite, par exemple lorsqu’on demande à Python d’effectuer un calcul
>>> s i n ( 1 2 3 4 )
0.60192765476249732
Ici, Python a stocké temporairement le nombre 1234, afin d’évaluer l’expression proposée. On parle alors
de variable temporaire.
Remarque : il arrive souvent que, par abus de langage, on confonde variables et identificateurs.
Comme en mathématiques, on définit aussi la notion de fonction, qui, lorsqu’on l’applique à un ou plusieurs
arguments, renvoie une valeur.
Dans l’exemple ci-dessus, j’ai appelé la fonction sin de Python, et j’ai passé 1234 en argument. Python m’a
renvoyé une valeur approchée de cette expression.
Lorsqu’une fonction est appelée, elle suit les instructions, puis renvoie une valeur. Cela dit, l’appel de cette
fonction a pu modifier beaucoup d’autres choses, par exemple en affichant des résultats dans la console, en
produisant un graphique ou un son, en modifiant l’identificateur passé en argument, etc. On parle d’effet de
bord. Par exemple, print ne fait qu’afficher son ou ses arguments, elle ne renvoie pas de valeur. Pour illustrer
ceci, j’utiliserai l’underscore , qui permet de rappeler le dernier résultat renvoyé par Python :
>>> x = 123 # Je d é c l a r e l ’ i d e n t i f i c a t e u r x
>>> y = 234 # Je d é c l a r e l ’ i d e n t i f i c a t e u r y
>>> x + y # E x p r e s s i o n à c a l c u l e r
357
>>>
# D e r n i e r r é s u l t a t r e n v o yé
357
>>> print ( x ∗ y ) # Je demande d ’ a f f i c h e r une e x p r e s s i o n , non de l a r e n v o y e r
28782
>>>
# D e r n i e r r é s u l t a t r e n v o yé
357
Dans cet exemple, print, qui n’a agi que par effet de bord, n’a pas renvoyé de valeur : la dernière valeur
renvoyée est donc bien 357.
2. Le langage Python
2.1. Principes
Python est un langage interprété, à la fois pédagogique et puissant. Il est très utilisé dans les milieux
scientifiques (informatique, mathématiques, physique, médecine, imagerie, etc.) et dans l’industrie.
Il est également très simple et clair. D’ailleurs, Python nous oblige presque à écrire des programmes clairs,
puisque l’indentation y est obligatoire, c’est elle qui permet par exemple de définir le corps d’une boucle for ou
while : toutes les instructions d’une même boucle seront indentées de la même façon.
Le séparateur d’instruction standard est le point-virgule ; .
2.2. Quelques consignes de programmation
– La première qualité d’un programme (qui renvoie le résultat attendu) est la lisibilité : ne cherchez pas à
optimiser votre code par de petits arrangements cosmétiques. N’hésitez pas par exemple à rajouter
des parenthèses facultatives, si elles permettent de faciliter la lecture.
Remarque : passer d’un code très rapide à un code très lent peut être louable 2, mais cela ne se fera pas
par de petits changements. Il faudra passer par des considérations assez théoriques de complexité.
– Donnez des noms parlants à vos fonctions et variables., introduisez les au début.
– Décomposez vos gros problèmes en petits, afin de gagner en lisibilité et en modularité.
2. et souvent nécessaire quand on travaille sur Project Euler . . .
11
Stéphane FLON
4. TYPES
CHAPITRE I. PROGRAMMATION
– Pour l’indentation, la coutume est d’utiliser quatre espaces par bloc, plutôt que des tabulations (mélanger
les deux pourrait conduire à des erreurs de code invisibles ).
– Ne surchargez pas programmes de tests inutiles : lorsqu’une fonction est censée prendre en argument un
entier naturel par exemple, inutile de faire un test en entrée pour le vérifier, c’est vous qui testerez la
fonction sur les bons objets (comme on dit we’re all consenting adults here , bien que je n’en sois pas
sûr dans cette classe . . .). Quand vous développerez dans la vraie vie, ce sera une autre histoire.
– De même, si votre fonction doit renvoyer un entier, faites en sorte qu’elle renvoie un entier, et non une
chaı̂ne de caractère par exemple (du genre ’ le résultat cherché vaut 1345353’).
– Factorisez vos démarches. Par exemple, vous ne devriez pas souvent employer un copier-coller au sein
d’un même programme, écrivez plutôt une routine qui applique toutes les opérations aux différents objets.
(Note pour moi-même : trouver un exemple simple)
– Je ne sais pas encore à quoi ressembleront les sujets de concours, mais je suis sûr qu’ils ne mesureront pas
la technicité en Python, ce dernier ne servant qu’à illustrer les concepts fondamentaux de l’informatique.
Il n’est même pas sûr que vous ayez à écrire ne serait-ce qu’une ligne de Python. C’est pourquoi mon
cours est volontairement très loin d’être exhaustif. Il est également évolutif (dites-moi si quelque chose
vous semble inutile, mal expliqué, ou manquant).
3. Auto-documentation
Pour obtenir de l’aide sur un objet, on peut exécuter la commande
help ( nom de la fonction )
Par exemple, pour obtenir de l’aide sur la primitive 3 min, on peut effectuer :
>>> h e l p ( min ) # Je demande l a do cumentation s u r l a f o n c t i o n min
Help on b u i l t −in f u n c t i o n min in module
builtin :
min ( . . . )
min ( i t e r a b l e [ , key=f u n c ] ) −> v a l u e
min ( a , b , c , . . . [ , key=f u n c ] ) −> v a l u e
With a s i n g l e i t e r a b l e argument , return i t s s m a l l e s t item .
With two or more arguments , return t h e s m a l l e s t argument .
Bien sûr, Python nous aide si notre programme ne tourne pas :
>>> def g ( x ) # J ’ a i o u b l i é l e ”: ”
F i l e ”<s t d i n >” , l i n e 1
def g ( x )
ˆ
Synta xError : i n v a l i d s y n t a x
ou
>>> 3 + [ 1 , 2 ] # On ne
Traceback ( most r e c e n t
F i l e ”<s t d i n >” , l i n e
TypeError : unsupported
p e u t pas a d d i t i o n n e r un e n t i e r a v e c une l i s t e
call last ):
1 , in <module>
operand type ( s ) f o r +: ’ i n t ’ and ’ l i s t ’
4. Types
En Python, le typage est implicite : il n’est pas nécessaire de déclarer le type des objets utilisés.
Pour obtenir le type d’un objet, il suffit d’utiliser la fonction type :
>>> type ( 4 2 )
<type ’ i n t ’>
>>> type ( ” H e l l o World ”)
<type ’ s t r ’>
3. Fonction intégrée dans le langage, built-in function en anglais.
12
Stéphane FLON
CHAPITRE I. PROGRAMMATION
4. TYPES
4.1. Types élémentaires principaux
4.1.1. Le type entier. C’est le type ’ int ’, pour integer.
Les principales fonctions associées aux entiers sont l’addition +, la multiplication ∗, la soustraction −, la
division /, la division euclidienne // (donnant le quotient dans une division euclidienne), le reste % dans une
division euclidienne, l’exponentiation ∗∗.
En Python, la taille des entiers n’est pas limitée, dans la mesure où on ne travaille pas en précision 32 ou
64 bits. En Python 2 cela dit, un entier grand change de type, et devient un grand entier, de type ’long’.
>>> type ( 1 2 3 4 )
<type ’ i n t ’>
>>> type ( 1 2 3 4 ∗∗ 1 2 3 4 )
<type ’ l o n g ’>
4.1.2. Le type booléen. Un type essentiel, même si on ne se rend pas forcément compte qu’on l’utilise si
souvent. Ce type ne possède que deux éléments, True et False.
On dispose des connecteurs logiques usuels or, and, not, le ou exclusif ˆ.
Remarque : le ou (resp. et) logique peut aussi s’écrire | (resp. &). Il y a en fait une différence subtile, testez
True or (1 / 0 == 0) et True | (1 / 0 == 0).
Remarque : le ou exclusif ˆ fonctionne pour les entiers (i.e. pour le type int), et fournit le ou exclusif bit à bit,
mais il ne faut surtout pas le confondre avec l’exponentiation ! De même, les opérateurs & et | peuvent prendre
en argument des entiers.
Python dispose des opérateurs de comparaison suivants (dont les arguments ne sont pas nécessairement booléens, mais renvoyant des valeurs booléennes) : l’égalité == (à ne pas confondre avec l’affectation), l’inégalité !=,
<, >, <= (inférieur ou égal), >= (supérieur ou égal).
Remarque : ces opérateurs fonctionnent avec des objets de types variés, n’hésitez pas à les tester pour comprendre à quelles comparaisons ils correspondent.
Remarque : il faut noter que, contrairement à beaucoup de langages, on peut regrouper plusieurs inégalités,
et donc se passer de la conjonction dans certains cas :
>>> 3 < 7 < 9
True
>>> 3 < 8 > 2
True
>>> 3 != 2 != 3
True
Cependant, je ne suis pas sûr qu’utiliser cette syntaxe soit une bonne idée d’un point de vue pédagogique
...
4.1.3. Le type flottant. Le type float est celui des nombres à virgule flottante, que l’on peut considérer
comme des approximations des réels. Comme d’habitude, en cas d’opérations mêlant flottants et entiers, le
résultat renvoyé est de type flottant :
>>> 3 . 5 + 3 . 5
7.0
>>> type ( ) # L ’ u n d e r s c o r e
<type ’ f l o a t ’>
r e n v o i e l e d e r n i e r r é s u l t a t
Les opérateurs sont ceux donnés pour int
>>> 1 2 . 5 // 2 . 3
5.0
>>> 1 2 . 5 % 2 . 3
1.0000000000000009
4.2. Les listes
Nous abordons un type non élémentaire : les listes, ou listes chaı̂nées, sont obtenues à partir d’objets d’autres
types. On peut par exemple créer une liste d’entiers
>>> l i s t e = [ 1 , 3 , 6 , 9 ]
>>> l i s t e
[1 , 3 , 6 , 9]
13
Stéphane FLON
4. TYPES
CHAPITRE I. PROGRAMMATION
Remarque : on peut aussi créer les listes d’objets de types différents, et on peut même imbriquer des listes
dans d’autres.
Pour créer des intervalles d’entiers , on peut utiliser la primitive range : range(a, b, h) est la liste des
entiers de a à b, dans l’ordre adéquat (indiqué par le signe de h), de pas h, et b exclu. Le terme initial a et le
pas h sont optionnels 4, et valent respectivement 0 et 1 par défaut.
>>>
[1 ,
>>>
[0 ,
>>>
[]
>>>
[4 ,
range ( 1 , 4 )
2 , 3]
range (4)
1 , 2 , 3]
range ( 4 , 1 )
r a n g e (4 ,1 , −1)
3 , 2]
On obtient la longueur de la liste par la primitive len
>>> l e n ( l i s t e )
4
Dans notre exemple, la liste est de longueur 4 (i.e. a quatre termes). Ces termes sont indexés de 0 à
len( liste ) − 1, et l’on y accède selon la syntaxe :
l i s t e [ indice du terme ]
>>> l i s t e [ 0 ]
1
>>> l i s t e [ l e n ( l i s t e ) − 1 ]
9
Cependant, ils sont aussi indexés par des indices négatifs, le terme d’indice −1 étant le dernier :
>>> l i s t e [ −1]
9
>>> l i s t e [ −2]
6
Remarque : l’indexation ne se fait pas sur Z tout entier, liste [7] fournit par exemple une erreur dans notre
cas.
>>> l i s t e [ 7 ]
Traceback ( most r e c e n t c a l l l a s t ) :
F i l e ”<s t d i n >” , l i n e 1 , in <module>
I n d e x E r r o r : l i s t i n d e x out o f r a n g e
On peut attribuer une nouvelle valeur v à un terme d’indice i d’une liste liste selon la syntaxe
liste [ i ] = v
Remarque : nous verrons que cette modification se fait en place, voir le paragraphe 8.1.
On peut aussi ajouter un nouveau terme nouveau terme à une liste, à sa droite, de la façon suivante :
l i s t e . append ( nouveau terme )
En reprenant notre exemple :
>>> l i s t e . append ( 1 6 )
>>> l i s t e
[1 , 3 , 6 , 9 , 16]
L’opérateur + concatène (ou accole ) les listes
>>> l i s t e + [ 1 1 9 , 9 1 1 ]
[ 1 , 3 , 6 , 9 , 16 , 119 , 911]
4. dans le cas de deux arguments donnés, ils sont interprétés comme a et b
14
Stéphane FLON
CHAPITRE I. PROGRAMMATION
4. TYPES
On peut trancher une liste entre deux indices i et j (ou i 6 j), en écrivant liste [ i : j ] : cela a pour effet de
ne conserver que les termes d’indices i à j − 1.
Illustration
Dans le cas où on ne met pas d’indice i (resp. j), on commence à 0 (resp. on s’arrête à len( liste )).
>>>
>>>
[3 ,
>>>
[1 ,
>>>
[6 ,
>>>
[1 ,
l i s t e = [1 , 3 , 6 , 9 , 16]
liste [1:3]
6]
liste [0:3]
3 , 6]
liste [2:]
9 , 16]
liste [:]
3 , 6 , 9 , 16]
Remarque : on peut même effectuer un tranchage (slicing en anglais) selon un certain pas k, en écrivant
liste [ i : j :k]
Il est même possible d’ajouter des termes dans une liste, tout en en supprimant d’autres, en utilisant le
tranchage :
>>> l i s t e [ 1 : 2 ] = [ 6 7 , 8 9 , 1 1 3 ]
>>> l i s t e
[ 1 , 67 , 89 , 113 , 6 , 9 , 16]
Ici, j’ai remplacé la sous-liste liste [1:2] , c’est-à-dire [3] , par [67, 89, 113].
On peut tester l’appartenance d’un objet elt dans une liste par elt in liste , elt not in liste renvoyant le
contraire.
Remarque : on dispose de nombreuses autres fonctions, comme max, min, la suppression del, ou la multiplication d’une liste par un entier.
4.3. D’autres exemples de types
4.3.1. Le type complex. Les nombres complexes sont représentés en Python. Plus précisément, le nombre
complexe a + ib (mis sous forme algébrique) peut s’écrire complex(a, b) ou a+bj. Les opérations standard
+, ∗, −, / voire ∗∗ sont acceptées, le module est la primitive abs, et la conjugaison d’un complexe z peut
s’écrire z.conjugate().
>>> z = 1 + 3 j # Notez l ’ a b s e n c e d ’ e s p a c e e n t r e 3 e t j
>>> z . c o n j u g a t e ( )
(1−3 j )
>>> z
(1+3 j )
15
Stéphane FLON
5. LES IDENTIFICATEURS
CHAPITRE I. PROGRAMMATION
4.3.2. Les uplets et les ensembles. Un uplet consiste en la donnée d’objets séparés par des virgules, (le plus
souvent) encadrés par des parenthèses, et se comportent un peu comme les listes :
>>> c o u p l e = ( 1 , 3 )
>>> t r i p l e t = ( 5 , 7 , 2 )
>>> c o u p l e + t r i p l e t
(1 , 3 , 5 , 7 , 2)
>>> c o u p l e [ 1 : 2 ]
(3 ,)
Un ensemble est la même chose, où l’on a remplacé les parenthèses par des accolades. Comme en mathématiques, l’ordre des éléments ne compte pas, ni leur éventuelle répétition
>>> ensemble = { 2 , 3 , 5 , 7}
>>> 4 in ensemble
False
>>> ensemble == { 5 , 3 , 3 , 5 , 2 , 7 , 7}
True
L’union, l’intersection, la différence s’écrivent respectivement |, & et −. La différence symétrique ∆ (donnée
par A∆B = (A \ B) ∪ (B \ A)) s’écrit ˆ.
4.3.3. Les chaı̂nes de caractère. Les chaı̂nes de caractères sont délimités par des apostrophes ’ ou des
guillemets”, et se comportent essentiellement comme les listes chaı̂nées.
4.4. Conversion de types
En Python, pour convertir un objet en un objet d’un certain type, il suffit d’écrire
nom du nouveau type ( o b j e t d e l a n c i e n t y p e )
>>> i n t ( 7 . 0 ) ; f l o a t ( 1 1 3 ) ; s t r ( 1 3 4 5 ) ; i n t ( ”5853 ” ) ; l i s t ( ’ Bonjour ’ )
7
113.0
’ 1345 ’
5853
[ ’B ’ , ’ o ’ , ’ n ’ , ’ j ’ , ’ o ’ , ’ u ’ , ’ r ’ ]
5. Les identificateurs
5.1. Généralités
Un identificateur est un nom donné à un objet. Pour déclarer une valeur à un identificateur, on utilise la
syntaxe
identificateur = valeur de la variable
>>> a = 3 4 ; b = ”Au r e v o i r ” ; c = 3 . 1 4 # Je d é c l a r e l e s i d e n t i f i c a t e u r s a , b e t c
>>> type ( a )
<type ’ i n t ’>
>>> b
’Au r e v o i r ’
On ne confondra pas le symbole d’égalité, qui est une affectation (ou assignation), avec l’égalité mathématique :
>>> a = 3
>>> 3 = b
F i l e ”<s t d i n >” , l i n e 1
Synta xError : can ’ t a s s i g n t o l i t e r a l
>>> a = 5 ; b = 7 ; a = b ; a , b
(7 , 7)
>>> a = 5 ; b = 7 ; b = a ; a , b
(5 , 5)
16
Stéphane FLON
CHAPITRE I. PROGRAMMATION
6. STRUCTURES DE CONTRÔLE
Remarque : il n’est pas possible de déclarer un identificateur comme antécédent d’une certaine valeur par une
fonction :
>>> s i n ( a ) = 1
F i l e ”<s t d i n >” , l i n e 1
Synta xError : can ’ t a s s i g n t o f u n c t i o n c a l l
Je ne m’étends pas sur les noms admissibles de variables : vous découvrirez à l’usage comment cela fonctionne, et cela suit des règles de bon sens.
5.2. Affectations conjointes
Une spécificité de Python est la possibilité d’affectations simultanées de variables, sous l’une des formes
suivantes :
Assignation à plusieurs identificateurs d’une même valeur :
>>> a = b = 57
>>> a ; b
57
57
Assignation en parallèle de valeurs à plusieurs identificateurs :
>>> a , b = 5 7 , 68
>>> a ; b
57
68
Cette assignation en parallèle n’est pas si anecdotique que cela : elle permet par exemple l’échange de
variables de manière élégante.
>>> a = 1 2 3 ; b = 234
>>> a , b = b , a
>>> a ; b
234
123
6. Structures de contrôle
6.1. La conditionnelle simple
Elle admet la syntaxe suivante :
i f condition :
instruction 1
else :
instruction 2
où condition est un booléen, instruction1 (resp. instruction2) une instruction effectuée si et seulement si
condition est vraie (resp. fausse)
>>> def f ( a ) :
...
i f a >= 0 :
...
print ( ”La r a c i n e c a r r é e de {} e s t {} ” . format ( a , s q r t ( a ) ) )
...
else :
...
print ( ”Le nombre {} n ’ admet pas de r a c i n e c a r r é e r é e l l e ” . format ( a ) )
...
>>> f ( 3 ) ; f ( −1)
La r a c i n e c a r r é e de 3 e s t 1 . 7 3 2 0 5 0 8 0 7 5 7
Le nombre −1 n ’ admet pas de r a c i n e c a r r é e r é e l l e
Remarque : la partie en else est facultative.
17
Stéphane FLON
6. STRUCTURES DE CONTRÔLE
CHAPITRE I. PROGRAMMATION
Il est fréquent que l’on ait de plus nombreux cas à distinguer. On peut bien sûr utiliser des tests if emboı̂tés,
mais on peut également utiliser une syntaxe permettant directement l’étude de plusieurs cas, grâce à elif ,
contraction de else if ( sinon, si ) :
if condition 1 :
instruction 1
e li f condition 2 :
instruction 2
...
else :
instruction n
Il faut noter que si plusieurs des conditions sont satisfaites, seule l’instruction de la première à l’être sera
exécutée 5 :
>>>
>>>
...
...
...
...
...
...
...
...
A
a = 3
if a > 1:
print ( ”A”)
elif a > 2:
print ( ”B”)
elif a > 0:
print ( ”C”)
else :
print ( ”D”)
6.2. La boucle inconditionnelle : une première approche
On présente ici une première approche de la boucle for . On se reportera à 8.3 pour découvrir des
itérations plus sophistiquées.
for i in r a n g e ( a , b ) :
instruction 1
instruction 2
.
.
instruction n
>>> f o r i in r a n g e ( 4 ) :
...
print ( ” e n t i e r c o u r a n t ”)
...
print ( i )
...
e n t i e r courant
0
e n t i e r courant
1
e n t i e r courant
2
e n t i e r courant
3
On peut par exemple programmer la suite de Fibonacci avec une boucle for, et grâce à une assignation
parallèle :
>>> def f i b o ( n ) :
...
a, b = 0, 1
...
f o r i in r a n g e ( n ) :
...
a , b = b , a+b
...
return a
5. comme l’expression sinon, si le laisse entendre
18
Stéphane FLON
CHAPITRE I. PROGRAMMATION
...
>>>
5
>>>
8
>>>
[0 ,
7. PROGRAMMATION FONCTIONNELLE
fibo (5)
fibo (6)
[ f i b o ( n ) f o r n in r a n g e ( 1 0 ) ] # Je demande l e s t e rm e s de f i b o d ’ i n d i c e s 0 à 9 , v o i r p l u s l
1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 , 34]
6.3. La boucle conditionnelle
La boucle conditionnelle, ou boucle while, permet d’effectuer un passage en boucle tant que la condition
d’entrée est satisfaite, selon la syntaxe suivante :
while c o n d i t i o n :
instructions
>>> while i < 5 :
...
print ( i )
...
i += 1
...
1
2
3
4
7. Programmation fonctionnelle
7.1. Déclaration de fonctions
La déclaration de fonctions suit la syntaxe suivante :
def n o m d e l a f o n c t i o n ( argument 1 , . . . , argument n ) :
instruction 1
.
.
.
instruction k
return v a l e u r a r e n v o y e r
>>> def p y t h a g o r i c i e n ( a , b , c ) : # P ré d i c a t t e s t a n t s i un t r i p l e t e s t p y t h a g o r i c i e n
...
return a ∗ a + b ∗ b == c ∗ c
...
>>> p y t h a g o r i c i e n ( 3 , 4 , 5 )
True
>>> p y t h a g o r i c i e n ( 4 , 9 , 1 2 )
False
Remarque : une instruction à la suite de la ligne avec return ne sera pas exécutée lors de l’appel de la
fonction :
>>> def c a r r e 1 ( x ) :
...
return x ∗ x
...
print ( ” c a l c u l e f f e c t u é ”)
...
>>>
>>> def c a r r e 2 ( x ) :
...
print ( ” c a l c u l non e n c o r e e f f e c t u é ”)
...
return x ∗ x
...
>>> c a r r e 1 ( 5 )
19
Stéphane FLON
8. QUELQUES APPROFONDISSEMENTS EN PYTHON
CHAPITRE I. PROGRAMMATION
25
>>> c a r r e 2 ( 5 )
c a l c u l non e n c o r e e f f e c t u é
25
7.2. Variables locales et globales
7.3. Le type NoneType et les procédures
En réalité, il n’est pas nécessaire de finir la déclaration d’une fonction par un return
>>> def b o n j o u r ( x ) :
...
print ( ”Bonjour ” + format ( x ) )
...
>>> b o n j o u r ( ”Ada ”)
Bonjour Ada
Si l’on omet le return, la fonction est ce qu’on appelle une procédure, c’est-à-dire qu’elle ne renvoie rien de
particulier, mais qu’elle agit uniquement par effet de bord.
Pour des raisons de cohérence dans la définition des fonctions, les procédures sont malgré tout des fonctions,
dont le résultat est un rien chosifié , appelé None, unique objet de type NoneType. Ainsi la fonction bonjour
ci-dessus ne renvoie-t-elle pas une chaı̂ne de caractères, mais . . . rien !
>>> type ( b o n j o u r ( ”Alan ” ) )
Bonjour Alan
<type ’ NoneType ’>
L’exemple suivant illustre la nécessité de bien comprendre le type de la fonction considérée :
>>> def f ( x ) :
...
return s i n ( x )
...
>>> def g ( x ) :
...
print ( s i n ( x ) )
...
>>> f ( 1 )
0.8414709848078965
>>> f ( f ( 1 ) )
0.7456241416655579
>>> g ( 1 )
0.841470984808
>>> g ( g ( 1 ) )
0.841470984808
Traceback ( most r e c e n t c a l l l a s t ) :
F i l e ”<s t d i n >” , l i n e 1 , in <module>
F i l e ”<s t d i n >” , l i n e 2 , in g
AttributeError : sin
Remarque : cela dit, une fonction qui n’est pas une procédure peut très bien agir par effet de bord, comme la
fonction carre2 définie page 19.
8. Quelques approfondissements en Python
8.1. Égalité(s) de variables
En Python, on peut effectuer essentiellement deux tests d’égalité : l’égalité structurelle et l’égalité physique.
L’égalité structurelle est celle qui ressemble le plus à l’égalité au sens mathématique : elle compare des
objets selon leur nature. L’opérateur correspondant est ==.
>>>
>>>
>>>
>>>
a
b
c
a
= 512
= 456
= 512
== b
20
Stéphane FLON
CHAPITRE I. PROGRAMMATION
8. QUELQUES APPROFONDISSEMENTS EN PYTHON
False
>>> a == c
True
>>> e n s e m b l e 1 = { 3 , 4 , 5}
>>> e n s e m b l e 2 = { 5 , 3 , 3 , 3 , 4 , 4 , 5}
>>> e n s e m b l e 1 == e n s e m b l e 2
True
L’égalité physique est quant à elle beaucoup plus informatique : elle vérifie si deux identificateurs sont liés
au même espace occupé en mémoire (pointent vers le même espace mémoire). Elle s’exprime à l’aide de is.
>>> a
>>> b
>>> c
>>> a
False
>>> a
False
= 512
= 456
= 512
is b
is c
Nous avons créé deux identificateurs a et c, dont les contenus sont mathématiquement égaux, mais qui
pointent vers des emplacements mémoire différents.
Bien entendu, l’égalité physique entraı̂ne l’égalité structurelle, mais la réciproque est fausse.
Remarque : en créant séparément deux identificateurs égaux structurellement, on s’attend à ce qu’ils soient
distincts physiquement, mais ce n’est pas nécessairement le cas : il ne faut pas le supposer a priori dans la
programmation.
>>> a
>>> b
>>> a
True
>>> a
True
= 42
= 42
== b # Réponse a t t e n d u e
i s b # Réponse i n a t t e n d u e
En réalité, on peut avoir accès à l’emplacement mémoire du contenu d’un identificateur avec la commande
id(), qui renvoie un entier 6.
>>> a = 387537743
>>> i d ( a ) # Je demande l ’ a d r e s s e mémoire v e r s l a q u e l l e a p o i n t e
60475352L
>>> i d ( 3 8 7 5 3 7 7 4 3 ) # Je demande l ’ a d r e s s e mémoire d ’ un e n t i e r que Python
doit stocker provisoirement
60475376L
>>> i d ( 3 8 7 5 3 7 7 4 3 ) # Je r é i t è r e l e s o p é r a t i o n s
60475400L
>>> i d ( a )
60475352L
Remarque : a is b et id(a) == id(b) sont logiquement équivalentes.
Concernant le traitement des adresses mémoire, il convient de distinguer les objets mutables des autres :
un objet est dit mutable 7 si on peut le modifier par certaines opérations tout en conservant son emplacement
mémoire.
Les listes et dictionnaires sont mutables, les entiers, booléens, flottants, uplets, chaı̂nes de caractères ne le
sont pas.
>>> a = 16585
>>> i d ( a )
60477128L
>>> a =986283
>>> i d ( a )
60475352L
6. qui dépend évidemment du contexte physique, i.e. de l’ordinateur employé, et de son état lors de l’appel de la fonction
7. ou modifiable en place, ou simplement modifiable
21
Stéphane FLON
8. QUELQUES APPROFONDISSEMENTS EN PYTHON
CHAPITRE I. PROGRAMMATION
>>> l = [ 1 , 2 , 3 , 4 ]
>>> i d ( l )
103629512L
>>> l [ 2 ] = 983636 # Je m o d i f i e l
>>> i d ( l ) # l a c o n s e r vé l a même a d r e s s e mémoire
103629512L
Attention cependant, un objet mutable ne conserve pas la même adresse mémoire quoi qu’il arrive :
>>> l = [ 1 , 2 , 3 , 4 ]
>>> i d ( l )
103628936L
>>> l . append ( 5 ) # J ’ a j o u t e 5 en queue de l
>>> l , i d ( l ) # l a é t é m o d i f ié en p l a c e
( [ 1 , 2 , 3 , 4 , 5 ] , 103628936L)
>>> l = l [ : ] # J ’ e f f e c t u e une c o p i e de l
>>> l , i d ( l ) # l e s t s t r u c t u r e l l e m e n t inchangé , mais pas p h y s i q u e m e n t
( [ 1 , 2 , 3 , 4 , 5 ] , 103629320L)
Dans cet exemple, la copie de l effectuée par l’instruction l=l [:]) est dite superficielle, car l’égalité n’est
a priori que structurelle. Il faut cependant faire attention au cas où la liste comporte elle-même des éléments
mutables, par exemple une liste !
>>> l 1 = [ 1 2 , 2 3 , ” H e l l o ” , [ 1 , 2 , 3 , 4 ] ]
>>> l 2 = l 1 # Copie p h y s i q u e
>>> l 3 = l 1 [ : ] # Copie s u p e r f i c i e l l e
>>> l 1 [ 0 ] = 91 # M o d i f i c a t i o n d ’ un é lé m e n t non m u t a b l e de l 1
>>> l 1 [ 3 ] [ 1 ] = 7 # M o d i f i c a t i o n en p l a c e d ’ un é lé m e n t m u t a b l e de l 1
>>> print ( l 1 ) ; print ( l 2 ) ; print ( l 3 )
[ 9 1 , 23 , ’ Hello ’ , [ 1 , 7 , 3 , 4 ] ]
[ 9 1 , 23 , ’ Hello ’ , [ 1 , 7 , 3 , 4 ] ]
[ 1 2 , 23 , ’ Hello ’ , [ 1 , 7 , 3 , 4 ] ]
La copie superficielle de l1 vers l3 est une copie par références (ou par pointeurs) : les éléments de l3 sont
physiquement égaux à ceux de l1. C’est pourquoi la modification en place d’un élément de l1 a aussi modifié
l’élément correspondant de l3.
Pour effectuer une copie purement superficielle, on peut utiliser deepcopy 8 du module copy :
>>> l 1 = [ 1 2 , 2 3 , ” H e l l o ” , [ 1 , 2 , 3 , 4 ] ]
>>> from copy import deepcopy
>>> l 4 = deepcopy ( l 1 )
>>> l 1 [ 0 ] = 91 # M o d i f i c a t i o n d ’ un é lé m e n t non m u t a b l e de l 1
>>> l 1 [ 3 ] [ 1 ] = 7 # M o d i f i c a t i o n en p l a c e d ’ un é lé m e n t m u t a b l e de l 1
>>> l 1 , l 4
( [ 9 1 , 23 , ’ Hello ’ , [ 1 , 7 , 3 , 4 ] ] , [ 1 2 , 23 , ’ Hello ’ , [ 1 , 2 , 3 , 4 ] ] )
8.2. Retour sur les fonctions
On peut définir des arguments optionnels pour une fonction, en attribuant à certains arguments des valeurs
par défaut. Pour ce faire, on utilise la syntaxe
def f ( a r g 1 , . . . , arg n , o p t i o n 1 = v a l d e f a u t 1 , . . . , option m = v a l d e f a u t m )
...
>>> def f ( x , y , z = 0 ) : # z e s t o p t i o n n e l e t sa v a l e u r par d é f a u t e s t 0
...
return x + 2 ∗ y + 3 ∗ z
...
>>> f ( 1 , 2 ) # z v a u t i c i 0 , sa v a l e u r par d é f a u t
5
>>> f ( 1 , 2 , 3 ) # z v a u t i c i 3
14
8. Cela sera très utile pour travailler sur les matrices
22
Stéphane FLON
CHAPITRE I. PROGRAMMATION
8. QUELQUES APPROFONDISSEMENTS EN PYTHON
Remarque : si possible, évitez d’utiliser des arguments par défaut mutables, car c’est un peu compliqué à
gérer.
On peut aussi appeler une fonction en passant les arguments par étiquettes, permettant de ne pas imposer
un ordre de passage des arguments :
>>>
...
...
>>>
...
...
>>>
>>>
8
>>>
8
def e v a l u e f e n x ( f , x ) :
return f ( x )
def cube ( x ) :
return x ∗∗ 3
a = 2
e v a l u e f e n x ( f = cube , x = a )
e v a l u e f e n x ( x = a , f = cube )
Cela permet de ne pas avoir à se rappeler l’ordre de passage des arguments, mais impose de connaı̂tre leurs
noms.
8.2.1. Définition d’une liste par compréhension. On peut se poser deux problématiques incontournables
pour les listes : donner la liste des images d’une liste par une application, et filtrer une liste selon un prédicat
(i.e. une fonction à valeurs booléennes).
En Python, ces deux opérations sont extrêmement simples à écrire, et peuvent être regroupées selon la
syntaxe
[ f o n c t i o n ( e l t ) f o r e l t in l i s t e i f p r e d i c a t ( e l t ) ]
Remarque : cela suit la définition par compréhension d’un ensemble 9 en mathématiques.
Par exemple, si je pars de la liste [2, 3, 5, 7, 11, 13, 17], et que je veux l’élever au carré, j’écris
>>> [ e l t ∗ e l t f o r e l t in l i s t e ]
[ 4 , 9 , 25 , 49 , 121 , 169 , 289]
Si je veux n’en conserver que les nombres congrus à 3 modulo 4, j’écris
>>> [ e l t f o r e l t in l i s t e i f ( e l t \% 4 == 3 ) ]
[3 , 7 , 11]
Si je veux élever au carré ceux qui sont congrus à 3 modulo 4, j’écris
8.3. Retour sur les boucles inconditionnelles : itérateurs, itérables, et générateurs
Nous avons vu un usage très limité de la boucle for, en se limitant à l’emploi de range. En fait, en Python
2, range produit une liste
>>> l = r a n g e ( 1 0 )
>>> l
[0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9]
La ligne for i in range(10) va indiquer à Python de donner pour valeur à i les termes successifs de cette
liste range(10), dans l’ordre naturel.
En réalité, cette façon de faire se généralise à n’importe quelle liste, pas seulement une liste constituée
d’entiers successifs, selon la syntaxe attendue
for i in l i s t e a e g r e n e r :
instructions
9. ou plutôt d’une partie d’un ensemble donné
23
Stéphane FLON
8. QUELQUES APPROFONDISSEMENTS EN PYTHON
CHAPITRE I. PROGRAMMATION
>>> def somme1 ( l i s t e ) :
...
s = 0
...
f o r i in r a n g e ( l e n ( l i s t e ) ) :
...
s += l i s t e [ i ]
...
return s
...
>>> def somme2 ( l i s t e ) :
...
s = 0
...
f o r e l t in l i s t e :
...
s += e l t
...
return s
...
>>> l = [ 4 5 , 5 5 , 3 7 6 ]
>>> somme1 ( l ) , somme2 ( l )
(476 , 476)
Les deux fonctions somme1 et somme2 fournissent les mêmes résultats, mais la seconde est plus pythonique , et finalement plus naturelle une fois qu’on a compris le principe.
Remarque : si on a besoin d’égrener la liste en sens inverse, on peut utiliser reverse. Si par exemple on veut
évaluer le polynôme P = 3 + X + 2X 2 , représenté par la liste [3, 1, 2], en x = 2, on peut observer que
P (x) = 3 + x(1 + 2x), et effectuer
>>>
>>>
>>>
...
...
>>>
13
v = 0
x = 2
f o r e l t in r e v e r s e d ( [ 3 , 1 , 2 ] ) :
v = x ∗ v + elt
v
Remarque : Python permet d’itérer sur d’autres objets qu’une liste, par exemple une chaı̂ne de caractère :
>>>
>>>
>>>
...
...
...
L a
>>>
...
...
...
l a
s = ”La malade p e d a l a mal ”
f o r c in s :
i f c != ” ” :
print ( c ) ,
m a l a d e p e d a l a m a l
f o r c in r e v e r s e d ( s ) :
i f c != ” ” :
print ( c ) ,
m a l a d e p e d a l a m a L
Les objets sur lesquels Python peut itérer sont appelés itérateurs. Par souci de simplicité, nous ne nous
étendrons pas sur leur usage.
Remarque : s’agissant des itérateurs, Python 3 procède un peu différemment de Python 2. L’idée est grossièrement de ne pas stocker toute une liste en mémoire, mais plutôt ses éléments successivement.
24
Stéphane FLON
CHAPITRE I. PROGRAMMATION
9. TRAITS DE PROGRAMMATION
9. Présentation de quelques traits de programmation
9.1. La programmation impérative
C’est celle que tout le monde connaı̂t, qui donne des instuctions à l’ordinateur pour modifier son état. C’est
le royaume des boucles conditionnelles (while), des boucles inconditionnelles (for), des embranchements (if), et
des assignations.
L’embranchement est utile si on sait distinguer les cas d’études possibles, et qu’il y en a un nombre limité.
Exercice (Embranchement)
1 Écrire, à l’aide d’un test if, une fonction minimum renvoyant le minimum des deux
entiers qu’elle prend en argument.
2 Écrire, à l’aide d’un test if, une procédure racines prenant en arguments trois réels
a, b, c, et renvoyant une phrase donnant les racines complexes de aX 2 + bX + c.
Remarque : on veut que tous les cas possibles pour le triplet (a, b, c) soient bien
considérés.
1
La boucle inconditionnelle est à employer quand on a un nombre déterminé de calculs à faire, que l’on peut
factoriser en une suite simple d’instructions.
Exercice (Boucle inconditionnelle)
1 Définir, en utilisant une boucle for, une fonction prenant en arguments un réel a
et un entier naturel n, et renvoyant an .
2 Définir, en utilisant une boucle for, une fonction fact prenant en argument un
entier naturel, et renvoyant sa factorielle.
2
La boucle conditionnelle est utile quand on a un nombre indéterminé de calculs à faire, que l’on peut
factoriser .
Exercice (Boucle conditionnelle)
1 En utilisant une boucle while, écrire une fonction logarithme binaire, qui à un réel
strictement positif a associe
min{n ∈ N, 2n > a}
3
2 En utilisant une boucle while, écrire une fonction plus petit qui, à un entier
naturel n > 2, associe son plus petit diviseur premier
Remarque : on évitera d’employer une boucle conditionnelle quand on connaı̂t le nombre exact d’opérations
à faire (i.e. quand on pourra utiliser une boucle inconditionnelle).
9.2. Programmation récursive
De manière informelle, une fonction est dite récursive si elle s’appelle elle-même dans sa définition.
Par exemple, on peut programmer la fonction factorielle de la façon suivante :
>>> def f a c t ( n ) :
...
i f n <= 0 :
...
return 1
...
else :
...
return n ∗ f a c t ( n − 1 )
>>> f a c t ( 5 )
120
Pour calculer fact (5), Python appelle la fonction fact et lui passe 5 en argument. Il se trouve donc dans le
second cas de l’embranchement, et doit donc renvoyer 5 · f act(4). Il fait donc passer 4 en argument à fact, etc.
25
Stéphane FLON
9. TRAITS DE PROGRAMMATION
CHAPITRE I. PROGRAMMATION
jusqu’à lui faire passer 0, qui le place dans le premier cas de l’embranchement, et permet de proche en proche
de remonter à fact (5).
Exercice (Récursivité)
1 Définir le minimum d’une liste de manière récursive.
2 Définir une fonction renvoyant la décomposition d’un entier n > 2 en produit
de facteurs premiers sous forme de liste (on pourra utiliser la fonction plus petit
ci-dessus).
Remarque : on pourra aussi si on veut reprogrammer la fonction plus petit de
manière récursive, mais c’est moins naturel.
4
9.3. Programmation fonctionnelle
La programmation fonctionnelle, qui dans sa version la plus pure s’oppose à la programmation impérative,
conçoit un programme comme une succession d’appels de fonctions.
Python permettant tous ces styles de programmation, nous nous autoriserons de les mélanger. Cela dit,
on peut retenir de l’approche fonctionnelle l’idée de concevoir des fonctions, et surtout des sous-fonctions pour
répondre à nos problèmes.
Plus concrètement, lorsque l’on fera face à un problème informatique relativement compliqué, on pourra
commencer par écrire la fonction principale, censée répondre à la question, en s’autorisant en son sein l’utilisation
de sous-fonctions, non encore définies, au nom clair et explicite. Ensuite seulement, on s’attachera à programmer
ces sous-fonctions (éventuellement en créant de nouvelles sous-fonctions).
Cette approche, dite descendante (top-down en anglais), a pour avantage de morceller le problème en sousproblèmes beaucoup plus simples, et facilite l’organisation de ces sous-problèmes. Elle permet aussi le partage
des tâches, chacun des membres d’une équipe s’attelant à un sous-programme précis, et la réutilisation, puisque
beaucoup des sous-fonctions seront communes à beaucoup de fonctions.
Enfin, elle confine l’apsect bas niveau à certaines sous-fonctions seulement, ce qui rend le programme
plus lisible et non tributaire, pour une large part, des spécifités du langage, des aspects bas niveau , ou du
choix des structures de données. On dit que le programme a une plus grande portabilité.
Remarque : poussé à l’extrême, ces principes de programmation conduisent à la programmation orientée objet.
Elle a pour principal inconvénient de conduire à une inflation de fonctions.
Par exemple, pour dresser la décomposition en produit de facteurs premiers d’un entier n > 2, on peut
se demander comment la connaissance du plus petit d’entre eux nous permet de trouver cette décomposition
(de manière récursive ou impérative), puis on cherche à programmer une fonction donnant ce plus petit facteur
premier.
Une particularité de la programmation fonctionnelle est qu’elle permet à des fonctions de prendre en argument des fonctions, ou de renvoyer des fonctions. Les fonctions de ce type sont appelées fonctionnelles.
Exercice (Fonctionnelle)
Programmer les fonctionnelles données par :
1 ajoute(f ) = (x 7→ f (x + 3) + 2).
2 compose(f, g) = f ◦ g.
5
9.4. Un exemple : l’ajout de zéros dans une liste
On se propose d’écrire des fonctions qui prennent en argument une liste, et renvoient la liste obtenue en
intercalant un 0 entre tous les termes consécutifs de la précédente.
Programmation impérative, boucle inconditionnelle, création de la liste résultat par constructions progressives :
>>> def a j o u t 1 ( l i s t e ) :
...
resultat = [ ]
...
f o r e l t in l i s t e :
...
r e s u l t a t += [ e l t , 0 ]
26
Stéphane FLON
CHAPITRE I. PROGRAMMATION
...
9. TRAITS DE PROGRAMMATION
return r e s u l t a t [ : − 1 ]
Programmation impérative, boucle inconditionnelle, création de la liste résultat de la bonne taille, puis
modification par effets de bord de celle-ci :
>>> def a j o u t 2 ( l i s t e ) :
...
r e s u l t a t = [ 0 f o r e l t in r a n g e ( 0 , 2 ∗ l e n ( l i s t e ) − 1 ) ]
...
f o r i in r a n g e ( l e n ( l i s t e ) ) :
...
resultat [2 ∗ i ] = l i s t e [ i ]
...
return r e s u l t a t
Programmation impérative, boucle inconditionnelle, utilisation du slicing (version assez pythonique), modification de l’argument par effet de bord :
>>> def a j o u t 3 ( l i s t e ) :
...
f o r i in r a n g e ( l e n ( l i s t e ) − 1 , 0 , −1):
...
liste [ i : i ] = [0]
...
return l i s t e
Programmation récursive :
>>> def a j o u t 4 ( l i s t e ) :
...
i f l e n ( l i s t e ) <= 1 :
...
return l i s t e
...
else :
...
return a j o u t 4 ( l i s t e [ : − 1 ] ) + [ 0 , l i s t e [ − 1 ] ]
Programmation impérative, boucle conditionnelle
>>> def a j o u t 5 ( l i s t e ) :
...
resultat = [ ]
...
while l i s t e != [ ] :
...
r e s u l t a t = [ l i s t e [ −1] , 0] + r e s u l t a t
...
l i s t e = l i s t e [: −1]
...
return r e s u l t a t [ : − 1 ]
Résultats :
>>> l i s t e = r a n g e ( 1 , 1 0 ) ; a j o u t 1 ( l i s t e ) ; l i s t e ;
[1 , 0 , 2 , 0 , 3 , 0 , 4 , 0 , 5 , 0 , 6 , 0 , 7 , 0 , 8 , 0 , 9]
[1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9]
>>> l i s t e = r a n g e ( 1 , 1 0 ) ; a j o u t 2 ( l i s t e ) ; l i s t e ;
[1 , 0 , 2 , 0 , 3 , 0 , 4 , 0 , 5 , 0 , 6 , 0 , 7 , 0 , 8 , 0 , 9]
[1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9]
>>> l i s t e = r a n g e ( 1 , 1 0 ) ; a j o u t 3 ( l i s t e ) ; l i s t e ;
[1 , 0 , 2 , 0 , 3 , 0 , 4 , 0 , 5 , 0 , 6 , 0 , 7 , 0 , 8 , 0 , 9]
[1 , 0 , 2 , 0 , 3 , 0 , 4 , 0 , 5 , 0 , 6 , 0 , 7 , 0 , 8 , 0 , 9]
>>> l i s t e = r a n g e ( 1 , 1 0 ) ; a j o u t 4 ( l i s t e ) ; l i s t e ;
[1 , 0 , 2 , 0 , 3 , 0 , 4 , 0 , 5 , 0 , 6 , 0 , 7 , 0 , 8 , 0 , 9]
[1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9]
>>> l i s t e = r a n g e ( 1 , 1 0 ) ; a j o u t 5 ( l i s t e ) ; l i s t e ;
[1 , 0 , 2 , 0 , 3 , 0 , 4 , 0 , 5 , 0 , 6 , 0 , 7 , 0 , 8 , 0 , 9]
[1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9]
27
Stéphane FLON
CHAPITRE II
Algorithmique
1. Algorithmes : principes généraux
Lors d’une première approche, on peut penser que face à un problème donné, que l’on souhaite traiter
informatiquement, se pose avant tout le choix du langage dans lequel on programmera, ainsi que celui de
la machine avec laquelle on travaillera. Cependant, avant de passer du problème, posé dans le langage naturel
(comme trier les notes des étudiants de manière croissante, rendre la monnaie, savoir dans quel ordre un voyageur
va parcourir une liste de sites touristiques), au programme dans le langage choisi, il faudra décrire la manière
dont nous le traiterons.
Pour le tri de notes par exemple, on peut d’abord chercher la plus grande, puis, dans la liste restante, la
suivante, etc. : c’est un algorithme de tri dit par sélection. On peut aussi créer une nouvelle liste, dans laquelle
on insère progressivement les notes de la liste initiale, en respectant l’orde imposé : c’est le tri par insertion.
Concernant le rendu de monnaie, vous avez déjà vu l’algorithme glouton.
Un algorithme consiste donc en un mode de traitement non ambigü d’une information, donnée en entrée,
produisant une certaine sortie, traitement que l’on décrira dans un langage à mi-chemin entre le naturel et celui
choisi pour programmer (on parlera de pseudo-langage), par exemple, pour la recherche du plus grand élément
d’une liste d’entiers
Entrée : l i s t e d ’ e n t i e r s L
S o r t i e : l e maximum de L
V a r i a b l e s : maxi , i
maxi <− L [ 1 ]
Pour i a l l a n t de 2 à T a i l l e L i s t e (L) F a i r e :
S i L [ i ] < maxi
maxi <− L [ i ]
Fin S i
Fin Pour
Renvoyer maxi
Remarque : on peut facilement adapter cet algorithme afin qu’il renvoie le premier (resp. le dernier) indice
pour lequel on atteint le maximum. Faites le en exercice.
Remarque : j’ai fait exprès d’indexer mes tableaux en commençant à 1, et non à 0 comme en Python, et de
ne pas mettre de : après le test Si. Cela n’a en fait aucune importance, l’écriture d’un algorithme tolère
certaines imprécisions, puisque la syntaxe est ici secondaire.
Cet algorithme est suffisamment précis pour décrire l’approche choisie pour aborder le problème, mais il ne
rentre pas dans les détails techniques d’implémentation dans un langage donné. Cela a pour avantages de mieux
saisir ce que l’on fait, et de pouvoir s’adapter à à peu près tous les langages de programmation.
Par exemple, dans Python, on peut utiliser des techniques propres au langage, comme le slicing ou la
définition en compréhension d’une liste, pour transcrire un algorithme, mais un néophyte dans ce langage ne
comprendrait pas forcément la philosophie derrière notre programme.
Remarque : si vous trouvez que l’algorithme proposé en exemple ressemble beaucoup au programme que nous
écririons en Python, vous avez raison, et c’est dû à la grande lisibilité de Python, ainsi qu’à son aspect haut
niveau.
Je vous proposerai dans ce cours des algorithmes écrits tantôt en pseudo-langage, tantôt en Python :
entraı̂nez-vous à passer de l’un à l’autre.
29
2. ALGORITHMES DE RECHERCHE
CHAPITRE II. ALGORITHMIQUE
Entraı̂nez-vous également à les modifier légèrement, par exemple pour qu’ils rendent un indice plutôt qu’un
élément (d’une liste), ou en passant d’une boucle conditionnelle à une boucle inconditionnelle, etc. tout en vous
demandant quels avantages et inconvénients ces changements produisent.
Nous nous intéressons dans ce cours à des problèmes très courants et élémentaires.
2. Algorithmes de recherche
2.1. Recherche dans une liste et variantes
On s’intéresse à la recherche d’un élément dans une liste :
Entrée : L i s t e L , é lé m e n t e
S o r t i e : un b o o lé e n dé t e r m i n a n t s i e e s t dans L
Variable : i
i <− 1
Tant que i <= T a i l l e L i s t e (L) e t L [ i ] != e F a i r e
i <− i + 1
Fin Tant que
Renvoyer i == T a i l l e L i s t e (L) + 1
Exercice (Calcul de la moyenne, de la variance, et de la médiane)
Proposez des programmes Python (ou des algorithmes en pseudo-langage) de calcul
de la moyenne, de la variance, et de la médiane d’une liste de flottants.
1
2.2. Recherche dans une liste triée
Dans le cas où un tableau est trié, mettons dans l’ordre croissant, on peut très rapidement trouver si un
élément donné se trouve dedans, en utilisant une recherche par dichotomie :
Entrée : L i s t e L , é lé m e n t e
S o r t i e : un b o o lé e n dé t e r m i n a n t s i e e s t dans L
Variables : i , j
i <− 1
j <− T a i l l e L i s t e (L)
Tant que i != j F a i r e :
S i L [ ( i + j ) // 2 ] < e F a i r e
i <− ( i + j ) // 2
Sinon F a i r e
j <− <− ( i + j ) // 2
Renvoyer L [ i ] == e
Nous avons vu en préambule la recherche du maximum dans une liste de nombres
Remarque : on peut naturellement adapter cet algorithme à la recherche par dichotomie du zéro d’une fonction
continue et monotone (faites cependant attention à la contrainte de traiter avec des flottants). Faites le en
exercice.
30
Stéphane FLON
CHAPITRE II. ALGORITHMIQUE
4. COMPLEXITÉ D’UN ALGORITHME
Exercice (Cas d’une chaı̂ne de caractères)
Proposer un algorithme effectuant la recherche d’un mot dans une chaı̂ne de caractères.
2
3. Que peut-on espérer d’un algorithme ?
Nous avons jusqu’à présent simplement proposé des algorithmes pour répondre à certains problèmes. On
peut toutefois tenter de répondre à deux questions naturelles :
– L’algorithme ainsi écrit fait-il vraiment ce qu’il est censé faire ? C’est le domaine de la preuve de programme
(ou d’algorithme).
– Cet algorithme est-il efficace, c’est-à-dire renvoie-t-il une réponse en temps raisonnable, et n’occupe-t-il
pas trop de ressources ? C’est le domaine de la complexité (temporelle et spatiale).
3.1. Preuve d’un programme
3.1.1. Notion d’invariant de boucle. Cette notion permet de prouver la correction des segments itératifs,
c’est-à-dire des boucles conditionnelles et inconditionnelles. L’idée consiste à chercher un prédicat Pk (i.e. une
fonction à valeurs booléennes), fonction du nombre k de passages en boucle, que l’on prouverait (i.e. qui serait
établi comme constant de valeur Vrai) par un raisonnement par récurrence :
– (Initialisation) le résultat est vrai avant le premier passage en boucle (i.e. après zéro passage en boucle,
soit encore en entrée de boucle).
– (Hérédité) s’il est vrai après le k-ième passage en boucle, alors il est vrai au (k + 1)-ième.
Le but étant bien sûr au final, en sortie de boucle, d’obtenir le résultat de correction du segment itératif.
Dans le premier exemple d’algorithme donné ci-dessus, on peut proposer Pk : après le k-ième passage en
boucle, maxi = max T [i], i ∈ [[1, (k + 1)]].
Exercice (Invariants de boucle)
1 Prouvez les segments itératifs des algorithmes précédents.
2 Écrire l’algorithme d’Euclide de manière impérative, et prouver cet algorithme à
l’aide d’un invariant de boucle.
3
3.2. Comment prouver un programme récursif ?
Nous n’entrerons pas dans les détails d’une preuve de programme récursif, mais on peut seulement essayer
de comprendre les mathématiques sous-jacentes.
Quand on écrit un programme récursif, comme celui permettant de programmer la factorielle, on peut
effectuer un raisonnement par récurrence.
Pour prouver la correction d’un algorithme d’Euclide récursif, on peut aussi réfléchir à une démonstration
par récurrence, mais c’est plus délicat car cet algorithme prend en entrée deux entiers.
Remarque : dans d’autres cas, pour des algorithmes utilisant des structures de données particulières (par
exemple des listes, ou des arbres), on peut exploiter la récursivité de la structure elle-même.
4. Complexité d’un algorithme
Nous avons proposé plusieurs algorithmes de recherche d’un élément dans une liste : l’un valable dans une
liste quelconque, l’autre dans une liste triée. On peut donc appliquer ces deux algorithmes à une liste triée, et
comparer leur efficacité.
Intuitivement, le second semble plus rapide. Si c’est bien le cas, comment le prouver, et même, comment le
formaliser ? Après tout, pour la recherche d’un élément dans un ensemble de 5 ou 10 notes, les deux algorithmes
semblent répondre en des temps équivalents. Si on prend un tableau à 100, 1000, ou 10000 éléments, on commence
à voir une différence de rapidité d’exécution. Si maintenant on prend un tableau à 109 éléments, le premier
algorithme ne nous répondra pas alors que le second donnera une réponse instantanée.
31
Stéphane FLON
4. COMPLEXITÉ D’UN ALGORITHME
CHAPITRE II. ALGORITHMIQUE
Pour déterminer l’efficacité d’un algorithme, nous allons donc nous intéresser à son comportement asymptotique. De plus, nous ne nous intéressons pas vraiment au temps effectivement pris par une implémentation
de l’algorithme sur une machine donnée dans un langage donné : en effet, ce temps ne donne d’indication que
pour ce langage et cette machine. Nous voudrions conserver l’approche abstraite de l’algorithmique, i.e. du
pseudo-langage dans lequel nous avons décrit l’algorithme.
L’idée est donc de partir d’un paramètre n (éventuellement plusieurs), jaugeant la taille de l’entrée (par
exemple le nombre de termes d’une liste ou d’une chaı̂ne de caractères, le nombre de bits d’un nombre entier,
etc.), de compter un certain nombre d’opérations hn (par exemple le nombre d’ajouts (append) à une liste,
le nombre de multiplications ou d’additions bit à bit, etc.), et d’estimer le comportement asymptotique de hn
lorsque n tend vers l’infini : il est donc pertinent d’utiliser les relations de comparaison o, O et ∼. En fait, la
relation la plus pertinente en est une autre, moins usitée en maths : la relation être de l’ordre de :
Définition (Grand Theta)
Soit u et v deux suites réelles, qui ne s’annulent pas à partir d’un certain rang. On
dit que u et v ont même ordre et on note
4.a
un = Θ(vn )
si un = O(vn ) et vn = O(un ).
Il est clair qu’il sagit d’une relation d’équivalence, que un = Θ(v
n ) si et seulement si les suites (vn /un )
et (un /vn ) sont bornées, si et seulement si la suite de terme général uvnn est majorée, et minorée par un réel
strictement positif.
Bien sûr, si un ∼ vn , alors un = Θ(vn ), et la réciproque est fausse.
Remarque : pourquoi avoir préféré la relation Θ plutôt que ∼ ?
– Tout d’abord, nous ne sommes intéressés que par l’ordre de grandeur : si pour un premier algo, hn ∼ n et
si pour un second, hn ∼ 2n, le second ira asymptotiquement deux fois plus lentement que le premier, mais
ce facteur deux n’est pas de nature à exclure le second au profit du premier, le gain n’est pas significatif.
En revanche, si hn ∼ 10000n pour le premier, et hn ∼ n2 , le premier est asymptotiquement à privilégier.
– Il y aurait quelque ridicule à faire un calcul extrêmement précis du nombre d’opérations effectuées : en
effet, une multiplication et une addition ne coûtent pas nécessairement la même chose, donc nous ne
devrions pas les compter avec le même poids. Il peut aussi y avoir des opérations que nous n’avons pas
comptabilisées. Enfin, une implémentation un peu astucieuse peut faire passer de 3n à 2n opérations par
exemple.
– Il peut aussi être plus difficile de donner un équivalent, qui est une information plus fine qu’un ordre de
grandeur.
Selon la taille de données n, l’algorithme va effectuer un certain nombre de tâches, dont certaines auront
un poids bien plus grand dans le temps d’exécution. Nous ne compterons que le nombre cn de ces opérations
coûteuses.
On dit qu’un algorithme est
– logarithmique si cn est de l’ordre de log2 (n).
– linéaire si cn est de l’ordre de n.
– quasi-linéaire si cn est de l’ordre de n log n.
– quadratique si cn est de l’ordre de n2 .
– polynomial si cn est de l’ordre de nk , pour un entier non nul k.
– exponentiel si cn est de l’ordre de an , où a > 1.
Ces classes de complexité sont données en ordre croissant : les algorithmes exponentiels sont très peu utiles,
les logarithmiques finissent en temps raisonnable pour n’importe quelle taille de l’entrée.
Remarque : bien sûr, il faut tempérer ce jugement, puisqu’il peut y avoir des coûts occultes (si par exemple
cn ∼ 10100 log2 (n), l’algorithme n’est pas si pratique que cela . . .).
Remarque : en fait, on peut distinguer plusieurs types de complexité :
– la complexité dans le meilleur des cas, i.e. la plus petite valeur possible de cn pour un objet de taille n.
– la complexité dans le pire des cas, i.e. la plus grande valeur possible de cn pour un objet de taille n.
– la complexité moyenne, i.e. la moyenne des valeurs de cn sur les différents objets de taille n (apparaissant
selon une distribution de probabilité à préciser).
32
Stéphane FLON
CHAPITRE II. ALGORITHMIQUE
4. COMPLEXITÉ D’UN ALGORITHME
Exercice (Évaluations de complexité)
Proposez des évaluations de complexité pour les algorithmes déjà étudiés (et éventuellement pour d’autres).
4
Pour vraiment bien comprendre la notion de complexité, il est recommandé de passer du temps sur le site
project Euler, car souvent, l’algorithme naı̈f permettant de résoudre le problème met bien trop longtemps pour
répondre à la question. L’ordinateur le plus puissant sur terre ne peut traiter un algorithme exponentiel (comme
celui des tours de Hanoı̈) que pour une très petite taille de l’entrée, alors qu’un vieux PC traite un algorithme
logarithmique instantanément : l’algorithmique, c’est de la technologie (et donc de l’argent, voir l’histoire de
Google).
Exercice (Un léger gain de temps)
On cherche à donner un algorithme permettant de trouver à la fois le maximum et
le minimum d’un tableau noté Tab de n éléments distincts.
1 Écrire un algorithme qui donne la place de l’élément minimum dans le tableau
ainsi que sa valeur.
2 Combien de comparaisons effectue cet algorithme ? Quelle est sa complexité ?
3 Écrire ensuite un algorithme pour trouver les places et valeurs à la fois du minimum
et du maximum dans ce tableau.
4 Pouvez-vous proposer un algorithme qui n’effectue que 3n
2 comparaisons ?
33
5
Stéphane FLON
Deuxième partie
Architecture des ordinateurs et représentation
des nombres
CHAPITRE III
Représentation des nombres
1. Introduction : représentation humaine des nombres entiers naturels
Avant d’aborder la représentation des nombres entiers dans un ordinateur, on peut réfléchir à notre propre
représentation des nombres. On se pose la question du choix d’une représentation d’un entier naturel n au moyen
de divers systèmes de numération.
1.1. Représentation par une infinité de symboles
Supposons disposer d’une infinité de symboles ou d’objets différents, et qu’à chaque entier naturel corresponde un tel symbole, de façon injective. La représentation d’un nombre est alors donnée par le symbole
correspondant.
Ce système de numération est clairement inutilisable, car nous devrions nous entendre sur la correspondance
entre les nombres et une infinité de symboles !
1.2. Représentation par un symbole unique disponible en grande quantité
Supposons disposer d’un symbole ou d’un objet u (des points, des cailloux, des boules, des barres verticales,
etc.), présent en quantité infinie, et que nous voulions représenter un entier naturel n à l’aide de u : bien sûr,
nous pourrions regrouper n exemplaires de u.
C’est par exemple ce système de numération que nous employons sur les faces d’un dé (usuel), chaque face
ayant pour valeur le nombre de points qu’on y trouve.
Ce système est clairement très limité, et il devient très vite extrêmement difficile de bien faire la correspondance entre les nombres abstraits et leurs représentants, ou de comparer deux quantités (mettons 35 et 38
cailloux par exemple).
Remarque : pour représenter 2n, on utilisera deux fois plus d’objets que pour représenter n.
1.3. La représentation décimale
Le plus souvent, nous utilisons la représentation dite décimale des nombres. Par exemple, l’écriture 236
s’interprète comme le nombre 2 × 100 + 3 × 10 + 6, soit encore comme 2 × 102 + 3 × 101 + 6 × 100 .
On observe notamment que dans cette représentation, à l’aide cette fois-ci d’un nombre limité de symboles
(les dix chiffres), le poids d’un symbole dépend de sa position : dans le nombre 222, les trois chiffres 2 n’ont pas
le même poids.
L’idée est qu’un nombre n’aura pas un symbole propre, ce qui conduirait à une inflation de symboles et à
une mémorisation impossible (cf. 1.1), mais une suite propre d’un ensemble restreint de symboles.
Remarque : par commodité, on peut imaginer cetteX
suite comme infinie (et donc tous les éléments sauf peutêtre un nombre fini sont nuls). Par exemple, 236 =
dk 10k , où d0 = 6, d1 = 3, d2 = 2, et dk = 0 pour tout
k>0
k > 3.
Remarque : pour représenter 2n, on utilisera au plus un symbole de plus que pour représenter n.
1.4. La représentation fiduciaire
La monnaie est un exemple très courant où l’on doit représenter un entier naturel par un ensemble de
symboles ou objets (les pièces de monnaie et les billets de banque). Je parle d’entier naturel pour simplifier, en
supposant que l’unité monétaire est l’objet de plus petite valeur, et que tous les autres en sont des multiples
entiers. Par exemple, dans le cas de l’euro, l’unité est le centime d’euro, et toutes les pièces et billets ont pour
valeur un multiple entier de cette unité.
Je peux représenter 1236 en donnant un ensemble de pièces et billets dont la valeur totale sera de 1236
centimes d’euro. Pour ce faire, je peux choisir par exemple
37
1. INTRODUCTION
CHAPITRE III. REPRÉSENTATION DES NOMBRES
(1) de donner un billet de 10 euros, une pièce de deux euros, sept pièces de 5 centimes et une pièce d’un
centime.
(2) de donner 1236 pièces d’un centime.
(3) de donner 618 pièces de deux centimes.
Je peux encore le faire de bien d’autres manières (mais je n’ai qu’un nombre fini de manières de le faire).
Cette non unicité est peu gênante pour l’échange pratique de monnaie, et est même plutôt un avanctage.
Cependant, elle l’est beaucoup plus dans une perspective informatique : en effet, en informatique, il est très
important de pouvoir tester l’égalité de deux objets. Pour cela, il faut s’entendre sur la signification de cette
égalité : si deux billets de 10 euros et quatre billets de 5 euros ne sont pas les mêmes ensembles de billets, il
représentent tous les deux la même valeur, à savoir 20 euros. Il nous faudrait donc dire qu’ils sont égaux de ce
point de vue.
Ces ensembles diffèrent par leur syntaxe (ils ne s’écrivent pas de la même façon), mais pas par leur sémantique
(ils correspondent à la même valeur). Une façon de pallier ce problème est de choisir un représentant distingué
d’un montant n, si possible de manière algorithmique, afin que tout le monde puisse appliquer la même méthode
et obtienne ainsi le même représentant.
On peut proposer d’utiliser un algorithme glouton.
1.5. L’algorithme glouton pour la représentation d’un entier par des pièces et billets
Supposons qu’une monnaie M soit constituée de p pièces et billets, de montant v0 , v1 , . . ., vp−1 . On suppose
que v0 = 1, que la suite (vi ) est strictement croissante, et que les pièces et billets de montant donné sont
disponibles en quantités infinies.
Exercice (Suite strictement croissante pour l’euro)
1
Donner cette suite dans le cas de la monnaie euro.
Pour représenter un montant n, nous prenons le plus grand entier dp−1 tel que dp−1 vp−1 6 n : en termes
mathématiques, dp−1 est le quotient (euclidien) de n par vp−1 . Nous sommes ramenés à représenter n0 =
n − dp−1 vp−1 , qui est – par définition de dp−1 – compris entre 0 et vp−1 − 1. On applique à nouveau notre
méthode, en cherchant le plus grand entier dp−2 tel que dp−2 vp−2 6 n0 , et on itère cette méthode jusqu’à arriver
à v0 , qui permet d’arriver à 0.
Par exemple, pour représenter 1236 en centimes d’euros avec l’algorithme glouton, on prendra un billet de
10 euros, une pièce de 2 euros, une pièce de 20 centimes, une de 10 centimes, une de 5 centimes, et enfin une
d’un centime.
Remarque : avec cet algorithme glouton, et dans le but de représenter un entier n, il n’est pas nécessaire
d’avoir une infinité de chaque pièce ou billet de montant donné : en fait, il faut et il suffit d’avoir une infinité de
représentants de vp−1 , et pour tout k ∈ [[0, p − 2]], d’avoir qk représentants de vk , où qk est le quotient (euclidien)
de vk+1 − 1 par vk .
Exercice (Quantité de pièces et billets pour l’euro)
Donner, pour chaque valeur de pièce ou billet en euros, le nombre de ses représentants
nécessaire et suffisant pour représenter n’importe quel entier naturel préalablement
fourni.
2
1.6. Extension de la notion de base
En réalité, la représentation décimale des entiers naturels suit exactement l’algorithme glouton : on dispose,
pour tout k ∈ N, d’un objet de valeur 10k , ou plus précisément (vk )k∈N = (10k )k∈N . En suivant l’algorithme
glouton, nous n’avons besoin que de 9 représentants de chaque valeur donnée 10k .
Ce processus s’étend donc très largement, en prenant par exemple un entier a > 2 et la suite de ses puissances
(chaque valeur ak devant être présente en a − 1 exemplaires) : c’est le système de numération en base a. Si (dk )
est la suite obtenue en appliquant l’algorithme glouton pour représenter n avec la suite (ak ), i.e. si
s
X
X
n=
dk ak =
dk ak ,
k>0
k=0
38
Stéphane FLON
CHAPITRE III. REPRÉSENTATION DES NOMBRES
3. REPRÉSENTATION DES ENTIERS
alors on notera
a
n = ds ds−1 . . . d0 .
2
Par exemple, 5 = 101 .
Remarque : on pourrait même proposer des systèmes de numération avec une autre suite strictement croissante,
comme (k!)k∈N . Vous pouvez vous amuser à représenter 555 par exemple avec ce système.
Outre la base 10, les bases les plus utilisées sont
– 2 (représentation binaire).
– 8 (représentation octale).
– 16 (représentation hexadécimale), les valeurs de 0 à 15 étant représentées par 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F .
Exercice (Représentation dans diverses bases)
Représenter 113 dans les base 2, 3, 5, 6, et 16.
3
2. Représentation de l’information dans un ordinateur
En informatique, on privilégie la base 2 : un ordinateur est entre autres constitué d’une grande quantité de
transistors, pour chacun desquels deux états individuels sont possibles : soit le courant passe, soit il ne passe
pas. Cette limitation au binaire n’est en fait pas restrictive : pour représenter un état parmi N distincts, par
exemple la note entière (et sur 20) d’un étudiant, il suffit de considérer m transistors, où 2m > N (dans notre
exemple, cinq transistors suffisent).
Une fois cette approche choisie, se pose la question de la représentation de l’information : en effet, toutes
les informations stockées dans un ordinateur 1 le sont sous la forme d’une suite de transistors dans un certain
état, allumés ou éteints. Il n’y a pas a priori de moyen de savoir la nature de l’information stockée.
Chaque état d’un transistor est ce qu’on appelle un bit (contraction de binary digit) : il s’agit donc de
l’unité élémentaire d’information en informatique, qui vaut par convention 1 si le courant passe, et 0 sinon. Ils
sont souvent regroupés en octets (bytes en anglais), i.e. en séquences de 8 bits.
Afin d’assurer une interopérabilité, il est donc essentiel de proposer des normes de représentation des informations. Nous nous intéressons à ces normes pour les entiers naturels, relatifs, et enfin pour les nombres
réels.
3. La représentation des nombres entiers en informatique
3.1. Représentation des entiers naturels
Évidemment, on ne peut espérer coder tous les entiers, en nombre infini, puisqu’un ordinateur ne comporte
qu’un nombre fini de transistors. Plutôt que d’essayer d’en coder le plus possible, on préfère se limiter à une
taille fixe d’octets pour représenter un nombre restreint d’entiers : en effet, si on autorisait une taille variable,
il faudrait savoir où sont les séparations quand on étudie une suite d’octets représentant plusieurs entiers. De
plus, le processeur de l’ordinateur est optimisé pour effectuer des opérations sur des entiers codés en taille fixe.
Les entiers sont généralement codés en 16, 32 ou 64 bits : on s’attend à ce qu’un codage en n bits nous
permette de représenter 2n entiers. C’est très facile si on souhaite coder les entiers de 0 à 2n − 1, en associant
à un entier la liste de ses chiffres (digits en anglais) en binaire.
Exercice (Opérateurs liés aux représentations binaires)
Comprendre, à la lumière de ce choix de représentation, les résultats de a&b, a ˆ b,
a | b, où a, b ∈ N.
Si on n’a pas su effectuer la conversion binaire, on pourra utiliser la primitive bin
de Python.
4
1. Par exemple des textes, des fichiers musicaux, des vidéos, des mots de passe, l’état de tel ou tel périphérique, etc.
39
Stéphane FLON
4. REPRÉSENTATION DES RÉELS
CHAPITRE III. REPRÉSENTATION DES NOMBRES
3.2. Représentation des entiers négatifs
Qu’en est-il pour les entiers négatifs ? On aimerait coder des entiers positifs et négatifs.
Une première idée consisterait à isoler un bit pour qu’il détermine le signe, mettons 0 pour + et 1 pour −.
Cette idée comporte plusieurs défauts :
(1) 0 est codé par deux séquences distinctes (puisque +0 = −0 ).
(2) On ne code que 2n − 1 entiers (puisque 0 est codé deux fois).
(3) Le test d’égalité à 0 est bancal (puisque 0 n’a pas qu’un seul représentant).
(4) L’addition ne se fait pas comme d’habitude, par addition bit à bit et retenues (le bit associé au signe
a un comportement particulier).
Une idée plus efficace consiste à coder un entier par son reste dans la division euclidienne par 2n : plus
précisément, les entiers de 0 à 2n−1 − 1 sont codés par leur représentation usuelle, et un entier x compris entre
−2n−1 et −1 est codé par la représentation de 2n + x.
Remarque : avec cette représentation, il est facile de voir si on travaille avec un entier positif ou négatif,
puisqu’il suffit de considérer son bit de plus grand poids (1 pour les négatifs, 0 pour les positifs). C’est d’ailleurs
pour cela que nous avons choisi de représenter −2n−1 plutôt que 2n−1 .
Cette représentation est appelée le complément à 2.
Exercice (Complément à 2)
Écrire les différentes représentations en complément à 2 sur 4 bits.
5
Exercice (Représentations d’entiers)
Écrire un programme Python fournissant la représentation en complément à deux
en 64 bits (on testera son programme sur des exemples variés).
6
Exercice (Passage à l’opposé)
Pn−1
En observant que 2n = 1 + k=0 2k , expliquer une façon très simple d’obtenir la
représentation de −x à partir de celle de x (on pourra utiliser l’opérateur ˆ).
7
Exercice (Limitation des entiers et nombre de bits)
Écrire un programme permettant de déterminer l’architecture de votre machine (32
ou 64 bits), fondée sur la sortie d’entiers trop grands du type int en Python 2.x
(en Python 3, on ne passe pas du type int au type long).
Remarque : dans beaucoup d’autres langage de programmation, l’addition par
exemple est une loi de composition interne sur les objets de type entier, les entiers
étant vus comme des éléments de Z/(2n )Z.
8
4. La représentation des nombres réels
De la même manière que pour les entiers, il serait illusoire de vouloir représenter tous les réels. En fait, la
situation est bien pire, puisque R n’est même pas dénombrable (i.e. il n’est pas en bijection avec N).
En réflechissant à l’avantage que nous pourrions avoir à utiliser des réels plutôt que des entiers, on arrive à
deux types de représentations, avec leurs avantages et inconvénients :
40
Stéphane FLON
CHAPITRE III. REPRÉSENTATION DES NOMBRES
4. REPRÉSENTATION DES RÉELS
4.1. Les nombres à virgule fixe
Si nous voulons représenter des quantités physiques d’argent français, en prenant l’euro pour unité, nous
sommes amenés à considérer des centièmes d’euros (i.e. des centimes), mais nous n’aurons pas à considérer des
tiers ou des millièmes d’euros : deux chiffres significatifs (en base 10) nous donneront toujours la valeur exacte
de l’argent dont nous disposons.
On peut donc consacrer quelques bits à la partie décimale, les autres servant à déterminer (le signe et) la
partie entière.
On utilise encore un complément à deux pour la partie entière.
Cette représentation, assez peu usitée car elle ne différe pas fondamentalement de la représentation des
entiers. Elle est adaptée à un domaine où les calculs doivent être parfaitement exacts, en finance par exemple.
4.2. Les nombres à virgule flottante
Plutôt que de vouloir représenter des réels régulièrement espacés, on peut s’inspirer de la notation scientifique, et représenter des réels à un nombre de chiffres significatifs près. Cela aura l’avantage de permettre de
coder de très petits et de très grands réels au sein d’une même représentation.
En simple précision (i.e. en 32 bits) selon le standard IEEE-754, on consacre un bit pour le signe s ∈ {−1, 1},
23 bits pour la mantisse M ∈ [1, 2[ et 8 pour l’exposant E, afin de représenter
(−1)s × M × 2E−127
Il y a donc environ 7 chiffres significatifs.
Nous avons retranché 127 à l’exposant afin de représenter des réels très petits et d’autres très grands .
Concrètement, pour coder un réel, le signe est le bit de gauche, on le convertit en binaire, on décale la virgule
(d’où le nom de virgule flottante), on remplit par la gauche sur les 23 bits de droite par la partie fractionnaire
de la mantisse 2, en rajoutant des 0 pour arriver à 23 s’il le faut.
On décale l’exposant, le convertit en binaire, et on l’écrit sur les bits 2 à 8.
Remarque : pour la précision en 64 bits (dite double), et toujours selon le standard IEEE-754, on consacre 11
bits à l’exposant, 52 à la mantisse, obtenant ainsi environ 16 chiffres significatifs.
Exercice (Nombres à virgule flottante)
Selon votre niveau, entraı̂nez-vous à représenter des nombres à virgule flottante, ou
programmez la conversion d’un décimal en nombre à virgule flottante sous forme de
liste de bits (et la conversion inverse).
9
4.3. Défauts de la représentation à virgule flottante
Cette représentation étant fondée sur la notion de chiffres significatifs, elle représente les réels par des
approximations : cela provoque des erreurs d’arrondis, rendant notamment les tests d’égalité et de comparaison
imprécis.
En fait, on peut retenir que pour les flottants, faire un test d’égalité n’a pas grand sens.
Si on veut tester l’égalité de deux réels α et β représentés par des flottants a et b, on écrira quelque chose
du genre |a − b| 6 ε pour une valeur adéquate liée à la précision (32 ou 64 bits). Il est donc clairement abusif
de parler d’égalité.
Les flottants sont le royaume de l’approximation, les entiers celui du calcul exact.
Même pour les nombres décimaux, la représentation ne sera qu’approchée, car la représentation se fait en
binaire (la partie fractionnaire est une somme de puissances de 2 d’exposants négatifs.
>>> 0 . 1 + 0 . 2
0.30000000000000004
L’addition en flottant n’est pas associative !
>>> (0.000000000000001+1) −1
1 . 1 1 0 2 2 3 0 2 4 6 2 5 1 5 6 5 e −15
>>> 0.000000000000001+(1 −1)
1 e −15
2. la mantisse appartient à [1, 2[, et commence donc toujours par 1 : il est inutile de coder cette partie entière
41
Stéphane FLON
4. REPRÉSENTATION DES RÉELS
CHAPITRE III. REPRÉSENTATION DES NOMBRES
En première approche, on peut penser que ces erreurs d’arrondi ne posent pas de problème sérieux, mais dans
certaines situations, elles peuvent s’accumuler tout en s’amplifiant, et finir par produire des résultats absurdes.
Cela se produit par exemple quand on étudie un phénomène chaotique, très sensible aux conditions initiales
(comme le fameux battement d’aile de papillon de Bornéo qui provoque un cyclone aux États-Unis).
Remarque : certains calculs sur les flottants provoquent ce que l’on appelle un dépassement de capacité.
Lorsque l’ordinateur ne sait pas quoi répondre, il répond NaN (Not a Number).
Voici un exemple concret (emprunté à Sylvie Boldo) où les erreurs d’arrondi conduisent à un résultat éloigné
de la véritable valeur (à savoir −0.827396) :
>>> def f ( a , b ) :
...
return 3 3 3 . 6 5 ∗ ( b ∗ ∗ 6 ) + ( a ∗ ∗ 2 ) ∗ ( 1 1 ∗ ( a ∗ ∗ 2 ) ∗ ( b ∗ ∗ 2 )
− ( b ∗ ∗ 6 ) − 121 ∗ ( b ∗ ∗ 4 ) − 2 )
+ 5.5 ∗ (b∗∗8) + a / (2 ∗ b)
...
>>> a = 7 7 6 1 7 . 0 ; b = 3 3 0 9 6 . 0
>>> f ( a , b )
−1.3141873685177936 e+26
Voici un autre exemple (encore emprunté à Sylvie Boldo), que vous comprendrez mathématiquement dans
pas longtemps :
Exercice (Un investissement rentable, ou pas)
Votre banquier vous propose l’investissement suivant :
– La première année, vous me donnez exp(1) − 1 euros.
– L’année suivante, je prends un euro de frais, et je multiplie par deux.
– L’année suivante, je prends un euro de frais, et je multiplie par trois.
– ...
– Après n années, je prends un euro de frais, et je multiplie par n.
– Pour récupérer votre argent, il y a un euro de frais.
Au bout de 50 ans d’investissement, combien d’argent cela vous fera-t-il gagner ?
Testez plusieurs programmes sur plusieurs machines, avec des approximations plus
ou moins fines de exp(1).
10
La représentation nécessairement imparfaite des nombres réels peut donc être source d’erreurs, qui ont
parfois de lourdes répercussions concrètes : durant la première guerre du Golfe, un missile Patriot américain
a décimé ses propres troupes à cause d’une approximation, bonne en première approche, mais qui s’est amplifiée. On comprend donc sans douleur que ce domaine constitue une branche très active de la recherche en
informatique.
42
Stéphane FLON
Troisième partie
Feuilles de TD
FEUILLE DE TD 1
Premiers pas en Python
Exercice 1 (Simples calculs)
Calculer les expressions suivantes :
0
p
1+
√
17, 21 5, le reste de la division euclidienne de 31 2 par 17.
Exercice 2 (Fonctions simples)
0
Programmer des fonctions carré et cube.
Exercice 3 (Primitives Python)
0
Reprogrammer les primitives suivantes (sans les utiliser . . .) :
1 Valeur absolue
2 max, min de deux réels, d’une liste.
Exercice 4 (Fonctionnelles)
1 evalue en 2(f)=f(2).
2 applique sur liste ( f )= ([l0 , . . . , ln ] 7→ [f (l0 ), . . . , f (ln )]).
3 maximum dans l intervalle(f)= (range(a, b) 7→ max(f (a), . . . , f (b − 1))).
Exercice 5 (Filtre)
2
Écrire une fonction filtre, prenant en argument un prédicat et une liste, et renvoyant la liste ôtée de
ses termes pour lesquels le prédicat est faux.
Exercice 6 (Ajouts de zéros)
1 Écrire une fonction qui à une liste associe la liste dans laquelle on a intercalé un zéro entre tous
les termes consécutifs.
2 Écrire une fonction qui à une liste associe la liste dans laquelle on a intercalé k zéros entre les
termes d’indices k − 1 et k (pour tout k).
CHAPITRE 1. TD 1
Exercice 7 (Parties d’un ensemble)
Écrire une fonction parties qui à un ensemble fini associe l’ensemble de ses parties (on laisse le choix
de la modélisation d’un ensemble fini).
Exercice 8 (Lci sur les termes d’une liste)
Écrire une fonction qui à une liste d’éléments d’un ensemble E, et à une loi de composition interne
sur E, associe la composée des termes de la liste par cette loi (les parenthèses se mettant à gauche).
Exercice 9 (Évaluation de polynômes)
On modélise un polynôme par une liste de flottants, le terme d’indice k de la liste correspondant au
coefficient d’indice k du polynôme.
1 Écrire une fonction evalue prenant en argument un couple P, x, et renvoyant la valeur (approchée)
de P en x.
2 Écrire à nouveau une telle fonction, en suivant le schéma de Horner.
Exercice 10 (Palindromes)
Écrire une fonction palindrome prenant en argument une chaı̂ne de caractères, et déterminant s’il
s’agit d’un palindrome (on supprimera les espaces et la ponctuation, et on ne tiendra pas compte de
la casse).
Exercice 11 (Tours de Hanoı̈)
Tours de Hanoı̈
Exercice 12 (Suites récurrentes)
1 Programmer une fonction u, qui à n associe le terme un de la suite définie par le terme initial 1 et
l’itératrice f : x 7→ ln(1 + x).
2 Programmer des fonctions u et v permettant de calculer les termes des suites u et v telles que
u0 = v0 = 1, et pour tout n ∈ N :
un+1 = un + 2vn
et
Exercice 13 (Approximation d’une suite par un réel)
vn+1 = un ∗ vn
0
On considère la suite de terme général un = ln(n)
n . Écrire une fonction rang qui prend en argument
un réel strictement positif ε, et renvoie le plus petit indice n tel que |un | 6 ε.
46
Stéphane FLON
CHAPITRE 2. TD 2
Exercice 14 (Somme maximale)
Ecrire la fonction d’argument une liste de nombres L (d’au moins 3 nombres), qui retourne l’indice
du premier élément du premier triplet consécutif de somme maximale dans L.
Exercice 15 (Plateau maximal)
Ecrire la fonction d’argument L une liste croissante au sens large de nombres entiers, et qui retourne
l’indice du premier élément de la plus grande sous-suite constante.
Exercice 16 (Suite ordonnée des nombres à petits diviseurs)
Construire une fonction d’argument un entier naturel non nul, et qui retourne la suite ordonnée des
n plus petits entiers de la forme 2p 3q 5r
47
Stéphane FLON
FEUILLE DE TD 2
Représentation des nombres
1. Représentation des entiers naturels
Exercice 1 (Algorithme glouton)
1
Programmer l’algorithme glouton, prenant en entrée un entier naturel et un système de numération
sous forme de liste finie strictement décroissante d’entiers et terminant à 1.
Exercice 2 (Conversion en base b)
0
Écrire une fonction convertir telle que convertir (a, b) écrive l’entier a (donné sous forme usuelle)
en base b, sous forme de liste d’entiers.
Exercice 3 (Nombre donné en base b)
0
Écrire une fonction valeur telle que valeur(L, b) renvoie la valeur de l’entier donné par la liste L
dans la base b.
Exercice 4 (Conversion entre bases)
0
Écrire une fonction changement de base prenant en argument une liste L, et deux entier b et c
supérieurs ou égaux à 2, et renvoyant dans la base c l’entier donné par la liste L dans la base b.
Exercice 5 (Opérations sur les nombres binaires)
2
Écrire des fonctions réalisant l’addition et la multiplication d’entiers naturels donnés en binaire.
Exercice 6 (Représentation des entiers relatifs)
3
Reprendre les exercices précédents en travaillant sur des entiers relatifs (et leur représentation en
complément à 2.
2. REPRÉSENTATION DES RÉELS
CHAPITRE 3. TD 3
2. Représentation des réels
Exercice 7 (Conversion de nombres en binaires)
Écrire une fonction qui à un entier associe son écriture binaire (sous forme de liste par exemple).
Écrire une fonction qui à un rationnel donné sous forme de couple d’entiers associe son écriture
binaire en virgule flottante, à une précision donnée.
Exercice 8 (Opérations sur les nombres binaires)
Définir les opérations d’addition et de multiplication de nombres binaires, d’abord pour des entiers,
puis pour des flottants.
50
Stéphane FLON
FEUILLE DE TD 3
Arithmétique
1. Problèmes élémentaires
Exercice 1 (Algorithmes élémentaires en arithmétique)
0
Reprogrammer
1 La division euclidienne.
2 Le calcul de pgcd et ppcm de deux entiers.
3 La recherche d’un couple de Bézout.
Exercice 2 (Décomposition en produit de facteurs premiers)
0
Écrire une fonction premier diviseur telle que premier diviseur(n) renvoie le plus petit diviseur premier de n (où n > 2).
Écrire une fonction donnant la décomposition d’un entier n > 2 en produit de facteurs premiers,
sous forme de liste par exemple.
2. Problème classiques
Exercice 3 (Crible d’Ératosthène)
2
Programmer une fonction qui à un entier n > 2 associe la liste des nombres premiers inférieurs ou
égaux à n, à l’aide du crible d’Ératosthène.
Exercice 4 (Nombres parfaits)
2
Un entier naturel n est dit parfait s’il est égal à la somme de ses diviseurs naturels stricts. Par
exemple, 6 est parfait puisque 6 = 1 + 2 + 3.
Écrire une fonction parfait testant si le nombre passé en argument est parfait.
Trouver les nombres parfaits inférieurs à 106 .
Exercice 5 (Nombres de Niven)
Un entier positif divisible par la somme de ses chiffres est appelé nombre de Niven.
Écrire une fonction niven déterminant si un nombre est de Niven.
3
4. CRYPTOGRAPHIE
CHAPITRE 3. TD 3
Exercice 6 (Nombres complets)
3
Un nombre est dit complet si son carré utilise les 10 chiffres exactement une fois. Trouver les nombres
complets.
3. Tests de primalité et méthodes de factorisation
0
Exercice 7 (Test de primalité de Fermat)
2
Exercice 8 (Test de primalité de Miller-Rabin)
2
Exercice 9 (La méthode p − 1)
2
Exercice 10 (La méthode ρ de Pollard)
Test de primalité de Solovay-Strassen
4. Cryptographie
52
Stéphane FLON
Téléchargement