Cours d`informatique du 17/11/2009

publicité
Cours d’informatique du 17/11/2009
A. Rappels sur les boucles
1) Rappel sur les invariants de boucle :
L’invariant de boucle est un outil pour concevoir cette boucle.
Il peut exister plusieurs invariants de boucle pour un même problème. Tout dépend de la
manière dont on résout le problème.
Cet invariant fixe la signification des variables du problème.
Reprenons le problème du tri étudié au cours du 10/11.
0
Zone non triée
n
Zone triée
N-1
Dans ce cas, on dit qu’à partir de n, le tableau est trié. Autrement dit, n marque le début de la
section triée.
Pour trier complètement le tableau, il faut tout d’abord prendre n = N. Ensuite il faut diminuer
n en ne violant pas l’invariant de la boucle.
Ici, l’invariant de boucle est :
P : ∀{j | n ≤ j < N} : ∀{i | 0 ≤ i ≤ j} : B[i] ≤ B[j]
A chaque itération, il faut restituer l’invariant de boucle P.
Reprenons l’exemple du programme « Bubble sort »
import numpy as np
def bubble(B, upto):
iMaxChanged = 0
for i in range(1, upto):
if B[i-1] > B[i]:
# la condition de tri n’est pas respectée
# pour B[i-1] et B[i]
B[i], B[i-1] = B[i-1], B[i]
# la condition du tri est restaurée
iMaxChanged = i
# Le tableau au dela de i est trié
return iMaxChanged
N = 10
B = np.random.randint(0, 10, N)
print B
n=N
while n > 0:
n = bubble(B, n)
print B
S0
On remarque que le rôle de la séquence d’instructions S 0 est de rétablir cet invariant de
boucle. Avec ce code, on vérifié l’invariant à chaque itération.
! Chaque boucle a son invariant de boucle, même pour les boucles imbriquées !
2) Fin d’une boucle :
Lorsque l’on crée une boucle, il faut vérifier qu’elle se termine réellement à un moment.
Pour vérifier cela, il faut vérifier que chaque instruction élémentaire et chaque boucle se
termine réellement.
Lorsqu’on utilise l’itérateur « for … in … », il n’y a aucun problème. A chaque itération,
l’indice augmente. Tant que le problème qu’il faut traiter contient un nombre fini d’éléments,
la boucle se termine toujours. En Python, il est donc plus sûr et plus simple d’utiliser un
itérateur tel que « for … in … » plutôt que l’instruction « while ». Il est en effet plus clair de
voir que la boucle s’arrêtera avec un itérateur.
Reprenons comme exemple le problème du « bubble sort » écrit plus haut :
Il y a deux boucles dans cet algorithme.
•
La première boucle avec l’itérateur « for … in … » se termine forcément puisque
l’indice i augmente d’une unité à chaque itération. L’indice « upto » étant fini, l’indice
i fini par atteindre la valeur « upto – 1 ». La boucle se termine alors.
•
Il est plus difficile de prouver que la deuxième boucle se termine. Pour le prouver, il
suffit de montrer que « iMaxChanged » est toujours plus petit que « upto ». Si le
tableau est trié, iMaxChanged = 0, et donc n = 0, ce qui implique que la boucle
s’arrête. Tant que le tableau n’est pas totalement trié, on a 1 ≤ i < upto, et donc toute
nouvelle valeur de i est inférieure à n-1. La valeur de n diminue donc à chaque
itération et fini par être nulle, ce qui arrête la boucle puisque le gardien est falsifié.
! Quand on travaille avec une boucle, il est toujours plus simple de d’abord
réfléchir à partir d’un dessin !
B. Problème des chaînes avec parenthèses
Commençons à étudier ce problème par quelques exemples :
•
•
•
a(b[{c}d])[e] → est correct
a(b[{c}d)[e] → est faux puisque le premier crochet n’est pas fermé
a(b[{c}d)[e]] → est faux. Cette fois, toutes les parenthèses ouvertes sont fermées mais
elles ne le sont pas dans le bon ordre.
Seul le premier exemple a une forme correcte.
Quel pourrait être l’algorithme qui vérifierait si l’ordre d’ouverture et de fermeture est
correct ?
Une première approche est de dire qu’on doit avoir des blocs imbriqués et enchaînés pour que
l’expression soit correcte. Cela peut se représenter avec un dessin.
Voici une idée d’algorithme :
 On pourrait tout d’abord créer une liste. Ensuite, en parcourant la chaîne de caractère,
chaque fois que l’on rencontre une parenthèse ouverte, on l’ajoute à la liste. Et lorsque
l’on trouve une parenthèse fermée, on la compare au dernier élément de la liste. S’il
s’agit de l’élément correspondant ouvert ( ‘(‘ si on a ‘)’, ‘{‘ si on a ‘}’ et ‘[‘ si on a
‘]’), on supprime cet élément de la liste. S’il s’agit d’un autre élément, il y a une erreur
dans l’ordre d’ouverture et de fermeture.
Reprenons le premier exemple correct. Voici, étape par étape, l’évolution de la liste :
1:(
5:(
2:([
6:/
3:([{
7:[
4:([
8:/
On observe donc qu’on retrouve une liste vide après avoir parcouru tout le tableau.
Essayons avec le deuxième exemple :
1:(
2:([
3:([{
4:([
5 : Il y a un problème : on trouve maintenant une parenthèse fermée. Or le crochet précédent
n’a pas été fermé.
 Attention, on pourrait avoir une autre idée : à chaque fois que l’on rencontre une
parenthèse ouvert, on incrémente un compteur d’une unité. Si on a une parenthèse
fermée, on le décrémenterait. Cet algorithme déterminerait s’il y a autant de
parenthèses ouvertes que fermées, mais ne saurait pas dire qi les parenthèses ont été
fermées dans le bon ordre.
Revenons donc à la première idée.
Cet algorithme peut s’apparenter à une pile de livre. Il est facile de prendre ou d’ajouter un
livre sur le haut de cette pile. Il est par contre plus difficile d’en insérer au milieu de cette pile.
De même dans ce programme, on ne travaille qu’à la fin de la liste.
Cette structure de donnée s’appelle donc structure de pile.
Cette structure a trois opérations :
- push(elem) → ajoute un élément à la liste
- elem = pop() → regarde, supprime le dernier élément de la liste et rend celui qu’on regarde
- elem = top() → consulte le dernier élément de la liste sans le supprimer
Avec ces trois opérations, nous pouvons écrire en deux parties l’algorithme qui déterminera si
la chaîne est correctement parenthèsée.
stack.py
# Stack
def newStack():
return []
def push(stack, elem):
stack.append(elem) # ajoute l’élément à la liste
def pop(stack): # supprime le dernier élément
return stack.pop() # pop existe déjà dans Python et supprime le dernier élément de la liste
def top(stack): # doit regarder sans supprimer
return stack[-1] # regarde le dernier élément
parentheses.py
import sys
import stack
# faut un dictionnaire des caractères
parens = {'(':')', '[':']', '{':'}'}
# crée une pile
leftParens = stack.newStack() # appelle la fonction newStack qu'on a importé
string = sys.argv[1]
for char in string:
if char in parens: # si c'est une parenthèse gauche
stack.push(leftParens, char)
elif char in parens.values(): # si c'est une parenthèse droite
# Faut vérifier si pile n'est pas vide
if len(leftParens) == 0:
print "no left parenthesis corresponding to", char
break
elif parens[stack.top(leftParens)] == char: # compare au correspondant grace à dico
stack.pop(leftParens)
else:
print stack.pop(leftParens), "does not match", char
break
# si ce n'est ni parenthèse gauche ni droite, ne fait rien
else: # s'exécute si on ne termine pas de boucle prématurément par break p. ex. . Ce else
permet donc d’éviter deux messages d’erreur.
if len(leftParens): # donne une valeur booléenne, mais on peut écrire if len(leftParens) > 0:
print "No right parenthese for : ", leftParens
En exécutant le code parentheses.py, on sait si la chaîne est correctement parenthésée ou non.
Téléchargement