Algorithmique avancée en Python

publicité
Algorithmique avancée en Python
. . . et non pas Python avancé
Denis Robilliard
sept. 2014
1
Introduction
Objectifs du cours
• connaı̂tre un panel d’algorithmes standards : énumération, tris, backtracking, listes, etc;
• avoir codé et compris ces algorithmes, sans utiliser de librairies ”toutes faites”;
• consolider les bases de la conception d’applications dans le paradigme dit de ”programmation procédurale” avec
analyse descendante.
Avertissement Le langage support dans ce cours est le langage python 3. C’est cette version précise qui est supposée
dans toute expression comme ”le langage python”, ou ”en python”, ... Attention la version 2 du langage est encore
très répandue lors de la rédaction de ce cours, et n’est pas tout à fait compatible avec les codes donnés ici (quoique
les modifications soient généralement mineures).
2
Les bases de Python
Le plus important :
le commentaire
’’’ ceci est un commentaire
qui
s ’ é tend
sur plusieurs
lignes ’ ’ ’
# ceci est un commentaire jusqu ’ à la fin de ligne
Le commentaire sert à décrire l’algorithme en français (ou anglais, ou ...). Rappel : il existait des algorithmes avant
les ordinateurs et le langage Python...
L’affectation :
(nom de) variable = expression
a = 2+3
print ( a ) # affiche 5 à l ’ é cran
Ici la variable a reçoit la valeur résultant du calcul de l’expression arithmétique. L’affectation peut, dans certains cas,
être un peu plus compliquée que cela, nous le verrons plus loin.
Entrée/sortie de base :
’’’ programme perroquet ’’’
a = input ( ’ entrez un message : ’)
print ( a )
Attention, le message est mémorisé comme une chaı̂ne de caractères. Si vous voulez saisir un nombre pour faire des
calculs, il faut une conversion :
’’’ programme addition ’’’
a = input ( ’ entrez un entier a : ’)
a = int ( a ) # je remplace la cha ^
ı ne par un entier
b = int ( input ( ’ entrez un entier b : ’))
print ( ’ a + b vaut : ’, a + b )
L’alternative :
si condition alors action sinon autre action Attention, le test d’égalité se note == pour se
différencier de l’affectation.
a = 2+3
if a == 4:
print ( ’ la variable a ’)
print ( ’ contient la valeur 4 ’)
else :
’’’ notez l ’ utilisation de " pour permettre
d ’ inclure une apostrophe dans la cha ^
ı ne ’’’
print (" a n ’ est pas plus grand que 4")
Notez l’indentation (décalage de l’alignement) du code des actions. Ce décalage doit être identique en nombre d’espace
ou de tabulation pour chaque ligne de l’action dans tout votre code. Un alignement incorrect est une erreur pour
Python :
’’’ ce code ne fonctionne pas ’’’
a = 2+3
if a > 4:
print ( ’ a est plus grand que 4 ’)
print ( ’ strictement plus grand , en fait ’) # mal indent é !
else :
print (" a n ’ est pas plus grand que 4")
Il n’y a pas forcément une partie sinon :
’’’ je ne m ’ int é resse qu ’ aux entiers pairs ’’’
a = int ( input ( ’ entrez un entier : ’))
if a % 2 == 0:
print (" l ’ entier " , a , " est pair ")
’’’ à partir d ’ ici le reste de mon code ’’’
La boucle tant que :
tant que condition faire action fintq
’’’ é num è re et affiche les entiers de 1 à 10 ’’’
i = 1
while i < 11:
print ( i )
i += 1 # raccourci pour i = i + 1
’’’ code hors boucle signal é par son indentation ’’’
print ( ’ termin é ’)
La boucle pour :
pour variable dans enumeration faire action finpour
’’’ é num è re et affiche les entiers de 1 à 10 ’’’
’’’ range (a , b ) donne la s é quence des entiers de a à b ( b exclus ) ’’’
for i in range (1 ,11):
print ( i )
’’’ code hors boucle signal é par son indentation ’’’
print ( ’ termin é ’)
Note : on ne doit pas changer la valeur de i dans les actions de la boucle.
Le 2nd plus important : la fonction
’’’ retourne le max de ses 2 param è tres
( qui doivent ^
e tre comparables ) ’’’
def maximum (a , b ):
if a > b :
return a
else :
return b
def main ():
print ( maximum (2 ,12))
main ()
La fonction sert à organiser le code en petits blocs que l’on peut mettre au point indépendamment les uns des autres
: la complexité du problème diminue. Note : le code d’une fonction peut aussi être appelé plusieurs fois, mais c’est
un intérêt secondaire.
Ne pas confondre retourner et afficher/”sortir à l’écran” La fonction du paragraphe précédent retourne une
valeur dont on fait ce qu’on veut : on peut l’afficher bien sûr, mais aussi l’utiliser dans un calcul, la stocker en mémoire,
etc.
Dans l’exemple suivant la fonction affiche directement le résultat:
’’’ sortir à l ’ é cran le max de ses 2 param è tres
( qui doivent ^
e tre comparables ) ’’’
def maximum (a , b ):
if a > b :
print ( a )
else :
print ( b )
def main ():
maximum (2 ,12)
main ()
Notez que cette fonction est plus limitée : on ne peut plus récupérer le résultat dans la fonction main() pour le
stocker dans une variable. Par conséquent préférer la solution avec retour du résultat, sauf si le but de la fonction est
spécifiquement d’afficher un message.
3
Types de données
Les variables ne sont pas typées, mais par contre les données sont typées. ainsi les 3 affectations suivantes sont légales,
mais l’opération qui suit est erronée :
a
a
a
a
=
=
=
=
2 # entier
3.14 # flottant
’ bonjour ’ # cha ^
ı ne
a + 1 # erreur : pas d ’ op é ration + entre une cha ^
ı ne et un entier
types de bases Les types de base classiques que l’on trouve dans la plupart des langages sont :
• booléens / bool : True, False
• entiers / int : 1 ; -12
• réels / float : 3.14 ; .4 ; 1.3E5
• chaı̂nes / str : ’bonjour’
3.1
tableaux
Les tableaux sont la représentation informatique de la notion de vecteurs et de matrices mathématiques. Les tableaux
ne sont pas un type de base en python, mais comme c’est une structure de données très pratique, plusieurs modules
ont été introduits pour les implanter, dont le module array, et le module numpy. C’est numpy que nous utiliserons.
Ces tableaux seront limités à contenir des éléments de types numériques et booléens.
Création de tableau Il faut importer le module numpy pour pouvoir l’utiliser. On crée un tableau sans modèle
avec zeros ou ones, initialisant respectivement les éléments à 0 ou 1 (dans le cas booléen respectivement False ou
True), ou encore empty qui crée un tableau non initialisé.
import numpy # importer numpy
tab = numpy . array ([1 ,2 ,3]) # à partir d ’ un expression mod è le
autre_tab = numpy . array ( tab ) # cr é ation d ’ une copie de tab
tab = numpy . empty (3 , dtype = int ) # tableau de 3 entiers non initialis é s
tab = numpy . zeros (3 , dtype = int ) # tableau de 3 entiers initialis é s à 0
tab = numpy . ones (3 , dtype = int ) # tableau de 3 entiers initialis é s à 1
tab = numpy . zeros (3 , dtype = float ) # tableau de 3 r é els initialis é s à 0.0
t ab = numpy . zeros ((2 ,2) , dtype = int ) # matrice 2 x2 d ’ entiers
tab = numpy . zeros ((3 ,5) , dtype = float ) # matrice 3 x5 de r é els
tab = numpy . zeros ((2 ,2 ,2) , dtype = bool ) # cube 2 x2x2 de bool é ens à False
Accès aux éléments de tableau Tous les tableaux sont indicés à partir de 0.
import numpy # importer numpy
tab = numpy . zeros (3 , dtype = int ) # tableau de 3 entiers , initialis é s à 0
tab [0] = 12 # acc è s au 1 er é l é ment
tab [2] = -1 # acc è s au dernier é l é ment
tab [1] = tab [0] # copie du 1 er dans le 2 è me
tab_bool = numpy . zeros ((2 ,2 ,2) , dtype = bool )
tab_bool [1 ,1 ,1] = True
tab_bool [1][1][1] = True # é criture alternative
Attributs de tableaux La taille d’un tableau unidimensionnel peut être obtenue avec l’opérateur len, mais les
tableaux numpy possèdent aussi un attribut size qui donne le nombre d’éléments notamment pour les tableaux multidimensionnels.
Tranches de tableaux On peut manipuler directement un morceau de tableau où tous les éléments ont des indices
successifs : une tranche (ou slice).
tab = numpy . array ([1 ,2 ,3 ,4 ,5 ,6])
print ( tab [0:2]) # affiche les 2 premiers é l é ments : [1 2]
print ( tab [:2]) # m ^
e me chose que le pr é c é dent
print ( tab [3:]) # affiche depuis l ’ indice 3 jusque la fin du tableau
tab [:3]= tab [3:] # copie les 3 derniers é l é ms dans les 3 premiers
3.2
Notion de type ”conteneur”
Un tableau est un type ”conteneur” : il a un contenu (ses éléments) qui est modifiable. Par opposition; un entier
n’a pas de contenu modifiable en python : il se comporte comme une valeur littérale (écrite en toutes lettres). Pour
les types conteneurs, l’affectation prend un sens particulier : ce n’est pas une copie qui est créée, mais un alias (un
synonyme). Si on veut une copie, il faut utiliser le constructeur numpy.array(tableau à copier) vu plus haut.
tab = numpy . array ([1 ,2 ,3 ,4 ,5 ,6])
tab2 = tab # tab2 n ’ est PAS une copie de tab
tab [0] = 10 # ATTENTION : modifie tab ET tab2 , qui sont le meme objet
3.3
Liste
Python possède nativement un type liste, qui est un conteneur et qui se comporte comme un tableau avec des opérateurs
plus souples (on peut insérer au milieu d’une liste, l’agrandir, ...). La manière dont les listes sont implantées rend
moins efficace l’accès à un élément donné, mais plus efficace l’insertion et l’agrandissement. Par ailleurs les listes
peuvent contenir des éléments de types différents, contrairement aux tableaux : l’utilisation des listes ou des tableaux
dépend donc des besoins.
L’opérateur len, l’indiçage par [] et les tranches fonctionnent aussi sur les liste. On accède au dernier élément par
l’indice -1 (ce qui fonctionne aussi sur les tableau mais est peu portable dans les autres langages)
Quelques uns des opérateurs spécifiques sont :
• liste.append(truc) : ajoute truc en fin de liste
• liste.extend(liste2) : concatène liste2 en fin de liste
• liste.insert(indice, truc) : insère truc dans liste à l’indice donné (l’indice 0 insère en début de liste)
• liste.remove(truc) : retire la 1ère occurrence de truc trouvée dans la liste (erreur si non trouvé)
• liste.pop(indice) : retire et retourne l’élément à l’indice donné (si pas d’indice fourni retire le dernier élem).
• liste.index(truc) : donne l’indice de la 1ère occurrrence de truc dans la liste
l = [] # cr é ation d ’ une liste vide
l = [1 , 2 , 3] # liste de 3 entiers
l = [1 , ’ bonjour ’ , 3.14] # liste m é langeant des types
l2 = [[2]] # cr é ation d ’ une liste contenant le 3 è me é lem de l
l2 . append (2.718) # ajout d ’ un é l é ment en fin de liste
l3 = l2 # ATTENTION : pas une copie mais un alias
l3 = l2 [:] # cr é ation d ’ une copie ( NE fonctionne PAS avec les tableaux !)
print ( l3 [ -1]) # affiche le dernier é l é ment
4
Notion de Pile
Une pile est un type abstrait (mais pensez à une pile d’assiette), c’est une collection de donnée telle que :
• on peut ajouter un élément à la pile (”empiler”).
• on peut tester si la pile est vide (parfois aussi si elle est pleine — plus de place disponible)
• on peut récupérer un élément sur la pile (”dépiler”), et c’est le dernier ajouté (principe LIFO : Last In First
Out — dernier entré premier sorti). Souvent par commodité on peut consulter le dernier ajouté sans avoir à le
dépiler.
En python on crée facilement une pile en prenant une liste et en se limitant aux opérateurs append, et pop() sans
indice.
5
Notion de File
Une file est un type abstrait cousin de la pile (mais cette fois pensez à la file à la caisse d’un commerce), c’est une
collection de donnée telle que :
• on peut ajouter un élément à la pile (”enfiler”).
• on peut tester si la file est vide (parfois aussi si elle est pleine — plus de place disponible)
• on peut récupérer un élément sur la file (”défiler”), et c’est le premier ajouté (principe FIFO : First In First Out
— premier entré premier sorti). Souvent par commodité on peut consulter le premier sans avoir à le défiler.
Comme pour la pile, une file en python peut être créée en prenant une liste et en se limitant aux opérateurs append,
et pop(0).
6
Récursivité
On parle de récursivité et d’appel récursif lorsqu’une fonction s’appelle elle-même, directement ou indirectement. Un
appel récursif lance une nouvelle invocation de la fonction, dont le code va être à nouveau exécuté : on obtient donc
l’équivalent d’une boucle. Si chaque invocation de la fonction effectue l’appel récursif, alors la boucle est infinie et le
programme est erroné. Par conséquent l’appel récursif doit se faire seulement sous condition (par exemple dans un
”if”), afin que la récursion se termine. La condition porte typiquement sur un ou plusieurs paramètres de la fonction,
qui jouent un rôle similaire à celui des variables contrôlant une itération classique.
Compraison récursion/itération :
def itere ( N ):
i = 1
while i <= N :
print ( i )
i += 1
def recurse ( N ):
i = 1
auxRecurse (i , N )
def auxRecurse (i , N ):
if i > N :
return
# nota : ici nous sommes dans le " else " sans avoir à le dire ...
print ( i )
auxRecurse ( i +1 , N )
# test
itere (10)
recurse (10)
Exemple de récursion indirecte :
def rec1 ( n ):
print ( n )
if n <= 0:
return
else :
rec2 (n -1)
def rec2 ( n ):
if n <= 0:
return
else :
rec1 (n -1)
# affiche un entier sur 2 de mani è re compliqu é e
rec1 (10)
Dérécursivation Lorsque l’appel récursif est placé en fin de la fonction récursive — on parle d’appel terminal —,
on transforme très facilement la fonction en boucle. Si l’appel n’est pas terminal, il faut utiliser une pile.
Exemple : afficher récursivement tous les codes possibles d’un cadenas à N roulettes portant les lettres de A à E.
def exoREF9aux ( chaine , nb ):
lettres = ’ ABCDE ’
if nb <= 0:
print ( chaine )
else :
for i in range ( len ( lettres )):
exoREF9aux ( chaine + lettres [ i ] , nb - 1)
def exoREF9 ():
print ( ’ entrez le nombre de roulettes : ’)
nb = int ( input ())
exoREF9aux ( ’ ’ , nb )
Attention, dans l’exemple ci-dessous l’appel récursif semble terminal mais ne l’est pas : il est dans une boucle donc
d’autre instructions (ici d’autres appels récursifs) seront exécutés après lui. On ne peut pas traduire directement par
une boucle, il faut ajouter une pile :
def exoREF9aux ( chaine , nb ):
lettres = ’ ABCDE ’
if nb <= 0:
print ( chaine )
else :
for i in range ( len ( lettres )):
exoREF9aux ( chaine + lettres [ i ] , nb - 1)
def exoREF9 ():
print ( ’ entrez le nombre de roulettes : ’)
nb = int ( input ())
exoREF9aux ( ’ ’ , nb )
Version dérecursivée:
def ex oR EF 9d er ecu rs iv e ():
print ( ’ entrez le nombre de roulettes : ’)
nb = int ( input ())
pile = []
pile . append ( ’A ’)
backtrack = False
while len ( pile ) > 0:
if not backtrack :
if len ( pile ) == nb :
print ( pile )
backtrack = True
else :
pile . append ( ’A ’)
backtrack = False
else :
# passer à la lettre suivante sur le sommet de pile
if pile [ -1] < ’E ’:
pile [ -1] = chr ( ord ( pile [ -1]) + 1)
backtrack = False
else :
# retour arri è re
pile . pop ()
7
Classes/structures/enregistrements
La notion ”d’enregistrement” consiste à regrouper des données de types potentiellement différents dans un même
”objet” (contrairement aux tableaux qui historiquement ne pouvaient comporter que des éléments d’un même type).
On accède à ces données par un nom de champ au lieu de l’indice des tableaux. En Python on crée un enregistrement
par le mot clé class et on définit les champs dans une fonction d’entête init (self ). De même il est possible de
définir une manière par défaut d’afficher l’enregistrement en définition la fonction de nom réservé str (self ).
class Etudiant :
def __init__ ( self ):
self . nom = ’’
self . prenom = ’’
self . moyenne = 0.0
def __str__ ( self ):
return self . nom + ’ ’+ self . prenom + ’ ’+ str ( self . moyenne )
def exoPromo ():
promo = []
for i in range (1):
etud = Etudiant ()
etud . nom = str ( input ( ’ nom é tudiant : ’))
etud . prenom = str ( input ( ’ prenom é tudiant : ’))
etud . moyenne = float ( input ( ’ moy . é tudiant : ’))
promo . append ( etud )
print ( promo [0]) # affiche le premier
moy_gen = 0.0
for i in range ( len ( promo )):
moy_gen += promo [ i ]. moyenne
moy_gen /= len ( promo )
print ( ’ moyenne generale = ’, moy_gen )
8
Listes à partir d’enregistrements
class ListeEtud :
def __init__ ( self ):
self . nom = ’’
self . prenom = ’’
self . moyenne = 0.0
self . suivant = None
def __str__ ( self ):
return self . nom + ’ ’+ self . prenom + ’ ’+ str ( self . moyenne )
def exoPromo2 ():
promo = None
for i in range (3):
etud = ListeEtud ()
etud . nom = str ( input ( ’ nom é tudiant : ’))
etud . prenom = str ( input ( ’ prenom é tudiant : ’))
etud . moyenne = float ( input ( ’ moy . é tudiant : ’))
etud . suivant = promo
promo = etud
tmp = promo # attention à ne pas modifier promo
while tmp != None :
print ( tmp ) # affiche le courant
tmp = tmp . suivant
Téléchargement