Dans ce chapitre, nous allons reprendre la notion fondamentale du chapitre précédent : les conditions. Une boucle permet de répéter une action tant qu'une condition renvoie Vrai. De ce fait, on pourra faire des actions répétitives très facilement.
Prenons un exemple, vous avez un tableau et vous voulez remplir toutes les entrées par les lettres de l'alphabet. Au lieu de prendre une ligne pour chaque entrée, on va créer une boucle qui permet d'affecter à chaque entrée une lettre de l'alphabet. Ce que je viens de vous proposer, c'est une boucle simple où il y aura qu'une instruction, mais on pourra complexifier ces boucles très facilement par la suite.
Les boucles, comme les conditions, on les utilise dans la vie de tous les jours. Par exemple, vous êtes en train de marcher. L'instruction à l'intérieur de la boucle est : "faire un pas", la condition peut être multiple du type : "tant que je ne suis pas arrivé et qu'il n'y a pas de trou alors …". On boucle cette instruction pour que nous puissions enchainer les pas et arriver à destination sans tomber dans un trou. S'il fallait écrire une instruction à chaque pas, le code serait très long !
- La boucle simple
- La boucle For Each
- La boucle Do Until
- La boucle While.. Wend
- Sortie anticipée d'une boucle
- Exercice : un problème du projet Euler sur les dates
La boucle simple
Dans cette première partie, nous allons étudier le code d'une boucle simple. Pour cela il faut d'abord savoir ce que fait une boucle et quel code elle remplace. Imaginons un code très simple et basique pour afficher les nombres de 1 à 10 dans une boite de dialogue en utilisant MsgBox. Voici la première solution que vous êtes en mesure de coder :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Sub boucle() MsgBox "1" MsgBox "2" MsgBox "3" MsgBox "4" MsgBox "5" MsgBox "6" MsgBox "7" MsgBox "8" MsgBox "9" MsgBox "10" End Sub |
Et encore, heureusement que je vous ai demandé jusqu'à 10 Imaginons que je vous demande jusqu'à 100. Il vous faut ajouter 90 lignes de code, ça pompe de l'énergie au programmeur (qui est un être de nature fainéant), ça ralentit le programme et c'est très inefficace. Venons-en à la notion clé de ce chapitre : la boucle. On utilise la boucle que je vais appeler For..Next. Voici à quoi peut être réduit le code précédent :
1 2 3 4 5 | For nombre = 1 to 10 MsgBox nombre Next nombre |
La boîte de message s'affiche 10 fois de 1 à 10.
Comment fonctionne ce code ?
Tout d'abord, on utilise le mot-clé For qui annonce le début de la boucle. Ensuite on utilise le nom d'une variable : ici nombre mais on peut mettre ce que l'on veut tant que l'on garde ce nom de variable dans toute la boucle. On dit où commence la boucle, ici 1 mais on aurait très bien pu afficher à partir de 5 seulement (tester par vous même). Le mot-clé suivant est To pour dire "jusqu'à" puis la valeur de la dernière valeur à afficher. La première ligne est maintenant terminée.
Sur la seconde ligne et les suivantes, on écrit ce que l'on veut, c'est le code à exécuter. On n'est pas obligé d'utiliser la variable nombre dans ce code, si on veut juste afficher le texte "Hip" 3 fois, on crée une boucle qui affiche le texte sans se préoccuper de la variable.
Enfin quand vous avez fini votre boucle, sur la dernière ligne, il faut dire au programme de passer à la valeur suivante par le mot-clé Next suivi du nom de la variable. Ainsi, le programme recommence au début avec la valeur suivante jusqu'à ce que la valeur atteigne la valeur plafond (ici 10).
Le mot-clé Step
En anglais, step signifie pas. C'est le pas à plusieurs significations : il y a le pas lorsque vous marchez, mais la signification qui nous intéresse ici est le pas comme écart entre deux valeurs. Dans notre boucle, par défaut le pas est de 1, c'est-à-dire que la variable est incrémentée (augmentée) de 1 à chaque tour de boucle. Grâce au mot-clé Step, on peut modifier ce pas. On va alors afficher que les chiffres pairs de 0 à 10.
1 2 3 4 5 | For nombre = 0 to 10 Step 2 MsgBox nombre Next nombre |
Ce code affiche successivement 0, 2, 4, 6, 8, 10 parce que le pas est de 2, vous pouvez aussi mettre un nombre à virgule pour aller de 0 à 1 par exemple en ajoutant à chaque fois 0,1.
Dans VBA, une virgule est indiquée par un point (.) et non par une virgule (,).
Vous pouvez alors avec ce mot-clé incrémenter à votre guise.
Encore plus fort ! Step fonctionne à l'envers ! On peut partir de 10 et arriver à 0 en affectant à Step la valeur de -1. On peut ainsi créer un compte à rebours comme ceci :
1 2 3 4 5 6 7 8 9 10 11 | Sub rebours() For n = 5 To 1 Step -1 MsgBox n Next n MsgBox "Boom !" End Sub |
Ce code effectue le compte à rebours à partir de 5 jusqu'à 1 avant que ça ne pète !
Cette boucle For..Next est très pratique pour balayer les numéros de ligne ou colonne.
Un dernier exemple avec l'affectation à chaque entrée d'un tableau une lettre de l'alphabet :
1 2 3 4 5 6 7 8 9 10 11 12 13 | Sub alphabet() Dim lettre(25) as String For n = 0 To 25 lettre(n) = Chr(n+65) Next n For n = 0 To 25 MsgBox lettre(n) Next n End Sub |
Dans un premier temps, on crée un tableau de 26 entrées (nombre de lettres de notre alphabet). Dans une première boucle, on affecte à chaque entrée une lettre de l'alphabet, dans la seconde boucle on affiche ces lettres.
Euh, c'est quoi Chr(n+65) ?
Chr est une fonction de VBA qui permet de renvoyer la lettre qui correspond à un code numérique. Ce code est le ASCII (American Standard Code of Information Interchange ou Code standard américain pour les échanges d'information). Ainsi grâce à ce code chaque nombre correspond à une lettre et par exemple A = 65, B = 66 et ainsi de suite. Comme notre code commence à 0, il faut ajouter 65 pour avoir la lettre A. Quand n est égal à 1, on ajoute 65 pour obtenir 66 et la lettre B. La fonction Chr permet de convertir ce nombre en lettre et on affecte cette valeur à l'entrée du tableau correspondante. Si le sujet vous passionne, vous trouverez ici la table ASCII complète
Les boucles imbriquées
On peut imbriquer les boucles pour par exemple parcourir les cellules d'une feuille de calculs. On va alors afficher les coordonnées de chaque cellule d'un tableau de 5 lignes et 5 colonnes. Voici le code, une petite explication juste après :
1 2 3 4 5 6 7 8 9 | Sub test_tableau() For colonne = 1 To 5 For ligne = 1 To 5 MsgBox Chr(colonne+64) & ligne Next ligne Next colonne End Sub |
Dans la première boucle, on utilise la variable colonne qui va représenter la colonne. Dans le premier tour de boucle, on utilise la valeur 1 pour colonne. On ajoute 64 à la valeur de colonne pour obtenir la valeur dans le code ASCII et affiche la lettre correspondant à la colonne.
Dans la deuxième boucle, on boucle le numéro de ligne grâce à la variable ligne. Ainsi, on va afficher tour à tour toutes les cellules de la première colonne, la colonne A. On obtient donc A1, A2, A3, A4 ,A5. Une fois que la boucle des lignes est terminée, la boucle sur les lignes est terminée et le code continue à s'exécuter. Il arrive à la ligne Next colonne. De ce fait, le code reprend au début de la boucle, avec la colonne B et refait la boucle concernant les lignes pour afficher toutes les cellules de la colonne B et ainsi de suite. Il y a donc 25 boites de dialogue qui s'affichent.
Cet exemple est terminé, j'espère qu'il vous a servi et que vous avez compris. Faites des tests chez vous, c'est comme ça que l'on apprend ! Passons maintenant à une autre boucle.
La boucle For Each
La boucle For Each est très semblable à la boucle que nous venons de voir. Elle permet de se déplacer dans un tableau très facilement ou de parcourir une classe d'objets. On n'utilise pas d'indice dans cette boucle parce qu'elle parcourt automatiquement tous les éléments de la classe (aussi appelée collection). Cela est très pratique si vous avez besoin de rechercher un objet particulier (avec une condition à l'intérieur de la boucle) au sein de la collection pour le supprimer. L'objet supprimé va décaler les objets vers le haut et il y aura donc moins d'objets à parcourir. Avec cette boucle, tous les objets sont parcourus sans erreur, avec la boucle For..Next, il y aura une erreur à la fin puisqu'il aura un objet non créé.
Prenons un exemple pour bien comprendre. Nous avons 10 feuilles dans un classeur, soit une collection de 10 feuilles. On cherche une feuille que l'on va supprimer. Avec la boucle For Each, on parcourt toute la collection, on analyse toutes les feuilles et supprime celle que l'on veut. Une fois toute la collection parcourue, la boucle s'arrête. En revanche, avec la boucle For..Next, on va mettre un indice qui va de 1 à 10 (puisqu'on a 10 feuilles). On supprime la feuille que l'on a trouvée, de ce fait, il n'en reste plus que 9 et les restantes se décalent pour que la collection ne soit pas de "trou". Dans ce cas, la feuille qui était en 10 se retrouve en 9 et il n'y a plus de feuille 10. La boucle va continuer de tourner jusqu'à ce que l'indice atteigne la valeur de 10. A ce moment, la boucle ne va pas trouver de feuille puisqu'elles ont été décalées et il va y avoir une erreur. On va alors utiliser la boucle For Each pour parcourir les collections.
Pour connaitre la syntaxe de cette boucle, on va afficher dans une boite de dialogue le nom de chaque feuille de calculs. Voici le code suivi d'explications :
1 2 3 4 5 6 7 8 9 | Sub afficher_noms() Dim une_feuille As Worksheet For Each une_feuille In Worksheets MsgBox une_feuille.Name Next une_feuille End Sub |
Dans un premier temps, on créé une variable une_feuille de type Worksheet (objet feuille). On utilise la boucle For Each pour parcourir chaque feuille de la collection Worksheets (collection de feuilles d'un classeur). La ligne signifie donc : pour chaque feuille de la collection de feuilles… On exécute alors une ligne de commande en affichant le nom de la feuille grâce à la propriété Name dans une boite de dialogue. Une fois que c'est fait, on passe à la feuille suivante grâce au mot-clé Next. Comme dans la boucle For..Next, il faut utiliser le même nom de variable (ou d'indice) pour que la boucle tourne correctement.
La boucle Do Until
Encore une nouvelle boucle qui fonctionne différemment de la boucle For..Next mais qui a la même logique. Elle va continuer de tourner tant qu'une condition est respectée. Je vous présente la syntaxe grâce à un exemple et une explication ensuite :
1 2 3 4 5 6 7 8 9 10 11 12 | Sub test_boucle() ma_variable = 0 Do Until ma_variable = 10 ma_variable = ma_variable + 1 MsgBox ma_variable Loop End Sub |
On initialise une variable à 0. Ensuite, le mot-clé Do Until est utilisé. Il signifie "jusqu'à", c'est à dire, jusqu'à ce que ma_variable soit égale à 10. Dans le code, on incrémente manuellement la variable puis on affiche sa valeur. Enfin, on utilise un nouveau mot-clé pour signaler la fin de la boucle : Loop (qui signifie boucler en anglais).
Attention à ne pas faire de boucle infinie, c'est-à-dire une boucle où la variable n'atteint jamais la valeur plafond ! Sinon, une boite de dialogue apparaît à chaque fois que vous cliquez sur Ok. On ne peut mettre fin au code.
La boucle While.. Wend
Les boucles, ce n'est pas encore fini ! En voici une autre ici que l'on appelle While..Wend. Cette boucle continue de boucler tant que la condition spécifiée est vraie. La boucle s'arrête dès que la condition est fausse. Voici un exemple simple, très semblable à celui de la boucle Do Until.
1 2 3 4 5 6 7 8 9 10 | Sub test_while() ma_variable = 0 While ma_variable < 12 ma_variable = ma_variable + 1 MsgBox ma_variable Wend End Sub |
Comme pour la boucle précédente, on initialise la variable à 0. Ensuite on utilise le mot-clé While qui signifie "tant que" suivi d'une condition, ici inférieur à 12. C'est-à-dire que tant que la variable est inférieure à 12 on entre dans la boucle, sinon on en sort et on poursuit le code. Dans la suite du code, on incrémente la variable de 1 en 1 (le pas est de 1) et on affiche à chaque fois la valeur. Quand la boucle doit remonter on utilise le mot-clé Wend (qui signifie s'acheminer) comme on utilise Loop dans la boucle Do Until.
Sortie anticipée d'une boucle
Dans certaines circonstances, il est souhaitable que la procédure sorte de la boucle plus tôt que prévu. Si, par exemple, vous effectuez une recherche de chaîne de caractères dans les éléments d'un tableau, il n'y a pas de raison de continuer à balayer les autres éléments du tableau une fois que vous avez trouvé ce que vous recherchez. Si vous recherchez dans un tableau de plusieurs milliers d'éléments, vous pouvez gaspiller un temps précieux si vous ne sortez pas de la boucle alors que vous avez trouvé l'objet de votre recherche. Dans le cas d'une boucle For..Next, la valeur de l'indice est aussi préservée ce qui signifie que vous pouvez l'utiliser pour localiser l'endroit où votre condition était correcte. Voici un exemple :
1 2 3 4 5 6 7 8 9 10 11 | Sub test_sortie() For variable = 1 To 100 If variable = 50 Then Exit For End If Next variable MsgBox variable End Sub |
Le mot-clé pour sortir d'une boucle, c'est Exit For dans une boucle For..Next ou For Each. Dans le cas d'une boucle Do Until, le mot-clé pour en sortir est Exit Do. Vous aurez noté que dans le cas d'une boucle For..Next, la valeur de l'indice (de la variable) est préservée. Si les boucles sont imbriquées, votre code ne sort que de la boucle dans laquelle il se trouve. Il ne sortira pas de la boucle extérieure à moins que vous n'y placiez une autre instruction Exit.
Dans l'exemple précédant, on a donc une variable qui commence à 1 et va jusqu'à 100. Dans cette boucle une instruction conditionnelle (If) est utilisée pour comparer la valeur de l'indice et celui que l'on cherche. Une fois la valeur atteinte on entre dans l'instruction conditionnelle et exécute le code : la sortie. Tant que la valeur de l'indice ne respecte pas la condition, on ne prend pas en compte l'instruction qui la suit (la sortie) et on continu à boucler. Enfin, lorsque la valeur est trouvée, on sort de la boucle et affiche cette valeur.
Exercice : un problème du projet Euler sur les dates
Le Projet Euler est un site qui propose depuis plus de dix ans des problèmes de maths à résoudre à l'aide de l'outil informatique. En général, les données à manipuler sont des nombres gigantesques et il faut donc réfléchir à des algorithmes efficaces pour avoir un résultat en une minute maximum.
Il existe deux façons de résoudre un tel problème : le brute-force ou une méthode plus réfléchie et plus élégante. Le brute-force consiste à tester toutes les solutions possibles jusqu'à trouver la bonne. Il est évident que dès que les nombres dépassent les limites du raisonnable (ce qui est généralement le cas dans le Projet Euler), cette méthode est à proscrire car les temps de calculs sur un ordinateur comme le votre ou le mien sont trop longs. Le choix de la technologie est libre et il se trouve qu'un exercice se prête bien au VBA mais aussi à l'utilisation de la fonction NBVAL() de Excel.
Nous allons résoudre le problème 19. Je vous invite à prendre connaissance des informations données dans l'énoncé, ici, je vais simplement recopier la question :
Que l'on peut traduire par :
Combien de premiers du mois sont tombés un dimanche entre le premier janvier 1901 et le 31 décembre 2000 ?
La méthode brute-force ici est une solution acceptable. En effet, Excel vous renverra la solution très rapidement (moins d'une minute) donc les conditions de participation au Projet Euler sont respectées. Vous pouvez néanmoins, si vous être matheux sur les bords, vous servir de l'énoncé pour résoudre le problème sur papier ou, si vous êtes moins maso, une solution informatique élégante.
Bref, vous l'aurez compris, les boucles vont nous servir à sauter de jours en jours, jusqu'à tomber sur l'année 2000, qui met fin au calcul et renvoie le résultat .
Quelques éléments sur les dates en VBA
Il existe un type Date tout prêt qui nous permet de manipuler des dates selon un format particulier. Voyons un exemple que vous devriez comprendre sans problème :
1 2 | Dim maDate As Date maDate = "12/03/2000" |
Les opérations de comparaison que vous connaissez fonctionnent très bien sur les dates (on peut donc savoir quelle date est plus vieille qu'une autre avec nos opérateurs habituels : >, <, etc.).
Des fonctions
Nous allons voir maintenant quelques fonctions de VBA pour analyser une variable de type Date.
Il faut d'abord savoir que dans Excel, tous les jours de la semaine et les mois sont numérotés. La seule particularité est que le jour de la semaine numéro 1 est un dimanche.
Pour obtenir le numéro du jour de la semaine, il nous suffit d'utiliser la fonction WeekDay(), qui attend en paramètre une variable de type Date. Cette fonction vous renverra un entier, qu'il vous suffit de comparer au tableau suivant pour connaître le jour.
Retour de WeekDay |
Correspondance |
---|---|
1 |
Dimanche |
2 |
Lundi |
3 |
Mardi |
4 |
Mercredi |
5 |
Jeudi |
6 |
Vendredi |
7 |
Samedi |
Pour connaître le jour du mois, il suffit d'utiliser la fonction Day(), qui attend en paramètre une variable de type Date, évidement.
Ainsi, avec la déclaration ci-dessus, Date(maDate)
renverra 12.
A vous de jouer !
Entre les boucles et ces quelques éléments ci-dessus, vous avez tout pour réussir votre (premier, pour certains) exercice du Projet Euler ! Je vous recommande d'afficher chaque résultat qui correspond dans une cellule, chaque résultat les uns en dessous des autres dans une colonne (la colonne A par exemple). La fonction NBVAL() de Excel, dont vous trouverez le guide d'utilisation à la fin de cette sous-partie de l'annexe des fonctions, va vous permettre de compter toutes les cellules non vides dans votre plage et donc d'obtenir la solution du problème.
Correction
Je vous propose une correction. Nous allons d'abord voir le code VBA puis ensuite la partie sur le tableur.
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 | Sub probleme19 () Dim dateFin As Date, dateActuelle As Date dateActuelle = "01/01/1901" ' on commence au 01/01/1901 dateFin = "31/12/2000" Range("A1").Select 'on sélectionne la première cellule pour y afficher le premier résultat While dateActuelle <= dateFin If WeekDay(dateActuelle) = 1 And Day(dateActuelle) = 1 Then ActiveCell.Value = dateActuelle ' on écrit le résultat dans la cellule active ActiveCell.Offset(1, 0).Select dateActuelle = dateActuelle + 1 ' on passe au jour suivant Else dateActuelle = dateActuelle + 1 End If Wend End Sub |
Retournez voir votre feuille de calculs, votre colonne A est remplie de dates . Dans une cellule d'une autre colonne, il ne vous reste plus qu'à rentrer la formule : = NBVAL("A1:A2000") (2000 pour être large, mais on peut aussi tester toute la colonne) pour avoir votre solution !
Naturellement, celle-ci ne sera pas divulguée ici, ce n'est pas du jeu sinon !
Critique
Cette méthode - sous Excel - est rapide et fonctionne. Cependant, si on y réfléchit, l'algorithme brute-force utilisé est assez mauvais. En effet, dans la vie courante, si on devait appliquer la méthode brute-force avec tous les calendriers de 1901 à 2000, on regarderait uniquement les premiers du mois, et non tous les jours. On peut donc s’intéresser, dans le code VBA, aux premiers du mois et non à tous les jours.
Si cette amélioration vous intéresse pour continuer à vous exercer, à vos tableurs !
C'est déjà terminé pour ce chapitre. Vous savez maintenant faire des boucles pour répéter des actions simples qui s'exécutent selon une condition. Nous allons voir maintenant une autre manière de ne pas répéter les actions répétitives mais d'une autre manière moins monotone : les fonctions et sous-routines. Rendez-vous au chapitre suivant !