Développement informatique - enssat.fr · spécification + programmation ... 1.2 Calcul des...

146

Transcript of Développement informatique - enssat.fr · spécification + programmation ... 1.2 Calcul des...

Développement informatique =

spécification + programmation

Une démarche méthodologiquepour concevoir des algorithmes

Marc Guyomard

Enssat

Lannion

Stage du groupe Liesse12-13 mars 2014

Avant donc que d’écrire, apprenez à penser...

Boileau, L’art poétique

Peinture de la couverture :« Montrer ce qui montre »

Inspiré d’un détail de« La création d’Adam » de Michel-Ange

Sirina Le Minous, 9 ans

Ce document est placé sous la licence Creative Commons symbolisée par le logo . Cette

licence permet à quiconque de remixer, arranger, et adapter le document à des fins non commercialestant que l’auteur est crédité en citant son nom et que les nouvelles œuvres sont diffusées selon lesmêmes conditions. Voir le site Web : http://creativecommons.org/licenses/

Table des matières

1 Introduction 1

1.1 Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

1.1.1 Aspect statique d’un système discret . . . . . . . . . . . . . . . . . . . . . . . . 1

1.1.2 Aspect dynamique d’un système discret . . . . . . . . . . . . . . . . . . . . . . 4

1.1.3 Opérations dans un système discret . . . . . . . . . . . . . . . . . . . . . . . . 5

1.2 Calcul des prédicats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

1.2.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

1.2.2 Substitution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

1.2.3 États, prédicats et satisfaction . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

1.2.4 Opérateurs booléens et expressions booléennes . . . . . . . . . . . . . . . . . . 8

1.2.5 Quantificateurs logiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

Quantification universelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

Quantification existentielle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

1.2.6 Quantificateurs numériques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

1.2.7 Implication et renforcement de prédicats . . . . . . . . . . . . . . . . . . . . . . 11

1.2.8 Du français aux prédicats, des prédicats au français . . . . . . . . . . . . . . . 12

1.3 Spécification des opérations – le triplet de Hoare . . . . . . . . . . . . . . . . . . . . . 14

1.3.1 Notion de précondition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

1.3.2 Notion de postcondition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

1.3.3 Le triplet de Hoare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

1.4 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

2 Enfonçons le clou 19

2.1 Exemple introductif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

2.2 Justification théorique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

3 Renforcement de la postcondition 27

3.1 Les deux maisons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

3.2 L’échafaudage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

3.3 Retour sur le problème de la planche et du clou . . . . . . . . . . . . . . . . . . . . . . 29

3.4 Renforcement ultime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

i

4 Heuristiques pour la découverte d’invariants 314.1 Méthode de l’éclatement de la postcondition . . . . . . . . . . . . . . . . . . . . . . . . 31

Exemple 2 : le premier zéro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324.2 Renforcement de la postcondition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

Exemple 3 : la somme des éléments d’un tableau . . . . . . . . . . . . . . . . . 35Exemple 4 : alternance pair/impair . . . . . . . . . . . . . . . . . . . . . . . . . 38Exemple 5 : le drapeau hollandais . . . . . . . . . . . . . . . . . . . . . . . . . 43

4.3 Diviser en sous-problèmes : la composition séquentielle . . . . . . . . . . . . . . . . . . 484.4 Méthode de l’hypothèse du « travail réalisé en partie » . . . . . . . . . . . . . . . . . . 49

Exemple 6 : est-ce le plus petit ? . . . . . . . . . . . . . . . . . . . . . . . . . . 494.5 Heuristiques fondées sur le renforcement de l’invariant . . . . . . . . . . . . . . . . . . 54

Exemple 7 : calcul de la valeur d’un polynôme en un point . . . . . . . . . . . 54Exemple 8 : la plus longue succession de zéros . . . . . . . . . . . . . . . . . . 58Exemple 9 : somme avant le maximum . . . . . . . . . . . . . . . . . . . . . . . 62

4.6 Renforcement exigé par le choix de la condition d’arrêt . . . . . . . . . . . . . . . . . . 66Exemple 10 : le Me zéro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66

5 Exercices de synthèse 735.1 Recherche linéaire bornée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73

Exemple 11 : recherche linéaire bornée avec résultat booléen . . . . . . . . . . 73Exemple 12 : recherche linéaire bornée avec arrêt au plus tôt . . . . . . . . . . 77Exemple 13 : recherche linéaire bornée avec arrêt forcé . . . . . . . . . . . . . . 80

5.2 Recherche dichotomique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83Exemple 14 : recherche dichotomique itérative . . . . . . . . . . . . . . . . . . 83

5.3 Un tri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86Exemple 15 : le tri par sélection simple . . . . . . . . . . . . . . . . . . . . . . 86

5.4 Le plus grand carré sous un histogramme . . . . . . . . . . . . . . . . . . . . . . . . . 93Exemple 17 : le plus grand carré, une version en Θ(N) . . . . . . . . . . . . . . 93

6 Conclusion 115

A Vade-mecum de la construction de boucles 117

B Le comportement des opérateurs booléens 119

C L’axiomatique de Hoare : ce qu’il faut retenir 123C.1 L’axiomatique de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123C.2 Le cas des expressions non définies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125

D Les erreurs à ne pas commettre 127

iii

Préambule

Que faudrait-il penser d’un « mathématicien » qui chercherait à résoudre un problème d’intégrationen examinant tour à tour la validité des solutions qui lui viennent à l’esprit, plutôt que de s’appuyer,le bon sens aidant, sur des techniques éprouvées (changement de variable, intégration par partie,etc.). Il est pourtant une discipline – la programmation – où un tel empirisme a, par ignorance, parinertie ou par méprise, souvent valeur de principe. Cependant, là aussi les techniques fondamentalesexistent : c’est la conception par raffinements successifs, la modification contrôlée de la spécification,la construction de boucles par invariants, etc. Invariant ? Le mot est souvent associé dans l’esprit debeaucoup, à « démonstration de programmes » avec tout ce que cela peut suggérer de difficile, voired’inutile.

Pourtant, bien comprise, la notion d’invariant se révèle un outil irremplaçable pour la constructionrationnelle de boucles, même – et surtout – lorsque l’on aborde pour la première fois le monde de laprogrammation. C’est ce que nous nous proposons de montrer dans ce document.

Largement issue des travaux de R. Floyd, de C.A.R. Hoare (cf. [19]), et de la discipline de pro-grammation développée par E.W. Dijsktra à Eindhoven [2, 5], notre présentation s’inspire pourtantplus des propositions de J. Arsac [9, 10, 11], et de O. Arsac-Mondou [12, 13] dans la mesure où laformalisation à tout prix n’est pas notre principal objectif.

La clé de voûte de la construction d’un programme est sa spécification 1 (comment pourrait-ilen être autrement ?). Différents formalismes de spécification existent, ils ont en commun l’usage plusou moins explicite de la logique du premier ordre. Notre choix s’est porté sur la spécification ditepré/post (précondition et postcondition). Comme indiqué ci-dessus, nous nous sommes focalisés surla construction de boucles. Il s’agit d’une activité difficile à appréhender d’un seul coup, trop denotions s’entrecroisent, aboutissant à une charge mentale excessive. La démarche préconisée tout aulong de ce document vise au contraire à éclater le travail de construction de boucle en plusieurs sous-parties, plus faciles à aborder et dont l’assemblage produit un programme documenté, souvent efficace(rapide à l’exécution) et correct par construction.

Dans le premier chapitre, les principales notions sont introduites. Le deuxième chapitre présenteun premier exemple de construction de boucle en introduisant ses cinq étapes fondamentales. Le troi-sième chapitre introduit le concept de renforcement de postcondition, dont la maîtrise est souventindispensable à la construction de boucles. Le quatrième chapitre, au cœur de ce document, déve-loppe plusieurs heuristiques 2 qu’il est possible d’appliquer pour faciliter la découverte d’invariants.Le cinquième chapitre est consacré à plusieurs exercices de synthèse. La conclusion forme le sixièmechapitre. Pour chaque exercice, le développement s’achève par la mention de sa complexité tempo-relle, qui permet de comparer l’efficacité des différentes solutions. Afin de préparer le lecteur à desapproches plus rigoureuses, la formalisation des notions manipulées (précondition et postcondition enparticulier) s’accroit au fil des exemples.

À propos de Python. Les algorithmes sont codés en utilisant le langage Python. Le lecteurpourra facilement transposer les schémas de codage à la plupart des autres langages algorithmiques.Cependant l’apprentissage de ce langage n’est pas notre objectif. Le sous-ensemble que nous utilisonsest constitué d’une infime partie qui se limite :

1. Spécifier un programme c’est dire ce que l’on attend de lui, c’est le quoi ; programmer, c’est s’intéresser au

comment. On retrouve les mêmes notions dans le langage usuel lorsque l’on parle de « définir un objectif », par opposition

à « définir les modalités de sa réalisation ».2. Une heuristique est une technique destinée à améliorer l’efficacité d’un processus de recherche mais qui ne garantit

pas son succès.

iv

– aux types réel, entier naturel et relatif et booléen. Les opérateurs usuels sont supposés connus(cf. cependant l’annexe B, page 119, sur la sémantique des opérateurs booléens binaires),

– aux variables scalaires,– aux listes considérées comme des tableaux de taille fixe,– aux affectations (notées = en Python), incluant les affectations multiples (a, b = exp1, exp2),– à la séquentialité (qui, en Python, n’est pas matérialisée par un opérateur),– aux alternatives (simples, doubles, multiples),– à la boucle « while » simple (sans l’option else).Ce n’est pas sans hésitation que nous avons adopté uniformément le symbole == pour représenter

l’égalité 3, trahissant ainsi une (bonne) tradition plusieurs fois centenaire 4. Il est dommage que cer-tains concepteurs de langages de programmation (ceux de la lignée de C, C++, Java, etc.) s’évertuent,à travers ce type de choix, à entretenir la séparation entre deux univers (celui des mathématiques etcelui de l’informatique) qui sont pourtant fait pour s’unir, comme le montre ce document ainsi qued’autres ouvrages plus prestigieux comme [7, 8], et ceux cités dans la rubrique « Les incontournables »de la bibliographie (cf. page 133). Ni les mathématiques, ni surtout l’informatique, n’ont à gagner àcet entêtement.

Prérequis.– Les notions Python citées ci-dessus.– Les formes de raisonnement par récurrence/induction.– Une première expérience d’environ 10 heures en programmation.

Notations et conventions.

1. Comme mentionné ci-dessus, dans la suite, nous n’utilisons que des tableaux de taille fixe.Ceux-ci sont représentés par des listes Python.

2. Soit t un tableau Python de N éléments. Par exemple :

t == [5, 1, 8, 12, 7, 8],

avec N == 6. Puisque la borne inférieure d’un tableau Python est toujours 0, ce tableau repré-sente l’ensemble des couples {(0, 5), (1, 1), (2, 8), (3, 12), (4, 7), (5, 8)}. En Python, la notationt[i :s] représente l’ensemble des couples {(0, t[i ]), (1, t[i+1]), ... , (s− i−1, t[s−1])}. Remarquonsque l’indice inférieur est toujours 0 et que la valeur t[s] ne fait pas partie du sous-tableau. Ainsi,pour l’exemple ci-dessus, t[2 : 5] représente l’ensemble de couples {(0, 8), (1, 12), (2, 7)}. Dansla suite du document, l’utilité de cette notation est marginale. Nous sommes plutôt intéressépar l’ensemble de couples {(i , t[i ]), (i + 1, t[i + 1]), ... , (s, t[s])}. À cette fin nous convenons dereprésenter ce sous-tableau par la notation t[i .. s].

3. Par convention, les identificateurs débutant par une majuscule sont considérés comme desconstantes, tandis que les identificateurs débutant par une minuscule sont des variables.

4. Les structures de contrôle (boucles et alternatives) n’étant pas « parenthèsées » en Python (pasde « end while » ni de « end if ») nous avons décidé de noter ces mots-clés sous la forme decommentaires Python.

3. Conformément à la notation Python, qui, de surcroit, réserve le symbole = à une opération, l’affectation, alors

que celle-ci n’entretient qu’un rapport très éloigné avec l’égalité.4. Le signe d’égalité = a été proposé en 1557 par le mathématicien et physicien Gallois Robert Recorde.

v Préambule

5. Les accolades normales { et } sont utilisées pour représenter un ensemble en extension tandisque les accolades grasses { et } sont utilisées dans le contexte des triplets de Hoare (cf. section1.3, page 14 et annexe C, page 123). Cependant, afin de préserver autant que faire se peut lasyntaxe Python, la notation { } est abandonnée dans les programmes annotés, au profit d’unenotation mentionnant explicitement la précondition (PRE :) et la postcondition (POST :).

6. De même, les crochets normaux [ et ] sont utilisés pour indicer des tableaux tandis que lescrochets gras [ et ] sont utilisés pour des expressions de substitutions (cf. section 1.2.2, page 7 etannexe C, page 123).

7. Lorsque l’usage de type « court-circuit » de l’un des opérateurs and ou or est obligatoire, nousnotons cet opérateur and ou or. Voir l’annexe B, page 119, pour de plus amples détails.

Remerciements. Je remercie Patrick Bosc pour la relecture attentive de ce document, pour sonsoutien et pour la rédaction de certains exercices, Pierre Alain, Daniel Rocacher et Patrice Struilloupour leurs remarques et conseils formulés lors de la rédaction de ce document, Laurent Trilling pouravoir attiré mon attention sur le potentiel des méthodes et techniques abordées dans cet ouvrage, ainsique Abder Aggoun et Marc Zonzon pour des discussions fructueuses sur les thèmes abordés dans cetouvrage.

Préambule vi

Chapitre 1Introduction

1.1 Applications

À l’instar des scientifiques de nombreuses autres disciplines (physique, économie, biologie, etc.),l’informaticien conçoit des modèles. Ceux-ci sont mis en œuvre et étudiés à travers des systèmes

par un ensemble de variables d’état (appelées en informatique variables de programmation ou plussimplement variables). C’est l’aspect statique du modèle. Son évolution dans le temps s’opère parun changement de la valeur des variables. C’est son aspect dynamique.

1.1.1 Aspect statique d’un système discret

Prenons comme exemple un modèle de calendrier 1. Ce modèle peut se représenter par les deuxvariables « jour » (notée j) et « mois » (notée m). À un instant donné (c’est l’aspect statique dumodèle), il se trouve dans un état : chacune des variables j et m est associée à une valeur biendéterminée. Ainsi, la date du 12 mars est représentée par l’état signalé par un • à la figure ci-dessous.

j

m

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

1

2

3

4

5

6

7

8

9

10

11

12

Figure 1.1 – Le modèle « calendrier » et l’état « 12 mars »

1. Pour simplifier, nous faisons abstraction des années bissextiles.

1

CHAPITRE 1. INTRODUCTION 2

Chaque variable prend ses valeurs sur un domaine (ou type) discret (comme les naturels, lesbooléens, les flottants 2, les relatifs, etc.). Pour l’exemple du calendrier, la variable j prend ses valeurssur l’intervalle d’entiers 1..31, tandis que la variable m prend ses valeurs sur l’intervalle d’entiers 1..12(en admettant que 1 code le mois de janvier, 2 code le mois de février, etc.). Le produit cartésien desdomaines des variables est appelé espace d’états du système.

Pour l’exemple du calendrier, l’espace d’états se représente dans le plan par le schéma de la figureci-dessous.

j

m

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

1

2

3

4

5

6

7

8

9

10

11

12

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

Figure 1.2 – L’espace d’états dans l’exemple du calendrier

En outre, les variables entretiennent (en général) une relation qui contraint les valeurs qu’ellespeuvent prendre, relation qui doit être instaurée initialement et satisfaite par chacun des états quepeut prendre le système. Le tout (l’espace d’états et la relation qui lie les différentes variables) constituel’invariant du système.

L’invariant peut se verbaliser en français. Pour l’exemple du calendrier on peut dire que :

1. les jours sont compris entre 1 et 31 et les mois entre 1 et 12,

2. s’il s’agit du mois de février, le jour ne peut être supérieur à 28,

3. si le mois précède le mois d’août, et si son code est pair mais différent de 2, le jour ne peutdépasser la valeur 30.

4. si le mois est situé après le mois d’août, et si son code est impair, le jour ne peut dépasser lavaleur 30.

Graphiquement l’invariant se présente comme le montre la figure 1.3, page 3.Nous pouvons également utiliser le langage de la logique des prédicats 3 (en général étendue à

l’égalité, à l’arithmétique et à la théorie des ensembles) pour exprimer l’invariant. Un prédicat estun énoncé exprimé dans ce langage. Pour le cas du calendrier nous pouvons reformuler l’invariantexprimé en français ci-dessus par le prédicat suivant 4 :

2. Les flottants constituent un sous-ensemble fini des rationnels centré autour de 0. Ils sont destinés à approximer

les réels des mathématiques.3. La logique des prédicats et ses extensions jouent un rôle particulièrement important dans la construction de

programmes.4. En Python, le symbole % dénote l’opérateur « reste de la division entière ».

3 1.1. APPLICATIONS

j

m

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

1

2

3

4

5

6

7

8

9

10

11

12

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••••••••••••

••

••

••

Figure 1.3 – L’invariant du modèle « calendrier

Invariant

(j ∈ 1 .. 31) and (m ∈ 1 .. 12)

and(m == 2 ⇒ j ∈ 1 .. 28)

and(m < 8 and m != 2 and m % 2 == 0 ⇒ j ∈ 1 .. 30)

and(m > 8 and m % 2 == 1 ⇒ j ∈ 1 .. 30).

Chaque représentation (graphique, français ou logique des prédicats) présente des avantages et desinconvénients. La représentation graphique est agréable par son aspect visuel mais elle est difficile àétendre dès lors que le domaine des variables est quelconque ou que la dimension de l’espace dépasse 2.Pour ces raisons, elle sera abandonnée à partir du second chapitre. Le français est partagé par unegrande communauté de langue, il est concis et très souple d’emploi mais un énoncé peut être ambigu.À l’inverse, la logique évite toute ambiguïté et permet de réaliser des calculs, des déductions, dedémontrer des équivalences, etc. Par contre, pour un problème un tant soit peu complexe, les énoncésde logique stricte sont verbeux et parfois peu parlant. La théorie des ensembles est alors une alternativeavantageuse 5. Pour toutes ces raisons, la préférence des professionnels penche vers la théorie desensembles.

Représentation des états. Plutôt que d’utiliser un repère cartésien comme ci-dessus, on préfère,pour des raisons de lisibilité, définir un état par un ensemble de couples. Si {x1, ... , xn} est l’ensembledes variables du système, un état particulier est l’ensemble des couples qui associent à chaque xi unevaleur ei du domaine de xi , soit :

{(x1, e1), ... , (xn, en)}.

Ainsi la date du 12 mars se note {(j , 12), (m, 3)}, ou encore {(m, 3), (j , 12)}.

5. L’utilisation du symbole ∈ dans la première ligne nous place déjà dans la théorie des ensembles, la nécessaire

introduction des variables de type tableau (cf. ci-dessous) rend cette décision incontournable.

CHAPITRE 1. INTRODUCTION 4

Variables non scalaires : les tableaux. Jusqu’à présent nous n’avons fait usage que de variablesscalaires. Cette limitation peut être levée par l’utilisation de tableaux. Un tableau t est une fonctiondéfinie sur un intervalle fini d’entiers (intervalle qui débute toujours en 0 en Python) et à valeurssur un ensemble quelconque 6. Un tableau t tel que t == [3, 5, 2, 1] est défini sur l’intervalle 0 .. 3 età valeurs dans N. Sa représentation graphique est :

dom(t)

codom(t)

1 2 3 4 5 6

1

2

3

4

5

6

Si l’on adopte la notation ensembliste, le tableau t ci-dessus peut être noté {(0, 3), (1, 5), (2, 2), (3, 1)}

(ceci n’est pas du Python). La notion de tableau s’étend facilement au cas de plusieurs dimensions.

1.1.2 Aspect dynamique d’un système discret

Un système discret informatique est destiné à évoluer. Il transite d’un état donné à une autre état.La seule façon pour un tel système de changer d’état est d’exécuter une affectation 7. La syntaxePython pour une telle opération est :

x1, x2, ... , xp = e1, e2, ... , ep

où p >= 1, où les xi sont des variables toutes différentes et où les ei sont des expressions du typerespectif des variables. La sémantique (c’est-à-dire le comportement) d’une telle opération est lasuivante : les p expressions ei sont évaluées, puis le résultat de chaque évaluation est placé dans lavariable correspondante (une description plus rigoureuse de cette sémantique est présentée à l’annexeC, page 123). L’affectation x = e se lit « x reçoit (ou devient) e ».

Dans l’exemple du calendrier, des affectations telles que j = j + 1 ou j ,m = 1,m + 1 peuventreprésenter un changement de jour.

Afin de préserver l’invariant, de tels changement d’état ne peuvent s’effectuer que si certainesconditions sont satisfaites. Ainsi, pour le cas du calendrier, si m == 1 et j == 31, il est impossiblede déclencher l’affectation j = j + 1 sans violer l’invariant. Ce type de condition s’exprime égalementà travers un prédicat.

Le cas des affectations de tableaux. Cette partie peut être sautée en première lecture. Le casde la modification du contenu d’une cellule d’un tableau mérite une attention particulière. Soit t

un tableau de naturels défini sur l’intervalle 0 .. N − 1 (N >= 1). Modifier la cellule désignée par i

(i ∈ 0 .. N − 1) s’écrit en Python :

t [ i ] = 5

6. Un tableau est une fonction particulière, une fonction est une relation, une relation est un ensemble de couples.

Ceci confère à la notion de tableau le statut d’entité ensembliste.7. La lecture d’une variable depuis un fichier ou depuis un clavier est une forme d’affectation.

5 1.1. APPLICATIONS

Pourtant t[i ] n’est pas une variable. La variable c’est t, et, par définition, les affectations ne modifientque des variables. On doit considérer que la notation ci-dessus n’est qu’une abréviation pour (cecin’est pas du Python) :

t = {(0, t[0]), ... , (i , 5), ... , (N − 1, t[N − 1])}.

Le compilateur Python s’arrange pour rendre cette affectation aussi efficace que possible à l’exécution.Qu’en est-il du cas où t apparait plusieurs fois à la gauche du signe d’affectation comme dans l’échangede deux éléments du tableaux :

t [ i ] , t [ j ] = t [ j ] , t [ i ]

Si i ∈ 0 .. N − 1 et j ∈ i + 1 .. N − 1, cette notation est un abréviation pour :

t = {(0, t[ 0]), ... , (i , t[j ]), ... , (j , t[i ]), ... , (N − 1, t[N − 1])}.

Le cas où j <= i est analogue.

1.1.3 Opérations dans un système discret

Les affectations et le contrôle de leur enchaînement sont regroupés dans une opération. Lecouple (partie statique, opération) constitue ce qui ci-dessous est dénommé application. Il seraitpossible d’imaginer que l’opération d’une application soit décrite par un ensemble de couples (condi-tion,affectation). L’histoire et les habitudes en ont décidé autrement : la dynamique des opérationss’est organisée autour de ce qu’il est convenu d’appeler des « structures de contrôle ». Il s’agit (prin-cipalement) des boucles (ou itératives) et des alternatives, que connaissent toutes les personnes quise sont essayées à la programmation. Dans ce document, pour ce qui concerne les itératives, seules lesboucles while sont prises en compte.

Si, au sein de l’application « calendrier », on souhaite définir l’opération tousLesJours, qui passesuccessivement par tous les jours de l’année depuis le 1er janvier jusqu’au 31 décembre, on peut rédigerle programme suivant 8 :

1 j ,m = 1 ,1

2 while not ( j == 31 and m == 12) :

3 i f (m == 2 and j == 28) or \

4 (m <= 7 and m % 2 == 0 and j == 30) or \

5 (m > 7 and m % 2 == 1 and j == 30) or \

6 ( j == 31) :

7 j ,m = 1 ,m +1

8 e l s e :

9 j = j+1

10 #end i f

11 #end whi le

Le schéma ci-dessous montre les 5 états qui sont atteints et les 4 transitions qui sont franchies lorsquel’on passe 28 juin au 2 juillet.

8. En Python, le symbole \ représente un passage à la ligne.

CHAPITRE 1. INTRODUCTION 6

j

m

1 2 3 4 27 28 29 30 31

5

6

7

8

• • •

• •

e1 e2 e3

e4 e5

1.2 Calcul des prédicats

Cette section n’est pas une introduction à la logique mathématique, elle se veut tout d’abord uneprésentation des notations et propriétés des opérateurs booléens et des quantificateurs qui sont utilisésdans la suite.

1.2.1 Introduction

Dans le cadre d’une application définie sur l’espace d’états E , un prédicat est une fonction boo-léenne définie sur E (et à valeur dans bool).

Dans l’application « calendrier », une expression telle que :

m > 3 and m < 7 and (j == 3 or j > 25)

est un prédicat. Il se traduit, dans la représentation graphique de l’espace d’états, par :

j

m

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

1

2

3

4

5

6

7

8

9

10

11

12

•••

•••

•••

•••

•••

•••

•••

L’ensemble de l’espace d’états (représenté pour l’application « calendrier » à la figure 1.2, page 2)correspond à la constante booléenne True, tandis que la constante False se traduit par un ensemblevide.

7 1.2. CALCUL DES PRÉDICATS

1.2.2 Substitution

Sot x une variable de programmation, e une expression dénotant une valeur du domaine de e etP un prédicat. La notation :

[x = e]P

représente le prédicat P dans lequel toutes les occurrences de la variable de programmation9 x sontremplacées par e dans P . La notation s’étend au cas d’une liste de variables toutes différentes x1, ... , xpassociée à une liste d’expressions e1, ... , ep :

[x1, ... , xp = e1, ... , ep]P .

Exemples.

[m = 3] (m > 3 and m < 6 and j == 3) == (3 > 3 and 3 < 6 and j == 3) (1.1)

[m, j = m+ 1, 1] (m == 3 and j == 31) == (m + 1 == 3 and 1 == 31) (1.2)

[m = m + 1] (m % 2 < 7) == ((m + 1) % 2 < 7) (1.3)

[x , y = y , x] (x == X and y == Y ) == (y == X and x == Y ) (1.4)

Le résultat des substitutions (1.1) et (1.2) se simplifie en False. L’exemple (1.3) montre qu’il peutêtre nécessaire d’encadrer l’expression par des parenthèses afin de respecter la priorité des opérateurs.L’exemple (1.4) montre qu’il y a bien eu échange.

Les exemples suivants exigent des développement plus conséquents.

[m = 4]([j = j + 1] (m > 3 and j == 2)

)

== Définition de la substitution

[m = 4] (m > 3 and j + 1 == 2)

== Définition de la substitution et arithmétique

(4 > 3 and j == 1)

== Calcul propositionnel

j == 1.

L’exemple qui suit peut être sauté en première lecture. Soit t un tableau de naturels défini surl’intervalle 0 .. 1.

[t[0], t[1] = t[1], t[0]] (t == [7, 23])

== Traduction ensembliste 10, cf. section 1.1.2, page 4

[t = {(0, t[1]), (1, t[0])}] (t == [7, 23])

== Définition de la substitution

{(0, t[1]), (1, t[0])} == [7, 23]

== Retour à la notation Python

[t[1], t[0]] == [7, 23]

== Identification

t[1] == 7 and t[0] == 23

== Retour à la notation de tableau

t == [23, 7].

Il y a bien eu échange entre t[0] et t[1].

9. La précision « de programmation » est nécessaire car, comme nous le verrons lors de l’étude des formules quan-

tifiées, il existe d’autres types de variables : les variables liées (à un quantificateur), qui ne sont pas concernées par les

substitutions.10. La nécessité du passage par la notation ensembliste n’apparaitra clairement qu’à travers l’exercice 3, page 17.

CHAPITRE 1. INTRODUCTION 8

1.2.3 États, prédicats et satisfaction

Soit s == {(x1, e1), ... , (xp , ep)} un état dans un espace d’états à p dimensions. Soit P un prédicat.On dit que s satisfait P si :

[x1, ... , xp = e1, ... , ep] P == True

Dans l’application « calendrier », l’état {(m, 4), (j , 4)} satisfait le prédicat P défini par(m == 4 and j ∈ 1 .. 5). En effet :

[m, j = 4, 4] (m == 4 and j ∈ 1 .. 5)

== Définition de la substitution

(4 == 4 and 4 ∈ 1 .. 5)

== Théorie des ensembles, arithmétique et calcul propositionnel

True.

Par contre l’état {(m, 5), (j , 12)} ne satisfait pas P . Le prédicat étant représenté par un ensemblede points E dans l’espace d’états, la satisfaction se traduit graphiquement par le fait que le point quimatérialise l’état appartient à l’ensemble E .

On dit qu’un prédicat est valide s’il est satisfait par tout état. Le prédicat (m <= 4 or m > 3)

est valide. On dit qu’un prédicat est insatisfiable s’il n’est satisfait par aucun état. Le prédicat(m >= 4 and m < 3) est insatisfiable.

1.2.4 Opérateurs booléens et expressions booléennes

Dans la suite, nous supposons le lecteur familiarisé avec les notions et propriétés de opérateursarithmétiques Python (+, −, ∗, /, //, %, etc.), ainsi qu’avec les notions et propriétés des opérateursrelationnels Python : ==, !=, <, <=, >= et >. Les deux premiers (== et !=) permettent de formerdes expressions booléennes à partir d’expressions de types quelconques, les quatre derniers exigentque le type soit doté d’une relation d’ordre. Dans cette section nous nous focalisons sur les opérateursbooléens Python. Ceux-ci permettent de former une expression booléenne à partir, selon l’arité del’opérateur, d’une ou de deux expressions booléennes. Ils sont au nombre de 3 :

Opérateur not and or

Priorité 3 2 1

Arité 1 2 2

Plus la priorité est grande, plus l’opérateur « attire » ses opérandes. Pour des opérateurs de mêmepriorité, l’évaluation de fait de gauche à droite. Il est toujours possible de forcer l’évaluation en utilisantdes parenthèses. Dans la suite du document, un quatrième opérateur booléen, l’implication, notée ⇒

est souvent utilisé dans les calculs. Il ne fait pas partie du langage Python. Nous lui attribuons lapriorité 0. Un prédicat tel que P or Q ⇒ R se parenthèse donc (P or Q) ⇒ R . L’expression booléenneP ⇒Q est une abréviation pour not(P) or Q.

Les opérateurs and et or se définissent par une sémantique de type court-circuit qui se démarquedes opérateurs ∧ et ∨ habituellement utilisés en logique. Cette sémantique, ainsi que la raison de sonexistence sont précisées dans l’annexe B, page 119.

À retenir cependant : le théorème de De Morgan s’applique pour ces deux opérateurs :

not(P and Q) == not(P) or not(Q)

not(P or Q) == not(P) and not(Q).

9 1.2. CALCUL DES PRÉDICATS

1.2.5 Quantificateurs logiques

Les opérateurs logiques étudiés ci-dessus (not, and et or) permettent de construire de nouveauxprédicats à partir de prédicats existants. Il existe d’autres façons d’obtenir ce type de résultat. Ellesconsistent à utiliser les quantificateurs logiques que sont la quantification universelle (∀) et la quanti-fication existentielle (∃). Les formules ainsi construites ne constituent pas des expressions du langagePython mais se révèlent utiles pour construire des programmes.

Quantification universelle

Soit P et Q deux prédicats, et (l) une liste (non vide) d’identificateurs. L’expression booléenne

∀(l) · (P ⇒Q)

est un nouveau prédicat appelé quantification universelle. Dans le cas où la liste (l) se réduit à unseul identificateur i , ∀i · (P ⇒ Q) s’interprète comme « pour tout i tel que P alors Q (est vrai) ».Notons que le symbole ⇒ est obligatoire dans cette formule. En général i est une variable présentedans les prédicats P et Q mais ce n’est pas formellement obligatoire. P et Q sont respectivementappelés domaine et terme, la variable i est appelée variable liée (au quantificateur ∀). Le cas général(celui où il existe une liste de plusieurs variables) est une extension triviale de ce cas particulier.

Exemple. Soit T un tableau Python de N éléments.

∀i · (i ∈ 0 .. N − 1⇒ T [i ] > 0) (1.5)

se lit « pour tout i de l’intervalle 0 ..N− 1, T [i ] est positif », soit encore « tous les éléments de T sontpositifs ».

Le quantificateur ∀ est en quelque sorte une généralisation de l’opérateur and. On pourrait réécrirela formule (1.5) ci-dessus en :

T [0] > 0 and ... and T [N − 1] > 0.

Répertorions quelques propriétés de la quantification universelle :

∀i · (False ⇒ P) == True (1.6)

∀i · (i == j ⇒ P) == [i = j]P (1.7)

∀i · (P and Q ⇒ R) == ∀i · (P ⇒ (Q ⇒ R)) (1.8)

∀i · (P ⇒ Q) and ∀i · (P ⇒ R) == ∀i · (P ⇒ (Q and R)) (1.9)

∀i · (P ⇒ Q) and ∀i · (R ⇒ Q) == ∀i · ((P or R)⇒ Q) (1.10)

∀i · (P ⇒ Q) or ∀j · (R ⇒ S) == ∀(i , j) · ((P and R)⇒ (Q or S)) (1.11)

La première règle ci-dessus s’interprète comme « lorsque la variable prend ses valeurs sur l’en-semble vide, la quantification est vraie, quel que soit le terme P ». Ainsi :

∀i · (i ∈ 0 ..−1 ⇒ T [i ] == 0) == True

∀i · (i ∈ 0 ..−1 ⇒ T [i ] != 0) == True.

CHAPITRE 1. INTRODUCTION 10

Quantification existentielle

Soit P et Q deux prédicats, et (l) une liste (non vide) d’identificateurs. L’expression booléenne

∃(l) · (P and Q)

est un nouveau prédicat appelé quantification universelle. Dans le cas où la liste (l) se réduit à un seulidentificateur i , ∃i · (P and Q) s’interprète comme « il existe (au moins) un i tel que P , pour lequelQ (est vrai) ».

Exemple. Soit T un tableau Python de N éléments. Le prédicat :

∃i · (i ∈ 0 .. N − 1 and T [i ] > 0) (1.12)

se lit « il existe (au moins) un i de l’intervalle 0 .. N − 1 tel que T [i ] est positif ».Le quantificateur ∃ est une généralisation de l’opérateur or. On pourrait réécrire la formule (1.12)

ci-dessus en :T [0] > 0 or ... or T [N − 1] > 0.

Ci-dessous on présente quelques règles qui concernent la quantification existentielle :

∃i · (False and P) == False (1.13)

∃i · (i == j and P) == [i = j]P (1.14)

∃i · (P and Q) or ∃i · (P and R) == ∃i · (P and (Q or R)) (1.15)

∃i · (P and Q) or ∃i · (R and Q) == ∃i · ((P or R) and Q) (1.16)

∃i · (P and Q) and ∃j · (R and S) == ∃(i , j) · ((P and R) and (Q and S)) (1.17)

Les règles de De Morgan se généralisent aux quantificateurs logiques de la manière suivante :

not(∃i · (P and Q)) == ∀i · (P ⇒ not Q)

not(∀i · (P ⇒ Q)) == ∃i · (P and not Q).

1.2.6 Quantificateurs numériques

De même que les quantificateurs ∀ et ∃ généralisent les opérateurs and et or, de même certainsopérateurs arithmétiques se généralisent par des quantificateurs. Ces derniers ne constituent pas desprédicats mais peuvent apparaitre, en tant qu’expressions arithmétiques dans des formules prédica-tives. Ils ne font pas partie du langage Python. La table ci-dessous présente ces quantificateurs ainsiqu’un exemple d’utilisation dans le cas d’un tableau Python T de N éléments.

Opérateur Utilisation Python Quantificateur Exemple

+ A+ B∑

P Q∑

i∈0..N−1(T [i ])

∗ A ∗ B∏

P Q∏

i∈0..N−1(T [i ])

min min(A,B) minP Q mini∈0..N−1(T [i ])

max max(A,B) maxP Q maxi∈0..N−1(T [i ])

Par abus de langage, la notation min(T [i .. s]) est parfois utilisée à la place de mink∈i ..s(T [k ])

(idem pour les trois autres quantificateurs). Nous présentons ci-dessous quelques propriétés de ces

11 1.2. CALCUL DES PRÉDICATS

quantificateurs.

∑x∈∅

A == 0∑

x∈{y} A == [x = y ] A∑

P Q +∑

P R ==∑

P(Q + R)

maxx∈∅ A == −∞

maxx∈{y} A == [x = y ] A

maxP and Q A == max(maxP A, maxQ A)

minx∈∅ A == ∞

minx∈{y} A == [x = y ] A

minP and Q A == min(minP A, minQ A)

minP A+maxP A == 0

(z == maxx∈P A) == (∃x · (x ∈ P and z == A) and ∀x · (x ∈ P ⇒ z <= A)).

Nous introduisons à présent un dernier quantificateur numérique. Il se démarque des précédentspar le fait qu’il ne généralise pas un opérateur classique. Il s’agit de # 11 qui est le quantificateur dedénombrement (de comptage). i étant une variable libre, P et Q des prédicats, il se note :

#i · (P | Q),

se définit par :

#i · (P | Q) =̂∑

P and Q

(1)

et se lit « le nombre de fois où le prédicat Q est vrai lorsque P (est vrai) ». Ainsi, toujours avec untableau T de N éléments, #i · (i ∈ 0 ..N − 1 | T [i ] == 0) représente le nombre de 0 existant dans T .

1.2.7 Implication et renforcement de prédicats

Étant donné un prédicat Q, dans la suite nous sommes fréquemment confrontés au problème dela recherche d’un prédicat P tel que P⇒Q. En terme graphique cela signifie que nous recherchons unprédicat P dont l’ensemble caractéristique 12 est inclus dans celui de Q. Les deux solutions trivialesP == Q et P == False ne conviennent pas en général (cf. section 3.4, page 29). Il faut rechercherun prédicat P « compris entre » Q et False. Il n’existe pas de solutions générales pour résoudre ceproblème, il faut raisonner au cas par cas.

Les principales formes de renforcement de la logique propositionnelle (celle dans laquelle les opé-randes booléens sont « atomiques », par opposition au cas de la logique des prédicats) sont représentéspar les trois implications suivantes :

11. Ce quantificateur n’a aucun lien avec le symbole de commentaire du langage Python, il n’y a pas de risque de

confusion : ce quantificateur ne fait pas partie du langage.12. C’est-à-dire l’ensemble de tous les états qui satisfont le prédicat P, et qui est noté {i | P}.

CHAPITRE 1. INTRODUCTION 12

(P and Q) ⇒ P

P ⇒ (P or Q)

(P and Q) ⇒ (P or Q)

La première formule est la plus utilisée dans la construction de programmes. En terme de renforcement,elle s’interprète comme : si l’on veut renforcer le prédicat P il suffit de lui adjoindre un prédicatquelconque Q. Une façon usuelle d’appliquer cette formule dans le cadre du calcul des prédicatsconsiste à remplacer une constante par une variable fraîche. Considérons le prédicat x ∈ 2 .. 4. Sareprésentation graphique dans un espace d’états à une, puis à deux dimensions (sur N) est présentéeà la figure 1.4, page 12.

x1 2 3 4 5 6 7 8

| | | | | | | |• • •

(1) une dimension

x

y

1 2 3 4 5 6 7 8

1

2

3

4

5

6

7

••••••••

••••••••

••••••••

××××××××

××××××××

××××××××

××××××××

××××××××

××××××××

××××××××

××××××××

××××××××

(2) deux dimensions

Figure 1.4 – Le prédicat x ∈ 2 .. 4 exprimé dans une et dans deux dimensions

Dans la figure 1.4, les × (schéma (2)) représentent le prédicat y ∈ N, tandis que les • représententle prédicat considéré, respectivement dans une et deux dimensions. À présent remplaçons la constante4 par la variable fraîche y . Si l’on veut que le prédicat renforçant implique bien l’ancien, il doit s’écrire(x ∈ 2..y and (y ∈ N and y == 4)). La partie (1) de figure 1.5, page 13 montre le conjoint renforçant,tandis que dans la partie (2), les × représentent l’union des deux conjoints (y ∈ N and y == 4) et(x ∈ 2 .. y), et les • matérialisent leur intersection. Il est facile de constater que cet ensemble de • estun sous-ensemble de la partie (2) de la figure 1.4. Autrement dit :

(x ∈ 2 .. y and (y ∈ N and y == 4)) ⇒ (x ∈ 2 .. 4).

Il s’agit d’une implication stricte.

1.2.8 Du français aux prédicats, des prédicats au français

La première étape de la réalisation d’une application informatique consiste généralement à couchersur papier une spécification informelle rédigée en français. Sa traduction (qui n’est pas unique engénéral) dans un langage plus formel constitue souvent l’étape suivante. Ci-dessous deux exemplessont présentés. Ils font l’hypothèse que l’on travaille sur un tableau T de relatifs défini sur l’intervalle0 .. N − 1.

Exemples.

13 1.2. CALCUL DES PRÉDICATS

x

y

1 2 3 4 5 6 7 8

1

2

3

4

5

6

7

• • • • • • • • •

(1) Le prédicat y ∈ N and y == 4

x

y

1 2 3 4 5 6 7 8

1

2

3

4

5

6

7

××××××××

××××××××

××××××××

× × × × × × × × ×• • •

(2) Le prédicat x ∈ 2 .. 4 and (y ∈ N and y == 4)

Figure 1.5 – Renforcement de x ∈ 2 .. 4 obtenu en remplaçant une constante par une variable

1. T contient autant de 1 que de −1 se traduit par :

#i∈0..N−1(T [i ] == 1) == #i∈0..N−1(T [i ] == − 1).

2. Le maximum et le minimum de T sont différents (donc N > 1) et ils alternent dans T :

maxi∈0..N−1(T [i ]) != mini∈0..N−1(T [i ])

and

∀(i , j) ·

i ∈ 0 .. N − 2

andj ∈ i + 1 .. N − 1

andT [i ] == maxh∈0..N−1(T [h])

andT [j ] == maxh∈0..N−1(T [h])

⇒∃k ·

k ∈ i + 1 .. j − 1

andT [k ] == minh∈0..N−1(T [h])

and

∀(i , j) ·

i ∈ 0 .. N − 2

andj ∈ i + 1 .. N − 1

andT [i ] == minh∈0..N−1(T [h])

andT [j ] == minh∈0..N−1(T [h])

⇒∃k ·

k ∈ i + 1 .. j − 1

andT [k ] == maxh∈0..N−1(T [h])

.

Le deuxième (resp. le troisième) conjoint exprime que si i et j (i < j)) désignent 2 positions quelconquesdu maximum (resp. du minimum) alors il existe au moins une occurrence du minimum (resp. dumaximum) entre les deux. Ceci caractérise bien l’alternance.

Le processus inverse de passage d’un prédicat à un énoncé en français peut également se révélerutile.

Exemples.

CHAPITRE 1. INTRODUCTION 14

1. Le prédicat suivant

(#i∈0..N−1T [i ] == 0) > 2

se traduit en français par : « il y a au moins trois 0 dans T ».

2. Le prédicat suivant :

∀i ·

i ∈ 0 .. N − 1

and

T [i ] == mink∈0..N−1(T [k])

⇒∀j ·

j ∈ 0 .. N − 1

and

T [j] == maxk∈0..N−1(T [k])

⇒ i < j

se traduit en français par : « dans T , tous les minima sont avant tous les maxima ».

1.3 Spécification des opérations – le triplet de Hoare

Considérons l’application représentée par les deux variables entières x et y , et définie sur le produitcartésien N×N. L’opération suivante, qui calcule le pgcd de deux entiers X et Y , est représentée enPython par le programme suivant :

1 x , y = X , Y

2 while x != y :

3 i f x > y :

4 x = x−y

5 e l s e :

6 y = y−x

7 #end i f

8 #end whi le

Nous ne nous intéressons pas à la façon dont ce programme a été construit. Pour X == 14 etY == 9, l’exécution est décrite dans le schéma ci-dessous par la trajectoire qui part de l’état e1 etqui s’achève à l’état e7 == {(x , 1), (y , 1)}, puisque 14 et 9 sont premiers entre-eux.

x

y

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

1

2

3

4

5

6

7

8

9

10

•e1

•e2

•e3•e4

• e5

• e6

• e7• e7

15 1.3. SPÉCIFICATION DES OPÉRATIONS – LE TRIPLET DE HOARE

1.3.1 Notion de précondition

Que se passe-t-il si le programme ci-dessus débute son exécution dans l’état {(x , 5), (y , 0)} ? Il estfacile de voir que, puisque x > y , le prochain état sera à nouveau {(x , 5), (y , 0)} et que l’exécution nes’arrêtera que sur une intervention extérieure (on dit que le programme boucle, ou qu’il ne se terminepas).

L’espace d’états de départ se partitionne donc en deux sous-ensembles : le sous-ensemble des étatsqui produisent un résultat « intéressant » (l’état {(x , 14), (y , 9)} en fait partie) et son complément, lesous-ensemble des états qui ne produisent pas de résultat intéressant vis-à-vis du problème à résoudre,ou qui ne produisent pas de résultat du tout (comme pour l’état de départ {(x , 5), (y , 0)}).

La caractérisation des états intéressants s’effectue par un prédicat dénommé précondition. Pournotre exemple le prédicat suivant :

X ∈ N and Y ∈ N and x == X and y == Y and x > 0 and y > 0

constitue une précondition acceptable. En l’occurrence il s’agit du prédicat le moins contraignantpossible (le plus faible possible). Il est clair que tout prédicat plus fort que ce dernier (comme parexemple X ∈ N and Y ∈ N and x == X and y == Y and x > 1 and y > 13) constitue uneprécondition acceptable puisque (bien qu’elle n’autorise pas en principe le calcul du pgcd de 2 et de2) elle conduit au résultat attendu.

1.3.2 Notion de postcondition

Caractériser le résultat recherché est également nécessaire. Ceci peut se faire en exprimant unerelation qui lie toutes les variables de programmation de l’opération. Pour notre exemple du pgcd,une postcondition possible est :

x == y and x == max({i | i ∈ N∗ and X%i = 0} ∩ {i | i ∈ N

∗ and Y%i = 0}).

Celle-ci se paraphrase par : « x et y sont égaux et x est la plus grande des valeurs présentes dansl’intersection de l’ensemble des diviseurs de X et de l’ensemble des diviseurs de Y ».

1.3.3 Le triplet de Hoare

Définition 1 (Le triplet de Hoare). Soit P et Q deux prédicats et S un programme. La formule :

{P} S {Q}

est un prédicat appelé triplet de Hoare. Sa signification est la suivante : si S débute son exécution

dans un état satisfaisant P alors :

1. S termine au bout d’un temps fini,

2. et S se termine dans un état satisfaisant Q.

Il est légitime de se poser la question de savoir ce qu’il advient si S ne débute dans un étatsatisfaisant P . On admet que n’importe quoi peut survenir : terminaison dans un état satisfaisant Q,terminaison dans un état ne satisfaisant pas Q, erreur, non terminaison.

Le couple (P ,Q) est appelé la spécification de l’opération. En guise d’exemple voici une spécifi-cation de l’opération pgcd en terme de triplet de Hoare :

CHAPITRE 1. INTRODUCTION 16

{Précondition : X ∈ N and Y ∈ N and x == X and y == Y and x > 0 and y > 0}

pgcd

{Postcondition : x == y and x == max({i | i ∈ N∗ and X%i = 0}∩{i | i ∈ N

∗ and Y%i =

0})}.

Le triplet de Hoare peut être considéré comme un contrat entre le concepteur de l’opération S etson (ses) utilisateur(s). Ce contrat se formule en ces termes : « si vous, utilisateur de S , garantissez quele prédicat P est satisfait immédiatement avant que S ne débute son exécution, alors, moi, concepteurde l’opération S , je vous garantis que S se terminera et que le prédicat Q sera satisfait à l’issue del’exécution ». Inversement, si le concepteur de S fait usage d’un fragment de code S ′, spécifié parle couple (P ′,Q ′), il lui revient de s’assurer que l’invocation de S ′ se fait dans une situation où leprédicat P ′ est satisfait.

Différents types d’activités sont envisageables autour du triplet de Hoare, selon que P , S ou Q

sont connus (donnés) ou non.

1. Si les trois constituants sont donnés, il s’agit de vérifier que le prédicat {P} S {Q} est valide(est toujours vrai). Autrement dit il s’agit de démontrer le programme S . Ceci s’effectue enutilisant l’axiomatique de Hoare (cf. annexe C, page 123). Du point de vue informatique, cetteactivité ne présente que peu d’intérêt puisqu’elle présuppose que S est connu.

2. Le cas le plus intéressant est celui ou la spécification (P ,Q) est donnée et S est (l’)inconnu(e).Il s’agit alors de résoudre une équation en S . Malheureusement, les méthodes traditionnelles desmathématiques se transposent difficilement à ce type d’équation. En particulier, il peut certesne pas y avoir de solution mais s’il en existe une, il en existe une infinité (ne serait-ce qu’eninsérant une séquence de longueur arbitraire de l’instruction pass 13 dans le code de S).

L’essentiel de ce document est consacré à ce second cas de figure, en particulier pour le cas où S

est fondé sur une ou plusieurs boucles. Le principe de la démarche consiste à partir de la précondition,ou de l’une de ses propriétés, pour aller vers la précondition. Les programmes se construisent « enremontant » !

1.4 Conclusion

Ce premier chapitre a permis d’acquérir un vocabulaire et des notions importantes pour la suite(espace d’états, domaine, type, application, invariant, opération, logique, prédicat, triplet de Hoare,précondition, postcondition, spécification, affectation, structure de contrôle, implication, renforcementde prédicats, etc.). La notion d’invariant, introduite ici dans le contexte particulier d’espace d’états, seretrouve très fréquemment dans la suite, avec une acception légèrement différente puisqu’en relation àla notion de boucle. Ce sera le concept clé pour comprendre et réussir la construction de programmesitératifs à laquelle sont pour l’essentiel consacrés les chapitres qui suivent.

Exercices

Exercice 1 : Super calendrier

13. L’instruction pass est en Python l’instruction qui ne fait rien.

17 1.4. CONCLUSION

Étendre l’application « calendrier » en ajoutant la dimension « année » (à partir de l’année 2000)et en tenant compte des années bissextiles. Fournir l’invariant le plus réaliste possible pour cettenouvelle application.

Exercice 2 : Géographie

Définir l’application « géographie » constitué des concepts suivants : pays, villes, capitales, fron-tières. Fournir un invariant aussi réaliste que possible pour cette application.

Exercice 3 : Substitution dans les tableaux

Soit une tableau t défini sur l’intervalle 0 .. 1 et tel que t == [1, 1]. Calculer :

[t[t[1]] = 0] (t[t[i ]] == 0).

Conclusion?

Exercice 4 : Du français aux prédicats (1)

Soit N un naturel donné. Traduire les énoncés suivants :

1. N est premier.

2. N est parfait (il est la somme de ses diviseurs).

Exercice 5 : Du français aux prédicats (2)

Soit T un tableau de relatifs défini sur l’intervalle 0 .. N − 1. Traduire les énoncés suivants sousla forme de prédicats :

1. Toutes les valeurs de T sont différentes de 0.

2. La variable booléenne b est vraie si et seulement si toutes les valeurs de T sont différentesde 0.

3. Tous les 0 de T sont dans T [i .. s].

4. Deux éléments consécutifs de T diffèrent d’au plus 1.

5. T est trié par ordre croissant.

6. Les nombres positifs de T sont triés.

7. T contient une permutation des valeurs de l’intervalle 0 .. N − 1.

8. Il existe au moins un 0 dans T et i désigne la position du 0 le plus à gauche.

9. S’il existe au moins un 0 dans T alors i désigne la position du 0 le plus à gauche.

10. Le minimum et le maximum de T sont présents en autant d’exemplaires.

CHAPITRE 1. INTRODUCTION 18

11. r est la plus longue successions de 0 existant dans T .

Exercice 6 : Du français aux prédicats (3)

Soit T un tableau de naturels de L (L > 0) lignes et de C (C > 0) colonnes. Traduire les énoncéssuivants sous la forme de prédicats :

1. Il existe au moins un 0 dans T

2. Les lignes et les colonnes de T sont triées par ordre croissant.

3. En parcourant T à partir du coin nord-ouest, en spirale, on rencontre les valeurs de T

dans l’ordre croissant.

Exercice 7 : Du français aux prédicats (4)

Soit T un tableau de naturels de N lignes et de N colonnes (N > 0). Traduire les énoncés suivantssous la forme de prédicats :

1. T est un carré magique.

2. N étant égal à 9, T est un sudoku résolu.

Exercice 8 : Triplet de Hoare

Soit le triplet de Hoare suivant :

{Précondition : X ∈ N and Y ∈ N and z ∈ N}

inconnue

{Postcondition : (z == X or z == Y ) and z >= X and z >= Y }.

Donner une description non littérale, en français, de l’opération ainsi spécifiée. Fournir unepostcondition alternative.

Chapitre 2Enfonçons le clou, ou introduction à laconstruction de boucles

Ce chapitre, basé sur un exemple informel, est destiné à nous faire toucher du doigt les principesqui seront appliqués plus tard. Il reprend certaines des notions introduites dans le premier chapitremais est délibérément orienté vers la construction de boucle.

2.1 Exemple introductif

Considérons l’énoncé suivant :

Énoncé 1

Construire le programme spécifié par :

Précondition : on dispose d’une planche, d’un marteau et d’un clou.

Postcondition : le clou est enfoncé dans la planche.

Décrit schématiquement, le travail à réaliser (c’est-à-dire l’exécution du « programme ») consiste àplanter légèrement le clou à la main de façon qu’il tienne seul, puis à taper sur la tête du clou avec lemarteau tant que la tête ne touche pas la planche. Le nombre de coups nécessaire est a priori inconnu.

Le raisonnement qui permet de passer d’une « appréhension raisonnable » d’un énoncé tel que celuici-dessus au programme est difficile à concevoir d’un bloc. Dans le cas d’une boucle nous proposonsde systématiser la conception en un développement comportant cinq étapes :

1. Invariant (ou hypothèse de récurrence). « Le clou est planté dans la planche ».

2. Condition d’arrêt. « La tête touche la planche ».

3. Progression. « Frapper un coup de marteau de façon à enfoncer le clou un tant soit peu ».

4. Initialisation. « Planter légèrement le clou à la main ».

5. Terminaison. « S’assurer que la postcondition sera atteinte au bout d’un nombre fini de coups ».

La terminaison s’exprime (en général) par une expression entière (dont l’évaluation fournit unentier naturel). En quoi ces cinq étapes rationalisent-elles le processus de construction d’une boucle ?

19

CHAPITRE 2. ENFONÇONS LE CLOU 20

Pourquoi, à l’issue de la boucle, la postcondition est-elle atteinte ? Quelles relations entretiennent cescinq points ? C’est ce que nous allons étudier maintenant.

A. La conjonction de l’invariant et de la condition d’arrêt conduit à la postcondition recherchée(l’implique logiquement) :

Le clou est planté dans la planche+

La tête touche la planche

Le clou est enfoncé dans la planche.

Soit pour résumer :

(invariant and condition d’arrêt) ⇒ postcondition.

Notons que la condition d’arrêt seule n’implique pas la postcondition : un clou posé sur laplanche la pointe en l’air a bien la tête qui touche la planche, mais il n’est pas enfoncé dans laplanche.

B. La progression doit :

(a) conserver l’invariant (imaginons un coup de marteau qui déplanterait le clou !). Plus pré-cisément, la progression est un fragment de programme défini par la précondition et lapostcondition suivantes. Précondition : invariant and not(condition d’arrêt). Postcondi-tion : invariant 1. Dans notre exemple, étant donné la précondition (le clou est planté dansla planche) and (la tête ne touche pas la planche), et la postcondition (le clou est enfoncédans la planche), une solution à la progression est de « frapper un coup de marteau sur latête du clou ». À retenir :

{invariant and not condition d’arrêt}

Progression

{invariant},

(b) faire décroître strictement l’expression de terminaison. Plus précisément, en situation debouclage (c’est-à-dire lorsque le prédicat « invariant and not(condition d’arrêt) » est sa-tisfait), la valeur de l’expression de terminaison après un pas de progression est positive ounulle et strictement inférieure à la valeur avant. Ce qui peut s’exprimer par :

{invariant and not(condition d’arrêt) and terminaison∈ N and terminaison == E}

Progression

{terminaison∈ N and terminaison< E}.

Ainsi, la condition d’arrêt sera atteinte au bout d’un temps fini : le programme se termine.Ne perdons cependant pas de vue que la condition exprimée ci-dessus est une conditionsuffisante de terminaison. Dans notre exemple, il est nécessaire de faire décroître la hauteur

1. On affirme notamment ici que l’invariant est vrai avant et après l’exécution de la progression. Il ne faut bien sûr

pas en conclure que l’invariant est vrai pendant toute l’exécution de la progression.

21 2.1. EXEMPLE INTRODUCTIF

du clou au dessus de la planche. Remarquons tout d’abord qu’une autre solution au point (a)ci-dessus serait de ne rien faire. Cependant cette décision, si elle conserve bien l’invariant,possède l’inconvénient de ne pas contribuer à se rapprocher de la postcondition. Notonsensuite que la décroissance stricte n’est pas suffisante. En effet, imaginons un clou de 3 cmde longueur tel que le i e coup de marteau enfonce le clou de 1/(i − 1)! cm. Compte tenude la propriété de convergence de la série correspondante (dont la somme est la valeure), le clou ne sera jamais complètement enfoncé. Il est donc prudent que l’expression determinaison soit une expression entière.

C. L’initialisation doit instaurer l’invariant. Plus précisément, elle doit, partant de la précondition,se terminer et atteindre l’invariant. Ce qui s’exprime par :

{précondition}

initialisation

{invariant}.

Il peut arriver que la précondition soit déjà une instance de l’invariant (ou plus généralementl’implique). Dans ce cas, il est bien sûr inutile de construire une initialisation.

Il est important de noter que les étapes 1 et 2 ci-dessus (recherche de l’invariant et de la conditiond’arrêt) portent sur des situations tandis que les étapes 3 et 4 (construction de la progression et del’initialisation) concernent des actions. À la lecture des points A, B et C il apparaît clairement quel’invariant est le seul constituant qui apparait dans les quatre « règles ». C’est l’étape clé autour delaquelle s’articule la conception des boucles, c’est la colle qui lie les autres constituants entre-eux. Lafigure 2.1, page 23, reprend et résume ces différents points.

Le codage de la boucle correspondante en Python se présente comme suit :

� �1 i n i t i a l i s a t i o n

2 while not(c o n d i t i o n d’ a r r ê t ) :

3 p r o g r e s s i o n

� �

Un des problèmes, pour le programmeur peu familiarisé avec la construction de boucle, est quecette notation minimale ne prévoit pas de faire apparaître dans le codage les briques qui ont servià construire le programme que sont la précondition et la postcondition, l’invariant, l’expression determinaison. On ne peut qu’encourager une présentation où ces rubriques apparaissent explicitementsous la forme d’un commentaire Python comme dans :

� �1 #

2 # PRE :

3 # ...

4 #

5 i n i t i a l i s a t i o n

6 # i n v a r i a n t

7 # ...

8 #terminaison

9 # ...

CHAPITRE 2. ENFONÇONS LE CLOU 22

10 while not(c o n d i t i o n d’ a r r ê t ) :

11 p r o g r e s s i o n

12 #end whi le

13 #

14 # POST :

15 # ...

16 # � �

Dans la grande majorité des langages de programmation ces rubriques ne servent qu’à faciliter lacompréhension du programme par un lecteur averti. Formulons le vœux qu’à l’instar de [7, 8, 36, 41],les langages du futur intègrent ce type de rubriques dans la syntaxe et la sémantique du langageafin que ces informations additionnelles puissent être utilisées par l’environnement de développementpour démontrer ou assister le programmeur dans la démonstration de la correction du programme.Revenons à notre exemple. Nous avons construit le programme suivant :

� �1 #

2 # PRE :

3 # On d i s p o s e d ’ une p l anche d ’ un marteau , d ’ un c l o u

4 #

5 p l a n t e r l égèrement l e c l o u à l a main

6 # i n v a r i a n t

7 # l e c l o u e s t p l a n t é dans l a p l anche

8 #terminaison

9 # non spé c i f i ée

10 while l a tê t e du c l o u ne touche pas l a p l anche :

11 f r a p p e r un coup de marteau s u r l a tê t e du c l o u

12 #end whi le

13 #

14 # POST :

15 # Le c l o u e s t en fonc é dans l a p l anche

16 # � �

Ce qu’il faut retenir de cet exemple

1. Un point à retenir absolument est que la construction d’une boucle débute par la recherched’un invariant. Cette recherche se fonde souvent sur une reformulation de la postconditionsous la forme d’une conjonction. Ce point est développé à la section 4.2, page 35.

2. Une difficulté, pour le programmeur peu expérimenté, est de s’imprégner de la démarche.Le schéma de la figure 2.1, page 23, peut y contribuer, mais il ne remplace pas la pratique.

Remarque importante. Il convient de ne pas confondre les rôles que jouent la condition d’arrêtet l’expression de terminaison dans la construction d’une boucle. D’une part, mais ce n’est pas le plusimportant, la condition d’arrêt est une expression booléenne (abusivement appelée parfois un test),

23 2.2. JUSTIFICATION THÉORIQUE

progression

invariantconditiond’arrêt

postconditionde la boucle

précondition de laboucle

andnot

(conserve)

initialisation

instaure

and

implique

: action : situation

Figure 2.1 – Articulation invariant/condition d’arrêt/progression/initialisation

alors que l’expression de terminaison est une expression délivrant un entier naturel. D’autre part etsurtout, les rôles se complètent puisqu’il est possible de construire une boucle incorrecte satisfaisantaux trois points A., B.-(a), et C. ci-dessus mais échouant sur le point B.-(b). C’est par exemple lecas lorsque la progression se limite à l’instruction pass (l’instruction vide en Python), qui préservel’invariant mais qui, par définition, ne modifie pas l’état du programme ni donc d’aucune expression determinaison quelle qu’elle soit. Dans la suite nous excluons donc systématiquement cette instructioncomme solution possible à toute progression.

2.2 Justification théorique

Dans une approche formelle de la programmation impérative 2, on définit chaque constructiond’un langage de programmation par un axiome ou par une règle d’inférence, selon les cas. La règled’inférence, qui définit le comportement de la boucle, se formule par :

{Q}I{P}, {P and not B}S{P}, P and B ⇒ R , {P and not B and T ∈ N and T==E}S{T ∈ N and T < E}

{Q} I while not B : S {R}(2.1)

2. On fait ici référence à l’axiomatique de Hoare (cf. [19], et annexe C, page 123) et aux travaux qui en ont découlé

(cf. [2, 1, 3, 5]).

CHAPITRE 2. ENFONÇONS LE CLOU 24

qui se paraphrase par :s’il existe un prédicat P (l’invariant) et une expression entière T (l’expression de terminaison) tels

que :

1. le programme I (l’initialisation) s’achève et fait passer de la préconditionQ à la postcondition P 3

(l’initialisation instaure l’invariant),

2. le programme S fait passer de la situation (P and not B) à la situation P (l’invariant estmaintenu par la progression),

3. la conjonction de l’invariant P et de la condition d’arrêt B implique la situation R (quand/si laboucle termine son exécution, la postcondition est bien satisfaite),

4. en situation de bouclage, l’expression de terminaison décroit strictement tout en restant positiveou nulle,

alors, le programme Python

1 I

2 while not B :

3 S

s’achève et fait passer de la précondition Q à la postcondition R .Les mathématiques nous apprennent que la terminaison d’un algorithme est un problème indé-

cidable. Il est donc possible que l’on construise un algorithme pour lequel on ne pourra exhiberune fonction de terminaison satisfaisante. La règle ci-dessus est bien une condition suffisante pourdémontrer la terminaison d’une itération.

La démarche présentée au chapitre 2.1, et résumée dans l’annexe A, page 117, (points 1, 2, 3, 4 et5) ainsi que la justification informelle associée (points A, B et C) vulgarisent la règle d’inférence 2.1présentée ci-dessus.

Exercices

Exercice 9 : Les haricots de Gries

Une boîte de conserve contient une certaine quantité B de haricots blancs et une certaine quantitéN de haricots noirs. On réalise l’opération suivante tant qu’elle reste possible :

On choisit 2 haricots au hasard dans la boîte :

si ils sont de la même couleur alorson les jette ;on met 1 haricot noir dans la boîte a

sinonon jette le haricot noir ;on replace le haricot blanc dans la boîte

fin si

On note que le nombre total de haricots diminue de 1 à chaque opération, on s’arrête doncquand il ne reste plus qu’un seul haricot dans la boite. Que peut-on dire de la couleur du haricot

3. P est à la fois la postcondition de I et l’invariant de la boucle.

25 2.2. JUSTIFICATION THÉORIQUE

restant dans la boite par rapport à B et N ?

a. On suppose pour cela que l’on dispose d’une réserve suffisante de haricots noirs.

Exercice 10 : L’aveugle, le muet et le duplex

Construire le programme spécifié par :

Précondition : Un aveugle et une personne muette sont à l’étage inférieur d’un duplex (dotéd’un escalier droit pour atteindre l’étage supérieur).

Postcondition : L’aveugle est à l’étage supérieur, au sommet de l’escalier. La personne muetteest toujours à l’étage inférieur.

CHAPITRE 2. ENFONÇONS LE CLOU 26

Chapitre 3Renforcement de la postcondition : leprincipe des échafaudages appliqué à laprogrammation

La construction directe d’un programme S tel que {P} S {Q} peut se révéler difficile (voireimpossible, cf. section 3.4, page 29). Il existe cependant une démarche qui permet de faciliter le travail.Comme nous le verrons à la fin de cette section, elle s’apparente à la construction d’échafaudages dansles métiers du bâtiment. Son principe consiste à rechercher une postcondition R , différente de Q, telleque :

1. si S ′ est tel que {P} S ′ {R}, alors S ′ est aussi tel que {P} S ′ {Q},

2. la découverte d’un programme S ′ tel que {P} S ′ {R} présente des avantages 1 par rapport à larecherche du programme S tel que {P} S {Q}.

Quelles relations doivent entretenir Q et R pour que le point 1 ci-dessus soit vérifié ? Comment, àpartir de la donnée de Q, découvrir une nouvelle postcondition R appropriée ? La réponse à la premièrequestion constitue la principale préoccupation de cette section. Quand à la seconde question, bien qu’iln’existe pas de réponse toute faite, nous verrons, dans l’exemple de l’échafaudage, une démarche quipeut parfois s’appliquer avec succès en programmation.

Nous verrons, au chapitre 4, comment la technique de construction de boucles et celle du renfor-cement de postconditions se marient en constituant une approche remarquable dans la constructionde programmes élégants et souvent efficaces.

3.1 Les deux maisons

On se place dans le cadre de la conception et de la construction de bâtiments. Considérons unarchitecte à qui un client demande de dessiner le plan et de construire, sur un terrain nu, une maisondotée soit de une soit de deux fenêtres. Posons :

1. P =̂ (le terrain nu)

1. Il s’agit d’avantages d’ordre économique : facilité dans la recherche d’une solution ou qualité du résultat obtenu.

27

CHAPITRE 3. RENFORCEMENT DE LA POSTCONDITION 28

2. Q1 =̂ (la maison à une fenêtre),

3. Q2 =̂ (la maison à deux fenêtres).

Exprimé formellement, le client demande à l’architecte de concevoir et de réaliser un programme deconstruction S tel que :

{P} S {Q1 or Q2}.

En supposant qu’il ne souhaite pas déterminer plus précisément avec le client son objectif (laquelledes maisons Q1 ou Q2 est effectivement souhaitée), comment l’architecte peut-il raisonnablementréagir à cette sollicitation ? La réponse est simple. Il suffit que l’architecte élabore un programme deconstruction S ′ tel que :

{P} S ′ {Q1} 2.

Le programme S ′ satisfera obligatoirement le client, S ′ est une solution possible au problème initial.Élargissons notre réflexion en nous posant la question de savoir comment on peut modifier une

postcondition initiale de façon à disposer d’une nouvelle postcondition acceptable. La réponse està rechercher dans les fondements théoriques de la programmation. Plus précisément, elle nous estfournie par la règle d’inférence suivante 3, appelée « règle de conséquence 1 » dans la suite (cf. annexeC, page 123) :

{P} S {R},R ⇒ Q

{P} S {Q}(3.1)

Cette règle d’inférence signifie que si

1. le programme S s’achève et fait passer de la situation P à la situation R ,

2. la situation R implique la situation Q,

alors le programme S s’achève et fait passer de la situation P à la situation Q.La condition R est plus « forte » que la condition Q, c’est pourquoi la technique de remplacement

de Q par R est appelée « renforcement de la postcondition ». On constate après coup que, dans leproblème de la maison à une ou deux fenêtres, la règle de conséquence a été utilisée correctementpuisque

Q1 ⇒ Q1 or Q2.

3.2 L’échafaudage

Il est bien sûr possible de renforcer une postcondition Q par toute formule R qui vérifie R ⇒ Q.Mais, ne perdons pas de vue qu’il ne s’agit pas tant de rechercher une nouvelle postcondition quirespecte la règle de conséquence (3.1) ci-dessus que de construire un programme correct. Il n’existepas de règle générale pour renforcer de manière appropriée la postcondition, cependant, on constateque le type de renforcement qui consiste à adjoindre à la postcondition initiale Q un conjoint T

convenablement choisi pour former la nouvelle postcondition (Q and T ) est fréquemment couronnéede succès. Que signifie « convenablement choisi » ? Il faut que le surcoût engendré par l’adjonctionde T soit inférieur à la contribution de T à la recherche de Q 4. On peut illustrer cette démarche parl’exemple suivant.

Le client souhaite maintenant une maison à deux fenêtres. Si on pose :

2. Ou alors bien sûr {P} S ′ {Q2}.3. Toujours selon l’axiomatique de Hoare déjà mentionnée.4. Passer d’une situation où l’on ne sait pas résoudre un problème à une situation où l’on sait le résoudre répond

évidemment à ce critère.

29 3.3. RETOUR SUR LE PROBLÈME DE LA PLANCHE ET DU CLOU

1. P =̂ (le terrain nu),

2. Q =̂ (la maison à deux fenêtres).

Le client demande à l’architecte de concevoir et réaliser le programme de construction S tel que :

{P} S {Q}.

En se fondant sur la démarche décrite ci-dessus, l’architecte est habilité à planifier la construction dequelque chose en plus de Q, par exemple un échafaudage, dont la construction conjointe à celle de lamaison va faciliter le travail des ouvriers. Si on pose :

T =̂ (l’échafaudage autour de la maison),

on a alors :

Q and T== and (la maison et l’échafaudage autour).

On constate que l’architecte a changé avec profit la postcondition initiale Q pour la postcondition(Q and T ). Cette dernière lui permet d’atteindre le but initial (la maison) tout en allégeant globa-lement le travail au prix du – faible – surcoût entraîné par la réalisation d’un échafaudage. À l’issuedu chantier, il suffira de démonter l’échafaudage pour répondre strictement à la spécification initiale.L’échafaudage se comporte en quelque sorte comme un catalyseur.

3.3 Retour sur le problème de la planche et du clou

Dans l’exemple de la section 2.1, page 19, nous avions spontanément choisi une spécificationrenforcée en supposant qu’un marteau était disponible. En toute rigueur il aurait été plus naturel departir de la spécification suivante :

Précondition : on dispose d’une planche et d’un clou.

Postcondition : le clou est enfoncé dans la planche.

de constater qu’il est quasiment impossible de progresser (il est difficile de taper sur un clou à mainsnues) pour en conclure qu’il est pratique de disposer d’un marteau. Techniquement, il est alors néces-saire de renforcer l’invariant en lui adjoignant la disponibilité d’un marteau. La nouvelle initialisationest alors spécifiée d’une part par la précondition initiale et d’autre part par le nouvel invariant. Unesolution à cette initialisation consiste alors à « ajouter » à l’initialisation existante l’action de se munird’un marteau.

C’est ce principe que nous allons tenter d’appliquer au chapitre 4, à partir de la page 31, sur larecherche d’invariants.

3.4 Renforcement ultime

Dans une approche formelle, la précondition et la postcondition sont chacune représentées parune formule logique (un prédicat). Une interprétation possible de cette formule logique consiste à la

CHAPITRE 3. RENFORCEMENT DE LA POSTCONDITION 30

considérer comme dénotant l’ensemble de tous les états la satisfaisant. Dans ce contexte, la formuleFalse représente l’ensemble vide (cf. section 1.2, page 6). Le triplet suivant :

{P} S {False}

est tel que l’ensemble des états que l’on veut atteindre est vide. Ceci implique que l’on ne pourra pasproduire un tel programme S puisqu’il devrait se terminer, mais dans aucun état ! C’est par exemplele cas de la spécification suivante où la postcondition dénote False :

{i == 4} S {i == 6 and i== 7}.

Ces remarques sont également valables pour une précondition valant False : on ne peut construireun programme ne débutant dans aucun état. Cependant, notre technique de construction, qui favoriseune démarche dans le sens de la postcondition vers la précondition, ne nous confronte que rarementà cette seconde situation.

Ainsi donc, s’il devait nous arriver de renforcer une postcondition Q par False, d’une part nousaurions bien False ⇒ Q (ceci est vrai pour toute formule Q) et d’autre part il nous serait impossiblede construire un programme répondant à la nouvelle spécification. Il convient d’être très vigilant afinde ne pas introduire de postconditions qui dénotent False ou de renforcer une postcondition au pointde lui faire prendre la valeur False.

Chapitre 4Heuristiques pour la découverted’invariants

La programmation est une activité dirigée par le but. La recherche de l’invariant d’une bouclen’échappe pas à cette règle et les différentes heuristiques proposées ci-dessous se fondent sur la relationqui lie postcondition et invariant. Nous étudions tout d’abord deux techniques de base concurrentes :

1. l’éclatement de la postcondition,

2. l’hypothèse du « travail réalisé en partie ».

La première, l’éclatement de la postcondition, fait l’hypothèse que la postcondition se présente sousla forme d’une conjonction. Comme c’est rarement le cas il sera fréquemment nécessaire de procéder àun renforcement préalable de la postcondition de façon à exhiber une ou plusieurs conjonctions. Cettetechnique auxiliaire sera étudiée. La seconde, par sa nature, conduit à un renforcement implicite dela postcondition quand cela est nécessaire.

Nous étudions ensuite des techniques complémentaires fondées sur un renforcement. Ces techniquesse déclinent de deux façons :

1. renforcement de l’invariant exigé par la progression,

2. renforcement de la postcondition exigé par le choix de la condition d’arrêt.

La nature heuristique de ces techniques ne garantit pas leur succès, mais elles permettent, en cas deréussite, d’obtenir des solutions correctes, élégantes et souvent efficaces.

4.1 Méthode de l’éclatement de la postcondition

Compte tenu de la propriété 1

(invariant and condition d’arrêt) ⇒ postcondition

il est légitime, lorsque la postcondition s’exprime comme une conjonction, d’identifier l’un des conjointsà l’invariant et le second à la condition d’arrêt. La démarche est identique dans le cas ou la postcondi-tion s’exprime par plusieurs conjonctions. On peut alors chercher d’une part à isoler l’un des conjointspour constituer la condition d’arrêt et d’autre part à mettre de côté le conjoint restant pour formerl’invariant :

1. Cette propriété est une prémisse de la règle d’inférence (2.1), page 23, où elle apparaît sous la forme P and B ⇒ R.

31

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 32

Postcondition : B1 and B2 and . . . and Bn.

Condition d’arrêt : Bi .

Invariant : B1 and B2 and . . . and Bi−1 and Bi+1 and . . . and Bn.

En supposant que l’on n’isole qu’un seul conjoint, comment va-t-on choisir parmi les n possibilités ?Il n’y a pas de réponse systématique et infaillible à cette question. On peut cependant proposer lesheuristiques suivantes :

1. Il est préférable que la condition d’arrêt puisse s’exprimer syntaxiquement dans le langage retenu(Python pour nous) afin que sa négation puisse constituer la condition de bouclage. Ceci exclutdes prédicats avec quantificateur telles que ∀j · (j ∈ ...⇒ ...) 2.

2. Il est préférable que l’invariant soit facile à instaurer à partir de la précondition. Avec un peud’expérience, une fois le choix de l’invariant effectué, il est possible d’évaluer rapidement ladifficulté que représente son instauration (l’initialisation)3.

3. Toutes les variables de la postcondition doivent apparaître dans l’invariant (autrement dit lacondition d’arrêt ne doit pas contenir de variables « orphelines »). Le cas échéant la progressionn’affecterait pas ces variables, qui apparaitraient donc comme inutiles.

Exemple 2 : le premier zéro

Énoncé 2

Construire le programme spécifié par :

Précondition : (T est un tableau d’entiers naturels constant de N éléments) and (il existe aumoins un 0 dans le tableau T ).

Postcondition : (La variable i désigne le 0 de T ayant le plus petit indice (le zéro le plus à« gauche »)).

On remarque que, puisqu’il existe au moins un 0 dans T , c’est donc que N >= 1. Cette dernièreformule peut être utilisée à volonté dans la suite. Puisque cet algorithme exige de parcourir tout oupartie du tableau et sauf à disposer d’opérateurs ad hoc prédéfinis (eux-mêmes construits sur la based’une boucle), on ne voit pas comment éviter de construire une itération. La postcondition ne seprésente pas explicitement sous la forme d’une conjonction. Il est cependant facile de la reformuler enexhibant trois conjoints :

Postcondition :(i ∈ 0 .. N − 1) and (T [0 .. i − 1] ne contient pas de 0) and (T [i ] == 0).

Il est alors possible d’appliquer la méthode de l’éclatement de la postcondition et les heuristiquesprésentées ci-dessus pour diviser cette postcondition en deux parties, la première constituant l’inva-riant et la seconde la condition d’arrêt :

1. Invariant. Les deux premiers conjoints sont faciles à établir en affectant 0 à i .

2. Ceci n’est qu’une heuristique. Il est possible, dans une étape intermédiaire, de passer outre, voir l’exemple 10,

page 66.3. Cette heuristique ne vaut que pour les « petits » programmes. La démarche utilisée ici conduit à construire les

programme de la fin vers le début. Il n’est pas impossible que l’initialisation soit elle-même très volumineuse.

33 4.1. MÉTHODE DE L’ÉCLATEMENT DE LA POSTCONDITION

Invariant

(i ∈ 0 .. N − 1) and (T [0 .. i − 1] ne contient pas de 0).

Soit :

i0 N − 1

T

! = 0

Le conjoint (T [i ] == 0) a donc été écarté. C’est effectivement un prédicat difficile à établirpuisqu’il faudrait localiser un 0 !

2. Condition d’arrêt. Il suffit de prendre le conjoint écarté, soit :

T [i ] == 0

On note que, en tant que condition, ce prédicat est correct sur le plan syntaxique (il ne contientpas de quantificateurs). Sa négation pourra donc être intégrée au programme final (sans risquede provoquer une erreur lors de la compilation).

3. Progression. La progression est spécifiée par :

{Précondition : (i ∈ 0..N−1) and (T [0..i−1] ne contient pas de 0) and not(T [i ] == 0)}

Progression

{Postcondition : (i ∈ 0 .. N − 1) and (T [0 .. i − 1] ne contient pas de 0)}.

Ainsi que nous l’avons mentionné dans l’alinéa « Remarque importante » du chapitre 2, page 22,il faut, pour des raisons de terminaison, exclure comme progression l’instruction pass. Unesolution « constructive » consiste à déplacer i d’une position vers la droite par l’affectation :

i = i+1

L’invariant est bien satisfait par la nouvelle situation. Pour le conjoint (i ∈ 0 .. N − 1) cela estintuitivement justifié par le fait que, puisqu’il n’y a pas de zéro dans T [0 ... i − 1], il doit y enavoir un après (selon la précondition), et donc i <= N − 1 (d’où (i ∈ 0 .. N − 1)).

4. Initialisation. L’initialisation est spécifiée par :

{Précondition : (T est un tableau d’entiers naturels constant de N éléments) and (ilexiste au moins un 0 dans le tableau T )}

Initialisation

{Postcondition : (i ∈ 0 .. N − 1) and (T [0 .. i − 1] ne contient pas de 0)}.

La solution a déjà été ébauchée ci-dessus. La postcondition de cette spécification est l’invariant

de la boucle en cours de construction. Passer de la précondition à l’invariant c’est atteindre lasituation :

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 34

0/i N − 1

T

qui satisfait bien l’invariant puisque, si i == 0, celui-ci, en substituant 0 à i , s’instancie en :(0 ∈ 0 .. N − 1) and (T [0 ..−1] ne contient pas de 0). Ceci s’obtient par l’affectation :

i = 0

5. Terminaison. L’expression N − 1 − i convient : elle reste toujours positive ou nulle et décroit àchaque pas de progression.

Au total, on a construit le programme spécifié suivant :

� �1 T = [ ... ]

2 N = l e n (T)

3 #

4 # PRE :

5 # (T e s t un t a b l e a u d ’ e n t i e r s n a t u r e l s con s t an t de N é l éments) and

6 # ( i l e x i s t e au moins un 0 dans l e t a b l e a u T )

7 #

8 i = 0

9 # i n v a r i a n t

10 # (i ∈ 0 .. N − 1) and (T [0 .. i − 1] ne c o n t i e n t pas de 0)

11 #terminaison

12 # N − 1− i

13 while not (T [ i ] == 0 ) :

14 i = i+1

15 #end whi le

16 #

17 # POST :

18 # (i ∈ 0 .. N − 1) and (T [0 .. i − 1] ne c o n t i e n t pas de 0) and (T [i ] == 0).

19 #

20 p r i n t ( i ) � �

Complexité. Dans le meilleur des cas, cet algorithme est en Θ(1) et au pire en Θ(N). On ne voitpas de moyens simples permettant d’améliorer cette solution pour l’aspect complexité.

Ce qu’il faut retenir de cet exemple

1. La notion d’invariant est liée à la notion de variable. Elle précise la relation qu’entretiennentles variables impliquées dans l’exécution de la boucle. Toutes les variables y participent.

2. Pour appliquer l’heuristique préconisée dans cette section (séparation de la postconditionen invariant et condition d’arrêt), il faut que la postcondition soit sous forme conjonctive. Ilest parfois possible d’y arriver par une simple reformulation. C’est le cas dans cet exemple,il n’en est pas toujours ainsi. Il faut alors employer une technique plus sophistiquée (cf.

35 4.2. RENFORCEMENT DE LA POSTCONDITION

section 4.2, ci-dessous).

3. Cet exercice est un exemple d’un algorithme de recherche connu sous le nom de « recherchelinéaire ». Nous étudierons, à la section 5.1, page 73 un exemple proche connu sous le nomde « recherche linéaire bornée ».

4.2 Renforcement de la postcondition

L’exemple ci-dessus est tel que, après une simple reformulation, la postcondition est écrite sousla forme d’une conjonction. Il est alors facile d’écarter l’un des conjoints de la postcondition pourobtenir l’invariant. La postcondition ne se présente pas toujours sous une forme aussi favorable. Il estsouvent nécessaire, comme nous allons le voir dans l’exemple ci-dessous, de renforcer la postconditionpour exhiber une conjonction qui permettra d’appliquer l’une des heuristiques. Nous avons vu, auchapitre 3, page 27, qu’un tel renforcement est légitime. Nous verrons, dans ce chapitre, d’autresexemples démontrant la richesse d’une telle démarche.

Exemple 3 : la somme des éléments d’un tableau

Énoncé 3

Construire le programme spécifié par :

Précondition : (T est un tableau d’entiers constant de longueur N) and (N >= 0).

Postcondition : (s représente la somme des N éléments du tableau T ).

On remarque que la postcondition ne se présente pas sous la forme requise : elle n’est pas sousforme conjonctive. Il est cependant facile de la renforcer afin d’obtenir une conjonction. La techniqueconsiste en général à remplacer une expression exp par une variable fraîche v puis d’ajouter leconjoint (v == exp) (cf. section 1.2.7, page 11). Dans la suite, ce traitement est dénommé « misesous forme constructive ».

Postcondition (version qui implique la précédente) :

(s est la somme des i premiers éléments du tableau T ) and (i ∈ 0 .. N) and (i == N).

Il s’agit bien d’un renforcement (cf. section 1.2.7, page 11). L’heuristique de l’éclatement de lapostcondition nous conduit à diviser la postcondition pour proposer :

1. Invariant. Les deux premiers conjoints sont faciles à établir. On les retient pour former l’invariant.

Invariant

(s est la somme des i premiers éléments de T ) and (i ∈ 0 .. N),

soit, sous forme graphique :

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 36

i0 N − 1

T

s ==

j∈0..i−1

T [j ]

and (i ∈ 0 .. N)

2. Condition d’arrêt. On reprend le conjoint écarté :

i == N

Le choix de (s est la somme des i premiers éléments de T ) comme condition d’arrêt a étérapidement écarté, cette formule ne pouvant s’exprimer dans la syntaxe d’un langage tel quePython en raison de la présence d’un quantificateur.

3. Progression. La progression est spécifiée par :

{Précondition :(s ==

∑j∈0..i−1 T [j ]

)and (i ∈ 0 .. N) and not(i == N)}

Progression

{Postcondition :(s ==

∑j∈0..i−1 T [j ]

)and (i ∈ 0 .. N)}.

Un début de solution visant à approcher de la postcondition consiste à intégrer T [i ] dans lasomme en faisant :

s = s+T [ i ]

(c’est possible puisque la formule ((i ∈ 0 ..N) and not(i == N)) nous garantit que T [i ] existe).On est alors dans la situation :

i0 N − 1

T

s ==

j∈0..i

T [j ]

and (i ∈ 0 .. N)

On note que, maintenant, T [i ] fait partie de la somme. En conséquence, l’invariant n’est plussatisfait. Son rétablissement s’obtient en faisant progresser i :

i = i+1

On a alors :

37 4.2. RENFORCEMENT DE LA POSTCONDITION

i0 N − 1

T

s ==

j∈0..i−1

T [j ]

and (i ∈ 0 .. N)

qui n’est autre que l’invariant. On a bien (i ∈ 0 .. N). En effet, avant la progression de i on a(i ∈ 0 .. N) and (i != N), soit (i ∈ 0 .. N − 1). Après, on a donc (i ∈ 0 .. N). On a construit laprogression :

1 s = s+T [ i ]

2 i = i+1

4. Initialisation. L’initialisation est spécifiée par :

{Précondition : (T est un tableau d’entiers constant de longueur N) and (N >= 0)}

Initialisation

{Postcondition :(s ==

∑j∈0..i−1 T [j ]

)and (i ∈ 0 .. N)}.

Comme toujours dans la construction de l’initialisation, la postcondition de cette spécificationest l’invariant de la boucle en cours de construction. Passer de la précondition à l’invariantpeut se réaliser en atteignant la situation suivante dans laquelle i s’identifie à 0 :

0/i N − 1

T

s ==

j∈0..0−1

T [j ]

and (0 ∈ 0 .. N)

qui s’établit par :

1 i , s = 0 ,0

5. Terminaison. L’expression N− i convient : elle reste toujours positive ou nulle et décroit à chaquepas de progression.

Finalement, on a construit le programme spécifié suivant :

� �1 T = [ ... ]

2 N = l e n (T)

3 #

4 # PRE :

5 # (T e s t un t a b l e a u d ’ e n t i e r s con s t an t de l ongueu r N) and (N >= 0)

6 #

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 38

7 i , s = 0 ,0

8 # i n v a r i a n t

9 # (s ==∑

j∈0..i−1 T [j ]) and (i ∈ 0 .. N)

10 #terminaison

11 # N − i

12 while not ( i == N ) :

13 s = s+T [ i ]

14 i = i+1

15 #end whi le

16 #

17 # POST :

18 # (s ==∑

j∈0..N−1 T [j ])

19 #

20 p r i n t ( s ) � �

Complexité. Cet algorithme est en Θ(N) dans le meilleur comme dans le pire des cas. On ne voitpas de solutions évidentes pour l’améliorer.

Cet exemple peut laisser une impression de facilité : la solution s’obtient par une démarche li-néaire, sans aucune remise en cause. Ce n’est pas toujours le cas. Le programmeur peut buter surdes difficultés, notamment dans la construction de la progression. Il est alors souvent guidé par ledéveloppement déjà effectué. C’est ce que montre l’exemple 4.

Ce qu’il faut retenir de cet exempleRenforcer une postcondition s’obtient en général dans la situation où il existe un conjoint de

la forme (x == f (exp)) (exp est une expression), on remplace alors ce conjoint par la conjonc-tion (x == f (y)) and (y == exp), où y est une variable de programmation a fraîche. Leconjoint (y == exp) devient un bon candidat pour la condition d’arrêt. C’est la mise sous formeconstructive.

a. Par opposition aux variables de quantification, qui n’ont aucune existence dans le programme proprement

dit.

Exemple 4 : alternance pair/impair

Cet exemple considère un tableau contenant initialement autant de nombres pairs que de nombresimpairs et cherche à placer les nombres pairs (resp. impairs) sur une position d’indice pair (resp.impair). Notons tout d’abord que si tous les nombres pairs sont correctement placés, il en est demême des nombres impairs (et réciproquement), d’où la spécification suivante (dont la postconditionest une disjonction) :

Énoncé 4

Construire le programme spécifié par :

Précondition : (t est un tableau de 2 ∗ N entiers naturels) and (S est le sac des valeurs

39 4.2. RENFORCEMENT DE LA POSTCONDITION

contenues dans t) and (N >= 0) and (t contient N entiers pairs et N entiers impairs).

Postcondition : (S est le sac des valeurs contenues dans t) and ((les positions d’indice paircontiennent les valeurs paires) or (les positions d’indice impair contiennent les valeursimpaires)).

Remarquons tout d’abord que le premier conjoint de la postcondition (qui précise que toutes lesvaleurs de départ se retrouvent à l’arrivée) évite d’être tenté de construire un programme trivial(qui placerait par exemple 0 en position 0, 1 en position 1, etc.) ne répondant manifestement pas auproblème posé. Si elle peut être atteinte, la postcondition doit pouvoir s’obtenir en n’effectuant quedes échanges entre les éléments du tableau t. Sous cette hypothèse, il est possible d’abandonner lepremier conjoint pour ne retenir que :

Postcondition (seconde version) : (dans t, les positions d’indice pair contiennent les valeurspaires) or (dans t, les positions d’indice impair contiennent les valeurs impaires).

Cette version de la postcondition ne se présente pas sous forme conjonctive. Il est facile de larenforcer afin qu’elle le soit, en appliquant la propriété a and b ⇒ a or b (cf. section 1.2.7, page 11).On obtient alors :

Postcondition (troisième version qui implique la seconde) : (dans t, les positions d’indice paircontiennent les valeurs paires) and (dans t, les positions d’indice impair contiennent lesvaleurs impaires).

En principe, il serait possible de l’éclater afin d’obtenir d’un côté l’invariant et de l’autre la condi-tion d’arrêt. Le flair d’un informaticien expérimenté lui permettra de sentir – privilège de l’expérience –qu’il s’agit d’une voie sans issue (ou pour le moins difficile). Quant aux autres, ils sont invités à tentercette approche. . .

On repart à présent de la deuxième version de la postcondition. L’énoncé dans son ensemble etcette postcondition en particulier présentent une symétrie dont il serait dommage de ne pas profiter.Pour cela, nous renforçons la postcondition de la manière suivante :

Postcondition (quatrième version qui implique la seconde) : ((p ∈ 0 .. 2 ∗ N) and (p estpair) and (les positions d’indice pair de l’intervalle 0 .. p − 2 contiennent des valeurspaires) and (p == 2 ∗ N)) or ((i ∈ 1 .. 2 ∗ N + 1) and (i est impair) and (les positionsd’indice impair de l’intervalle 1 .. i − 2 contiennent des valeurs impaires) and (i == 2 ∗

N + 1)).

Il ne s’agit toujours pas d’une forme conjonctive. Nous allons à nouveau renforcer cette version. Pourcela posons :

A =̂

((p ∈ 0 .. 2 ∗ N) and (p est pair) and(les positions d’indice pair de l’intervalle 0 .. p − 2 contiennent des valeurs paires)

)

B =̂

((i ∈ 1 .. 2 ∗ N + 1) and (i est impair) and(les positions d’indice impair de l’intervalle 1 .. i − 2 contiennent des valeurs impaires)

)

Ce qui nous permet de réécrire la postcondition :

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 40

Postcondition (quatrième version réécrite) :(A and (p == 2 ∗ N)) or (B and (i == 2 ∗ N + 1)).

Il est maintenant possible, par application des règles du calcul propositionnel, de renforcer cetteversion afin d’obtenir une forme conjonctive :

(A and (p == 2 ∗N)) or (B and (i == 2 ∗ N + 1))

⇐ Calcul propositionnel

(A and B and (p == 2 ∗ N)) or (B and A and (i == 2 ∗ N + 1))

== Calcul propositionnel

A and B and ((p == 2 ∗ N) or (i == 2 ∗ N + 1))

En développant nous obtenons :

Postcondition (cinquième version impliquant la quatrième) : (p ∈ 0 .. 2 ∗ N) and (p estpair) and (les positions d’indice pair de l’intervalle 0 .. p − 2 contiennent des valeurspaires) and (i ∈ 1 .. 2 ∗ N + 1) and (i est impair) and (les positions d’indice impair del’intervalle 1..i−2 contiennent des valeurs impaires) and ((p == 2∗N) or (i == 2∗N+1)).

1. Invariant. Les six premiers conjoints de la postcondition sont faciles à instaurer. On les conservepour constituer l’invariant :

Invariant

(p ∈ 0 .. 2 ∗ N) and (p est pair) and (les positions d’indice pair de l’intervalle 0 .. p − 2

contiennent des valeurs paires) and (i ∈ 1 ..2∗N+1) and (i est impair) and (les positionsd’indice impair de l’intervalle 1 .. i − 2 contiennent des valeurs impaires).

2. Condition d’arrêt. On prend pour condition d’arrêt le conjoint écarté, soit :

(p == 2 ∗ N) or (i == 2 ∗ N + 1)

3. Progression. La progression est spécifiée par :

{Précondition : (p ∈ 0 .. 2 ∗ N) and (p est pair) and (les positions d’indice pair del’intervalle 0 .. p − 2 contiennent des valeurs paires) and (i ∈ 1 .. 2 ∗ N + 1) and (i estimpair) and (les positions d’indice impair de l’intervalle 1 .. i − 2 contiennent des valeursimpaires) and not((p == 2 ∗N) or (i == 2 ∗ N + 1))}

Progression

{Postcondition : (p ∈ 0 .. 2 ∗ N) and (p est pair) and (les positions d’indice pair del’intervalle 0 .. p − 2 contiennent des valeurs paires) and (i ∈ 1 .. 2 ∗ N + 1) and (i estimpair) and (les positions d’indice impair de l’intervalle 1 .. i − 2 contiennent des valeursimpaires)}.

Avant la progression, on est donc placé soit dans la situation suivante (où i > p) :

41 4.2. RENFORCEMENT DE LA POSTCONDITION

p i0 2N − 1

t

les valeurs d’indice pair sont bien placées

les valeurs d’indice impair sont bien placées

soit dans celle-ci (où i < p) :

i p0 2N − 1

t

les valeurs d’indice impair sont bien placées.

les valeurs d’indice pair sont bien placées

La position relative de i et de p ne joue aucun rôle, on peut donc indifféremment prendre enconsidération t[p] ou t[i ] (ce qui a pour effet de rompre la symétrie exploitée jusqu’alors) pourproposer comme progression :

1 i f ( t [ i ] % 2) == 1 :

2 i = i+2

3 e l s e :

4 t [ i ] , t [ p ] = t [ p ] , t [ i ]

5 p = p+2

6 #end i f

La précondition de la progression garantit que i != 2 ∗ N + 1 et que p != 2 ∗ N : i et p

appartiennent bien au domaine de de définition de T . Préserver la symétrie aurait nécessité deconsidérer les quatre cas possibles (t[i ] pair et t[p] impair, t[i ] impair et t[p] impair, etc.), cequi aurait conduit à une progression peu élégante.

4. Initialisation. L’initialisation répond à la spécification suivante :

{Précondition : (t est un tableau de 2∗N entiers naturels) and (N >= 0) and (t contientN entiers pairs et N entiers impairs)}

Initialisation

{Postcondition : (p ∈ 0 .. 2 ∗ N) and (p est pair) and (les positions d’indice pair del’intervalle 0 .. p − 2 contiennent des valeurs paires) and (i ∈ 1 .. 2 ∗ N + 1) and (i estimpair) and (les positions d’indice impair de l’intervalle 1 .. i − 2 contiennent des valeursimpaires)}.

La situation suivante :

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 42

i/1p/0 2N − 1

t

satisfait l’invariant. La transition depuis la précondition s’obtient par l’affectation :

1 p , i = 0 ,1

5. Terminaison. Le problème est plus délicat que dans les exemples précédents : une seule des deuxvariables i et p décroit à chaque pas de progression. En outre, lorsque c’est le cas, elle décroitde 2. En ne se basant que sur une seule des variables, on risque soit de ne pas faire décroîtrel’expression de terminaison, soit de lui faire prendre une valeur négative ! L’expression suivanteévite ces inconvénients : elle est positive ou nulle et décroit de 1 à chaque pas de progression :

((2 ∗ N)− p + (2 ∗ N) + 1− i)//2.

Au final, on a construit le programme spécifié suivant :

� �1 t = [ ... ]

2 N = l e n ( t )//2

3 #

4 # PRE :

5 # (t e s t un t a b l e a u de 2 ∗ N e n t i e r s n a t u r e l s ) and (S e s t l e sac des

6 # va l e u r s con t enue s dans t) and (N >= 0) and (t c o n t i e n t N e n t i e r s

7 # p a i r s e t N e n t i e r s imp a i r s)

8 #

9 p , i = 0 ,1

10 # i n v a r i a n t

11 # (p ∈ 0 .. 2 ∗ N) and (p e s t p a i r ) and ( l e s p o s i t i o n s d ’ i n d i c e p a i r de

12 # l ’ i n t e r v a l l e 0 .. p − 2 c o n t i e n n en t des v a l e u r s p a i r e s ) and

13 # (i ∈ 1 .. 2 ∗ N + 1) and (i e s t impa i r) and ( l e s p o s i t i o n s d ’ i n d i c e

14 # impa i r de l ’ i n t e r v a l l e 1 .. i − 2 c o n t i e n n en t des v a l e u r s imp a i r e s)

15 #terminaison

16 # ((2 ∗ N)− p + (2 ∗N) + 1− i)//2

17 while not((p == 2 ∗ N) or (i == 2 ∗ N + 1)) :

18 i f ( t [ i ] % 2) == 1 :

19 i = i+2

20 e l s e :

21 t [ i ] , t [ p ] = t [ p ] , t [ i ]

22 p = p+2

23 #end i f

24 #end whi le

25 #

26 # POST :

27 # (S e s t l e sac des v a l e u r s con t enue s dans t) and (( l e s p o s i t i o n s

28 # d ’ i n d i c e p a i r c o n t i e n n en t l e s v a l e u r s p a i r e s ) or ( l e s

29 # p o s i t i o n s d ’ i n d i c e impa i r c o n t i e n n en t l e s v a l e u r s imp a i r e s)−

43 4.2. RENFORCEMENT DE LA POSTCONDITION

30 #

31 p r i n t ( t ) � �

Complexité. Cet algorithme est en Θ(N) dans le meilleur comme dans le pire des cas. On ne voitpas de solutions évidentes pour l’améliorer.

Remarque. Une autre solution aurait consisté à partir de la troisième version de la postconditionpour, après différents renforcements, aboutir à :

Postcondition : (p ∈ 0 .. 2 ∗ N) and (p est pair) and (les positions d’indice pair de l’in-tervalle 0 .. p − 2 contiennent des valeurs paires) and (i ∈ 1 .. 2 ∗ N + 1) and (i estimpair) and (les positions d’indice impair de l’intervalle 1 .. i − 2 contiennent des valeursimpaires) and (p == 2 ∗ N) and (i == 2 ∗ N + 1).

Une condition d’arrêt possible aurait été :

(p == 2 ∗ N) and (i == 2 ∗ N + 1)

version conjonctive de celle que nous avons choisie. Le lecteur est invité à poursuivre ce développementet à le comparer avec celui réalisé ci-dessus.

Ce qu’il faut retenir de cet exemple

1. Il est préférable de préserver les symétries présentes dans l’énoncé tant que cela restepossible. Cela évite en particulier des analyses par cas toujours coûteuses.

2. Pour une spécification donnée il existe toujours plusieurs solutions. Les critères de choixles plus importants portent sur la complexité (temporelle mais aussi spatiale).

3. L’éclatement de la postcondition se fait toujours sur une forme conjonctive, celle-ci exigeparfois des calculs non triviaux pour être atteinte.

Exemple 5 : le drapeau hollandais

L’exemple qui suit, le drapeau hollandais, est un classique. Il a été dénommé ainsi par son auteur,E.W. Dijkstra, parce que la version originale consistait à reconstituer les couleurs du drapeau de sonpays. Notre version est légèrement différente puisque nous considérons des entiers naturels et non descouleurs.

Énoncé 5

Construire le programme spécifié par :

Précondition : (t est un tableau de N entiers naturels) and (S est le sac des valeurs contenuesdans t) and (N >= 1) and (V == t[0]).

Postcondition : (S est le sac des valeurs contenues dans t) and (t se compose de trois parties,à gauche les valeurs inférieures à V , à droite les valeurs supérieures à V et au centre toutes

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 44

les occurrences de la valeur V ).

Schématiquement, la postcondition se présente comme suit (la zone du milieu n’est pas vide puisquequ’elle contient au moins un exemplaire de V ) :

< V == V > V q > p

0 p q N − 1

t

De même que dans l’exemple précédent, toutes les modification de t vont s’effectuer par deséchanges entre des valeurs de t ; en conséquence nous pouvons abandonner le premier conjoint dela postcondition. Nous décidons également d’évaluer la complexité en dénombrant les échanges. Parailleurs, afin d’expliciter les différentes opérations, il est préférable de disposer d’une version formellede cette postcondition :

Postcondition (version formalisée) :∃ (p, q) · ((p ∈ 0 .. N − 1) and (q ∈ p + 1 .. N) and

∀j · (j ∈ 0 .. p − 1⇒ t[j ] < V ) and∀j · (j ∈ p .. q − 1⇒ t[j ] == V ) and∀j · (j ∈ q .. N − 1⇒ t[j ] > V )

).

Outre le fait que cette version de la postcondition n’est pas sous forme conjonctive, il sembledifficile d’établir cette postcondition sans connaitre les limites des trois zones. Ceci est obtenu enremplaçant les variables de quantification existentielle p et q par des variables de programmation(respectivement b et w) :

Postcondition (version 2 qui implique la précédente) :(b ∈ 0 .. N − 1) and (w ∈ b + 1 .. N) and∀j · (j ∈ 0 .. b − 1⇒ t[j ] < V ) and∀j · (j ∈ b .. w − 1⇒ t[j ] == V ) and∀j · (j ∈ w .. N − 1⇒ t[j ] > V ).

Première tentative. Il est alors possible d’appliquer la stratégie classique du remplacement d’uneexpression (N ici) par une variable (r). Nous obtenons la forme constructive suivante :

Postcondition (version 3 qui implique la précédente) :(b ∈ 0 .. N − 1) and (w ∈ b + 1 .. N) and (r ∈ w .. N) and∀j · (j ∈ 0 .. b − 1⇒ t[j ] < V ) and∀j · (j ∈ b .. w − 1⇒ t[j ] == V ) and∀j · (j ∈ w .. r − 1⇒ t[j ] > V ) and(r == N)

à partir de laquelle, en éliminant le dernier conjoint, nous obtenons un invariant convenable, que l’onpeut schématiquement représenter par :

45 4.2. RENFORCEMENT DE LA POSTCONDITION

< V == V > V w > b

0 b w r N − 1

t

valeurs à classer

Cependant (le lecteur est invité à le vérifier), cette solution conduit à réaliser, dans le pire des cas,deux échanges par pas de progression. On peut espérer mieux.

Seconde tentative. De quelle façon ? L’expression N n’est pas la seule à pouvoir être remplacée parune variable. Tentons de remplacer l’expression w présente dans la dernière quantification universellede la version 2 de la postcondition par la variable fraîche r .

Postcondition (version 4) :(b ∈ 0 .. N − 1) and (w ∈ b + 1 .. N) and (r ∈ w .. N)

∀j · (j ∈ 0 .. b − 1⇒ t[j ] < V ) and∀j · (j ∈ b .. w − 1⇒ t[j ] == V ) and∀j · (j ∈ r .. N − 1⇒ t[j ] > V ) and(r == w).

Il est à nouveau possible de décomposer cette version de la postcondition en invariant et conditiond’arrêt.

1. Invariant. Les six premiers conjoints sont faciles à établir. On les garde pour constituer l’invariantet on écarte le dernier conjoint.

Invariant

(b ∈ 0 .. N − 1) and (w ∈ b + 1 .. N) and (r ∈ w .. N)

∀j · (j ∈ 0 .. b − 1⇒ t[j ] < V ) and∀j · (j ∈ b .. w − 1⇒ t[j ] == V ) and∀j · (j ∈ r .. N − 1⇒ t[j ] > V ).

Cette formule peut se schématiser par :

< V == V > V w > b

0 b w r N − 1

t

valeurs à classer

Alors que dans la version précédente, la zone des valeurs restant à classer ne possédait qu’uneseule extrémité variable (celle désignée par r), cette fois les deux extrémités (w et r − 1) sontvariables. Cette constatation laisse espérer une plus grande souplesse (et donc une meilleureefficacité) pour le déplacement des valeurs.

2. Condition d’arrêt. On reprend le conjoint écarté :

r == w

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 46

1. Progression. La progression se spécifie par :

{Précondition : (b ∈ 0 .. N − 1) and (w ∈ b + 1 .. N) and (r ∈ w .. N)

∀j · (j ∈ 0 .. b − 1⇒ t[j ] < V ) and∀j · (j ∈ b .. w − 1⇒ t[j ] == V ) and∀j · (j ∈ r .. N − 1⇒ t[j ] > V ) andnot(r == w)}

Progression

{Postcondition : (b ∈ 0 .. N − 1) and (w ∈ b + 1 .. N) and (r ∈ w .. N)

∀j · (j ∈ 0 .. b − 1⇒ t[j ] < V ) and∀j · (j ∈ b .. w − 1⇒ t[j ] == V ) and∀j · (j ∈ r .. N − 1⇒ t[j ] > V )}.

Considérons la valeur t[w ], qui n’est pas encore classée. Trois cas sont à prendre en compte selonla position relative de t[w ] et de V .

a) t[w ] == V . C’est le cas le plus simple. Il suffit, pour rétablir l’invariant, de déplacer w versla droite :

w = w+1

b) t[w ] < V . La place de t[w ] est dans la première zone. On échange les valeurs aux positionsw et b avant de rétablir l’invariant en avançant ces deux variables :

1 t [w ] , t [ b ] = t [ b ] , t [w ]

2 b ,w = b+1, w+1

c) t[w ] > V . La place de t[w ] est dans le zone de droite. On échange les valeurs aux positionsw et r − 1 avant de rétablir l’invariant en déplaçant r vers la gauche.

1 t [w ] , t [ r −1] = t [ r −1] , t [w ]

2 r = r−1

Au total, la progression se présente sous la forme :

1 i f t [w ] == V :

2 w = w+1

3 e l i f t [w ] < V :

4 t [w ] , t [ b ] = t [ b ] , t [w ]

5 b ,w = b+1, w+1

6 e l s e :

7 t [w ] , t [ r −1] = t [ r −1] , t [w ]

8 r = r−1

9 #end i f

1. Initialisation. L’initialisation se spécifie par :

47 4.2. RENFORCEMENT DE LA POSTCONDITION

{Précondition : (t est un tableau de N entiers naturels) and (N >= 1) and (V == t[0])}

INitialisation

{Postcondition : (b ∈ 0 .. N − 1) and (w ∈ b + 1 .. N) and (r ∈ w .. N)

∀j · (j ∈ 0 .. b − 1⇒ t[j ] < V ) and∀j · (j ∈ b .. w − 1⇒ t[j ] == V ) and∀j · (j ∈ r .. N − 1⇒ t[j ] > V )}.

Il faut donc atteindre la situation suivante :

V

b/0 w/1 r/NN − 1

t

valeurs à classer

que l’on obtient pas l’affectation :

1 b ,w , r = 0 ,1 ,N

1. Terminaison. La zone des valeurs restant à classer diminue de 1 à chaque pas de boucle. r − w

est donc une expression de terminaison possible.

Finalement, on a construit le programme spécifié suivant :

� �1 t = [ ... ]

2 N = l e n ( t )

3 V = t [ 0 ]

4 #

5 # PRE :

6 # (t e s t un t a b l e a u de N e n t i e r s n a t u r e l s ) and (S e s t l e sac

7 # des v a l e u r s con t enue s dans t) and (N >= 1) and (V == t[0])

8 #

9 b ,w , r = 0 ,1 ,N

10 # i n v a r i a n t

11 # (b ∈ 0 .. N − 1) and (w ∈ b + 1 .. N) and (r ∈ w .. N)

12 # ∀j · (j ∈ 0 .. b − 1⇒ t[j ] < V ) and

13 # ∀j · (j ∈ b .. w − 1⇒ t[j ] == V ) and

14 # ∀j · (j ∈ r .. N − 1⇒ t[j ] > V )

15 #terminaison

16 # r − w

17 while not (w == r ) :

18 i f t [w ] == V :

19 w = w+1

20 e l i f t [w ] < V :

21 t [w ] , t [ b ] = t [ b ] , t [w ]

22 b ,w = b+1, w+1

23 e l s e :

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 48

24 t [w ] , t [ r −1] = t [ r −1] , t [w ]

25 r = r−1

26 #end i f

27 #end whi le

28 #

29 # POST :

30 # (S e s t l e sac des v a l e u r s con t enue s dans t) and (t se compose

31 # de t r o i s p a r t i e s , à gauche l e s v a l e u r s i n f é r i e u r e s à V , à d r o i t e

32 # l e s v a l e u r s supé r i e u r e s à V e t au c e n t r e t o u t e s l e s o c c u r r e n c e s

33 # de l a v a l e u r V )

34 #

35 p r i n t ( t ) � �

Complexité. La longueur de la zone des valeurs restant à classer diminue de 1 à chaque pas deprogression, alors qu’en même temps au plus un échange est réalisé. La complexité réelle exige au pireN échanges.

Ce qu’il faut retenir de cet exemplePour renforcer la postcondition :

1. on peut remplacer n’importe quelle variable d’une quantification existentielle par unevariable de programmation fraîche,

2. on peut remplacer n’importe quelle expression par une variable fraîche et non pas seule-ment par une constante.

Remarque. Cet algorithme est au cœur d’un excellent algorithme de tri : le « quick sort » (ou trirapide).

4.3 Diviser en sous-problèmes : la composition séquentielle

Nous avons compris que construire un programme fondé sur une boucle se fait en décomposantle problème en cinq sous-problèmes. Il existe une autre forme de décomposition qui est à la basede la programmation algorithmique (nous l’avons déjà appliquée inconsciemment à de nombreusesoccasions), c’est celle qui est fondée, dans l’axiomatique de Hoare (cf. annexe C, page 123, et [19]),sur la règle d’inférence suivante, dite règle de la séquentialité :

{P} S1 {R}, {R} S2 {Q}{P} S1 ; S2 {Q}

Dans cette règle, le « ; » est l’opérateur de séquentialité (qui n’est pas explicité en Python, maisqui correspond approximativement au passage à la ligne). Cette règle affirme que si l’on dispose desprogrammes S1 et S2 spécifiés respectivement par les couples (P ,R) et (R ,Q), alors si l’exécution duprogramme S1 suivie de celle du programme S2 débute dans un état satisfaisant P , elle se terminedans un état satisfaisant Q. Cette règle peut également être appliquée « de bas en haut » : si onrecherche un programme spécifié par le couple (P ,Q) il suffit de trouver une situation intermédiaire

49 4.4. MÉTHODE DE L’HYPOTHÈSE DU « TRAVAIL RÉALISÉ EN PARTIE »

R et deux programmes S1 et S2 tels que {P} S1 {R} et que {R} S2 {Q} ; la composition séquentiellede S1 et de S2 (c’est-à-dire S1 ; S2) est une solution à notre problème. Cette stratégie est appliquéeen particulier dans l’exemple 6 suivant.

4.4 Méthode de l’hypothèse du « travail réalisé en partie »

Une des propriétés de l’invariant est que c’est une situation qui se retrouve identique à elle-mêmeà chaque pas de boucle 4. Il est légitime d’exploiter cette particularité pour rechercher un invariant.En faisant l’hypothèse qu’une partie du travail a déjà été réalisée, on peut se poser la question :

« Dans quelle situation suis-je ? »

La réponse est en général une solution possible au problème de la recherche d’un invariant.

Dans la démarche de l’éclatement de la postcondition, par construction, la conjonction de l’inva-riant et de la condition d’arrêt implique le but. Ici ça n’est pas le cas puisque la condition d’arrêtest produite séparément. Il est alors nécessaire de vérifier après coup cette implication. Considéronsl’exemple suivant.

Exemple 6 : est-ce le plus petit ?

On cherche à savoir si l’élément désigné par K est bien le seul représentant du minimum dutableau T .

Énoncé 6

Construire le programme spécifié par :

Précondition : (T est un tableau constant de N entiers naturels) and (N >= 1) and (K

constant) and (K ∈ 0 .. N − 1).

Postcondition : infk == (T [K ] est inférieur à tous les autres éléments).

Principe d’une solution naïve. Une solution consiste à parcourir le tableau une première fois àla recherche du minimum, puis une seconde fois pour évaluer sa multiplicité et enfin à vérifier à lafois que ce minimum coïncide avec T [K ] et que sa multiplicité est de 1. Les deux boucles peuventfacilement être fusionnées en une seule. Cette solution est en Θ(N). Elle peut cependant être améliorée(en O(N)) en appliquant les techniques décrites à la section 5.1, page 73.

Une seconde solution. Remarquons qu’en l’absence d’instructions spécifiques, il semble difficiled’éviter de parcourir tout ou partie du tableau pour effectuer cette vérification. On va donc construire(au moins) une boucle. Appliquons la stratégie de l’hypothèse du travail réalisé en partie : on a réaliséune partie du travail (on a parcouru une partie du tableau sans avoir trouvé d’élément plus petit ouégal à T [K ], à l’exception éventuelle de T [K ] lui-même). On est donc dans la situation schématiséepar :

4. Cette propriété est une conséquence de la prémisse {P and not B} S {P} de la règle 2.1, page 23.

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 50

i0 N − 1

T

(i ∈ 0 .. N) and ∀j · (j ∈ ((0 .. i − 1)− {K}) ⇒ T [j ] > T [K ])

Cette situation peut être envisagée comme un invariant possible. On peut remarquer que ce schémane fait aucune hypothèse sur la position relative de i et de K . La position K peut être confondue aveci , être située avant, ou après, la formule en tient compte. Certains partisans des méthodes formellesavancent souvent comme argument que, puisque les schémas ne sont jamais que des cas particuliers,il est préférable de les éviter. À notre avis, pour un informaticien en formation, ils présentent plusd’avantages que d’inconvénients et il serait dommage de s’en priver. La condition d’arrêt qui doitaccompagner cet invariant stipule que, soit on a découvert à la position i (différente de K ) un élémentinférieur ou égal à T [K ], soit tout le tableau a été parcouru. Les deux cas ne peuvent cependant êtreévalués dans un ordre quelconque. La version :

((i != K ) and (T [i ] <= T [K ])) or (i == N)

évalue l’expression relationnelle (T [i ] <= T [K ]) avant (i == N). Si i == N , on va comparer T [K ]

à T [N ], c’est-à-dire à un élément qui n’appartient pas au tableau (T [N ] n’existe pas), l’expressionn’est pas définie. Il faut ordonnancer correctement les opérandes, afin de profiter du caractère « court-circuit » des opérateurs booléens de Python, pour obtenir une condition toujours définie (cf. AnnexeB, page 119) :

(i == N) or ((i != K ) and (T [i ] <= T [K ]))

La conjonction de l’invariant et de cette condition d’arrêt constitue donc la postcondition de laboucle que l’on cherche à construire :

{Précondition : (T est un tableau constant de N entiers naturels) and (N >= 1) and (K

constant) and (K ∈ 0 .. N − 1)}

Boucle

{Postcondition : (i ∈ 0 .. N) and ∀j · (j ∈ (0 .. i − 1)− {K} ⇒ T [j ] > T [K ]) and((i == N) or ((i != K ) and (T [i ] <= T [K ])))}.

Cependant, cette postcondition exclut la variable infk qui, selon l’énoncé, constitue pourtant lerésultat recherché. Cette postcondition est en fait une situation intermédiaire (au sens du prédicatR de la règle d’inférence de la section 4.3, page 48). Nous sommes donc en train de construire leprogramme Python suivant :

1 Boucle

2 Partie 2

dont le fragment Partie 2 se spécifie par :

51 4.4. MÉTHODE DE L’HYPOTHÈSE DU « TRAVAIL RÉALISÉ EN PARTIE »

{Précondition : (i ∈ 0 .. N) and ∀j · (j ∈ (0 .. i − 1)− {K} ⇒ T [j ] > T [K ]) and((i == N) or ((i != K ) and (T [i ] <= T [K ])))}

Partie 2

{Postcondition : infk == (T [K ] est inférieur à tous les autres éléments)}.

Commençons par construire le premier fragment, c’est-à-dire le programme Boucle. Une partieimportante du travail a déjà été réalisée puisque nous disposons déjà de l’invariant et de la conditiond’arrêt, qu’il suffit de rappeler :

1. Invariant.

Invariant

(i ∈ 0 .. N) and ∀j · (j ∈ (0 .. i − 1)− {K} ⇒ T [j ] > T [K ]).

2. Condition d’arrêt.(i == N) or ((i != K ) and (T [i ] <= T [K ]))

3. Progression. La progression répond à la spécification suivante :

{Précondition : (i ∈ 0 .. N − 1) and ∀j · (j ∈ (0 .. i − 1)− {K} ⇒ T [j ] > T [K ]) andnot((i == N) or ((i != K ) and (T [i ] <= T [K ])))}

Progression

{Postcondition : (i ∈ 0 .. N − 1) and ∀j · (j ∈ (0 .. i − 1)− {K} ⇒ T [j ] > T [K ])}.

L’affectation

i = i+1

est une solution pour cette progression.

4. Initialisation. Il s’agit, rappelons-le, d’instaurer l’invariant à partir de la précondition et donc deconstruire le programme Initialisation répondant à la spécification :

{Précondition : (T est un tableau constant de N entiers naturels) and (N >= 1) and (K

constant) and (K ∈ 0 .. N − 1)}

Initialisation

{Postcondition : (i ∈ 0 .. N − 1) and ∀j · (j ∈ (0 .. i − 1)− {K} ⇒ T [j ] > T [K ])}.

L’affectation

i = 0

est une solution. On trouvera dans l’annexe C, page 123, une application de la logique de Hoareà la démonstration de la correction de cette initialisation.

5. Terminaison. L’expression N − i décroit à chaque pas de la progression tout en restant positiveou nulle. Elle assure bien la terminaison.

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 52

On a construit le programme Boucle suivant :

1 #

2 # PRE :

3 # (T e s t un t a b l e a u con s t an t de N e n t i e r s n a t u r e l s ) and (N >= 1) and

4 # (K con s t an t ) and (K ∈ 0 .. N − 1)

5 #

6 i = 0

7 # i n v a r i a n t

8 # (i ∈ 0 .. N) and ∀j · (j ∈ (0 .. i − 1)− {K} ⇒ T [j ] > T [K ])

9 #terminaison

10 # N − i

11 while not((i == N) or ((i != K ) and (T [i ] <= T [K ]))) :

12 i = i+1

13 #end whi le

14 #

15 # POST :

16 # (i ∈ 0 .. N) and ∀j · (j ∈ (0 .. i − 1)− {K} ⇒ T [j ] > T [K ]) and

17 # ((i == N) or ((i != K ) and (T [i ] <= T [K ])))

18 #

Reste à construire le programme Partie 2, qui affecte la variable infk . Cette valeur dépend duconjoint de la condition d’arrêt qui a permis la sortie de la boucle. Nous savons que :

– si la boucle s’arrête via la première sous-formule de la condition d’arrêt (i == N), c’est que letableau tout entier vérifie la condition,

– si la boucle s’arrête via la seconde sous-formule c’est que le sous-tableau T [0 .. i ] ne vérifie pasla condition et donc que le tableau T dans son intégralité ne la vérifie pas non plus.

Cependant, il n’est pas indifférent d’écrire :

i n f k = ( i == N)

ou :

i n f k = not((i != K ) and (T [i ] <= T [K ]))

en effet, cette dernière formule n’est pas définie si i == N , seule la première affectation convient.Au total, on a construit le programme spécifié suivant :

� �1 T = [ ... ]

2 N = l e n (T)

3 #

4 # PRE :

5 # (T e s t un t a b l e a u con s t an t de N e n t i e r s n a t u r e l s ) and (N >= 1) and

6 # (K con s t an t ) and (K ∈ 0 .. N − 1)

7 #

8 i = 0

9 # i n v a r i a n t

10 # (i ∈ 0 .. N) and ∀j · (j ∈ (0 .. i − 1)− {K} ⇒ T [j ] > T [K ])

53 4.4. MÉTHODE DE L’HYPOTHÈSE DU « TRAVAIL RÉALISÉ EN PARTIE »

11 #terminaison

12 # N − i

13 while not((i == N) or ((i != K ) and (T [i ] <= T [K ]))) :

14 i = i+1

15 #end whi le

16 i n f k = ( i == N )

17 #

18 # POST :

19 # infk == (T [K ] e s t i n f é r i e u r à tou s l e s a u t r e s é l éments )

20 #

21 p r i n t ( i n f k ) � �

Complexité. Cette solution est plus performante que la solution naïve ébauchée ci-dessus : dans lemeilleur des cas cet algorithme est en Θ(1) et au pire en Θ(N). On ne voit pas de solutions évidentespour améliorer cette version pour ce qui concerne cet aspect complexité.

Selon notre expérience, confronté à un exercice d’apparence aussi simple, un informaticien tra-vaillant de manière empirique va, péniblement, après de nombreuses tentatives, produire (et non pasconstruire) un programme très long, sans élégance, truffé de variables booléennes fraîches, dans le-quel « tout est dans la progression » et « rien » dans la condition d’arrêt. En appliquant l’heuristiquedu travail réalisé en partie, la construction n’est certes pas élémentaire mais elle est décomposée ensous-problèmes plus faciles à résoudre et sans remise en cause.

Ce qu’il faut retenir de cet exemple

1. Face à des difficultés de développement, revenir aux fondamentaux, c’est-à-dire aux pointsA, B. (a), B. (b) et C de l’annexe A, page 117. Ils ont probablement été oubliés ou négligés.

2. Lorsque la postcondition se présente sous la forme b == (...), où b est une variablebooléenne, ne pas hésiter à appliquer la règle de séquentialité (cf. section 4.3, page 48) enécartant la variable b, le temps de la construction d’une boucle, pour la réintroduire après

la boucle. Le développement est en général simplifié, à condition de disposer d’opérateurscourts-circuits.

Remarques.

1. Afin de comprendre en profondeur les notions et mécanismes mis en jeu, le développement decet exemple a été particulièrement détaillé. À l’avenir, dans des situations analogues, nous nousautoriserons une plus grande concision.

2. Cet exercice est un exemple de recherche linéaire. Un problème proche, celui de la recherchelinéaire bornée, est traité selon différentes méthodes à la section 5.1, page 73.

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 54

4.5 Heuristiques fondées sur le renforcement de l’invariant exigé

par la progression

Nous avons vu (point A. du chapitre 2.1, page 19) que la conjonction de l’invariant P et de lacondition d’arrêt B doit impliquer la postcondition R :

(P and B) ⇒ R .

Il est rare que la postcondition se présente sous une forme telle qu’il soit possible de choisir un invariantP et une condition d’arrêt B de telle sorte que :

(P and B) == R .

L’obtention d’une forme conjonctive propice à la mise en évidence d’un invariant et d’une conditiond’arrêt oblige souvent à réexprimer la postcondition en introduisant une variable fraîche. Ce faisant,on a renforcé la postcondition initiale par une postcondition auxiliaire qui implique la première. Cettetechnique de renforcement a déjà été présentée dans le chapitre 3, page 27, et utilisée plusieurs fois(cf. exemple 3, page 35).

Il est cependant des cas où la nécessité, ou simplement l’occasion, de renforcer la postconditionn’apparaît que plus tard au cours du développement. Le renforcement de la postcondition s’exprimealors souvent indirectement par un renforcement de l’invariant. Dans cette situation on adjoint àl’invariant « naturel » (c’est-à-dire celui issu des heuristiques présentées aux chapitres 4.1 et 4.4), unconjoint supplémentaire P ′ afin, soit d’assurer la progression, soit de formuler une condition d’arrêt.Ceci s’exprime formellement par :

(P and P ′ and B) ⇒ (P and B)

⇒ On a soit (P and B) == R , soit au pire (P and B) ⇒ R

(P and P ′ and B) ⇒ R .

La démarche que nous allons adopter est de nature heuristique. Elle se décompose en quatreparties :

1. On développe la boucle avec l’invariant « naturel ».

2. On constate que la valeur d’une expression manque pour poursuivre la construction de la pro-gression,

3. On fait l’hypothèse que cette valeur est disponible (dans une variable fraîche). Ceci conduit àadjoindre un nouveau conjoint à l’invariant existant.

4. On réitère la construction de la boucle à partir du nouvel invariant.

Nous allons développer cette situation sur trois exemples de difficultés croissantes.

Exemple 7 : calcul de la valeur d’un polynôme en un point

Énoncé 7

Construire le programme spécifié par :

Précondition : Soit C un réel quelconque, soit A un tableau constant de réels défini sur l’in-tervalle 0 .. N , N >= 0, qui représentent les coefficients d’un polynôme p :

p(x) == A[N ] ∗ xN + · · ·+ A[0].

55 4.5. HEURISTIQUES FONDÉES SUR LE RENFORCEMENT DE L’INVARIANT

Postcondition : r représente la valeur du polynôme au point C :

r == A[N ] ∗ CN + · · ·+ A[0].

La postcondition n’est pas sous forme conjonctive, pour y parvenir on peut la renforcer en rem-plaçant l’expression (implicite) N + 1 par la variable fraîche i pour obtenir la forme constructivesuivante :

Postcondition (version issue du renforcement de la précédente) :

(r == A[i − 1] ∗ C i−1 + · · ·+ A[0]) and (i ∈ 0 .. N + 1) and (i == N + 1).

soit encore, en introduisant le quantificateur∑

:

Postcondition (version équivalente à la précédente) :

(r ==∑

j∈0..i−1

A[j ] ∗ C j) and (i ∈ 0 .. N + 1) and (i == N + 1).

D’où le développement suivant obtenu en éclatant la postcondition en invariant et condition d’arrêt.

1. Invariant. Les deux premiers conjoints sont faciles à établir. On les conserve pour constituerl’invariant :

Invariant

(r ==∑

j∈0..i−1 A[j ] ∗ Cj) and (i ∈ 0 .. N + 1).

2. Condition d’arrêt. On reprend le conjoint écarté :

i == N + 1

3. Progression. On sait (cf. point B. (a) de l’annexe A, page 117) que la progression est spécifiéepar :

{Précondition : (r ==∑

j∈0..i−1 A[j ] ∗ Cj) and (i ∈ 0 ..N + 1) and not(i == N + 1)}

Progression

{Postcondition : (r ==∑

j∈0..i−1 A[j ] ∗ Cj) and (i ∈ 0 .. N + 1)}.

On peut envisager l’intégration, sous le quantificateur∑

, du monôme de degré i (A[i ] existepuisque i != N + 1) par l’affectation :

r = r+A [ i ] ∗C i

Cependant, et c’est le point clé de l’exercice, si on ne souhaite pas utiliser un opérateur d’expo-nentiation (par exemple parce que trop coûteux en temps) ou si tout simplement le langage deprogrammation n’offre pas cet opérateur, on peut – première solution – passer par la construc-tion d’une boucle pour calculer C i . On peut aussi, c’est notre solution, supposer cette valeurdisponible en postulant l’égalité

d == C i ,

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 56

où d est une variable réelle fraîche.

Ceci nous conduit à un nouvel invariant, issu du précédent en lui adjoignant la formule ci-dessus. Onréalise alors une seconde tentative de développement fondée sur l’invariant suivant :

1. Invariant.

Invariant

(r ==∑

j∈0..i−1 A[j ] ∗ Cj) and (i ∈ 0 .. N + 1) and (d == C i ).

2. Condition d’arrêt. Elle reste inchangée :

i == N + 1

3. Progression. La nouvelle spécification de la progression est :

{Précondition : (r ==∑

j∈0..i−1 A[j ] ∗ Cj) and (i ∈ 0 .. N + 1) and (d == C i) and

not(i == N + 1)}

Progression

{Postcondition : (r ==∑

j∈0..i−1 A[j ] ∗ Cj) and (i ∈ 0 .. N + 1) and (d == C i )}.

L’intégration du monôme de degré i dans la somme s’obtient par l’affectation :

r = r+A [ i ] ∗d

Suite à cette dernière affectation, le conjoint quantifié s’écrit (r ==∑

j∈0..i A[j ] ∗ d), qui n’estpas la version attendue dans l’invariant. Le rétablissement du premier et du troisième conjoints’obtient par l’affectation :

i , d = i +1,d∗C

On a construit la progression :

1 r = r+A [ i ] ∗d

2 i , d = i +1,d∗C

4. Initialisation. L’initialisation se spécifie par :

{Précondition : (C est une constante réelle) and (A un tableau constant de réels définisur l’intervalle 0 .. N) and (N >= 0)}

Initialisation

{Postcondition : (r ==∑

j∈0..i−1 A[j ] ∗ Cj) and (i ∈ 0 .. N + 1) and (d == C i)}.

Passer de la précondition à la postcondition (l’invariant de la boucle) peut se faire en prenanti == 0. C’est la façon la plus simple d’y parvenir. Ce choix impose, pour les deux conjointsrestants (substitution de i par 0) :

r ==

j∈0..0−1

A[j ] ∗ C j

and (d == C 0)

57 4.5. HEURISTIQUES FONDÉES SUR LE RENFORCEMENT DE L’INVARIANT

soit encore :(r == 0.0) and (d == 1.0)

La situation 5 où(i == 0) and (r == 0.0) and (d == 1.0)

s’obtient par l’affectation suivante :

1 i , r , d = 0 , 0 . 0 , 1 . 0

4. Terminaison. L’expression N+1−i convient. Elle décroit strictement à chaque pas de progressionet reste positive ou nulle.

Finalement, on a construit le programme spécifié suivant :

� �1 A = [ ... ]

2 N = l e n (A)−1

3 C = ...

4 #

5 # PRE :

6 # So i t C un ré e l que lconque , s o i t A un t ab l e a u de r é e l s dé f i n i

7 # su r l ’ i n t e r v a l l e 0 .. N , N >= 0, qu i r e p r é s e n t e n t l e s c o e f f i c i e n t s

8 # d ’ un po l ynôme p : p(x) == A[N ] ∗ xN + · · ·+ A[0].

9 #

10 i , r , d = 0 , 0 . 0 , 1 . 0

11 # i n v a r i a n t

12 # (r ==∑

j∈0..i−1 A[j ] ∗ Cj) and (i ∈ 0 .. N + 1) and (d == C i )

13 #terminaison

14 # N + 1− i

15 while not(i == N + 1) :

16 r = r+A [ i ] ∗d

17 i , d = i +1,d∗C

18 #end whi le

19 #

20 # POST :

21 # r r e p r é s e n t e l a v a l e u r du po l ynôme au po i n t C :

22 # r == A[N ] ∗ CN + · · ·+ A[0]

23 #

24 p r i n t ( r ) � �

Complexité. Cette solution est beaucoup plus efficace que celle qui calcule C i par une boucle interneà la boucle construite ici. En nombre de conditions évaluées de même qu’en nombre de multiplications,elle est en Θ(N) alors que la solution avec une boucle interne est en Θ(N2). Si l’on ne se préoccupe

5. Puisque N >= 0, une solution alternative aurait été d’instaurer l’invariant avec i == 1 On aurait alors

(r ==∑

j∈0..1−1 A[j ] ∗ C j) and (d == C1) qui conduirait à l’initialisation :

1 i , r , d = 1,A[0],C

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 58

que du nombre de multiplications, il est possible d’améliorer notre solution en employant la méthodeconnue sous le nom de schéma de Horner. Celle-ci fournit la valeur du polynôme p au point C par lasuite récurrente pi ci-dessous :

{p0(C ) = A[N ]

pi (C ) = pi−1(C ) ∗ C + A[N − i ] pour N > 0,

pN(C ) étant la valeur recherchée.L’exemple développé ci-dessus illustre le cas d’un double renforcement. Celui de la postcondi-

tion tout d’abord, puis celui de l’invariant ensuite. Ce dernier renforcement se répercute de manièrerétrograde sur la postcondition (celle-ci n’a pas été reprise ici).

Ce qu’il faut retenir de cet exempleLorsque, dans la recherche d’une progression, une expression exp difficile à calculer apparait,

plutôt que de calculer cette expression sur place, intégrer le conjoint (v == exp) dans l’invariant,v étant une variable fraîche. Si ce renforcement de l’invariant est couronné de succès, la solutionobtenue sera en général plus efficace que celle obtenue sans renforcement.

Remarques.

1. Le schéma de Horner, simplement évoqué ci-dessus, est à connaître. Outre le calcul efficace dela valeur d’un polynôme en un point, il permet également d’effectuer la conversion d’une chaînede chiffres en nombre, de diviser un polynôme par un binôme linéaire, etc.

2. Certains problèmes s’expriment naturellement sous la forme d’une suite récurrente. Obtenir lecode correspondant se limite alors le plus souvent à ordonnancer des calculs. C’est en particulierle cas dans la classe des problèmes solubles par « programmation dynamique ».

Exemple 8 : la plus longue succession de zéros

Cet exercice est, à l’instar du précédent, un exemple typique de double renforcement.

Énoncé 8

Construire le programme spécifié par :

Précondition : Soit T un tableau constant d’entiers de N éléments (N >= 0).

Postcondition : lg est la longueur de la plus longue succession de 0 (consécutifs) dans T .

La postcondition n’est pas sous forme conjonctive et pour y parvenir, on peut la renforcer enintroduisant une variable fraîche i de la manière suivante :

Postcondition (version issue du renforcement de la précédente) : (lg est la longueur de laplus longue succession de 0 contenue dans le sous-tableau T [0 .. i − 1]) and (i ∈ 0 ..

N) and (i == N).

59 4.5. HEURISTIQUES FONDÉES SUR LE RENFORCEMENT DE L’INVARIANT

Première tentative. Cette formulation nous suggère immédiatement un invariant et une conditiond’arrêt :

1. Invariant. Les deux premiers conjoints sont faciles à établir, on les conserve pour constituerl’invariant.

Invariant

(lg est la longueur de la plus longue succession de 0 contenue dans le sous-tableau T [0 ..

i − 1]) and (i ∈ 0 .. N).

Sous forme graphique, on obtient :

i0 N − 1

T 0 0· · · · · · · · ·

lg

2. Condition d’arrêt. On retient le conjoint écarté :

i == N

3. Progression. Il faut rechercher un fragment de programme spécifié par :

{Précondition : (lg est la longueur de la plus longue succession de 0 contenue dans lesous-tableau T [0 .. i − 1]) and (i ∈ 0 .. N) and not(i == N)}

Progression

{Postcondition : (lg est la longueur de la plus longue succession de 0 contenue dans lesous-tableau T [0 .. i − 1]) and (i ∈ 0 .. N)}.

La valeur T [i ] existe puisque i != N . Si T [i ] != 0 il n’y a rien à faire d’autre qu’à incrémenteri . Sinon (T [i ] == 0), comment exprimer que T [i ] peut faire partie de la plus longue chaînede 0 du sous-tableau T [0 .. i ] (avant de rétablir l’invariant) ? Cela peut se faire en calculant,par une boucle rétrograde, la longueur p de la plus longue chaîne de 0 consécutifs s’achevanten i . On peut remarquer que tous les éléments de T [0 .. i − 1] ont déjà été examinés et qu’ilserait dommage, s’il est possible de s’en passer, de les examiner à nouveau, même en partie.Supposons que cette longueur p soit déjà connue. Ceci nous conduit à un nouvel invariant, issudu précédent par renforcement, en lui adjoignant cette hypothèse. Nous devons reconstruire laboucle sur la base du nouvel invariant.

Seconde tentative. Nous entamons une seconde construction.

1. Invariant. En veillant à ne pas oublier le domaine de variation de la variable p, le nouvel invariantest :

Invariant

(lg est la longueur de la plus longue succession de 0 contenue dans le sous-tableau T [0 ..

i − 1]) and (i ∈ 0 ..N) and (p ∈ 0 .. i) and (p est la longueur de la plus longue succession

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 60

de 0 consécutifs s’achevant en i − 1).

i0

T

N − 1

0 0· · · · · · · · · 0 0· · ·

lg p

C’est l’occasion de remarquer l’illusion que peut produire un schéma sur notre perception : ceschéma suggère que les sous-tableaux associés à lg et p doivent être distincts alors qu’ils peuventtrès bien être confondus.

2. Condition d’arrêt. On peut retenir à nouveau :

i == N 6

3. Progression. La progression est spécifiée par :

{Précondition : (lg est la longueur de la plus longue succession de 0 contenue dans lesous-tableau T [0 .. i − 1]) and (i ∈ 0 ..N) and (p ∈ 0 .. i) and (p est la longueur de la pluslongue succession de 0 consécutifs s’achevant en i − 1) and not(i == N)}

Progression

{Postcondition : (lg est la longueur de la plus longue succession de 0 contenue dans lesous-tableau T [0 .. i − 1]) and (i ∈ 0 ..N) and (p ∈ 0 .. i) and (p est la longueur de la pluslongue succession de 0 consécutifs s’achevant en i − 1)}.

On peut affirmer que T [i ] existe et que l’invariant est vrai. Si T [i ] == 0 alors p s’allonge d’uneunité tandis que lg devient la plus grande des deux valeurs p et lg . Sinon lg reste inchangé tandisque p devient nul. Le rétablissement de l’invariant s’obtient en incrémentant i . En Python onobtient la progression suivante :

1 i f T [ i ] == 0 :

2 p = p+1

3 l g = sup ( l g , p )

4 e l s e :

5 p = 0

6 #end i f

7 i = i+1

4. Initialisation. L’initialisation est spécifiée par :

6. C’est cette condition d’arrêt qui est retenue dans la suite. Une variante peut être fondée sur la remarque suivante.

La plus longue succession de zéros possible incluant T [i ] est celle qui comprendrait les p zéros situés avant i et les

(éventuels) N − i zéros situés entre les positions i et N − 1. Une solution a priori plus efficace consisterait à s’arrêter

quand on sait que p + N − i n’améliore pas le score (lg) déjà obtenu.

61 4.5. HEURISTIQUES FONDÉES SUR LE RENFORCEMENT DE L’INVARIANT

{Précondition : (T tableau constant d’entiers de n éléments) and (N >= 0)}

Initialisation

{Postcondition : (lg est la longueur de la plus longue succession de 0 contenue dans lesous-tableau T [0 .. i − 1]) and (i ∈ 0 ..N) and (p ∈ 0 .. i) and (p est la longueur de la pluslongue succession de 0 consécutifs s’achevant en i − 1)}.

Prendre i == 0 instaure la postcondition (l’invariant de la boucle) à condition d’imposer auxdeux variables lg et p la contrainte lg == 0 and p == 0. Atteindre cette situation s’obtientpar l’affectation suivante :

1 i , l g , p = 0 ,0 ,0

4. Terminaison. L’expression N − i convient : elle décroit de 1 à chaque pas de progression tout enrestant positive ou nulle.

Au final, on a construit le programme spécifié suivant :� �

1 T = [ ... ]

2 N = l e n (T)

3 #

4 # PRE :

5 # (T t a b l e a u con s t an t d ’ e n t i e r s de N é l éments) and (N >= 0)}6 #

7 i , l g , p = 0 ,0 ,0

8 # i n v a r i a n t

9 # (lg e s t l a l ongueu r de l a p l u s longue s u c c e s s i o n de 0 contenue

10 # dans l e sous−t a b l e a u T [0 .. i − 1]) and (i ∈ 0 .. N) and (p ∈ 0 .. i) and

11 # (p e s t l a l ongueu r de l a p l u s longue s u c c e s s i o n de 0

12 # s ’ achevant en i − 1)

13 #terminaison

14 # N − i

15 while not(i == N) :

16 i f T [ i ] == 0 :

17 p = p+1

18 l g = sup ( l g , p )

19 e l s e :

20 p = 0

21 #end i f

22 i = i+1

23 #end whi le

24 #

25 # POST :

26 # (lg e s t l a l ongueu r de l a p l u s longue s u c c e s s i o n de 0 consé c u t i f s

27 # contenue dans l e t a b l e a u T )

28 #

29 p r i n t ( l g ) � �

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 62

Complexité. Cette solution exige N passages dans la boucle puisque l’on incrémente i de 1 à chaquepas de progression. Elle est donc en Θ(N) .

Ce qu’il faut retenir de cet exemple

1. L’application de la technique du renforcement de l’invariant (qui, dans cet exemple, succèdeà un renforcement de la postcondition) conduit à une solution simple à un problème quine l’est pas. Cette technique n’est pas toujours couronnée de succès mais, quand elle l’est,le résultat est nettement amélioré sur le plan de la complexité.

2. Le programme obtenu par application de cette technique est en général peu lisible, ça n’estpas surprenant : il s’agit du résultat d’une longue réflexion. C’est pourquoi (nous insistons)il est important qu’un dossier de développement (y compris les retours-arrières, suite auxéchecs éventuels) accompagne le programme.

Exemple 9 : somme avant le maximum

Cet exercice offre un dernier exemple pour ce chapitre portant sur le renforcement de l’invariantexigé par la progression.

Énoncé 9

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers relatifs constant injectif (toutes les valeurs de T

sont différentes) de N éléments, N >= 1.

Postcondition : m est la position du maximum de T et s est la somme des éléments deT [0 ..m − 1].

Précisons la postcondition. Si m est la position du maximum de T [0 ..N− 1], on souhaite, à l’issuedu programme, être dans la situation suivante :

m0 N − 1

T

s ==∑

j∈0..m−1 T [j ]

T [m] == maxj∈0..N−1 T [j ]

soit plus formellement et en ajoutant les domaines de variation des variables de programmation :

Postcondition :(m ∈ 0..N−1) and (T [m] == maxj∈0..N−1 T [j ]) and (s ∈ Z) and (s ==

∑j∈0..m−1 T [j ]).

63 4.5. HEURISTIQUES FONDÉES SUR LE RENFORCEMENT DE L’INVARIANT

Première solution. Une solution triviale consiste à parcourir le tableau une première fois à larecherche de la position m du plus grand élément puis à parcourir la tranche T [0 .. m−] pour encalculer la somme. La complexité réelle au pire de cette solution est de 2 ∗ N − 1 itérations, lacomplexité la meilleure est de N . La complexité asymptotique est donc en Θ(N). Il est illusoire dechercher une meilleure complexité asymptotique. Peut-on trouver une meilleure complexité réelle aupire ?

Seconde solution, première tentative. Renforçons la postcondition de façon à obtenir uneconjonction exploitable :

im0 N − 1

T

s ==∑

j∈0..m−1 T [j ]

T [m] == maxj∈0..i−1 T [j ]

Préoccupons-nous du domaine de variation des variables de programmation (i , m et s). Pour quem puisse prendre une valeur il faut que le sous-tableau T [0 .. i − 1] ne soit pas vide, ceci impose que i

ne puisse pas prendre la valeur 0. i varie donc sur l’intervalle 1 ..N , tandis que m ∈ 0 .. i − 1 et s ∈ Z.En ajoutant le domaine de variation de ces variables, on obtient :

Postcondition (version issue du renforcement de la précédente) : (i ∈ 1 ..N) and (m ∈ 0 .. i −

1) and (T [m] == maxj∈0..i−1 T [j ]) and (s ∈ Z) and (s ==∑

j∈0..m−1 T [j ]) and(i == N).

Cette postcondition permet d’effectuer une première tentative.

1. Invariant. Les cinq premiers conjoints sont faciles à établir, on les retient pour constituer l’inva-riant.

Invariant

(i ∈ 1 .. N) and (m ∈ 0 .. i − 1) and (T [m] == maxj∈0..i−1 T [j ]) and (s ∈ Z) and(s ==

∑j∈0..m−1 T [j ]).

2. Condition d’arrêt. On retient le conjoint écarté :

i == N

3. Progression. Comment passer de la situation où l’invariant est vrai, ainsi que not(i == N), à lasituation où l’invariant est vrai, tout en progressant vers la postcondition de l’énoncé ? On peutconsidérer l’élément T [i ]. Si i n’est pas la position du maximum de T [0 .. i ] il n’y a rien d’autreà faire que d’incrémenter i . Sinon, une solution consiste à calculer dans s, par une boucle, lasomme des éléments de T [0 .. i − 1]. Il est cependant facile de s’apercevoir que, si par exemple letableau est trié par ordre croissant, des sommes vont être recalculées. La complexité va excéder

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 64

celle de la solution naïve. Il est peut-être possible de profiter des calculs déjà réalisés. Pour cela,on va considérer que cette somme est disponible (dans la variable v par exemple). Mettre àjour s n’exige alors pas de boucle, s prend simplement la valeur de v . Retenir cette hypothèsec’est renforcer l’invariant par le conjoint v ==

∑j∈0..i−1 T [j ]. Nous devons alors reconstruire

la boucle à partir du nouvel invariant.

Seconde solution, seconde tentative. L’invariant (et indirectement la postcondition) se renforcepar l’ajout du conjoint v ==

∑j∈0..i−1 T [j ].

1. Invariant. Il devient donc :

Invariant

(i ∈ 1 .. N) and (m ∈ 0 .. i − 1) and (T [m] == maxj∈0..i−1 T [j ]) and (s ∈ Z) and(s ==

∑j∈0..m−1 T [j ]) and (v ∈ Z) and (v ==

∑j∈0..i−1 T [j ]).

Soit, sous forme graphique :

im0 N − 1

T

s ==∑

j∈0..m−1 T [j ]

(T [m] == maxj∈0..i−1 T [j ]) and (v ==∑

j∈0..i−1 T [j ])

2. Condition d’arrêt. On conserve la même condition d’arrêt :

i == N

3. Progression. La progression se spécifie par :

{Précondition : (i ∈ 1..N) and (m ∈ 0.. i−1) and (T [m] == maxj∈0..i−1 T [j ]) and (s ∈

Z) and (s ==∑

j∈0..m−1 T [j ]) and (v ∈ Z) and (v ==∑

j∈0..i−1 T [j ]) andnot(i == N)}

Progression

{Postcondition : (i ∈ 1..n) and (m ∈ 0..i−1) and (T [m] == maxj∈0..i−1 T [j ]) and (s ∈

Z) and (s ==∑

j∈0..m−1 T [j ]) and (v ∈ Z) and (v ==∑

j∈0..i−1 T [j ])}.

Si i est la position du nouveau maximum dans le tableau T [0 .. i ], s prend la valeur de v . Lesvariables m et v sont ensuite actualisées.

1 i f T [ i ]>T [m ] :

2 # i e s t l a p o s i t i o n du nouveau maximum

3 s ,m = v , i

4 #end i f

5 v = v+T [ i ]

65 4.5. HEURISTIQUES FONDÉES SUR LE RENFORCEMENT DE L’INVARIANT

et l’invariant est alors rétabli par :

i = i+1

4. Initialisation. L’initialisation est spécifiée par :

{Précondition : (T est un tableau injectif constant de N éléments) and (N >= 1)}

Initialisation

{Postcondition : (i ∈ 1..N) and (m ∈ 0..i−1) and (T [m] == maxj∈0..i−1 T [j ]) and (s ∈

Z) and (s ==∑

j∈0..m−1 T [j ]) and (v ∈ Z) and (v ==∑

j∈0..i−1 T [j ])}.

Faire en sorte que i == 1 impose aux autres variables les contraintes suivantes (on peut levérifier par substitution, cf. section 1.2.2, page 7) :

(s == T [0]) and (v == T [0]) and (m == 0)

L’invariant est donc instauré par l’affectation :

1 i , s , v ,m = 1 ,T [ 0 ] ,T [ 0 ] , 0

4. Terminaison. La variable i est incrémentée à chaque pas de progression. L’expression N − i

convient pour assurer la terminaison.

Finalement, on a construit le programme spécifié suivant :

� �1 T = [ ... ]

2 N = l e n (T)

3 #

4 # PRE :

5 # (T e s t un t a b l e a u i n j e c t i f c on s t an t de n é l éments) and (N >= 1)}6 #

7 i , s , v ,m = 1 ,T [ 0 ] ,T [ 0 ] , 0

8 # i n v a r i a n t

9 # (i ∈ 1 .. N) and (m ∈ 1 .. i − 1) and (T [m] == maxj∈0..i−1 T [j ]) and (s ∈ Z) and

10 # (s ==∑

j∈0..m−1 T [j ]) and (v ∈ Z) and (v ==∑

j∈0..i−1 T [j ])

11 #terminaison

12 # N − i

13 while not(i == N) :

14 i f T [ i ]>T [m ] :

15 # i e s t l a p o s i t i o n du nouveau maximum

16 s ,m = v , i

17 #end i f

18 v = v+T [ i ]

19 i = i+1

20 #end whi le

21 #

22 # POST :

23 # m e s t l a p o s i t i o n du maximum de T e t s e s t l a somme

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 66

24 # des é l éments de T [0 ..m − 1] .

25 #

26 p r i n t (m, s ) � �

Complexité. Cette solution exige au pire et au mieux N itérations. La complexité la pire est doncaméliorée par rapport à la solution naïve. Comme prévu la complexité asymptotique est en Θ(N).

Remarques.

1. La précondition impose que les valeurs de T soient toutes différentes. Nous obtenons une spé-cification légèrement différente en levant cette contrainte. Le cas où le maximum serait présentplusieurs fois nous conduirait à un programme qui calculerait l’une des sommes entre l’indice0 et la position de l’un des maxima.

2. Que peut-on dire du développement réalisé ci-dessus si l’on remplace la précondition P initialepar la précondition P ′ suivante : « Soit T un tableau d’entiers relatifs constant de N éléments(N >= 1) dans lequel le maximum n’est présent qu’en un seul exemplaire ». On note tout d’abordque P ⇒ P ′ (P ′ affaiblit P). On remarque ensuite qu’en vertu de la règle de conséquence 2 del’axiomatique de Hoare (cf. annexe C, page 123), le programme que nous aurions construit avecla précondition P ′ (et qui, le lecteur pourra le constater, pourrait être identique à celui obtenuci-dessus) serait correct par rapport à la précondition P . Nous avons dès le départ surspécifié leproblème à résoudre.

Ce qu’il faut retenir de cet exempleIl est important de bien spécifier le domaine de variation des variables de programmation,

et de s’assurer que ce domaine n’est jamais vide. La construction de l’initialisation est alorsgrandement facilitée puisqu’en général il suffit de prendre le maximum (ou le minimum, selonles cas) de ce domaine et d’en déduire la valeur initiale des autres variables.

4.6 Renforcement de la postcondition exigé par le choix de la

condition d’arrêt

Dans cette section, il convient de garder présent à l’esprit les deux points suivants :

1. toute formule qui implique logiquement la postcondition est candidate au choix du couple (in-variant, condition d’arrêt) (cf. chapitre 3, page 27),

2. la distribution entre invariant et condition d’arrêt est totalement arbitraire, seul le développe-ment d’une boucle correcte compte.

L’exemple développé ci-dessous va être l’occasion de montrer comment ces deux points vont êtresuccessivement mis à contribution dans la construction d’une boucle.

Exemple 10 : le Me zéro

67 4.6. RENFORCEMENT EXIGÉ PAR LE CHOIX DE LA CONDITION D’ARRÊT

Énoncé 10

Construire le programme spécifié par :

Précondition : (M constant) and (M ∈ N) and (M >= 1) and (T est un tableau constant deN éléments) and (T contient au moins M zéros).

Postcondition : i désigne la position du Me zéro dans T .

Notons tout d’abord que la précondition implique que N >= M . En utilisant le quantificateur #

défini à la section 1.2.6, page 10, la postcondition se formalise de la manière suivante :

Postcondition : (i ∈ 0 .. N − 1) and (#j∈0..i−1(T [j ] == 0) == M − 1) and (T [i ] == 0).

A priori, il est inutile de renforcer cette postcondition avant de la décomposer en invariant etcondition d’arrêt, on dispose d’assez de conjoints candidats.

Première tentative.

1. Invariant. Seul le premier conjoint est facile à établir. Il va constituer l’invariant.

Invariant

(i ∈ 0 .. N − 1).

2. Condition d’arrêt. Elle s’impose d’elle-même :

(#j∈0..i−1(T [j ] == 0) == M − 1) and (T [i ] == 0).

Notons cependant l’existence d’un quantificateur. La présence de celui-ci enfreint la premièreheuristique du chapitre 4.1, page 31. Il est inutile de poursuivre, on sait que le quantificateuren question doit disparaître de la condition d’arrêt afin que celle-ci puisse être codée dans lasyntaxe d’un langage algorithmique.

Seconde tentative. Il faut renforcer la postcondition en identifiant l’expression quantifiée à unevariable fraîche comme ci-dessous :

Postcondition : (p ∈ 0 ..M − 1) and (i ∈ 0 .. N − 1) and (p == #j∈0..i−1(T [j ] == 0)) and(p == M − 1) and (T [i ] == 0).

1. Invariant. Afin d’éviter l’écueil de la tentative précédente, cette fois l’invariant doit contenirl’expression quantifiée. Les deux derniers conjoints sont difficiles à établir. On les écarte.

Invariant

(i ∈ 0 .. N − 1) and (p ∈ 0 ..M − 1) and (p == #j∈0..i−1(T [j ] == 0)).

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 68

2. Condition d’arrêt. La condition d’arrêt est constituée des conjoints écartés, soit :

(p == M − 1) and (T [i ] == 0)

Il s’agit bien d’une condition syntaxiquement correcte (en Python en particulier).

3. Progression. Elle se spécifie par :

{Précondition : (i ∈ 0..N−1) and (p ∈ 0..M−1) and (p == #j∈0..i−1(T [j ] == 0)) andnot((p == M − 1) and (T [i ] == 0))}

Progression

{Postcondition : (i ∈ 0..N−1) and (p ∈ 0..M−1) and (p == #j∈0..i−1(T [j ] == 0))}.

Le fragment de code suivant répond à cette spécification :

1 i f T [ i ] == 0 :

2 p = p+1

3 #end i f

4 i = i+1

4. Initialisation. L’initialisation doit satisfaire la spécification suivante :

{Précondition : (M constant) and (M ∈ N) and (M >= 1) and (T est un tableauconstant de N éléments) and (T contient au moins M zéros)}

Initialisation

{Postcondition : (i ∈ 0..N−1) and (p ∈ 0..M−1) and (p == #j∈0..i−1(T [j ] == 0))}.

La situation (i == 0) oblige à avoir (p == 0). Cette situation est atteinte depuis la préconditionpar l’affectation suivante :

1 i , p = 0 ,0

5. Terminaison. L’expression N − 1 − i décroit de 1 à chaque pas de progression, tout en restantpositive ou nulle. Ceci assure la terminaison.

Nous avons construit le programme spécifié suivant :

� �1 M = ...

2 T = [ ... ]

3 N = l e n (T)

4 #

5 # PRE :

6 # (M con s t an t ) and (M ∈ N) and (M >= 1) and (T e s t un t a b l e a u

7 # cons tan t de N é l éments) and (T c o n t i e n t au moins M zé r o s )

8 #

9 i , p = 0 ,0

10 # i n v a r i a n t

11 # (i ∈ 0 .. N − 1) and (p ∈ 0 ..M − 1) and (p == #j∈0..i−1(T [j ] == 0))

69 4.6. RENFORCEMENT EXIGÉ PAR LE CHOIX DE LA CONDITION D’ARRÊT

12 #terminaison

13 # N − 1− i

14 while not((p == M − 1) and (T [i ] == 0)) :

15 i f T [ i ] == 0 :

16 p = p+1

17 #end i f

18 i = i+1

19 #end whi le

20 #

21 # POST :

22 # i dé s i g n e l a p o s i t i o n du Me zé ro dans T .

23 #

24 p r i n t ( i ) � �

Complexité. La boucle est exécutée au plus N fois. La complexité au pire est donc en Θ(N). Lacomplexité la meilleure est en Θ(M).

Ce qu’il faut retenir de cet exempleDes heuristiques ont été présentées au chapitre 4, page 31. Il est toujours possible de les

ignorer. C’est ce que nous avons fait ici pour ce qui concerne l’heuristique du quantificateurprésent dans une condition d’arrêt. La présence d’une formule quantifiée, que ce soit dans lacondition d’arrêt comme ici, ou dans la partie progression, ne doit pas constituer un obstaclepour le développeur. Il suffit en général de renforcer la postcondition ou l’invariant, en identifiantl’expression quantifiée à une variable fraîche, puis de réaliser un nouveau développement.

Exercices

Exercice 11 : La division euclidienne

Construire le programme spécifié par :

Précondition : Soit A >= 0 et B > 0 deux constantes entières.

Postcondition : On recherche q, le quotient et r , le reste de la division entière de A par B,définis par la relation :

A == q ∗ B + r and 0 <= r and r < B

On s’interdit l’utilisation d’opérateurs multiplicatifs.

Exercice 12 : Un brouillon dans une corbeille à papier

On a trouvé le texte suivant dans une corbeille à papier d’une salle de TP de l’Enssat :

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 70

Précondition : Soit T un tableau constant d’entiers, de N (N >= 1) éléments.

Postcondition : La variable sup contient la plus grande valeur de T .

1. Invariant :(i ∈ 1 .. N) and ∀j · (j ∈ 0 .. i − 1⇒ T [j ] <= sup)

2. Condition d’arrêt :i == N

3. Progression :

if T [i ] > sup :

sup = T [i ]

#end if

i = i + 1

4. Initialisation :

i , sup = 1,T [0]

5. Terminaison : N − i

1. À votre avis quelle(s) erreur(s) justifie(nt) ce rejet ?

2. Fournir une version correcte.

Exercice 13 : Le meilleur intervalle

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers naturels constants de N (N >= 1) éléments.

Postcondition : L’entier g est la plus grande valeur que peut prendre l’expression T [s]−T [i ]

lorsque 0 <= i <= s < N .

On recherche une solution construite sur une seule boucle dans laquelle on passe au plus N fois.Par contre on ne recherche pas le (l’un des) couple(s) (i , s) qui détermine la valeur de g .

Exercice 14 : Recherche dans une bâtière

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers constants de L (L >= 1) lignes et C (C >= 1)

colonnes. Chaque ligne de T est triée par ordre croissant, ainsi que chaque colonne. Lavaleur V est présente dans T .

Postcondition : x et y sont les coordonnées d’une (quelconque) occurrence de V : T [x ][y ]== V .

Exemple de bâtière :2 5 7

4 7 7

6 8 9On recherche de préférence une solution construite sur une seule boucle dans laquelle on

71 4.6. RENFORCEMENT EXIGÉ PAR LE CHOIX DE LA CONDITION D’ARRÊT

passe au plus L+ C fois.

Exercice 15 : Un problème de sous-tableau le plus long (1)

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers constants de N (N >= 1) éléments.

Postcondition : L’entier lgConsMax est la longueur de la plus longue succession d’entiers consé-cutifs dans T .

Exercice 16 : Un problème de sous-tableau le plus long (2)

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers constants de N (N >= 1) éléments.

Postcondition : La variable l représente la longueur du plus long sous-tableau de T telle quetous les éléments du sous-tableau sont supérieurs ou égaux au premier élément du sous-tableau et sont inférieurs ou égaux au dernier.

On recherche une solution construite sur une seule boucle dans laquelle on passe au plus N fois.

Exercice 17 : Un problème de sous-tableau le plus court (1)

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers constants de N (N >= 2) éléments. T contient aumoins 2 zéros.

Postcondition : L’entier lgMin2Zero est la longueur du sous-tableau de T le plus court conte-nant 2 zéros.

On recherche une solution construite sur une seule boucle dans laquelle on passe au plus N fois.

Exercice 18 : Un problème de sous-tableau le plus court (2)

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers constants de N (N >= 1) éléments.

Postcondition : L’entier tousLesMax est la longueur du sous-tableau de T le plus court conte-nant tous les exemplaires du maximum de T .

On recherche une solution construite sur une seule boucle dans laquelle on passe au plus N fois.

Exercice 19 : La plus grande somme

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers relatifs constant de N (N >= 1) éléments.

CHAPITRE 4. HEURISTIQUES POUR LA DÉCOUVERTE D’INVARIANTS 72

Postcondition : L’entier m est la plus grande valeur que peut prendre la somme des élémentsd’un sous-tableau, lorsque l’on considère tous les sous-tableaux possibles de T . On admetque la somme de éléments d’un tableau de longueur négative ou nulle est 0.

On recherche une solution construite sur une seule boucle dans laquelle on passe au plus N fois.

Exercice 20 : Fusion

Construire le programme spécifié par :

Précondition : Soit S (resp. T ) un tableau d’entiers constants de N (N >= 0) (resp. M (M >=

0)) éléments, trié(s) par ordre croissant. Soit r un tableau d’entiers de N +M places.

Postcondition : r est un tableau trié contenant l’union multiensembliste des valeurs de S etde T .

On recherche une solution construite sur une seule boucle dans laquelle on passe au plus N +M

fois.

Chapitre 5Exercices de synthèse

Trois exemples classiques sont développés dans ce chapitre : la recherche linéaire bornée, la re-cherche dichotomique et un tri. Des compléments (variantes, complexités, etc.) peuvent être trouvéesdans [22], important ouvrage dédié à la recherche d’informations et aux tris. Un quatrième et dernierexemple est proposé. Il s’intitule « Le plus grand carré sous un histogramme » et est destiné à illustrerle cas pour lequel le renforcement exigé par la progression conduit à introduire un tableau (et non plusune variable scalaire). Ce problème trouve son application par exemple dans le traitement d’images.

5.1 Recherche linéaire bornée

La recherche linéaire est une classe d’algorithmes où, étant donné un prédicat p(i) tel qu’il existe unensemble non vide de valeurs le satisfaisant, on recherche l’un des éléments de cet ensemble. L’exercice2, page 32, fournit un exemple d’une telle recherche.

La recherche linéaire bornée s’apparente à la recherche linéaire mais ici, l’ensemble des solutionspeut être vide. En général, le résultat fourni en cas d’échec est soit une valeur conventionnelle située endehors du domaine de définition de p, soit un booléen qui signale l’échec. La solution triviale consistantà parcourir la totalité du tableau est évoquée, voire construite, mais, dans la mesure où il est possibled’interrompre l’exécution de la boucle dès que le résultat est acquis, cette solution est toujours l’objetd’une amélioration. Ci-dessous, le problème est décliné de trois façons différentes. Une quatrièmesolution, dénommée « méthode de la sentinelle », consiste à transformer la recherche linéaire bornéeen recherche linéaire en ajoutant artificiellement la valeur recherchée afin d’être sûr de la trouver.Cette solution n’est pas considérée ci-dessous car elle présente en général plus d’inconvénients qued’avantages, en particulier lorsque le prédicat s’implante par une liste chainée pour laquelle il fautmaintenir une dernière valeur de la liste par un pointeur ad hoc.

L’exercice consiste, partant d’un tableau T , à déterminer si la somme des éléments de T [0 ..N−1]

est ou non supérieure à une valeur entière donnée S .

Exemple 11 : recherche linéaire bornée avec résultat booléen

Le booléen en question est dénommé sss (pour somme supérieure à S).

73

CHAPITRE 5. EXERCICES DE SYNTHÈSE 74

Énoncé 11

Construire le programme spécifié par :

Précondition : (T est un tableau de N entiers naturels) and (N >= 0) and (S constant) and(S ∈ N).

Postcondition : sss == (∑

j∈0..N−1 T [j ] > S).

La postcondition n’est pas sous forme constructive, remplaçons la constante N de la quantificationpar la variable fraîche i :

Postcondition : (i ∈ 0 .. N) and (sss == (∑

j∈0..i−1 T [j ] > S)) and (i == N).

Nous sommes prêts à débuter la construction de cet algorithme.

1. Invariant. Les deux premiers conjoints sont faciles à établir. On les conserve pour constituerl’invariant.

Invariant

(i ∈ 0 .. N) and (sss == (∑

j∈0..i−1 T [j ] > S)).

2. Condition d’arrêt. On reprend le conjoint écarté :

(i == N)

3. Progression. La progression est spécifiée par :

{(i ∈ 0 .. N) and (sss == (∑

j∈0..i−1 T [j ] > S)) and not(i == N)}

Progression

{(i ∈ 0 .. N) and (sss == (∑

j∈0..i−1 T [j ] > S))}.

Une solution à cette spécification est :

1 s s s = s s s or(∑

j∈0..i T [j ] > S)

2 i = i+1

Il est clair qu’en raison de la présence d’un quantificateur, cette solution ne peut rester en l’état.Le réflexe est maintenant acquis, on renforce l’invariant par le conjoint

sp ==∑

j∈0..i

T [j ]

(sp pour somme partielle) pour obtenir l’invariant suivant :

(i ∈ 0 .. N) and (sss == (∑

j∈0..i−1 T [j ] > S)) and (sp ==∑

j∈0..i T [j ]).

75 5.1. RECHERCHE LINÉAIRE BORNÉE

Cependant, cet invariant est à écarter car il n’est pas défini pour i == N (T [N ] n’existe pas).Le problème aurait été évité si on avait choisi de renforcer par le conjoint :

sp ==∑

j∈0..i−1

T [j ].

Reprenons la construction à partir de ce nouvel invariant.

1. Invariant.

Invariant

(i ∈ 0 .. N) and (sss == (∑

j∈0..i−1 T [j ] > S)) and (sp ==∑

j∈0..i−1 T [j ]).

2. Condition d’arrêt. Elle est inchangée :

(i == N)

3. Progression. La progression est spécifiée par :

{Precondition : (i ∈ 0 .. N) and (sss == (∑

j∈0..i−1 T [j ] > S)) and(sp ==

∑j∈0..i−1 T [j ]) and not(i == N)}

Progression

{Postcondition : (i ∈ 0 .. N) and (sss == (∑

j∈0..i−1 T [j ] > S)) and(sp ==

∑j∈0..i−1 T [j ])}.

dont une solution est :

1 s s s = s s s or ( sp+T [ i ]>S )

2 sp = sp+T [ i ]

3 i = i+1

4. Initialisation. L’initialisation est spécifiée par :

{Précondition : (T est un tableau de N entiers naturels) and (N >= 0) and (S ∈ N)}

Initialisation

{Postcondition : (i ∈ 0 .. N) and (sss == (∑

j∈0..i−1 T [j ] > S)) and(sp ==

∑j∈0..i−1 T [j ])}.

La séquence

1 i , s s s , sp = 0 , Fa l se , 0

répond à cette spécification.

5. Terminaison. N − i est une expression de terminaison convenable.

Nous avons construit la solution suivante :

CHAPITRE 5. EXERCICES DE SYNTHÈSE 76

1 T = [ ... ]

2 N = l e n (T)

3 S = ...

4 #

5 # PRE :

6 # (T e s t un t a b l e a u de N e n t i e r s n a t u r e l s ) and (N >= 0) and (S ∈ N)

7 #

8 i , s s s , sp = 0 , Fa l se , 0

9 # i n v a r i a n t

10 # (i ∈ 0 .. N) and (sss == (∑

j∈0..i−1 T [j ] > S)) and (sp ==∑

j∈0..i−1 T [j ])

11 #terminaison

12 # N − i

13 while not(i == N) :

14 s s s = s s s or ( sp+T [ i ]>S )

15 sp = sp+T [ i ]

16 i = i+1

17 #end whi le

18 #

19 # POST :

20 # sss == (∑

j∈0..N−1 T [k ] > S)

21 #

22 p r i n t ( s s s )

Complexité. On passe exactement N fois dans le corps de la boucle. La complexité de cet algorithmeest donc en Θ(N). Il est facile de constater que l’arrêt pourrait avoir lieu dès que sss prend la valeurTrue.

Cette remarque nous conduit à modifier la condition d’arrêt et la progression de la constructionprécédente afin d’améliorer la complexité la meilleure.

2. Condition d’arrêt.(i == N) or sss

Notons que, dans le cas présent, l’opérateur or ne nécessite pas une sémantique court-circuit.

3. Progression. La première expression évaluée dans la progression est sss or (sp + T [i ] > S).Cette évaluation se fait dans le contexte de la précondition de la progression. Celle-ci, aprèsdéveloppement (théorème de De Morgan), comprend le conjoint not(sss). Le calcul suivantpermet une simplification :

sss or (sp + T [i ] > S)

== Précondition not(sss) et calcul propositionnel

sp + T [i ] > S

En intégrant ces modifications, nous obtenons la version suivante :

� �1 T = [ ... ]

2 N = l e n (T)

3 S = ...

77 5.1. RECHERCHE LINÉAIRE BORNÉE

4 #

5 # PRE :

6 # (T e s t un t a b l e a u de N e n t i e r s n a t u r e l s ) and (N >= 0) and (S ∈ N)

7 #

8 i , s s s , sp = 0 , Fa l se , 0

9 # i n v a r i a n t

10 # (i ∈ 0 .. N) and (sss == (∑

j∈0..i−1 T [j ] > S)) and (sp ==∑

j∈0..i−1 T [j ])

11 #terminaison

12 # N − i

13 while not((i == N) or sss) :

14 s s s = sp+T [ i ]>S

15 sp = sp+T [ i ]

16 i = i+1

17 #end whi le

18 #

19 # POST :

20 # sss == (∑

j∈0..N−1 T [k ] > S)

21 #

22 p r i n t ( s s s ) � �

Complexité. La complexité de cet algorithme est maintenant en O(N).

Ce qu’il faut retenir de cet exemple

1. Cet exercice offre un exemple assez insidieux (et inédit pour nous) d’expression non définiesuite à un renforcement. Nous ne pouvons qu’insister sur l’importance de s’assurer que lesfonctions (opérateurs, tableaux) ne sont appliquées que sur leur domaine de définition (cf.annexe C.2, page 125).

2. Le développement réalisé peut sembler laborieux. Compte tenu de l’importance de la re-cherche linéaire bornée, il est cependant à connaitre parfaitement. L’expérience aidant ledéveloppement deviendra machinal. Sous forme générique, la solution pourra même fairepartie d’un catalogue prédéfini auquel il suffira de faire référence.

Exemple 12 : recherche linéaire bornée avec arrêt au plus tôt

Dans une première étape, on considère une spécification plus forte que dans la version précédente,dans la mesure où l’on souhaite connaître, s’il y a lieu, la position du plus petit indice pour lequella somme dépasse la valeur S . Il sera ensuite facile d’obtenir le résultat booléen sss comme dans laversion précédente.

La postcondition exprime donc que toutes les sommes s’achevant avant i sont inférieures ou égalesà S et la plus petite somme supérieure à S , si elle existe, est celle s’achevant en i (i < N), sinon (parconvention) i vaut N . Soit en formalisant :

CHAPITRE 5. EXERCICES DE SYNTHÈSE 78

Énoncé 12

Construire le programme spécifié par :

Précondition : (T est un tableau de N entiers naturels) and (N >= 0) and (S constant) and(S ∈ N).

Postcondition : (i ∈ 0 .. N) and (∀j · (j ∈ 0 .. i − 1⇒ (∑

k∈0..j T [k ]) <= S)) and((i == N) or (

∑k∈0..i T [k ] > S)).

Afin d’éviter l’évaluation de T [i ] lorsque i == N dans la postcondition, l’opérateur or doit se com-porter comme un court-circuit.

1. Invariant. Les deux premiers conjoints sont faciles à établir. On les conserve pour constituerl’invariant.

Invariant

(i ∈ 0 .. N) and (∀j · (j ∈ 0 .. i − 1⇒ (∑

k∈0..j T [k ]) <= S)).

2. Condition d’arrêt. Le conjoint écarté :

((i == N) or (∑

k∈0..i

T [k ] > S))

devient donc la condition d’arrêt. Cependant, de même que dans l’exemple 10, page 66, ils’exprime à travers une quantification qu’il faut éliminer. La technique habituelle, qui consisteraità introduire le conjoint ci-dessous :

sp ==∑

j∈0..i

T [j ]

conduirait à l’invariant :

(i ∈ 0 .. N) and (∀j · (j ∈ 0 .. i − 1⇒ (∑

k∈0..j T [k ]) <= S)) and (sp ==∑

j∈0..i T [j ]).

Ce prédicat n’est pas défini pour i == N en raison du conjoint ajouté. Il s’agit du mêmephénomène que dans l’exercice précédent. Cependant, comme dans l’exemple 10, page 66, cettefois le renforcement concerne la postcondition puisque la quantification provient de la conditiond’arrêt.

La nouvelle postcondition est obtenue en renforçant la postcondition initiale par le conjointsp ==

∑j∈0..i−1 T [j ], soit :

(i ∈ 0 .. N) and (∀j · (j ∈ 0 .. i − 1⇒ (∑

k∈0..j T [k ]) <= S)) and (sp ==∑

j∈0..i−1 T [j ]) and((i == N) or (

∑k∈0..i T [k ] > S)).

Soit encore, en appliquant une substitution convenable sur le dernier conjoint :

79 5.1. RECHERCHE LINÉAIRE BORNÉE

Postcondition : (i ∈ 0 .. N) and (∀j · (j ∈ 0 .. i − 1⇒ (∑

k∈0..j T [k ]) <= S)) and(sp ==

∑j∈0..i−1 T [j ]) and ((i == N) or (sp + T [i ] > S)).

Reprenons la construction à partir de cette nouvelle postcondition.

1. Invariant. On écarte le denier conjoint :

Invariant

(i ∈ 0 .. N) and (∀j · (j ∈ 0 .. i − 1⇒ (∑

k∈0..j T [k ]) <= S)) and (sp ==∑

j∈0..i−1 T [j ]).

2. Condition d’arrêt. On prend le dernier conjoint de la postcondition :

((i == N) or (sp + T [i ] > S))

3. Progression. La progression est spécifiée par :

{Precondition : (i ∈ 0 .. N) and (∀j · (j ∈ 0 .. i − 1⇒ (∑

k∈0..j T [k ]) <= S)) and(sp ==

∑j∈0..i−1 T [j ]) and not((i == N) or (sp + T [i ] > S))}

Progression

{Postcondition : (i ∈ 0 .. N) and (∀j · (j ∈ 0 .. i − 1⇒ (∑

k∈0..j T [k ]) <= S)) and(sp ==

∑j∈0..i−1 T [j ])}.

Une solution à cette spécification est :

1 sp = sp+T [ i ]

2 i = i+1

4. Initialisation. L’initialisation est spécifiée par :

{Précondition : (T est un tableau de N entiers naturels) and (N >= 0) and (S ∈ N)}

Initialisation

{Postcondition : (i ∈ 0 .. N) and (∀j · (j ∈ 0 .. i − 1⇒ (∑

k∈0..j T [k ]) <= S)) and(sp ==

∑j∈0..i−1 T [j ])}

qui a comme solution triviale :

1 i , sp = 0 ,0

5. Terminaison. N − i est une expression de terminaison convenable.

Au total, nous avons construit la solution suivante (la variable booléenne sss est calculée à la suitede la boucle avec les mêmes précautions que celles prises dans l’exercice 6, page 49) :

� �1 T = [ ... ]

2 N = l e n (T)

3 S = ...

CHAPITRE 5. EXERCICES DE SYNTHÈSE 80

4 #

5 # PRE :

6 # (T e s t un t a b l e a u de N e n t i e r s n a t u r e l s ) and (N >= 0) and (S ∈ N)

7 #

8 i , sp = 0 ,0

9 # i n v a r i a n t

10 # (i ∈ 0 .. N) and (∀j · (j ∈ 0 .. i − 1⇒ (∑

k∈0..j T [k ]) <= S)) and

11 # (sp ==∑

j∈0..i−1 T [j ])

12 #terminaison

13 # N − i

14 while not((i == N) or (sp + T [i ] > S)) :

15 sp = sp+T [ i ]

16 i = i+1

17 #end whi le

18 #

19 # POST :

20 # (i ∈ 0 .. N) and (∀j · (j ∈ 0 .. i − 1⇒ (∑

k∈0..j T [k ]) <= S)) and

21 # ((i == N) or (∑

k∈0..i T [k ] > S))

22 #

23 s s s = not ( i == N)

24 p r i n t ( s s s ) � �

Complexité. La complexité de cet algorithme est en O(N).

Ce qu’il faut retenir de cet exemple

1. Cet solution présente le défaut d’exiger un opérateur or court-circuit.

2. Il s’agit probablement de la solution la plus intuitive, en particulier lorsque l’on appliquel’heuristique du travail réalisé en partie. Elle doit également être bien maitrisée. Elle est àmême de fournir plus d’information que la précédente puisqu’elle calcule le plus petit i .

Exemple 13 : recherche linéaire bornée avec arrêt forcé

Peut-on se passer du court-circuit de la solution précédente ? La version suivante, connue sous lenom de « recherche linéaire bornée avec arrêt forcé » montre que oui. Son principe est le suivant :lorsque la solution existe et est découverte, on force N (ou plutôt une variable, sorte de double de N)à devenir i de sorte que l’arrêt se produise immédiatement. La spécification est la même que dans laversion précédente. En supposant le travail préliminaire réalisé, nous partons de l’énoncé suivant :

Énoncé 13

Construire le programme spécifié par :

Précondition : (T est un tableau de N entiers naturels) and (N >= 0) and (S constant) and(S ∈ N).

81 5.1. RECHERCHE LINÉAIRE BORNÉE

Postcondition : (i ∈ 0 .. N) and (∀j · (j ∈ 0 .. i − 1⇒ (∑

k∈0..j T [k ]) <= S)) and(sp ==

∑j∈0..i−1 T [j ]) and ((N == i) or (sp + T [i ] > S)).

On renforce (à nouveau) la postcondition en remplaçant le i de la formule (N == i) par unevariable fraîche n. On obtient :

Postcondition : (i ∈ 0 .. N) and (∀j · (j ∈ 0 .. i − 1⇒ (∑

k∈0..j T [k ]) <= S)) and(sp ==

∑j∈0..i−1 T [j ]) and ((N == n) or (sp+T [i ] > S)) and (n ∈ 0..N) and (n == i).

Sous forme de schéma, on est placé dans l’une des deux situations suivantes :

i/n0 N − 1

T

<= S

> S

sp ==∑

j∈0..i−1 T [j ]

ou :

0 i/N/n

T

<= S

sp ==∑

j∈0..i−1 T [j ]

On peut à présent développer la construction de la boucle :

1. Invariant. On écarte le dernier conjoint.

Invariant

(i ∈ 0..N) and (∀j ·(j ∈ 0.. i−1⇒(∑

k∈0..j T [k ]) <= S)) and (sp ==∑

j∈0..i−1 T [j ]) and((N == n) or (sp + T [i ] > S)) and (n ∈ 0 .. N).

2. Condition d’arrêt. Le conjoint écarté :

(n == i)

devient donc la condition d’arrêt.

3. Progression. La progression est spécifiée par :

CHAPITRE 5. EXERCICES DE SYNTHÈSE 82

{Precondition : (i ∈ 0 .. N) and (∀j · (j ∈ 0 .. i − 1⇒ (∑

k∈0..j T [k ]) <= S)) and(sp ==

∑j∈0..i−1 T [j ]) and ((N == n) or (sp + T [i ] > S)) and (n ∈ 0 .. N) and

not(n == i)}

Progression

{Postcondition : (i ∈ 0 .. N) and (∀j · (j ∈ 0 .. i − 1⇒ (∑

k∈0..j T [k ]) <= S)) and(sp ==

∑j∈0..i−1 T [j ]) and ((N == n) or (sp + T [i ] > S)) and (n ∈ 0 .. N)}.

Une solution à cette spécification est :

1 i f sp+T [ i ] > S :

2 n = i

3 e l s e :

4 sp = sp+T [ i ]

5 i = i+1

6 #end i f

4. Initialisation. L’initialisation est spécifiée par :

{Précondition : (T est un tableau de N entiers naturels) and (N >= 0) and (S ∈ N)}

Initialisation

{Postcondition : (i ∈ 0 .. N) and (∀j · (j ∈ 0 .. i − 1⇒ (∑

k∈0..j T [k ]) <= S)) and(sp ==

∑j∈0..i−1 T [j ]) and ((N == n) or (sp + T [i ] > S)) and (n ∈ 0 .. N)}.

qui a comme solution :

1 i , sp , n = 0 ,0 ,N

5. Terminaison. N − i est une expression de terminaison convenable.

Finalement, nous avons construit la solution suivante (la variable booléenne sss est calculée à lasuite de la boucle, selon la position relative de i et de N) :

� �1 T = [ ... ]

2 N = l e n (T)

3 S = ...

4 #

5 # PRE :

6 # (T e s t un t a b l e a u de N e n t i e r s n a t u r e l s ) and (N >= 0) and (S ∈ N)

7 #

8 i , sp , n = 0 ,0 ,N

9 # i n v a r i a n t

10 # (i ∈ 0 .. N) and (∀j · (j ∈ 0 .. i − 1⇒ (∑

k∈0..j T [k ]) <= S)) and

11 # (sp ==∑

j∈0..i−1 T [j ]) and ((N == n) or (sp + T [i ] > S)) and (n ∈ 0 .. N)

12 #terminaison

13 # N − i

14 while not(n == i) :

83 5.2. RECHERCHE DICHOTOMIQUE

15 i f sp+T [ i ] > S :

16 n = i

17 e l s e :

18 sp = sp+T [ i ]

19 i = i+1

20 #end i f

21 #end whi le

22 #

23 # POST :

24 # (i ∈ 0 .. N) and (∀j · (j ∈ 0 .. i − 1⇒ (∑

k∈0..j T [k ]) <= S)) and

25 # ((N == i) or (∑

k∈0..i T [k ] > S))

26 #

27 s s s = not (N == i )

28 p r i n t ( s s s ) � �

Complexité. La complexité de cet algorithme est en O(N).

Ce qu’il faut retenir de cet exempleCette solution marie les avantages des deux solutions précédentes : inutilité des opérateurs

court-circuit pour la première, obtention d’informations supplémentaires pour la seconde, pourun coût identique. Le principe de cette solution est également à connaitre.

5.2 Recherche dichotomique

Exemple 14 : recherche dichotomique itérative

Le principe de la recherche dichotomique dans un tableau (appelée parfois recherche binaire)consiste à couper l’intervalle de recherche en 2 jusqu’à ce que l’on puisse effectuer la recherchedirectement (par exemple sur un sous-tableau d’un seul élément). Plusieurs variantes existent (cf.[1, 2, 5, 6, 17]). Pour notre part, nous cherchons à construire une solution répondant à la spécificationsuivante (on recherche la position de la valeur V dans un tableau T non vide trié) :

Énoncé 14

Construire le programme spécifié par :

Précondition : (T est un tableau d’entiers constant de N entiers naturels, trié de manièreascendante) and (N >= 1) and (V constant) and (V ∈ N).

Postcondition : (i ∈ 0 .. N − 1) and ((V ∈ T [0 .. N − 1]) ⇒ (V == T [i ])).

Recherchons une forme constructive pour la postcondition.

(i ∈ 0 .. N − 1) and ((V ∈ T [0 .. N − 1]) ⇒ (T [i ]) == V )

== {T [i ]} == T [i .. i ]

(i ∈ 0 .. N − 1) and ((V ∈ T [0 .. N − 1]) ⇒ (V ∈ T [i .. i ]))

CHAPITRE 5. EXERCICES DE SYNTHÈSE 84

⇐ Renforcement

(i ∈ 0 .. N − 1) and (s ∈ i .. N − 1) and ((V ∈ T [0 .. N − 1])⇒ (V ∈ T [i .. s])) and (i == s).

Cette dernière formule permet de débuter la construction.

1. Invariant. Les trois premiers conjoints sont faciles à établir. On les conserve pour constituerl’invariant.

Invariant

(i ∈ 0 .. N − 1) and (s ∈ i .. N − 1) and ((V ∈ T [0 .. N − 1])⇒ (V ∈ T [i .. s])).

2. Condition d’arrêt. On reprend le conjoint écarté :

(i == s)

3. Progression. La progression est spécifiée par :

{Precondition : (i ∈ 0 .. N − 1) and (s ∈ i .. N − 1) and ((V ∈ T [0 .. N − 1])⇒ (V ∈

T [i .. s])) and not(i == s)}

Progression

{Postcondition : (i ∈ 0 .. N − 1) and (s ∈ i .. N − 1) and ((V ∈ T [0 .. N − 1])⇒ (V ∈

T [i .. s]))}.

D’après la précondition, le sous-tableau T [i .. s] contient au moins deux éléments. Il s’agit dedécouvrir un nouvel intervalle de recherche i .. s de sorte que la valeur s− i diminue strictement.Ainsi que l’illustre le schéma ci-dessous, une solution consiste à comparer V à la valeur T [m]

située au milieu du tableau. Si V > T [m] le nouvel intervalle est m + 1 .. s, si V <= T [m] lenouvel intervalle est i ..m. À condition de prendre pour m la valeur ⌊(i + s)/2⌋, la longueur del’intervalle s − i décroit strictement.

i sm

T

i sm

T

i s

T

V>T[m] V

<=T [m]

Notons que le fait que le tableau T soit trié est implicitement exploitée ici. Le code Python

correspondant est :

1 m = i n t (math . f l o o r ( ( i+s ) / 2 ) )

2 i f T [m]<V :

3 i = m + 1

4 e l s e :

5 s = m

6 #end i f

4. Initialisation. L’initialisation est spécifiée par :

85 5.2. RECHERCHE DICHOTOMIQUE

{Précondition : (T est un tableau d’entiers constant de N entiers naturels, trié demanière ascendante) and (N >= 1) and (V ∈ N)}

Initialisation

{Postcondition : (i ∈ 0 .. N − 1) and (s ∈ i .. N − 1) and ((V ∈ T [0 .. N − 1])⇒ (V ∈

T [i .. s]))}.

Cette spécification a comme solution :

1 i , s = 0 ,N−1

5. Terminaison. Dans la rubrique « progression », nous avons largement anticipé ce choix. L’expres-sion s − i convient.

Au final, nous avons construit la solution suivante :

� �1 import math

2 T = [ ... ]

3 N = l e n (T)

4 V = ...

5 #

6 # PRE :

7 # (T e s t un t a b l e a u d ’ e n t i e r s con s t an t de N e n t i e r s n a t u r e l s ,

8 # t r i é de maniè r e a s c endan t e) and (N >= 1) and (V ∈ N)

9 #

10 i , s = 0 ,N−1

11 # i n v a r i a n t

12 # (i ∈ 0 .. N − 1) and (s ∈ i .. N − 1) and ((V ∈ T [0 .. N − 1])⇒ (V ∈ T [i .. s]))

13 #terminaison

14 # s − i

15 while not(i == s) :

16 m = i n t (math . f l o o r ( ( i+s ) / 2 ) )

17 i f T [m]<V :

18 i = m + 1

19 e l s e :

20 s = m

21 #end i f

22 #end whi le

23 #

24 # POST :

25 # (i ∈ 0 .. N − 1) and ((V ∈ T [0 .. N − 1]) ⇒ (T [i ] == V ))

26 # � �

Si on le souhaite, à partir du résultat i , il est ensuite facile de déterminer si V est présent dans letableau ou non.

CHAPITRE 5. EXERCICES DE SYNTHÈSE 86

Complexité. L’intervalle de recherche est coupé en deux à chaque pas de progression. La complexitéde cet algorithme est donc en Θ(log2N).

Ce qu’il faut retenir de cet exemple

1. Pour cet exercice, la première difficulté est sa spécification. Si elle n’est pas fournie commeici, il est nécessaire d’y réfléchir posément avant de se lancer dans le développement.

2. Cet exercice est l’un des seuls du document à présenter une réelle difficulté dans la recherchede l’expression de terminaison et dans la démonstration de sa décroissance stricte. Uneerreur est facile à réaliser, mais aussi à détecter puisqu’elle se paye le plus souvent dès lapremière exécution par un bouclage infini. La corriger est plus délicat. La solution la plusraisonnable consiste à être vigilant et rigoureux dès la construction.

3. La recherche dichotomique s’applique à de nombreux autres domaines que celui de larecherche d’un élément dans un tableau. Compte tenu de l’efficacité de la méthode, c’estun paradigme de programmation qui mérite souvent d’être envisagé.

5.3 Un tri

Exemple 15 : le tri par sélection simple

Il n’est pas de bon manuel de programmation sans exercice ou exemple de tri. Nous avons choiside construire le tri par sélection simple. Ce choix permet de développer un programme présentantdes boucles imbriquées, il permet également de bien identifier le moment où l’on se dirige vers unesolution (le tri par sélection simple) plutôt qu’une autre (le tri par insertion simple par exemple).

Énoncé 15

Construire le programme spécifié par :

Précondition : (t est un tableau de N entiers naturels) and (S est le sac des valeurs contenuesdans t) and (N >= 1).

Postcondition : (t est trié par ordre croissant) and (S est le sac des valeurs contenues dans t).

De même que dans les exemples 4 et 5, en nous engageant sur l’utilisation exclusive d’échangespour modifier le tableau, on peut abandonner le conjoint qui exprime que globalement les valeursprésentes dans le tableau ne changent pas.

La postcondition n’est pas sous forme constructive. On la renforce afin de mettre en évidenceplusieurs conjoints :

Postcondition : (i ∈ 0 .. N) and (t[0 .. i − 1] est trié) and (i == N).

Nous sommes prêts à débuter la construction de cet algorithme.

Première tentative.

1. Invariant. Les deux premiers conjoints sont faciles à établir, on les conserve pour constituerl’invariant.

87 5.3. UN TRI

Invariant

(i ∈ 0 .. N) and (t[0 .. i − 1] est trié).

soit, sous forme graphique :

i0 N − 1

t

trié

2. Condition d’arrêt. On reprend le conjoint écarté :

(i == N)

3. Progression. La progression est spécifiée par :

{Précondition : (i ∈ 0 .. N) and (t[0 .. i − 1] est trié) and not(i == N)}

Progression

{Postcondition : (i ∈ 0 .. N) and (t[0 .. i − 1] est trié)}.

À ce stade du développement, nous sommes face à plusieurs options. Deux d’entre elles sedistinguent selon que l’on s’autorise, ou non, à modifier la tranche t[0 .. i − 1] dans le contextede la progression (selon que l’on considère que cette tranche est variable ou constante). Unesolution consiste à insérer t[i ] dans la partie déjà triée du tableau. Cette méthode s’appelle letri par insertion. Ce n’est pas celle que nous allons développer. Nous souhaitons choisir, parmiles valeurs restant à trier, celle qui se placera à la position i . Pour cela, puisque, dans cettehypothèse, nous ne nous autorisons pas à modifier la partie déjà triée, nous devons considérerque tous les éléments de la partie triée sont inférieurs ou égaux à tous les éléments de la partienon triée. Il restera à choisir pour la position i le plus petit élément de la partie non triée.

Seconde tentative. Elle est basée sur la postcondition suivante :

Postcondition : (i ∈ 0 .. N) and (t[0 .. i − 1] est trié) and ∀j · (j ∈ 0 .. i − 1 ⇒ ∀k · (k ∈

i .. N − 1⇒ t[j ] <= t[k ])) and (i == N − 1).

Cette postcondition mérite une explication. Pourquoi (i == N − 1) plutôt que (i == N) ? Cedernier conjoint est tout à fait correct mais on peut constater que si la tranche t[0 .. N − 2] est triéeet que t[N − 1] est une valeur supérieure ou égale à tous les éléments de t[0 ..N − 2], le tableau t esttrié.

1. Invariant. Les trois premiers conjoints sont faciles à établir, on les conserve pour constituer l’in-variant.

CHAPITRE 5. EXERCICES DE SYNTHÈSE 88

Invariant

(i ∈ 0..N) and (t[0.. i−1] est trié) and ∀j ·(j ∈ 0.. i−1⇒∀k ·(k ∈ i ..N−1⇒t[j ] <= t[k ])).

soit, sous forme graphique (sans le domaine de variation de i) :

i0 N − 1

t

trié

∀j · (j ∈ 0 .. i − 1⇒∀k · (k ∈ i .. N − 1⇒ t[j ] <= t[k ]))

2. Condition d’arrêt. On retient le conjoint écarté :

(i == N − 1)

3. Progression. La progression est spécifiée par :

{Précondition : (i ∈ 0 .. N) and (t[0 .. i − 1] est trié) and ∀j · (j ∈ 0 .. i − 1⇒∀k · (k ∈

i .. N − 1⇒ t[j ] <= t[k ])) and not(i == N − 1)}

Progression

{Postcondition : (i ∈ 0 ..N) and (t[0 .. i − 1] est trié) and ∀j · (j ∈ 0 .. i − 1⇒∀k · (k ∈

i .. N − 1⇒ t[j ] <= t[k ]))}.

Une solution consiste à identifier la position m du (de l’un des) minimum du sous-tableaut[i ..N − 1] avant d’échanger les éléments en position i et m. L’invariant est ensuite rétabli par :

i = i+1

4. Initialisation. L’initialisation est spécifiée par :

{Précondition : (t est un tableau de N entiers naturels) and (N >= 1)}

Initialisation

{Postcondition : (i ∈ 0 ..N) and (t[0 .. i − 1] est trié) and (∀j · (j ∈ 0 .. i − 1⇒∀k · (k ∈

i .. N − 1⇒ t[j ] <= t[k ])))}.

L’affectation

i = 0

répond à cette spécification : t[0 .. −1] est trié, et tous les éléments de ce sous-tableau sontinférieurs ou égaux à ceux de t[0 .. N − 1] 1.

1. On rappelle que, sur un domaine vide, toute formule quantifiée universellement est valide (elle s’évalue à True

pour toute substitution). Voir la propriété (1.6), page 9.

89 5.3. UN TRI

5. Terminaison. Si la recherche du minimum de t[i .. N − 1] se termine, il en est de même de laboucle en cours de construction. N − i est une expression de terminaison convenable.

Nous avons construit le programme (à raffiner) suivant :

1 t = [ ... ]

2 N = l e n ( t )

3 #

4 # PRE :

5 # (t e s t un t a b l e a u de N e n t i e r s n a t u r e l s ) and (N >= 1)

6 #

7 i = 0

8 # i n v a r i a n t

9 # (i ∈ 0 .. N) and (t[0 .. i − 1] e s t t r i é) and

10 # ∀j · (j ∈ 0 .. i − 1⇒∀k · (k ∈ i .. N − 1⇒ t[j ] <= t[k ]))

11 #terminaison

12 # N − i

13 while not(i == N − 1) :

14 "identifier la position m du minimum du sous-tableau t[i .. N − 1]"

15 t [ i ] , t [m] = t [m] , t [ i ]

16 i = i+1

17 #end whi le

18 #

19 # POST :

20 # (t e s t t r i é pa r o r d r e c r o i s s a n t )

21 #

22 p r i n t ( t )

Il nous reste maintenant à construire la boucle correspondant à la ligne "identifier la position m

du minimum du sous-tableau t[i .. N − 1]" du programme ci-dessus. Ce fragment de programme estspécifié par :

{Précondition : (i constant) and (i ∈ 0 .. N − 1) and (t[i .. N − 1] est un tableau d’entiers)}

"identifier la position m du minimum du sous-tableau t[i .. N − 1]"

{Postcondition : (m ∈ i .. N − 1) and (t[m] == minj∈i ..N−1(t[j ]))}.

La postcondition peut se schématiser par :

i0 N − 1m

t

t[m] == minj∈i ..N−1(t[j ])

On renforce cette postondition afin de la mettre sous forme constructive :

CHAPITRE 5. EXERCICES DE SYNTHÈSE 90

Postcondition (renforcement de la précédente) : (k ∈ i + 1 .. N) and (m ∈ i .. k − 1) and(t[m] == minj∈i ..k−1(t[j ])) and (k == N).

Nous sommes maintenant prêts pour réaliser le développement de la boucle interne.

1. Invariant. Les trois premiers conjoints sont faciles à établir, on les conserve pour constituer l’in-variant.

Invariant

(k ∈ i + 1 .. N) and (m ∈ i .. k − 1) and (t[m] == minj∈i ..k−1(t[j ])).

soit, sous forme graphique

i k0 N − 1m

t

t[m] == minj∈i ..k−1(t[j ])

2. Condition d’arrêt. On reprend le conjoint écarté, soit :

(k == N)

3. Progression. La progression est spécifiée comme suit :

{Précondition : (k ∈ i+1 ..N) and (m ∈ i ..k−1) and (t[m] == minj∈i ..k−1(t[j ])) andnot(k == N))}

Progression

{Postcondition : (k ∈ i + 1 .. N) and (m ∈ i .. k − 1) and (t[m] == minj∈i ..k−1(t[j ]))}.

Strictement parlant, progresser (vers la postcondition) va se faire en mettant à jour la variable m,si t[k ] se révèle être le plus petit élément sur t[i ..k ] ; rétablir l’invariant se fait en incrémentantla variable k . Le fragment de code correspondant est :

1 i f t [ k ] < t [m] :

2 m = k

3 #end i f

4 k = k+1

4. Initialisation. L’initialisation est spécifiée comme suit :

{Précondition : (i constant) and (i ∈ 0..N−1) and (t[i ..N−1] est un tableau d’entiers)}

Initialisation

{Postcondition : (k ∈ i + 1 .. N) and (m ∈ i .. k − 1) and (t[m] == minj∈i ..k−1(t[j ]))}.

91 5.3. UN TRI

Atteindre l’état (k == i +1) oblige à atteindre simultanément l’état (m == i), ce qui s’obtient(puisque i est considéré comme constant) par la séquence :

1 k ,m = i +1, i

5. Terminaison. La variable k est incrémentée de 1 à chaque pas de progression. L’expression N− k

est décroissante tout en restant positive ou nulle, ce qui assure la terminaison.

On a construit la boucle de recherche du minimum suivante :

1 #

2 # PRE :

3 # (i con s t an t ) and (i ∈ 0 .. N − 1) and (t[i .. N − 1] e s t un t a b l e a u d ’ e n t i e r s )

4 #

5 k ,m = i +1, i

6 # i n v a r i a n t

7 # (k ∈ i + 1 .. N) and (m ∈ i .. k − 1) and (t[m] == minj∈i ..k−1(t[j ]))

8 #terminaison

9 # N − k

10 while not(k == N) :

11 i f t [ k ] < t [m ] :

12 m = k

13 #end i f

14 k = k+1

15 #end whi le

16 #

17 # POST :

18 # (m ∈ i .. N − 1) and (t[m] == minj∈i ..N−1(t[j ]))

19 #

En intégrant cette itération ainsi que l’échange dans la première boucle, on obtient le programmefinal suivant :

� �1 t = [ ... ]

2 N = l e n ( t )

3 #

4 # PRE :

5 # (t e s t un t a b l e a u de N e n t i e r s n a t u r e l s ) and (N >= 1)

6 #

7 i = 0

8 # i n v a r i a n t

9 # (i ∈ 0 .. N) and (t[0 .. i − 1] e s t t r i é) and

10 # ∀j · (j ∈ 0 .. i − 1⇒∀k · (k ∈ i .. N − 1⇒ t[j ] <= t[k ]))

11 #terminaison

12 # N − i

13 while not(i == N − 1) :

14 k ,m = i +1, i

CHAPITRE 5. EXERCICES DE SYNTHÈSE 92

15 # i n v a r i a n t

16 # (k ∈ i + 1 .. N) and (m ∈ i .. k − 1) and (t[m] == minj∈i ..k−1(t[j ]))

17 #terminaison

18 # N − k

19 while not(k == N) :

20 i f t [ k ] < t [m] :

21 m = k

22 #end i f

23 k = k+1

24 #end whi le

25 t [ i ] , t [m] = t [m] , t [ i ]

26 i = i+1

27 #end whi le

28 #

29 # POST :

30 # (t e s t t r i é pa r o r d r e c r o i s s a n t )

31 #

32 p r i n t ( t ) � �

Complexité. La complexité de cet algorithme est en Θ(N2).

Remarque. Supposons à présent que nous ayons à traiter l’exemple suivant :

Énoncé 16

Construire le programme spécifié par :

Précondition : (t est un tableau de N entiers naturels) and (N >= 1) and (S est le sac desvaleurs contenues dans t) and (t est trié par ordre décroisant).

Postcondition : (t est trié par ordre croissant) and (S est le sac des valeurs contenues dans t).

Deux stratégies s’offrent à nous. Soit on développe l’algorithme de manière classique. Celui-ci serasans doute en Θ(N). Soit on affaiblit la précondition en supprimant le conjoint qui précise que letableau est trié par ordre décroissant. Cette modification de la précondition est légitime (cf. la règlede conséquence 2, page 123). Nous retombons alors dans la situation de l’exemple 15, pour laquellenous disposons d’une solution (en Θ(N2)).

Deux enseignements peuvent être tirés de cette réflexion. Le premier concerne la complexité desalgorithmes de tri fondés sur des comparaisons de « clés ». On lit trop souvent que les meilleursalgorithmes de ce type sont en Θ(N · ln(N)), oubliant de préciser le rôle que joue la préconditiondans ce résultat. Le second enseignement porte sur la stratégie de l’affaiblissement de la précondition.Celle-ci peut s’appliquer en toute sécurité par rapport à la correction du programme (malgré le risquede voir s’accroître la complexité). Compte tenu de notre décision de construire les programmes enremontant de la postcondition vers la précondition, s’orienter vers la technique du renforcement de lapostcondition est cependant plus naturel.

93 5.4. LE PLUS GRAND CARRÉ SOUS UN HISTOGRAMME

Ce qu’il faut retenir de cet exemple

1. Cet algorithme a été développé en raffinant une partie de la progression de la boucle externepar une seconde boucle, construite séparément. C’est une démarche de développement donton peut s’inspirer.

2. Il faut savoir bien gérer l’espace d’état. Sous cet aspect, il s’agit de l’exemple le plus com-plexe auquel nous avons été confrontés : l’espace d’état de la boucle externe est constituédu couple (i , t). Lors de la construction de la boucle interne, i et t[0 .. i−1] sont considéréscomme des constantes, elles laissent la place à l’espace d’état (k ,m). Situer relativementles domaines de variation des différentes variables et les « serrer » au maximum devientplus difficile tout en gardant toute son importance, notamment pour la construction desinitialisations. Le risque d’erreur est grand, la vigilance s’impose.

5.4 Le plus grand carré sous un histogramme

Exemple 17 : le plus grand carré, une version en Θ(N)

Jusqu’à présent les seuls renforcements exigés par la progression que nous avons traité se sontlimités à l’introduction de variables scalaires fraîches. Cet exemple, assez difficile à résoudre, estl’occasion de montrer qu’il est parfois nécessaire, pour obtenir des algorithmes efficaces, de renforcerl’invariant par un tableau.

Définition 2 (d’un histogramme). Soit T un tableau de M entiers relatifs. Son histogramme H

est un tableau qui, pour chaque valeur v pouvant apparaitre dans T , comptabilise le nombre de

fois où v apparait dans T .

Considérons par exemple tableau T ci-dessous, à valeurs dans l’intervalle 0 .. 5 :

1 3 3 3 2 0 5 3 2 5

0 1 2 3 4 5 6 7 8 9

T

L’histogramme H de T est un tableau défini sur l’intervalle 0 .. 5 et à valeurs dans 0 .. 10 :

1 1 2 4 0 2

0 1 2 3 4 5

H

Un tel tableau se représente souvent graphiquement de la manière suivante :

0 1 2 3 4 5

0

1

2

3

4

H

CHAPITRE 5. EXERCICES DE SYNTHÈSE 94

On peut noter (c’est une remarque qui trouve son application ci-dessous) qu’il est possible de définirl’histogramme d’un histogramme. L’histogramme H ′ de H est :

0 1 2 3 4

0

1

2

H ′

Il est facile de constater que, pour tout histogramme de N colonnes, il est impossible d’y placerun carré dont le côté serait supérieur à N . Afin de nous simplifier la tâche nous introduisons cettecontrainte dans la précondition. Le cas d’un histogramme quelconque ne poserait aucune difficulté :il suffirait d’effecteur un prétraitement. Cette généralisation n’est pas abordée ici.

Énoncé 17

Construire le programme spécifié par :

Précondition : (H est un tableau constant défini sur l’intervalle 0 .. N − 1 et à valeur dans0 .. N) and (N >= 0). H représente un histogramme.

Postcondition : (c est le côté du plus grand carré qu’il est possible de placer sous l’histo-gramme H).

Dans l’histogramme ci-dessous, c = 3 est la valeur du côté du plus grand carré qu’il est possiblede placer sous H . En général plusieurs carrés peuvent correspondre à la solution recherchée mais lavaleur c est unique.

0 1 2 3 4 5 6 7 8 9 10 11 12

0

1

2

3

4

5

Dans le schéma ci-dessus, deux carrés répondant au problème posé sont mis en évidence. Un troisièmecarré existe, son coin inférieur gauche est à l’abscisse 7. Notons par ailleurs que le côté « sud » d’untel carré se superpose toujours à l’axe des abscisses.

Dans l’état actuel la postcondition n’est pas sous forme constructive. On la renforce afin de mettreen évidence plusieurs conjoints :

Postcondition : (c est le côté du plus grand carré qu’il est possible de placer sous l’histo-gramme H [0 .. i − 1]) and (i ∈ 0 .. N) and (i == N).

Nous sommes prêts à débuter la construction de cet algorithme.

Première tentative.

1. Invariant. Les deux premiers conjoints sont faciles à établir, on les conserve pour constituerl’invariant.

95 5.4. LE PLUS GRAND CARRÉ SOUS UN HISTOGRAMME

Invariant

(c est le côté du plus grand carré qu’il est possible de placer sous l’histogramme H [0 .. i −

1]) and (i ∈ 0 .. N).

Soit sous la forme d’un schéma :

H

0 i N − 1

c

2. Condition d’arrêt. On prend le conjoint écarté :

(i == N).

3. Progression. La progression est spécifiée par :

{Précondition : (c est le côté du plus grand carré qu’il est possible de placer sousl’histogramme H [0 .. i − 1]) and (i ∈ 0 .. N) and not(i == N)}

Progression

{Postcondition : (c est le côté du plus grand carré qu’il est possible de placer sousl’histogramme H [0 .. i − 1]) and (i ∈ 0 .. N)}.

H [i ] ne peut améliorer le résultat c courant que si H [i ] > c , auquel cas il faut rechercher leplus grand s tel que s serait le côté du plus grand carré dont le côté « est » coïncide avec lafrontière séparant les colonnes i − 1 et i . s peut ou non devenir le nouveau c . Le calcul de s

peut s’effecteur par une itération, cependant, le coût occasionné par cette solution nous incite àrechercher une amélioration.

Seconde tentative.

1. Invariant. On renforce l’invariant précédent en supposant que l’on connaisse le côté du plus grandcarré de l’histogramme H [0 .. i−1] calé sur la frontière séparant les colonnes i−1 et i (dénomméci-dessous le « carré auxiliaire », par opposition au « carré principal »).

Invariant

(c est le côté du plus grand carré qu’il est possible de placer sous l’histogramme H [0 .. i −

1]) and (i − a est le côté du plus grand carré de H [0 .. i − 1] immédiatement à la gauchede la colonne i) and (0 <= a <= i <= N).

Soit sous forme graphique :

CHAPITRE 5. EXERCICES DE SYNTHÈSE 96

H

0 a i N − 1

c

2. Condition d’arrêt. La condition d’arrêt est inchangée :

(i == N) 2

3. Progression. La progression est spécifiée par :

{Précondition : (c est le côté du plus grand carré qu’il est possible de placer sous l’histo-grammeH [0..i−1]) and (i−a est le côté du plus grand carré de H [0..i−1] immédiatementà la gauche de la colonne i) and (0 <= a <= i <= N) and not(i == N)}

Progression

{Postcondition : (c est le côté du plus grand carré qu’il est possible de placer sous l’histo-grammeH [0..i−1]) and (i−a est le côté du plus grand carré de H [0..i−1] immédiatementà la gauche de la colonne i) and (0 <= a <= i <= N)}.

Trois cas sont à considérer.

1. Le carré auxiliaire peut s’élargir (de 1) vers i et peut également s’agrandir en hauteur :

a i

avant

a i

après

La valeur de c doit alors être mise à jour. La condition liée à cette progression est lasuivante :

(H [i ] >= i − a+ 1) and (min(H [a .. i − 1]) >= i − a+ 1).

Le premier conjoint signifie qu’il y a de la place dans la colonne i pour élargir, tandis quele second conjoint rend compte du fait qu’il y a de la place au-dessus de l’ancien carréauxiliaire pour agrandir en hauteur. Notons que cette condition sera à raffiner puisqu’ellefait usage d’un quantificateur.

2. La hauteur de la colonne i ne permet pas au carré de s’élargir :

2. Voir la note en bas de page de l’exercice 8, page 58 pour une remarque concernant une autre condition d’arrêt

possible.

97 5.4. LE PLUS GRAND CARRÉ SOUS UN HISTOGRAMME

a i

avant

a i

après

La condition associée à cette progression peut s’exprimer par :

H [i ] < i − a+ 1.

La variable a doit alors être mise à jour :

a = i−H [ i ]+1

Par contre c est inchangé car l’aire du carré principal n’augmente pas.

3. La hauteur de la colonne H [i ] permettrait d’élargir le carré auxiliaire mais il n’y a pas deplace au « nord » du carré auxiliaire existant : le carré auxiliaire se décale vers la droitesans changer d’aire.

a i

avant

a i

après

La condition peut s’exprimer par :

(H [i ] >= i − a+ 1) and (min(H [a .. i − 1]) < i − a+ 1).

La variable a doit être mise à jour mais l’aire du carré principal ne change pas : c n’est pasmodifié.

Au final, en utilisant explicitement le quantificateur min (cf. section 1.2.6, page 10) qui reste àraffiner, la progression s’écrit :

1 i f (H [ i ]>= i−a+1) and (min(H [ a.. i −1]) >= i−a+1) :

2 c = max ( c , i−a+1)

3 i = i+1

4 e l i f H [ i ]< i−a+1 :

5 a = i−H [ i ]+1

6 i = i+1

7 e l s e :

8 a = a+1

9 i = i+1

10 #end i f

CHAPITRE 5. EXERCICES DE SYNTHÈSE 98

4. Initialisation. L’affectation suivante établit l’invariant :

a , i , c = 0 ,0 ,0

5. Terminaison. N− i est une expression entière qui décroit strictement à chaque pas de progression,elle garantit la terminaison.

Au total, on a construit la version suivante (qui reste à raffiner) :

1 H = [ ... ]

2 N = l e n (H)

3 #

4 # PRE :

5 # (H e s t un histogramme de N e n t i e r s n a t u r e l s ) and (N >= 0)

6 #

7 a , i , c = 0 ,0 ,0

8 # i n v a r i a n t

9 # (c e s t l e cô té du p l u s grand c a r r é qu ’ i l e s t p o s s i b l e de p l a c e r

10 # sous l ’ h i s togramme H [0 .. i − 1]) and (i − a e s t l e cô té du p l u s grand

11 # ca r r é de H [0 .. i − 1] imméd ia tement à l a gauche de l a co l onne i)

12 # and (0 <= a <= i <= N) .

13 #terminaison

14 # N − i

15 while not(i == N) :

16 i f (H [ i ]>= i−a+1) and (min(H [ a.. i −1]) >= i−a+1) :

17 c = max ( c , i−a+1)

18 i = i+1

19 e l i f H [ i ]< i−a+1 :

20 a = i−H [ i ]+1

21 i = i+1

22 e l s e :

23 a = a+1

24 i = i+1

25 #end i f

26 #end whi le

27 #

28 # POST :

29 # (c e s t l e cô té du p l u s grand c a r r é s i t u é sous l ’ h i s togramme H)

30 #

31 p r i n t ( c )

Raffiner cette solution consiste à faire disparaitre le quantificateur min. La première solution quivient à l’esprit (et qui a été appliquée avec succès dans les exemples précédents à plusieurs occasions)consisterait à renforcer l’invariant en introduisant une variable fraîche m telle que :

m == min(H [a .. i − 1]).

Cependant, cette fois, cette solution est mise en échec. En effet, imaginons que nous nous apprêtionsà exécuter la dernière branche de l’alternative et que la situation courante soit la suivante :

99 5.4. LE PLUS GRAND CARRÉ SOUS UN HISTOGRAMME

a/m i

0

1

2

3

4

5

6

Ainsi que le montre le schéma suivant, la nouvelle valeur de m ne peut être obtenue à partir de cellesde l’ancienne et de H [i ] :

a m i

0

1

2

3

4

5

6

La découverte d’une solution efficace passe par le renforcement de l’invariant par un tableau. Soitf , défini sur l’intervalle 0 .. N , l’histogramme de H [a .. i − 1].

Exemple. Si l’on s’intéresse à la portion d’histogramme H [a .. i − 1] définie par :

H

a i

0

1

2

3

4

5

alors l’histogramme f se présente comme suit :

f

0 1 2 3 4 5 6 7

0

1

2

3

Ainsi par exemple, sur H [a..i−1], il y a 3 colonnes de hauteur 2, d’où, par exemple, dans l’histogrammef , la colonne f [2] = 3.

CHAPITRE 5. EXERCICES DE SYNTHÈSE 100

Exemple. Montrons comment il est possible d’utiliser le tableau f pour raffiner l’expression min.Soit le carré grisé suivant, de côté 4, entre a et i − 1 :

H

a i

0

1

2

3

4

5

6

À cet instant de l’exécution, l’histogramme f vaut :

f

0 1 2 3 4 5 6 7

0

1

2

3

On sait déjà que min(H [a .. i − 1]) >= i − a puisque le carré grisé existe. Pour savoir s’il est possibled’agrandir ce dernier en hauteur, il suffit d’évaluer f [i − a] (soit f [4] ici). Si le résultat est 0 c’estqu’aucune colonne de H [a .. i − 1] n’a exactement 4 comme hauteur. Il y a donc de la place pouragrandir le carré. Dans le cas contraire c’est impossible.

La mise à jour de f doit prendre en compte les trois cas de la progression. Si a progresse de 1,il faut diminuer f [H [a]] de 1, si i progresse de 1, il faut augmenter f [H [i ]] de 1. Si a progresse d’unevaleur quelconque, il faut diminuer chaque position correspondante de f de 1. On est maintenant prêtpour effectuer la troisième (et dernière) tentative de développement.

Troisième tentative.

1. Invariant. L’invariant est renforcé par la définition de f proposée ci-dessus.

Invariant

(c est le côté du plus grand carré qu’il est possible de placer sous l’histogramme H [0 .. i −

1]) and (i − a est le côté du plus grand carré de H [0 .. i − 1] immédiatement à la gauchede la colonne i) and (f est l’histogramme de H [a .. i − 1]) and (0 <= a <= i <= N).

2. Condition d’arrêt. La condition d’arrêt est inchangée :

(i == N)

3. Progression. La construction ayant déjà été largement esquissée, nous n’allons pas reprendre ledéveloppement depuis le début, nous allons considérer l’un après l’autre les trois cas déjà étudiés(un par branche de l’alternative triple), en nous focalisant sur les modifications induites par ledernier renforcement.

101 5.4. LE PLUS GRAND CARRÉ SOUS UN HISTOGRAMME

1. Le premier cas étudié (lignes 16 à 18 ci-dessus) se caractérise d’une part par l’existence dela quantification min qui a motivé le développement en cours, et d’autre part par le fait que,sur le plan de la mise à jour de f , i s’incrémente de 1. En conséquence, le second conjointde la condition est remplacé par (f [i − a] == 0) tandis que le rétablissement de l’invariantpour le renforcement effectué (l’introduction du conjoint lié à f ) se limite à incrémenterf [H [i ]] de 1. Soit au total :

1 i f (H [ i ]>= i−a+1) and ( f [ i−a ] == 0) :

2 c = max ( c , i−a+1)

3 f [H [ i ] ] = f [H [ i ] ]+1

4 i = i+1

2. Le second cas est celui ou le nouveau carré auxiliaire a comme côté H [i ], il diminue d’unevaleur comprise dans l’intervalle 1 .. a− i . C’est le cas le plus difficile à traiter car il exigeune boucle qu’il faut construire. Par contre la condition de l’alternative n’est pas affectée.Le fragment de code viendra s’insérer entre les lignes 19 et 20 du programme ci-dessus.

{Précondition : (f est l’histogramme de H [a .. i − 1])}

Progression, seconde partie

{Postcondition : (f est l’histogramme de H [i − H [i ] + 1 .. i − 1])}.

Renforçons la postcondition afin de la mettre sous forme constructive :

Postcondition : (f est l’histogramme de H [k .. i − 1]) and (k ∈ a .. i − H [i ] +

1) and (k == i − H [i ] + 1).

1. Invariant. On obtient un invariant en écartant le dernier conjoint :

Invariant

(f est l’histogramme de H [k .. i − 1]) and (k ∈ a .. i − H [i ] + 1).

Sous forme graphique la portion de l’histogramme H [k .. i − 1] se décrit par :

a k i

H

2. Condition d’arrêt. On reprend le conjoint écarté, soit :

(k == i − H [i ] + 1)

3. Progression. Il suffit de décrémenter f [H [k ]] de 1 avant de rétablir l’invariant en avan-çant la frontière k :

CHAPITRE 5. EXERCICES DE SYNTHÈSE 102

1 f [H [ k ] ] = f [H [ k ] ]−1

2 k = k+1

4. Initialisation. L’invariant est instauré par l’affectation :

k = a

5. Terminaison. L’expression i − H [i ] + 1 − k décroit de 1 à chaque pas de progressiontout en restant positive ou nulle.

Nous reviendrons sur l’aspect complexité plus tard. Au total nous obtenons le fragment decode suivant comme seconde branche de l’alternative :

1 e l i f (H [ i ]< i−a+1) :

2 k = a

3 # i n v a r i a n t

4 # (f e s t l ’ h i s togramme de H [k .. i − 1]) and (k ∈ a .. i − H [i ] + 1)

5 #terminaison

6 # i − H [i ] + 1− k

7 while not(k == i − H [i ] + 1) :

8 f [H [ k ] ] = f [H [ k ] ]−1

9 k = k+1

10 #end whi le

11 a = i−H [ i ]+1

12 i = i+1

3. Le troisième cas est simple à traiter. En effet il se traduit par un décalage du carré auxiliaire.Pour ce qui concerne le maintien du « nouveau » conjoint de l’invariant, il suffit d’insérer,entre les lignes 22 et 23 du programme ci-dessus, le fragment de code qui diminue de 1 lavaleur de f [H [a]] et augmente de 1 la valeur de f [H [i ]]. Soit au total pour cette branche del’alternative :

1 e l s e :

2 f [H [ a ] ] = f [H [ a ] ]−1

3 f [H [ i ] ] = f [H [ i ] ]+1

4 a = a+1

5 i = i+1

6 #end i f

Le fragment de code

1 f [H [ i ] ] = f [H [ i ] ]+1

2 i = i+1

est commun aux trois branches de l’alternative. Il peut être factorisé à la fin de la progression.

4. Initialisation. L’initialisation précédente doit être complétée par l’initialisation de l’histogrammef . Celle-ci s’effectue par une boucle (qui n’est pas construite ici) qui place dans f l’histogrammede H [0 ..−1], autrement dit, qui initialise f à 0. Au total on a donc :

103 5.4. LE PLUS GRAND CARRÉ SOUS UN HISTOGRAMME

1 j = 0

2 # i n v a r i a n t

3 # (f [0 .. j − 1 == 0) and (j ∈ 0 .. N)

4 #terminaison

5 # i − H [i ] + 1− k

6 while not(j == N) :

7 f [ j ] = 0

8 j = j+1

9 #end whi le

10 a , i , c = 0 ,0 ,0

5. Terminaison. Les deux boucles se terminent, il en est donc de même du programme complet.

Au final nous avons construit le programme suivant :

� �1 H = [ ... ]

2 N = l e n (H)

3 #

4 # PRE :

5 # (H e s t un histogramme de N e n t i e r s n a t u r e l s ) and (N >= 0)

6 #

7 f = [ ... ]

8 j = 0

9 # i n v a r i a n t

10 # (f [0 .. j − 1] == 0) and (j ∈ 0 .. N)

11 #terminaison

12 # N − j

13 while not(j == N) :

14 f [ j ] = 0

15 j = j+1

16 #end whi le

17 a , i , c = 0 ,0 ,0

18 # i n v a r i a n t

19 # (c e s t l e cô té du p l u s grand c a r r é qu ’ i l e s t p o s s i b l e de p l a c e r

20 # sous l ’ h i s togramme H [0 .. i − 1]) and (i − a e s t l e cô té du p l u s grand

21 # ca r r é de H [0 .. i − 1] imméd ia tement à l a gauche de l a co l onne i)

22 # and (f e s t l ’ h i s togramme de H [a .. i − 1]) and (0 <= a <= i <= N) .

23 #terminaison

24 # N − i

25 while not(i == N) :

26 i f (H [ i ]>= i−a+1) and ( f [ i−a ] == 0) :

27 c = max ( c , i−a+1)

28 e l i f (H [ i ]< i−a+1) :

29 k = a

30 # i n v a r i a n t

31 # (f e s t l ’ h i s togramme de H [k .. i − 1]) and (k ∈ a .. i − H [i ] + 1)

32 #terminaison

CHAPITRE 5. EXERCICES DE SYNTHÈSE 104

33 # i − H [i ] + 1− k

34 while not(k == i − H [i ] + 1) :

35 f [H [ k ] ] = f [H [ k ] ] −1

36 k = k+1

37 #end whi le

38 a = i−H [ i ]+1

39 e l s e :

40 f [H [ a ] ] = f [H [ a ] ]−1

41 a = a+1

42 #end i f

43 f [H [ i ] ] = f [H [ i ] ] +1

44 i = i+1

45 #end whi le

46 #

47 # POST :

48 # (c e s t l e cô té du p l u s grand c a r r é s i t u é sous l ’ h i s togramme H)

49 #

50 p r i n t ( c ) � �

Complexité. L’imbrication de deux boucles comme dans le code ci-dessus est souvent à l’origined’une complexité quadratique. Pourtant ça n’est pas le cas ici, en effet, nous observons que :

1. la boucle interne est telle que, si l’on alignait par la pensée toutes ses exécutions, la variable k

parcourrait au pire l’intervalle 0 .. N − 1, sans aucun recouvrement,

2. la variable i quant à elle parcourt exactement l’intervalle 0 .. N ,

3. chaque élément de H est traité au plus deux fois pour ce qui concerne sa relation avec le tableauf : une fois pour incrémenter l’un des f [k ] et au plus une autre fois pour le décrémenter.

Nous pouvons en déduire que la complexité de cette solution est en Θ(N).

Ce qu’il faut retenir de cet exemple

1. Renforcer l’invariant par une variable scalaire n’est pas toujours couronné de succès. Onpeut alors se tourner vers un renforcement par un tableau mais chaque situation est un casparticulier et, comme le montre cet exemple il faut parfois déployer des trésors d’ingéniositépour obtenir une solution efficace.

2. L’existence de deux boucles imbriquées ne signifie pas complexité non linéaire. Là encoreil faut étudier chaque cas séparément.

Exercices

Exercice 21 : Comparaison de deux tableaux

Précondition : Soit S et T deux tableaux d’entiers constants de N (N >= 0) éléments

105 5.4. LE PLUS GRAND CARRÉ SOUS UN HISTOGRAMME

Postcondition : La variable booléenne egaux est vraie si et seulement si les deux tableaux sontégaux.

Trois types de solutions sont possibles : l’une où le booléen fait partie de la construction de laboucle, la seconde mettant en œuvre la recherche avec arrêt au plus tôt et la troisième avec arrêtforcé. Développer les trois solutions dans cet ordre.

Exercice 22 : Esope reste ici et se repose

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers constants de N (N >= 0) éléments

Postcondition : La variable booléenne palind est vraie si et seulement si le tableau se lit dela même façon de la gauche vers le droite et de la droite vers la gauche (c’est-à-dire si letableau représente un palindrome).

Trois types de solutions sont possibles : l’une où le booléen fait partie de la construction de laboucle, la seconde mettant en œuvre la recherche avec arrêt au plus tôt et la troisième avec arrêtforcé. Développer les trois solutions dans cet ordre.

Exercice 23 : Le problème de Rubin

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers constants de L (L >= 1) lignes et C (C >= 1)

colonnes.

Postcondition : Le booléen lz est vrai si et seulement s’il existe (au moins) une ligne constituéeuniquement de zéros dans T .

On recherche de préférence des solutions construites sur une seule boucle dans laquelle on passeau plus L ∗ C fois.Trois types de solutions sont possibles : l’une où le booléen fait partie de la construction de laboucle, la seconde mettant en œuvre la recherche avec arrêt au plus tôt et la troisième avec arrêtforcé. Développer les trois solutions dans cet ordre.

Exercice 24 : Olympiades de la programmation 2005

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers constants de N (N >= 3) éléments. Soit K ∈

1 .. N − 2.

Postcondition : Le booléen mil est vrai si et seulement s’il existe un couple de valeurs i et s

(i != s) tel que T [K ] est exactement le milieu de l’intervalle T [i ] .. T [s].

Trois types de solutions sont possibles : l’une où le booléen fait partie de la construction de laboucle, la seconde mettant en œuvre la recherche avec arrêt au plus tôt et la troisième avec arrêtforcé. Développer les trois solutions dans cet ordre.On recherche des solutions construites sur une seule boucle dans laquelle on passe au plus N

CHAPITRE 5. EXERCICES DE SYNTHÈSE 106

fois.

Exercice 25 : Un problème de minmax

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers constants de N (N >= 1) éléments.

Postcondition : Le booléen minAvantMax est vrai si et seulement si aucun exemplaire du pluspetit élément de T n’est situé après la première occurrence du plus grand élément.

Trois types de solutions sont possibles : l’une où le booléen fait partie de la construction de laboucle, la seconde mettant en œuvre la recherche avec arrêt au plus tôt et la troisième avec arrêtforcé. Développer les trois solutions dans cet ordre.On recherche des solutions construites sur une seule boucle dans laquelle on passe au plus N

fois.

Exercice 26 : Tous les mêmes

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers constants de N (N >= 1) éléments

Postcondition : La variable booléenne ident est vraie si et seulement si tous les éléments dutableau sont identiques.

Trois types de solutions sont possibles : l’une où le booléen fait partie de la construction de laboucle, la seconde mettant en œuvre la recherche avec arrêt au plus tôt et la troisième avec arrêtforcé. Développer les trois solutions dans cet ordre.

Exercice 27 : Recherche linéaire dans un tableau à deux dimensions

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers constants de L (L >= 1) lignes et C (C >= 1)

colonnes. Soit V une constante entière. V est présent dans le tableau (il existe au moinsune occurrence).

Postcondition : x et y sont les coordonnées d’une (quelconque) occurrence de V : T [x ][y ]== V .

Exercice 28 : Gammes sur 7 et 23 (1)

Construire le programme spécifié par :

Précondition : Soit t un tableau d’entiers variable de N (N >= 0) éléments.

Postcondition : t est une « permutation » des valeurs initiales telle que tous les 7 sont avanttous les 23. Cette permutation ne diffère de la configuration initiale que par l’emplacementdes valeurs de 7 et 23.

107 5.4. LE PLUS GRAND CARRÉ SOUS UN HISTOGRAMME

On recherche une solution construite sur une seule boucle dans laquelle on passe au plus N fois.

Exercice 29 : Gammes sur 7 et 23 (2)

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers constants de N (N >= 0) éléments.

Postcondition : Le booléen existe7Avant23 est vrai si et seulement s’il existe au moins uneoccurrence de la valeur 23 dans T et s’il y a au moins un 7 avant la première occurrencela valeur de 23.

Trois types de solutions sont possibles : l’une où le booléen fait partie de la construction de laboucle, la seconde mettant en œuvre la recherche avec arrêt au plus tôt et la troisième avec arrêtforcé. Développer les trois solutions dans cet ordre.On recherche des solutions construites sur une seule boucle dans laquelle on passe au plus N

fois.

Exercice 30 : Gammes sur 7 et 23 (3)

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers constants de N (N >= 0) éléments.

Postcondition : Le booléen un7Avant23 est vrai si et seulement s’il existe au moins une oc-currence de la valeur 23 dans T et s’il y a exactement un 7 avant la première occurrencede la valeur 23.

Trois types de solutions sont possibles : l’une où le booléen fait partie de la construction de laboucle, la seconde mettant en œuvre la recherche avec arrêt au plus tôt et la troisième avec arrêtforcé. Développer les trois solutions dans cet ordre.On recherche des solutions construites sur une seule boucle dans laquelle on passe au plus N

fois.

Exercice 31 : Gammes sur 7 et 23 (4)

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers constants de N (N >= 0) éléments.

Postcondition : Le booléen pas7Avant23 est vrai si et seulement s’il existe au moins uneoccurrence de la valeur 23 dans T et s’il n’y a pas de 7 avant la première occurrence de lavaleur 23.

Trois types de solutions sont possibles : l’une où le booléen fait partie de la construction de laboucle, la seconde mettant en œuvre la recherche avec arrêt au plus tôt et la troisième avec arrêtforcé. Développer les trois solutions dans cet ordre.On recherche des solutions construites sur une seule boucle dans laquelle on passe au plus N

fois.

CHAPITRE 5. EXERCICES DE SYNTHÈSE 108

Exercice 32 : Gammes sur 7 et 23 (5)

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers constants de N (N >= 0) éléments.

Postcondition : Le booléen existe7Et23 est vrai si et seulement s’il existe au moins une occur-rence de chacune de ces valeurs dans T .

Trois types de solutions sont possibles : l’une où le booléen fait partie de la construction de laboucle, la seconde mettant en œuvre la recherche avec arrêt au plus tôt et la troisième avec arrêtforcé. Développer les trois solutions dans cet ordre.On recherche des solutions construites sur une seule boucle dans laquelle on passe au plus N

fois.

Exercice 33 : Gammes sur 7 et 23 (6)

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers constants de N (N >= 0) éléments.

Postcondition : Le booléen existeSoit7Soit23 est vrai si et seulement s’il existe au moins uneoccurrence de l’une des valeurs 7 ou 23 dans T .

Trois types de solutions sont possibles : l’une où le booléen fait partie de la construction de laboucle, la seconde mettant en œuvre la recherche avec arrêt au plus tôt et la troisième avec arrêtforcé. Développer les trois solutions dans cet ordre.On recherche des solutions construites sur une seule boucle dans laquelle on passe au plus N

fois.

Exercice 34 : Gammes sur 7 et 23 (7)

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers constants de N (N >= 0) éléments.

Postcondition : Le booléen altern7Et23 est vrai si et seulement si les valeurs 7 et 23 alternentdans cet ordre dans T .

Trois types de solutions sont possibles : l’une où le booléen fait partie de la construction de laboucle, la seconde mettant en œuvre la recherche avec arrêt au plus tôt et la troisième avec arrêtforcé. Développer les trois solutions dans cet ordre.On recherche des solutions construites sur une seule boucle dans laquelle on passe au plus N

fois.

Exercice 35 : Tri par insertion simple

Construire le programme spécifié par :

Précondition : Soit t un tableau d’entiers de N (N >= 1) éléments.

109 5.4. LE PLUS GRAND CARRÉ SOUS UN HISTOGRAMME

Postcondition : t est un tableau trié et les valeurs présentes dans t sont une « permutation »des valeurs initiales.

On s’inspire du tri par sélection simple de la section 5.3, page 86, mais lorsqu’il s’agit de prendret[i ] en compte on l’insère dans le sous-tableau trié t[0 .. i − 1].

Exercice 36 : On a trouvé dans une poubelle

On a trouvé le texte suivant dans une poubelle de l’Enssat :

Précondition : Soit T un tableau constant d’entiers, de N (N >= 0) éléments.

Postcondition : La variable ez est vraie si et seulement s’il existe (au moins) un zéro dansT .

1. Invariant :(i ∈ 0 .. N − 1) and ∀j · (j ∈ 0 .. i − 1⇒ T [j ] != 0)

2. Condition d’arrêt :(i == N − 1) or (T [i ] == 0)

3. Progression :

i = i + 1

4. Initialisation :

i = 0

5. Terminaison : (inachevé)

Puis, après la boucle : ez = not(i == N)

1. À votre avis quelle(s) erreur(s) justifie(nt) ce rejet ?

2. Fournir une version correcte.

Exercice 37 : Multiplicité du maximum

Construire le programme spécifié par :

Précondition : Soit T un tableau d’entiers constants de N (N >= 1) éléments.

Postcondition : La variable m représente le nombre d’occurrences du plus grand élément deT : m == #j · (j ∈ 0 .. N − 1 | T [j ] == max k · (k ∈ 0 .. N − 1 | T [k ]))

On recherche une solution construite sur une seule boucle dans laquelle on passe au plus N fois.

Exercice 38 : p-compatibilité

Deux tableaux d’entiers constants T1 et T2 de N (N >= 1) éléments chacun sont p-compatiblessi le sous-tableau T1[N − p .. N − 1] et le sous-tableau T2[0 .. p − 1] sont égaux et s’il n’existepas de valeur q supérieure à p telle que T1 et T2 possèdent cette propriété.

CHAPITRE 5. EXERCICES DE SYNTHÈSE 110

Construire le programme spécifié par :

Précondition : Soit T1 et T2 deux tableaux d’entiers constants de N (N >= 0) élémentschacun.

Postcondition : La variable p représente le degré de compatibilité de T1 et de T2.

On recherche une solution construite sur une seule boucle dans laquelle on passe au plus N fois.

Exemples.

1 4 2 3T1

2 3 0 8T2

2-compatibles

1 4 2 3T1

5 6 0 8T2

0-compatibles

1 4 2 3T1

2 3 0 8T2

4-compatibles

Exercice 39 : L’élément majoritaire

Définition 3 (Élément majoritaire). Soit S un sac de N éléments. L’élément m est majo-

ritaire dans S si sa multiplicité dans S est supérieure à ⌊N/2⌋+ 1. Le sac S est alors dit

majoritaire.

Le but de cet exercice est de déterminer si S est ou non majoritaire et si oui, quel est son élémentmajoritaire.

Précondition : S est un sac constant de N éléments (N >= 0).

Postcondition : Le couple (m, nbc) est tel que, s’il existe un élément majoritaire c’est m et samultiplicité est nbc , sinon, m est quelconque et nbc vaut 0.

Il est facile de construire un algorithme naïf dont la complexité au pire est en N2 en prenantun élément dans S et en dénombrant sa multiplicité jusqu’à soit trouver l’élément majoritairesoit avoir épuisé S . Une approche alternative consiste à trier les éléments de S pour détecter s’ilexiste ou non une séquence d’éléments identiques de multiplicité suffisante. Une telle solutiona une complexité en N · log2(N). On va chercher une solution dont la complexité est en Θ(N),donc linéaire.

On remarque tout d’abord que si S possède un élément majoritaire m et qu’on procède parappariement de ce dernier avec un élément différent, un reliquat se forme contenant un certainnombre d’exemplaires de m. Ainsi, si S contient 4 "a", 1 "b", 1 "c" et 1 "d", on forme parexemple les paires ("a", "b"), ("a", "c") et ("a", "d"), le reliquat contenant un exemplaire de"a" (on obtiendrait un reliquat contenant 3 "a" si on appariait "b" et "c" d’une part, "a" et "d"d’autre part).

Qu’advient-il de l’appariement quand on ignore si S est majoritaire ou non, autrement dit avecle seul critère d’appariement de valeurs différentes ? Prenons le cas d’un sac S majoritaire conte-nant 5 "a", 2 "b" et 2 "c". Si l’appariement associe les "b" et les "c", le reliquat contient 5

111 5.4. LE PLUS GRAND CARRÉ SOUS UN HISTOGRAMME

"a" ; par contre, si on forme deux couples ("a", "b") et deux couples ("a", "c"), il reste unreliquat composé d’un seul "a". Ici encore, le reliquat contient l’élément majoritaire (en unnombre variable d’exemplaires selon l’appariement réalisé). Considérons maintenant un sac S

non majoritaire avec 3 "a", 2 "b" et 2 "c". Si on forme deux paires ("a", "b") et une paire("a", "c"), le reliquat contient un "c", pourtant il n’y a pas élément majoritaire. Avec un sac S

non majoritaire composé de 2 "a", 1 "b" et 1 "c", il est possible que l’appariement produise lescouples ("a", "b") et ("a", "c") et le reliquat est alors vide (ce qui témoigne alors de l’absenced’élément majoritaire).

L’affaire semble mal engagée puisqu’on ne peut rien conclure de la présence d’un reliquat nonvide . . . , sauf à procéder à l’affaiblissement de la postcondition de la manière suivante. Onrecherche non pas l’élément majoritaire mais un candidat majoritaire m qui est tel que, s’ilexiste un élément majoritaire c’est m, mais cet élément n’est pas forcément majoritaire. Cettesolution est fondée sur un partitionnement de S en 3 sacs r , p et c comme suit :

1. r est le sac des valeurs restant à traiter,

2. p est un sac de couples de valeurs qui est tel que, pour tout couple, les deux valeurs sontdifférentes,

3. c est le reliquat ou sac des « célibataires », toutes les valeurs de c sont identiques. C’est lesac contenant (s’il y a lieu) le candidat majoritaire.

Propriété 1. Si les sacs r et c sont vides alors S n’est pas majoritaire.

Propriété 2. Si le sac r est vide mais c ne l’est pas (soit m la valeur présente dans c)

alors :

1. si S possède un élément majoritaire, c’est la valeur m,

2. si S ne possède pas d’élément majoritaire, on ne peut rien affirmer à propos de m

(pas même que m est l’élément qui possède la plus grande multiplicité dans S).

Une conséquence de cette dernière propriété est que si le sac r est vide, la présence de la valeurm dans c ne prouve pas que m est élément majoritaire de S . On peut simplement affirmer quem est un candidat majoritaire : si S possède un élément majoritaire c’est obligatoirement m.Il faut alors vérifier si le candidat est majoritaire ou non.

Question 1. Démontrer les deux propriétés ci-dessus.

Question 2. Construire une version abstraite du programme.

Question 3. Choisir une mise en œuvre des sacs S , p, c et r (raffinement des structures dedonnées) afin d’obtenir un algorithme en Θ(N) exécutable en Python.

Exercice 40 : Tableaux cycliques : algorithme de Shiloach

Définition 4 (Tableau cyclique). Un tableau T [0 .. N − 1] non vide est dit cyclique si on

considère que l’élément qui suit T [N − 1] est T [0].

Construire le programme spécifié par :

Précondition : Soit A et B deux tableaux d’entiers cycliques de même longueur N (N >= 1).

CHAPITRE 5. EXERCICES DE SYNTHÈSE 112

Postcondition : La variable aEgb est vraie si les deux tableaux A et B sont égaux.

Exemples. Les deux tableaux cycliques :

A = [5, 0, 9, 3, 0, 0, 8, 0, 34] et B = [3, 0, 0, 8, 0, 34, 5, 0, 9]

sont égaux. Par contre les deux suivants :

A = [5, 0, 9, 3, 0, 1, 8, 0, 34] et B = [3, 0, 0, 8, 0, 34, 5, 0, 9]

ne le sont pas.Une solution naïve est en O(N2). Nous recherchons une solution en O(N).

Exercice 41 : Tri par sélection double

Précondition : Soit t un tableau d’entiers de N (N >= 0) éléments.

Postcondition : t est un tableau trié et les valeurs présentes dans t sont une « permutation »des valeurs initiales.

On souhaite construire cet algorithme de tri en se basant sur la méthode suivante : soit t[i .. s]

un sous-tableau de t, on recherche, par l’intermédiaire d’une (seule) itération le plus grand etle plus petit élément de t[i .. s], valeurs que l’on place (par des échanges) respectivement auxpositions d’indices i et s, puis on réitère si nécessaire sur le sous-tableau t[i + 1 .. s − 1].

Exercice 42 : Tri décroissant croisant

Construire le programme spécifié par :

Précondition : Soit t un tableau d’entiers relatifs de N (N >= 0) éléments.

Postcondition : t est un tableau qui préserve le sac des valeurs initiales et qui est tel que :

1. les valeurs négatives apparaissent dans l’orde décroissant,

2. les valeurs positives ou nulles apparaissent dans l’ordre croissant,

3. là où initialement il y avait une valeur négative (resp. positive ou nulle) il y a encoreune valeur négative (resp. positive ou nulle).

Exercice 43 : Recherche linéaire, le problème de Leino

Construire le programme spécifié par :

Précondition : Soit T un tableau constant d’entiers naturels de N (N >= 1) éléments. Il existeau moins un zéro dans T et deux valeurs consécutives varient au plus de 1.

Postcondition : i est les plus petit indice tel que T [i ] == 0.

D’après la règle de conséquence 2 de l’axiomatique de Hoare (cf. annexe C, page 123) on peutaffaiblir la précondition en supprimant le dernier conjoint. Une recherche séquentielle classiquesuffit alors, mais il est possible d’améliorer l’efficacité moyenne de l’algorithme en exploitant ce

113 5.4. LE PLUS GRAND CARRÉ SOUS UN HISTOGRAMME

troisième conjoint pour adopter un pas de recherche variable.

Exercice 44 : Recherche linéaire bornée, le premier doublon

Construire le programme spécifié par :

Précondition : Soit T un tableau constant défini sur l’intervalle 0 .. N − 1 (N >= 0)) et àvaleurs dans l’intervalle 0 .. 9.

Postcondition : S’il existe (au moins) un doublon dans T , i désigne le doublon ayant le pluspetit indice dans T , sinon i == N .

CHAPITRE 5. EXERCICES DE SYNTHÈSE 114

Chapitre 6Conclusion

Nous avons cherché à montrer que la programmation en général, et la construction de bouclesen particulier exigent rigueur et méthode de la part du programmeur. Mais quid de la créativité,objecteront certains. Les exemples développés ci-dessus démontrent que la programmation est etreste une activité créative. Dans toutes les activités humaines, la méthode et la technique sont lemarchepied de la créativité, la programmation d’y déroge pas.

Un lecteur averti pourrait formuler de nombreuses remarques ou critiques, tant sur la forme dudocument que sur le fond. Sur la forme : pourquoi n’avoir choisi que des exemples « d’école » ?Une seule réponse, en forme de question. Pouvait-il en être autrement ? Pour construire de « gros »programmes, il faut déjà savoir maîtriser la construction de plus petits. Nous n’avons fait que poserla première pierre.

Sur le fond ? Pourquoi s’en tenir à la boucle « while » et exclure la boucle « for » ? La réponseest que l’on ne sait pas construire les boucles « for ». On peut seulement reconnaître après coup uneboucle « for » dans la construction d’une boucle « while ». La boucle « for » apparaît alors commedu « sucre syntaxique » sans intérêt véritable. Plus généralement, la programmation est une activitéplus sérieuse et plus difficile qu’il n’y parait au premier abord ; en particulier, il ne s’agit pas d’un jeuconsistant à tenter d’utiliser toutes les singularités d’un langage informatique.

Le lecteur devra cependant se garder d’exiger de la méthode présentée ci-dessus plus qu’elle nepeut donner. Notre démarche est efficace quand il est possible d’exprimer une solution sous formerécurrente. C’est fréquemment le cas. Il existe cependant des problèmes, rencontrés en particulierdans le domaine de l’intelligence artificielle, où l’on ne connaît pas d’expression récurrente de lasolution. L’exemple suivant entre (probablement) dans cette catégorie 1. Soit un tableau d’entiers t delongueur 10. On veut instancier t de sorte qu’à l’issue du traitement, chaque cellule t[i ] contiendra lenombre de valeurs i présentes dans le tableau. Pour des problèmes présentant ce type de difficulté, ilfaut se tourner vers des techniques (générer et tester, programmation par contraintes, mise en œuvred’heuristiques) qui sortent du cadre de ce document.

1. Cet exemple est dû au logicien Martin Gardner, il a été communiqué à l’auteur par A. Aggoun. Il est également

discuté dans [20].

115

CHAPITRE 6. CONCLUSION 116

Annexe AVade-mecum de la construction deboucles

Ce qu’il faut absolument retenir

Le programme s défini par :

{précondition}

s

{postcondition}

s’il est fondé sur une boucle, se construit en cinq étapes :

1. découverte de l’invariant,2. recherche de la condition d’arrêt,3. construction de la progression,4. construction de l’initialisation,5. recherche d’une expression de terminaison.

Ces cinq points sont tels que :

A. (invariant and condition d’arrêt) ⇒ postcondition.

B. (a) {invariant and not(condition d’arrêt)}progression

{invariant}.

B. (b) {invariant and not(condition d’arrêt) and terminaison∈ N and terminaison == E}progression

{terminaison∈ N and terminaison< E}.

C. {précondition}

117

ANNEXE A. VADE-MECUM DE LA CONSTRUCTION DE BOUCLES 118

initialisation

{invariant}.

Heuristiques

Pour la découverte d’invariants :– La postcondition doit être sous forme conjonctive. Le cas échéant, il faut la renforcer pour la

mettre sous forme « constructive ».– Il est préférable de choisir un invariant facile à instaurer.– Il est préférable de choisir une condition d’arrêt qui ne contienne pas de sous-formules quan-

tifiées.

Pour la progression : Si l’expression exp exige une boucle pour être calculée directement dans laprogression, essayer d’introduire le conjoint (x == exp) dans l’invariant, puis recommencer laconstruction de la boucle.

Heuristique générale : Si l’algorithme est difficile à construire, on peut tenter d’affaiblir sa post-condition afin d’obtenir un résultat plus général ; reste alors à ajouter un fragment de code pourvérifier que la solution obtenue satisfait (ou non) la postcondition initiale. Ainsi par exemple,si on cherche à savoir s’il existe au moins deux 0 consécutifs dans un tableau (algorithme quiest assez difficile à construire directement), il est préférable de rechercher la longueur lg de laplus longue succession de 0 consécutifs (cf. exemple 8, page 58) puis de vérifier que lg >= 2 (ouencore d’arrêter la boucle dès que lg atteint la valeur 2).

Annexe BLe comportement « court-circuit » desopérateurs booléens de Python

Dans cette annexe on s’intéresse aux opérateurs booléens de Python. Dans une première étape ondéfinit les opérateurs traditionnels ¬, ∧ et ∨ avant de justifier l’existence et la sémantique particulièredes opérateurs Pytho and et or, homologues de ∧ et ∨. Quelques propriétés des opérateurs ¬, ∧ et∨ sont ensuite répertoriées avant de fournir une définition plus précise des opérateurs and et or.

Les opérateurs booléens classiques

En logique traditionnelle, les trois opérateurs booléens de négation, du et logique et du ou logiquesont respectivement notés ¬, ∧, et ∨. Les expressions booléennes ¬P , P ∧Q et P ∨Q ne sont définiesque si leurs(s) opérande(s) l’est (le sont). Ces trois opérations se définissent alors par les tables devérité suivantes :

P ¬P

True False

False True

P Q P ∧ Q P ∨ Q

True True True True

True False False True

False True False True

False False False False

Le problème des expressions non définies

Il peut arriver que des expressions ne soient pas définies parce que faisant appel à une fonctionappliquée hors de son domaine de définition (comme par exemple une division par 0 ou l’accès à uneposition inexistante dans un tableau). Les trois opérateurs ci-dessus sont alors inopérants. Ainsi lesexpressions

x == 0 ∨ (y/x) > 5

et(y/x) > 5 ∨ x== 0

sont toutes deux indéfinies quand x == 0.

119

ANNEXE B. LE COMPORTEMENT DES OPÉRATEURS BOOLÉENS 120

En programmation, il existe pourtant des situations pour lesquelles on souhaite donner un sens àune expression booléenne dont le second opérande n’est pas défini (comme dans (x == 0 ∨ (y/x) >

5) lorsque x == 0). Pour traiter ce type de situation, de nombreux langages de programmationdéfinissent des opérateurs que l’on qualifie d’opérateurs court-circuits (ou d’opérateurs conditionnels).C’est le cas des opérateurs and et or en Python. Dans ce contexte, l’expression Python :

x == 0 or (y/x) > 5

est définie dans tous les cas (y compris quand x == 0), alors que l’expression :

(y/x) > 5 or x== 0

n’est pas définie quand x == 0.

Quelques propriétés des opérateurs ¬, ∧ et ∨

Lorsque leurs opérandes sont définis, les opérateurs ¬, ∧ et ∨ vérifient les propriétés suivantes :

Commutativité :

P ∧ Q == Q ∧ P

P ∨ Q == Q ∨ P

Tiers exclu :

P ∨ ¬ P == True

Contradiction :

P ∧ ¬ P == False

Associativité :

(P ∧ Q) ∧ R == P ∧ (Q ∧ R)

(P ∨ Q) ∨ R == P ∨ (Q ∨ R)

Distributivité :

P ∧ (Q ∨ R) == (P ∧ Q) ∨ (P ∧ R)

P ∨ (Q ∧ R) == (P ∨ Q) ∧ (P ∨ R)

De Morgan :

¬(P ∧ Q) == ¬(P) ∨ ¬(Q)

¬(P ∨ Q) == ¬(P) ∧ ¬(Q)

Simplification du ∨ :

P ∨ P == P

P ∨ False == P

P ∨ True == True

P ∨ (P ∧ Q) == P

Simplification du ∧ :

P ∧ P == P

P ∧ False == False

P ∧ True == P

P ∧ (P ∨ Q) == P

121

Définition des opérateurs not, and et or de Python

Ces définitions, appliquées par induction structurelle sur une expression booléenne de Python,permettent de leur attribuer une valeur de vérité.

Si P et Q sont définis :

not P == ¬P (B.1)P and Q == P ∧ P (B.2)P or Q == P ∨ P (B.3)

Si P est défini mais pas Q :

False and Q == False (B.4)True and Q n’est pas défini (B.5)True or Q == True (B.6)False or Q n’est pas défini (B.7)

Si P n’est pas défini, que Q le soit ou non :

not P n’est pas défini (B.8)P and Q n’est pas défini (B.9)P or Q n’est pas défini (B.10)

Exemple. Appliquons les propriétés ci-dessus sur l’exemple suivant :

x != 0 and (y/x) > 5

Deux cas sont à considérer : x == 0 et x != 0.

1. Débutons par x == 0 :

x != 0 and (y/x) > 5

== Hypothèse x == 0

False and (y/x) > 5

== Propriété (B.4)

False.

2. Étudions le cas x != 0 :

x != 0 and (y/x) > 5

== Les deux opérandes sont définis, la règle (B.2) s’applique

x != 0 ∧ (y/x) > 5

== Hypothèse x != 0

True ∧ (y/x) > 5

== Propriété de l’opérateur ∧

(y/x) > 5.

Du point de vue opérationnel, l’évaluation de telles expressions booléennes se fait de gauche àdroite, mais s’arrête dès que le résultat est acquis (d’où le qualificatif de court-circuit).

ANNEXE B. LE COMPORTEMENT DES OPÉRATEURS BOOLÉENS 122

Convention d’utilisation Python

Dans ce document, pour éviter toute confusion, et afin de faciliter la traduction dans un langagemuni des deux catégories d’opérateurs, nous souhaitons distinguer, par un moyen conventionnel quel-conque, les usages qui exigent obligatoirement une sémantique de type court-circuit des autres. Nousdécidons de noter and et or les premiers et de conserver la typographie usuelle pour les seconds. Laréférence [35] fournit plus de détails sur la sémantique des opérateurs booléens en Python.

Annexe CL’axiomatique de Hoare : ce qu’il fautretenir

Cette annexe regroupe les axiomes et les règles de l’axiomatique de Hoare. Chaque règle se présente

sous la formeantécédentsconséquent

. Les règles privées d’antécédent s’appellent des axiomes.

C.1 L’axiomatique de base

Axiome de l’instruction vide : En Python l’instruction qui ne fait rien se note pass.

{P} pass {P}

Axiome de l’affectation :

{[x , y = E ,F ]P} x , y = E ,F {P}

Règle de conséquence 1 :{P} S {R}, R ⇒ Q

{P} S {Q}

Règle de conséquence 2 :P ⇒ R , {R} S {Q}

{P} S {Q}

Règle de séquentialité :{P} S1 {R}, {R} S2 {Q}

{P} S1 ; S2 {Q}

Règle de l’alternative double :

{P and B} S1 {Q}, {P and not(B)} S2 {Q}{P} if B : S1 else : S2 {Q}

123

ANNEXE C. L’AXIOMATIQUE DE HOARE : CE QU’IL FAUT RETENIR 124

Règle de la boucle :

{Q}I{P}, {P and not B}S{P}, P and B ⇒ R ,

{P and not B and T ∈ N and T==E}S{T ∈ N and T < E}

{Q} I while not B : S {R}

Remarques.

1. La règle de l’affectation est exprimée avec une double affectation. L’adaptation au cas d’uneaffectation simple ou d’une affectation de plus de deux variables est trivial.

2. Dans le conséquent de la règle de séquentialité, l’opérateur « ; » (opérateur de compositionséquentielle) correspond approximativement en Python au passage à la ligne.

3. Compte tenu de l’indécidabilité de la terminaison d’un algorithme, la règle d’inférence pour laboucle constitue une condition suffisante de terminaison.

4. Rappelons qu’un tableau est une fonction définie sur un intervalle d’entiers. Modifier un tableauen un point i , par l’affectation t[i ] = E , c’est donc remplacer, dans t, le couple (i , t[i ]) par lecouple (i ,E ), autrement dit, c’est réaliser l’affectation (globale) de tableau :

t = (t − {(i , t[i ])}) ∪ {(i ,E )}.

L’axiome de l’affectation, appliqué à un élément de tableau, comme dans t[i ] = E , doit donc secomprendre comme

{[t = (t − {(i , t[i ])}) ∪ {(i ,E )}]P} t = (t − {(i , t[i ])}) ∪ {(i ,E )} {P}.

Deux exemples d’application à la démonstration de la correction d’un programme. Lepremier exemple vise simplement à illustrer l’utilisation formelle de la logique de Hoare en démons-tration. L’intérêt pratique de la démonstration de la correction d’un programme est discutable dansla mesure où elle exige de disposer au préalable d’un programme susceptible d’être correct. Prenonsl’exemple de l’initialisation proposée dans l’exemple 6, page 49. Il faut démontrer :

{(N >= 1) and (K ∈ 0..N−1)} i = 0 {(i ∈ 0..N−1) and ∀j ·(j ∈ (0.. i−1)−{K} ⇒ T [j ] > T [K ])}.

On recherche une règle (ou un axiome) dont le conséquent s’identifie à la preuve à réaliser. C’estpar exemple le cas de la règle de conséquence 2. Les deux antécédents se présentent sous la forme :

(N >= 1) and (K ∈ 0 .. N − 1) ⇒ R

{R} i = 0 {(i ∈ 0 .. N − 1) and ∀j · (j ∈ (0 .. i − 1)− {K} ⇒ T [j ] > T [K ])}.

Le prédicat R de la règle de conséquence 2 reste non instancié mais l’axiome de l’affectation per-met de calculer une instance possible en substituant toute occurrence de la variable i par 0 dans lapostcondition du second conséquent :

[i = 0](i ∈ 0 .. N − 1) and ∀j · (j ∈ (0 .. i − 1)− {K} ⇒ T [j ] > T [K ])

== Définition de la substitution

(0 ∈ 0 .. N − 1) and ∀j · (j ∈ (0 ..−1)− {K} ⇒ T [j ] > T [K ])

== Calcul propositionnel et ensembliste

True

125 C.2. LE CAS DES EXPRESSIONS NON DÉFINIES

Il reste alors à démontrer le premier antécédent (maintenant complètement instancié)

(N >= 1) and (K ∈ 0 .. N − 1) ⇒ True

== Calcul propositionnel et ensembliste

True

ce qui achève la démonstration.Le second exemple considère un tableau constant T de N (N >= 1) éléments et le triplet de Hoare

suivant :

{True} j = T [i ] {j == T [i ]}.

Il s’agit donc de démontrer que, partant d’un état quelconque, si on affecte la valeur de T [i ] à lavariable j alors j vaut T [i ], ce qui, à première vue semble raisonnable. La démonstration se faitcomme dans le premier exemple.

On recherche une règle (ou un axiome) dont le conséquent s’identifie à la preuve à réaliser. C’estpar exemple le cas de la règle de conséquence 2. Les deux antécédents se présentent sous la forme :

True ⇒ R

{R} j = T [i ] {j == T [i ]}.

Le prédicat R de la règle de conséquence 2 reste non instancié mais l’axiome de l’affectation permetde calculer une instance possible en substituant toute occurrence de la variable j par T [i ] dans lapostcondition du second conséquent :

[j = T [i ]](j == T [i ])

== Définition de la substitution

T [i ] == T [i ]

== Calcul propositionnel

True.

Il reste alors à démontrer le premier antécédent (maintenant complètement instancié)

True ⇒ True

== Calcul propositionnel

True

ce qui achève la démonstration.

C.2 Le cas des expressions non définies

Pourtant, dans ce dernier exemple, à y regarder de plus près, nous n’aurions pas dû pouvoirréaliser la preuve puisque l’expression T [i ] n’est définie que si i ∈ 0 ..N − 1. Le problème provient decertaines des règles proposées à la section C.1 ci-dessus, qui ne tiennent pas compte du domaine dedéfinition des expressions. La solution passe par la reformulation des trois règles qui font référence àdes expressions : l’axiome de l’affectation, la règle de l’alternative double et la règle de la boucle. Sil’on considère que dom(E ) est l’expression qui définit le domaine de définition de l’expression E , lestrois règles se reformulent comme suit :

ANNEXE C. L’AXIOMATIQUE DE HOARE : CE QU’IL FAUT RETENIR 126

Axiome de l’affectation :

{(dom(E ) and dom(F )) and [x , y = E ,F ]P} x , y = E ,F {P}

Règle de l’alternative double :

dom(B), {P and B} S1 {Q}, {P and not(B)} S2 {Q}{P} if B : S1 else : S2 {Q}

Règle de la boucle :

dom(B), {Q}I{P}, {P and not B}S{P}, P and B ⇒ R ,

{P and not B and T ∈ N and T==E}S{T ∈ N and T < E}

{Q} I while not B : S {R}

Cette fois, la démonstration du triplet

{True} j = T [i ] {j == T [i ]}

devrait, comme on s’y attend, aboutir à une impasse. Reprenons à partir du calcul de R :

dom(T [i ]) and [j = T [i ]](j == T [i ])

== Définition de la substitution

dom(T [i ]) and T [i ] == T [i ]

== Définition du domaine de T [i ]

i ∈ 0 .. N − 1 and True

== Calcul propositionnel

i ∈ 0 .. N − 1.

Cette dernière formule peut s’interpréter comme « on ne peut affecter T [i ] à la variable j que si iappartient au domaine de définition de T ».

Préciser la définition de dom(E ) exigerait de définir cette opération sur toutes les façons possiblesde formuler une expression. La démarche procéderait par induction structurelle sur E . En tout étatde cause, outre le cas des indices de tableaux, il faudrait y inclure la division par 0, les débordementssur les opérations arithmétiques, l’évaluation d’une racine carrée, etc. Ces considérations dépassent lecadre de notre propos mais l’annexe D ci-après, dans sa rubrique « Utilisation d’une expression nondéfinie » précise les précautions à prendre dans la pratique.

Annexe DLes erreurs à ne pas commettre

La construction correcte de programmes corrects (et de boucles en particulier) exige une longuepratique, de l’expérience et parfois de la persévérance. Nous proposons, dans cette annexe, de réper-torier les principales erreurs commises par les débutants (et les autres !) lors de la construction deprogrammes. Nous en profitons pour présenter les fautes méthodologiques souvent rencontrées. Nousillustrons certaines des erreurs en reprenant l’exemple 6, page 49. Nous conseillons au lecteur d’avoircet exemple présent à l’esprit au cours de la lecture de cette annexe.

Principales erreurs

Elles sont au nombre de 7. Les quatre premières concernent la construction de boucles. Les troissuivantes sont plus générales.

Violation de la règle A. L’implication

(invariant and condition d’arrêt) ⇒ postcondition

n’est pas vérifiée. Autrement dit, la conjonction de l’invariant et de la condition d’arrêt n’im-plique pas la postcondition. Dans l’exemple 6, page 49, ce type d’erreur pourrait être provoquépar le choix suivant de la condition d’arrêt :

(i == N + 1) or (T [i ] <= T [K ]).

La conjonction de l’invariant et de la condition d’arrêt se verbalise par : soit tout élément deT [0 ..N − 1] est supérieur à T [K ] à l’exception de T [K ] soit il existe un indice i , (i ∈ 0 ..N − 1),tel que T [i ] <= T [K ]. Cette formulation est effectivement différente de la formulation de lasituation intermédiaire qui a servi de base à la construction (il manque le conjoint (i != K )).Opérationnellement, quand i == K la condition d’arrêt est satisfaite, bien qu’il se puisse queT [K ] soit inférieur à tous les autres éléments.

Une variante de cette erreur consiste à proposer un invariant (plus rarement une conditiond’arrêt) qui – seul – implique la postcondition :

invariant ⇒ postcondition

Il est clair que dans ce cas l’initialisation suffira puisque celle-ci est destinée à établir l’invariant.Il est donc inutile de construire une boucle.

127

ANNEXE D. LES ERREURS À NE PAS COMMETTRE 128

Toujours dans le cadre de la violation de la règle A, un raisonnement incorrect fréquent consisteà considérer que, puisque le programme (supposé fondé sur une boucle) est correct c’est doncque sa construction l’est aussi. Considérons l’exemple 2, page 32, (recherche de la position du0 ayant le plus petit indice dans un tableau d’entiers constant T possédant au moins un 0). Laconstruction suivante :

1. Invariant.i ∈ 0 .. N − 1

2. Condition d’arrêt.T [i ] == 0

3. Progression.

i = i+1

4. Initialisation.

i = 1

conduit au programme (non annoté) suivant :

1 i = 0

2 while not(T [i ] == 0) :

3 i = i+1

4 #end whi le

Ce programme est (probablement) correct vis-à-vis de sa spécification mais sa construction estincorrecte 1 puisque (i ∈ 0 .. N − 1) and (T [i ] == 0) n’implique pas que i est la positiondu 0 de plus petit indice dans T . Nous rappelons que, bien entendu, nous nous intéressons àla construction correcte de programmes corrects. Annoncer sans autres précautions « monprogramme est correct » ne peut que provoquer la réaction : « prouvez-le ! ».

Violation de la règle B.(i). La progression est incorrecte par rapport à sa spécification. Autrementdit le triplet :

{invariant and not(condition d’arrêt)}progression

{invariant}

est faux. On peut en général interpréter cette erreur comme « la progression ne rétablit pasl’invariant ».

Dans l’exemple 6, page 49, le choix suivant de la progression :

i = i+2

viole doublement la règle B.(i). En effet, d’une part cette progression ne garantit pas que i variesur son domaine de définition et d’autre part, un élément (T [i + 1]) n’étant pas consulté, on nepeut garantir que ∀j · (j ∈ (0 .. i − 1)− {K} ⇒ T [j ] > T [K ]).

1. Leurs auteurs tentent d’ailleurs de justifier la correction en intégrant des considérations sur la progression, consi-

dérations qui ne sont pas pertinentes à ce niveau du raisonnement, lequel n’intègre que l’invariant, la condition d’arrêt

et la postcondition.

129

Violation de la règle B.(ii). Le programme suivant, supposé respecter les trois points A, B.(i)et C :

1 #

2 # PRE :

3 # ...

4 #

5 initialisation

6 # i n v a r i a n t

7 # ...

8 #terminaison

9 # ...

10 while not(condition d’arrêt) :

11 pass

12 #end whi le

13 #

14 # POST :

15 # ...

16 #

ne satisfait pas le point B.(ii). La progression « rétablit » bien l’invariant (puisque l’instructionpass ne modifie pas l’état du programme). Si la condition d’arrêt est fausse à la premièreitération, le programme boucle indéfiniment, sinon, la boucle est inutile. Les deux cas posentproblème.

Violation de la règle C. L’initialisation est incorrecte par rapport à sa spécification. Autrementdit le triplet :

{précondition}initialisation

{invariant}

est faux. On peut en général interpréter cette erreur comme « l’initialisation n’instaure pasl’invariant ». Dans l’exemple 6, page 49, une initialisation telle que :

i = −1

viole la règle C puisque i prend une valeur en dehors de son domaine de variation.

Production d’une postcondition qui dénote False. Comme nous l’avons vu à la section 3.4,page 29, il est impossible de construire un fragment de code ayant comme postcondition uneexpression qui dénote False. Cette erreur est fréquente dans la construction d’une boucle où l’ona d’une part dans l’invariant un conjoint A et d’autre part dans la condition d’arrêt un élémentB qui sont tels que A and B ⇔ False.

Dans le cadre de l’exemple 6, page 49, cette erreur serait produite par exemple en introduisantdans l’invariant le domaine de définition (i ∈ 0 .. N − 1) (au lieu de (i ∈ 0 .. N). La conjonctionde l’invariant et de la condition d’arrêt (qui comprend i == N) dénote False.

Utilisation d’une expression non définie. Cette erreur se manifeste en général soit par une divi-sion par 0, soit par l’accès à un élément de tableau inexistant. Un tableau est une fonction totalesur un intervalle. L’expression qui désigne la position d’un élément doit dénoter une valeur quiappartient à cet intervalle (cf. annexe C.2, page 125).

ANNEXE D. LES ERREURS À NE PAS COMMETTRE 130

Dans le cadre de l’exemple 6, page 49, nous avons pris la précaution d’ordonner correctementles deux opérandes de l’opérateur or de la condition d’arrêt, en remarquant que la solutionalternative peut conduire à accéder à un élément de tableau inexistant.

Domaine de variation vide dans un invariant. Nous avons vu, en développant l’exemple 6,page 49, que dans l’invariant d’une boucle, le domaine de variation d’une variable de program-mation ne doit pas être vide. En effet, dans le cas contraire, il est impossible de proposer unevaleur pour instaurer l’invariant (c’est-à-dire pour effectuer l’initialisation). C’est par exemplele cas lorsque l’invariant comprend le conjoint (i ∈ 0 .. N − 1), dans le contexte où la constanteN est telle que N ∈ N. Si on a effectivement N == 0, on a également i ∈ 0 .. −1, (soit encorei ∈ ∅), il est impossible d’affecter une valeur initiale à i .

Principales fautes méthodologiques

Abus de la programmation défensive. Le style programmation défensive peut schématique-ment être défini comme consistant à introduire une alternative (ou une assertion Python)pour s’assurer de la validité d’une précondition. Ce style peut se justifier quand l’appelé n’a pasconfiance en l’appelant ou, si l’on préfère, quand le développeur n’a pas confiance en l’utilisateur.Cependant, il est des cas fréquents, où cette suspicion ne se justifie pas. Ainsi dans le cas de laconstruction de la progression d’une boucle préconditionnée par (invariant and not(conditiond’arrêt)), il est inutile de débuter la progression en s’assurant que la négation de la conditiond’arrêt est bien vérifiée.

Dans le cadre de l’exemple 6, page 49, cette faute se manifesterait en rédigeant la progressionsuivante :

1 i f i != N :

2 i = i+1

3 #end i f

En effet, la précondition de la progression comprend le conjoint (i != N) (qui fait partie de lanégation de la condition d’arrêt), il est donc inutile de s’assurer une seconde fois de la validitéde cette formule.

Introduction non justifiée de variables. Les variables nécessaires à la construction d’une boucleapparaissent toutes dans l’invariant. Introduire des variables fraîches dans la condition d’arrêtconstitue donc une erreur. Par contre, il n’est pas rare que la progression ou l’initialisationexigent des variables auxiliaires.

Sous-spécification de l’invariant. Comme nous le signalons dans le point précédent, concevoir l’in-variant est l’occasion d’introduire et de spécifier le rôle des variables nécessaires à la constructiond’une boucle. Il faut éviter les formulations vagues, ambigües, et profiter de la rubrique invariantpour nommer toutes les entités qui y sont présentes. Dans le cadre de l’exemple 6, page 49, uneformulation de l’invariant telle que

« une partie du tableau satisfait la propriété voulue »

tomberait dans ce travers. « une partie. . . » laquelle ? « la propriété » laquelle ?

Renforcement non justifié de l’invariant initial. L’invariant proposé dans la première tentativede construction d’une boucle doit être un invariant « naturel ». C’est une faute méthodologiqueque de proposer d’emblée un invariant contenant des conjoints dont la nécessité n’est pas justifiéeou dont la présence prématurée risque de troubler le lecteur.

131

Ordre de rédaction incorrect des constituants d’une boucle. L’erreur principale consiste àrédiger l’initialisation de la boucle ou sa progression avant de rédiger l’invariant. La constructiond’une boucle s’articule autour d’un invariant. En particulier, les spécifications de l’initialisationet de la progression utilisent l’invariant. Il serait pour le moins curieux de tenter de rédiger unfragment de programme dont on ne connaîtrait pas la spécification. Une autre erreur consisteà construire la progression avant de proposer une condition d’arrêt. La condition d’arrêt faitpartie de la précondition de la progression, il n’est pas raisonnable de construire un fragmentde programme dont on ne connaîtrait pas la précondition.

Le mieux est de respecter l’ordre qui a été utilisé dans ce document : invariant, condition d’arrêt,progression, initialisation, terminaison.

Confusion variable de programmation/variable de quantification. En termes ensemblistes,la précondition et la postcondition d’un programme constituent chacune un sous-ensemble duproduit cartésien du domaine de variation des variables de programmation (ou variables d’état).La portée des variables de programmation est le programme dans sa totalité. Au contraire,la portée de chaque variable de quantification est la formule quantifiée. Ce sont des variablesmuettes, qui en particulier ne doivent pas apparaître dans le code exécutable.

Initialisation d’une variable qui n’appartient pas à l’invariant L’initialisation instaure l’in-variant, elle ne fait rien d’autre. Seules les variables y apparaissant doivent être initialisées.Dans le cadre de l’exemple 6, page 49, cette faute se manifesterait dans le programme final sion rédigeait l’initialisation suivante :

1 i , i n f k = 0 , True

infk ne fait pas partie de l’invariant, cette affectation n’a rien à faire à cet endroit. Bien quen’ayant pas d’incidence sur la correction de l’algorithme, cette faute trahit une incompréhensiondu processus de construction des boucles.

Ce qu’il faut retenir de cette annexeDans une approche purement formelle de la programmation, les campagnes de tests sont

inutiles, le programme peut être mis en application dès lors que toutes les preuves, automatiquesou assistées, ont été réalisées. Notre démarche est intellectuelle et semi-formelle, malgré tous lesefforts que nous déployons à respecter les conseils présentés dans ce chapitre, il nous arrivede faire des erreurs. Dans le meilleur des cas, elles se manifestent au travers des tests quenous devons réaliser. La solution pour corriger ces erreurs est alors de vérifier les sept pointsmentionnés ci-dessus dans la section « Principales erreurs ». Le pire des cas est celui où, bienque le programme soit incorrect, aucun test n’a permis de mettre en évidence l’erreur (ou leserreurs). Cette situation milite en faveur de la multiplication des tests. De nombreuses méthodesou recommandations existent sur ce sujet (sur lequel nous ne pouvons nous étendre ici), citonsen particulier [21, 37, 38, 39, 40]. Mais ne perdons pas de vue que, selon la célèbre formuled’E.W. Dijkstra, prononcée dans son allocution à l’occasion de la réception du « Turing award »,en 1972 : « un test permet de montrer la présence d’erreurs dans un programme a, jamais demontrer leur absence b ».

a. Ce qui est déjà appréciable.b. Program testing can be a very effective way to show the presence of bugs, but it is hopelessly inadequate

for showing their absence.

ANNEXE D. LES ERREURS À NE PAS COMMETTRE 132

Un dernier conseil

Lorsqu’on vous demande d’expliquer l’erreur que vous venez de constater dans votre programme,la seule réponse est, parmi la liste des sept erreurs possibles mentionnées ci-dessus, de fournir celle quiest à l’origine de l’erreur (en précisant bien entendu où et dans quelle circonstance elle apparait). Cecipermet de rester en cohérence avec la construction proprement dite et de se préparer à la correction.

Bibliographie

Les incontournables :

[1] Cohen, E. Programming in the 1990’s, an Introduction to the Calculation of Programs.

Springer-Verlag, 1990. ISBN 0-387-97382-6. Avis : ♥♥. Orienté calcul de programmes, mais laconstruction de boucles y tient une part un peu trop réduite. Bon chapitre sur un point horssujet pour nous : l’élimination de la récursivité terminale. Excellent ouvrage malgré la réserve.23, 83

[2] Dijkstra, E.W., Feijen, W.H.J. A method of programming. Addison-Wesley, ISBN 0-201-17536-3,1988. Avis : ♥♥♥. Ouvrage rédigé sur commande par l’un des meilleurs informaticiens du XX e

siècle (le premier auteur). Excellent malgré une rédaction qui semble précipitée. Orienté calculde programmes. iii, 23, 83

[3] Gries, D. The science of Programming. Springer Verlag. ISBN 0-387-90641-0, 1981.http://blg89.net/blog/wp-content/uploads/2013/11/The-Science-Of-Programming-Gries-038790641X.pdf

Consulté le 5 mars 2014. Avis : ♥. Longtemps la référence sur le calcul de programmes, a unpeu vieilli. Peu d’exemples sur la construction de boucles. Vulgarise [16] et le rend accessible àun plus large public. 23

[4] Gries, D., F.B. Schneider A Logical Approach to Discrete Math. Springer Verlag.ISBN 0-387-94115-X, 1994. http://ldc.usb.ve/~carrasquel/Gries.pdf Consulté le 28 dé-cembre 2013. Avis : ♥♥. Ouvrage plutôt centré mathématiques, mais comporte des digressionsintéressantes sur la programmation et son lien avec l’induction.

[5] Kaldewaij, A. Programming: the Derivation of Algorithms. Prentice Hall. ISBN 0-12-204108-1.1990.http://ldc.usb.ve/~mosquera/Algoritmos/AlgoritmosI/Teoria/ene-mar-2008/Kaldewaij%20-%20Prentice%20Hall%20-%20Programming.%20The%20Derivation%20of%20Algorithms.pdf

Consulté le 28 décembre 2013. Avis : ♥♥♥. L’un des meilleurs ouvrages d’exercices de program-mation pour celui qui s’intéresse à l’approche calculatoire. De nombreux exemples et exercices.iii, 23, 83

[6] Manber, U. Introduction to Algorithms. A Creative Approach. Addison-Wesley,ISBN 0-201-12037-2, 1989. http://fr.scribd.com/doc/122951386/Introduction-to-Algorithms-Manber

Consulté le 28 décembre 2013. Avis : ♥♥♥. Excellent ouvrage peu formel sur les relations entreprogrammation et induction. Dépasse le cadre de la simple introduction à la programmation. 83

133

BIBLIOGRAPHIE 134

Pour en savoir plus :

[7] Abrial, J.R. The B-Book : Assigning Programs to Meanings. Cambridge University Press,1996. Avis : ♥♥♥. Ouvrage fondamental, très formalisé et d’un abord difficile pour le débutant.La méthode B (dont [7] est le manuel de référence) s’accompagne d’un atelier comprenant enparticulier un assistant de preuves (http://www.atelierb.eu/). Depuis les années 1990, cetteméthode sert de vitrine pour les méthodes formelles dans l’industrie du logiciel. iv, 22, 134

[8] Abrial, J.R. Modeling in Event-B : System and Software Engineering. Cambridge UniversityPress, 2010. Avis : ♥♥♥. Ouvrage fondamental, une des branches d’avenir pour une informatiquerigoureuse et sûre. S’accompagne d’un atelier comprenant en particulier un assistant de preuves(http://www.event-b.org/). iv, 22

[9] Arsac, J. Premières leçons de programmation. Cédic/F. Nathan, ISBN 2-7124-0304-5, 1980. iii

[10] Arsac, J. Les bases de la programmation. Dunod, ISBN 2-04-015569-4, 1983. iii

[11] Arsac, J. Préceptes pour programmer. Dunod, ISBN 2-04-018854-1, 1991. iii

[12] Arsac-Mondou, O., Bourgeois-Camescasse, C., Gourtray, M. Premier livre de programmation.

Cédic/F. Nathan, ISBN 2-7124-0313-4, 1982. iii

[13] Arsac-Mondou, O., Bourgeois-Camescasse, C. Gourtray, M. Pour aller plus loin en program-

mation. Cedic/F. Nathan, ISBN 2-7124-0317-7, 1983. iii

[14] Bentley, J. Programming Pearls. Addison-wesley, ISBN 0-201-10331-1, 1986.

[15] Bentley, J. More Programming Pearls. Confessions of a Coder. Addison-Wesley,ISBN 0-201-11889-0, 1988.

[16] Dijkstra, E.W. A Discipline of Programming. Prentice-Hall, ISBN 0-13-215871-X, 1976. Avis :♥♥♥. Un ouvrage pionnier, d’une influence considérable mais d’un abord difficile. Une collectiond’exercices de programmation prétexte à une refondation de la discipline. 133

[17] Dromey, G. Program derivation. The Development of Programs From Specifications.

Addison-Wesley, ISBN 0-20141624-7, 1989. 83

[18] Guyomard, M. Structures de données et méthodes formelles. Springer, collection Télé-com. Avis : Pour une approche formalisée de la spécification et du raffinement ; pourun important catalogue de structures de données inductives. ISBN 978-2-8178-0199-5, 2011.http://link.springer.com/book/10.1007%2F978-2-8178-0200-8

[19] Hoare, C. A. R. An axiomatic basis for computer programming. Communications of the ACM,Volume 12 Issue 10, Oct. 1969. Avis : ♥♥♥. Le bulletin de naissance de l’informatique en tantque science, aujourd’hui d’une importance historique uniquement. iii, 23, 48

[20] Hofstadter, D. Ma Thémagie. InterEditions, ISBN 2-7296-0234-8. 1988. 115

[21] Julliand, J. Cours et exercices d’algorithmique. Vuibert, 2010.ISBN 10 : 2311000209 / ISBN 13 : 9782311000207. 131

[22] Knuth, D.E. The art of computer programming. Volume 3. Sorting and Searching. SecondEdition (Reading, Massachusetts : Addison-Wesley, 1998), ISBN 0-201-89685-0. Avis : Avec lesdeux premiers tomes : un monument. Indispensable à l’informaticien professionnel. Assez peudans l’esprit du présent document cependant. 73

[23] Ledgard, J-F. Proverbes de la programmation. Dunod, ISBN 2-04-010238-8, 1975.

135 BIBLIOGRAPHIE

[24] Monin, J.F. Introduction aux méthodes formelles. Hermes, ISBN 2-7462-0140-2, 2000. Avis :♥♥. Sur les relations qu’entretiennent la logique et la théorie des ensembles avec la spécificationet la programmation.

[25] Wirth, N. Systematic Programming. An Introduction. Prentice Hall, ISBN 0-13-880369-2,1973.

Vidéo :

[26] http://www.youtube.com/watch?v=t3gtm4KH2b8 Video Youtube de Bertrand Meyer (en an-glais). Consulté le 28 décembre 2013.

[27] http://www.youtube.com/watch?v=spcfzbisBv4 Video Youtube de Rustan Leino sur les inva-riants de boucles (en anglais). Consulté le 28 décembre 2013.

[28] http://www.youtube.com/watch?v=5t4WntcsZZo Video Youtube de Rustan Leino sur la véri-fication de programmes (en anglais). Consulté le 28 décembre 2013.

[29] http://www.youtube.com/watch?v=kbJO-U9Wp-s Video Youtube de Rustan Leino sur la ter-minaison. Consulté le 28 décembre 2013.

Sites Web :

[30] http://en.wikipedia.org/wiki/Loop_invariant Site Web. Wikipédia, site sur les invariantsde boucle (en anglais). Consulté le 28 décembre 2013.

[31] http://courses.cs.washington.edu/courses/cse331/12sp/lectures/lect03-reasoning-loops.pdf Site Web.

Transparents de l’Université de Washington. M. Ernst. Consulté le 28 décembre 2013.

[32] http://www.cs.cornell.edu/courses/cs1110/2012fa/lectures/11-06-12/presentation-21.pdf Site Web.

Transparents de l’Université Cornell. Consulté le 28 décembre 2013.

[33] http://se.ethz.ch/~meyer/publications/methodology/invariants.pdf Site Web. Articlede B. Meyer et al. ETH Zurich. Consulté le 28 décembre 2013.

[34] http://archives.evergreen.edu/webpages/curricular/2001-2002/dsa01/loops.html SiteWeb. Site sur la dérivation de programmes. The evergreen State College, Olympia, Washington.Consulté le 28 décembre 2013.

BIBLIOGRAPHIE 136

[35] http://python.developpez.com/cours/DiveIntoPython/php/frdiveintopython/power_of_introspection/and_or.php Site

Web. Consulté le 17 décembre 2013.

122

[36] http://gforge.inria.fr/docman/view.php/999/5097/krakatoa.pdf Site Web.Marché, C. Manuel (version Anglaise) du logiciel Krakatoa permettant la certi-fication de programmes Java. Krakatoa est un freeware disponible sur le sitehttp://gforge.inria.fr/docman/view.php/999/5097/krakatoa.pdf Consulté le 9 oc-tobre 2008 (enregistrement préalable exigé).

22

[37] https://www.lri.fr/~longuet/Enseignements/11-12/App5-Intro.pdf Site Web. Site du la-boratoire LRI sur le test de logiciels. Consulté le 30 décembre 2013.

131

[38] http://www.liafa.jussieu.fr/~sighirea/cours/methtest/c_test_1.pdf Site Web. Site dulaboratoire Liafa sur le test de logiciels. Consulté le 30 décembre 2013.

131

[39] http://perso.ens-lyon.fr/christophe.alias/verif/bardin.pdf Site Web. Site de S. Bar-din sur l’automatisation du test logiciel. Consulté le 30 décembre 2013.

131

[40] http://lifc.univ-fcomte.fr/~bouquet/Test/cours_Test.pdf Site Web. Transparents d’uncours de l’Université de Franche-Comté sur le test de logiciels. Consulté le 30 décembre 2013.

131

[41] http://research.microsoft.com/en-us/projects/jennisys/ Site Web. Projet Jennisys :Recherches sur la fiabilité du logiciel à Microsoft. Consulté le 7 mars 2014.

22