Chapitre 4: Structures de données : objets et tableaux
Ce chapitre sera consacré à la résolution de quelques problèmes simples. En chemin, nous allons étudier deux nouveaux types de valeurs, les tableaux et les objets, et quelques techniques les concernant.
Considérons la situation suivante : votre tante Émilie l’excentrique, dont la rumeur dit qu’elle vit avec cinquante chats (en fait personne n’arrive à les compter), vous envoie régulièrement des courriels pour vous tenir au courant de ses exploits. Ils sont de la forme suivante :
Mon cher neveu, Ta mère m’a dit que tu t’es mis au parachutisme. Est-ce que c’est vrai ? Fais bien attention à toi, jeune homme ! Tu te souviens de ce qui est arrivé à mon mari ? Et c’était seulement du deuxième étage !
Quoi qu’il en soit, les choses sont très intéressantes de mon côté. J’ai passé toute la semaine à essayer d’attirer l’attention de M. Drake, le gentil monsieur qui a emménagé juste à côté, mais je pense qu’il a peur des chats. À moins qu’il n’y soit allergique ? Je vais essayer de poser Gros Igor sur son épaule la prochaine fois qu’il vient. Je suis curieuse de voir le résultat.
Par ailleurs, l’arnaque dont je t’ai parlé fonctionne mieux que prévu. J’ai déjà récupéré cinq « paiements » et seulement une seule réclamation. Je commence malgré tout à avoir quelques remords. Et puis tu as sans doute raison, ça doit être illégal d’une manière ou d’une autre.
(…etc.)
Grosses bises, Tante Émilie
Décédé le 27/04/2006 : Black Leclère
Est né le 05/04/2006 (mère, Lady Pénélope) : Lion Rouge, Docteur Hobbles III, Petit Iroquois
Pour amuser cette vieille dame, vous voudriez garder une trace de la généalogie de ses chats, pour pouvoir ajouter des commentaires comme « P.-S. J’espère que Docteur Hobbles II a bien fêté son anniversaire samedi ! », ou bien « Comment va cette vieille Lady Pénélope ? Elle a cinq ans maintenant, n’est-ce pas ? », en évitant de préférence de demander des nouvelles des chats décédés. Vous avez une grande quantité de vieux courriels de votre tante et, par chance, elle est très constante dans sa manière de donner les renseignements sur les naissances et décès des chats à la fin de ses courriels, toujours dans le même format.
Vous n’avez pas envie de parcourir à la main tous ces messages. Heureusement, nous avions justement besoin d’un exemple, nous allons donc écrire un programme qui va faire le travail pour nous. Pour commencer, nous allons écrire un programme qui va nous donner la liste des chats qui sont toujours vivants à la fin du dernier courriel.
Avant que vous ne posiez la question, au début de cette correspondance, la tante Émilie n’avait qu’un seul chat : Spot (elle était encore assez conformiste à cette époque).

Il est généralement préférable d’avoir une idée de départ sur ce que va faire un programme avant de se mettre à l’écrire… Voici le plan :
- Commencer par un ensemble de noms de chats ne comprenant que « Spot ».
- Parcourir chaque courriel dans l’archive dans l’ordre chronologique.
- Chercher les paragraphes qui commencent par « Est né le » ou « Décédé le ».
- Ajouter les noms trouvés dans les paragraphes qui commencent par « Est né le » à l’ensemble de noms.
- Supprimer les noms de chats trouvés dans les paragraphes qui commencent par « Décédé le » de notre ensemble.
On extraira les noms d’un paragraphe de la façon suivante :
- Trouver les deux-points (:) dans le paragraphe.
- Prendre la partie après ce signe.
- Dans cette partie, séparer les noms en cherchant les virgules.
Cet énoncé d’exercice peut rendre nécessaire d’oublier quelques instants les exceptions possibles et d’accepter aveuglément que tante Émilie utilise toujours le même format d’écriture, qu’elle n’oublie jamais un nom de chat, ni ne fait de faute de frappe. Mais votre tante est comme ça et ça tombe bien pour nous.
D’abord, je vais vous expliquer les propriétés. Beaucoup de valeurs en
JavaScript ont d’autres valeurs qui leur sont associées. Ces associations
sont appelées propriétés. Chaque chaîne de caractères a une propriété
appelée length, (longueur), qui correspond à un nombre, la quantité
de caractères dans cette chaîne.
On peut accéder aux propriétés de deux manières :
var texte = "brume pourpre"; show(texte["length"]); show(texte.length);
La deuxième manière est un raccourci de la première et ne fonctionne que lorsque le nom de la propriété s’écrit comme un nom de variable ― lorsqu’il n’y a pas d’espace ou de symbole et lorsqu’elle ne commence pas par un chiffre.
Les valeurs null et undefined n’ont pas de propriété. Essayez de lire des
propriétés de ces valeurs donnera une erreur. Essayez le code suivant, juste
pour voir le type de message d’erreur que votre navigateur va retourner dans ce
cas de figure (dans certains navigateurs, ce message sera assez mystérieux).
var rienDuTout = null; show(rienDuTout.length);
Les propriétés d’une chaîne de caractères ne peuvent pas être
changées. Elles sont plus nombreuses que la seule longueur length,
comme nous allons le voir, mais vous ne pouvez ajouter ni supprimer aucune
propriété.
C’est différent avec les valeurs du type object. Leur rôle principal est de conserver d’autres valeurs. Ils ont, en quelque sorte, leur propre jeu de « tentacules » sous forme de propriétés. Vous pouvez les modifier, les supprimer ou en ajouter de nouvelles.
Un objet peut s’écrire de la façon suivante :
var chat = {couleur: "gris", nom: "Spot", taille: 46}; chat.taille = 47; show(chat.taille); delete chat.taille; show(chat.taille); show(chat);
Comme les variables, chaque propriété attachée à un objet a un nom
sous forme d’une chaîne de caractères. La première instruction crée un
objet dans lequel la propriété "couleur" contient la chaîne "gris",
la propriété "nom" est liée à la chaîne "Spot", et la propriété
"taille" fait référence au nombre 46. La deuxième ligne modifie la
propriété taille en lui donnant une nouvelle valeur, ce qui se fait de
la même manière que pour la modification d’une variable.
Le mot-clé delete supprime les propriétés. Essayer de lire une
propriété qui n’existe pas donnera la valeur undefined.
Si une propriété qui n’existe pas encore est affectée avec l’opérateur
=, elle est ajoutée à l’objet.
var vide = {}; vide.plusVraiment = 1000; show(vide.plusVraiment);
Les propriétés dont le nom ne pourrait pas être une variable doivent être mises entre guillemets au moment de la création de l’objet et utilisées avec des crochets :
var truc = {"gabba gabba": "hey", "5": 10}; show(truc["5"]); truc["5"] = 20; show(truc[2 + 3]); delete truc["gabba gabba"];
Comme vous pouvez le voir, on peut mettre n’importe quelle expression entre les crochets. Elle sera convertie en une chaîne pour définir le nom de la propriété. On peut aussi utiliser des variables pour donner un nom à une propriété :
var nomDePropriete = "length"; var texte = "grandeLigne"; show(texte[nomDePropriete]);
L’opérateur in peut servir à tester si un objet possède une certaine
propriété. Son résultat est un booléen.
var poupeeRusse = {}; poupeeRusse.contenu = poupeeRusse; show("contenu" in poupeeRusse); show("contenu" in poupeeRusse.contenu);
Quand les valeurs d’un objet sont affichées sur la console, on peut cliquer à la souris pour inspecter leurs propriétés. La fenêtre de sortie devient une fenêtre « inspecteur ». Le petit « x » en haut à droite s’utilise pour retourner à la fenêtre de sortie et la flèche gauche permet de retourner aux propriétés de l’objet inspecté.
show(poupeeRusse);
La solution pour le problème des chats passe par un ensemble de noms. Un ensemble (ou « set ») est un groupe de valeurs dans lequel aucune valeur ne peut apparaître plus d’une fois. Si les noms de chats sont des chaînes de caractères, pouvez-vous imaginer une façon pour qu’un objet devienne un ensemble de noms ?
Écrivez maintenant la façon dont un nom peut être ajouté à cet ensemble, comment on peut le supprimer et comment on peut vérifier si un certain nom est bien présent dans l’ensemble.
Une solution consiste à mémoriser le contenu de l’ensemble sous la forme
de propriétés d’un objet. Pour ajouter un nom, on crée une propriété
avec ce nom en lui affectant une valeur, n’importe laquelle. Pour supprimer
un nom, on supprimera la propriété de l’objet. L’opérateur in sera
utilisé pour savoir si une certaine propriété fait partie de l’ensemble1.
var set = {"Spot": true}; // Ajoute "Croc Blanc" à l’ensemble set["Croc Blanc"] = true; // Supprime "Spot" delete set["Spot"]; // Regarde si "Asoka" est dans l’ensemble show("Asoka" in set);
Les valeurs des objets peuvent apparemment changer. Les types de valeurs vues dans le chapitre 2 sont toutes invariables, il n’est pas possible de changer une valeur existante pour ces types de données. Vous pouvez les associer ou en tirer de nouvelles valeurs, mais lorsque vous prenez une chaîne de caractères particulière, le texte à l’intérieur ne peut pas être modifié. Avec les objets, d’un autre côté, le contenu d’une valeur peut être modifié en changeant ses propriétés.
Lorsque nous considérons deux nombres, 120 et 120, il est possible dans
tous les cas pratiques de les considérer comme des nombres identiques. Avec
des objets, il y a une différence importante entre avoir deux « références
» du même objet et avoir deux objets distincts qui possèdent les mêmes
propriétés. Considérons le code suivant :
var objet1 = {valeur: 10}; var objet2 = objet1; var objet3 = {valeur: 10}; show(objet1 == objet2); show(objet1 == objet3); objet1.valeur = 15; show(objet2.valeur); show(objet3.valeur);
objet1 et objet2 sont deux variables attachées à la même valeur. Il
n’y a en fait qu’un seul objet, c’est pourquoi en changeant objet1 on
change également la valeur de objet2. La variable objet3 pointe vers
un autre objet qui contient au départ la même propriété que objet1
mais elle a une existence distincte.
L’opérateur JavaScript ==, lorsqu’il compare des objets, ne retournera
la valeur booléenne true que si chacune des valeurs qu’on lui donne à
comparer sont exactement les mêmes. Comparer des objets différents ayant des
contenus identiques donnera le résultat false. C’est utile dans certaines
situations, mais peu adapté à d’autres.
Les valeurs d’un objet peuvent jouer de nombreux rôles. Se comporter comme un ensemble n’est que l’un d’entre eux. Nous allons voir d’autres utilisations dans ce chapitre et le chapitre 8 montrera d’autres façons importantes d’utiliser les objets.
Dans le plan d’action pour le problème des chats ― en fait, appelons-le un algorithme au lieu d’un plan, cela nous donne l’impression qu’on sait de quoi on parle ― dans l’algorithme, on parle de parcourir chaque courriel contenu dans une archive. Mais comment se présente cette archive ? Et d’où vient-elle ?
Ne vous inquiétez pas de la deuxième question pour le moment. Le chapitre 14 explique quelques-unes des possibilités pour importer des données dans vos programmes. Pour l’instant, on dira que les courriels sont déjà là, comme par magie. La magie est parfois très facile, avec les ordinateurs.
La façon dont l’archive est enregistrée reste une question pertinente. Elle contient quantité de courriels. Un courriel peut être vu comme une chaîne de caractères, c’est évident. Toute l’archive pourrait être mise dans une énorme chaîne de caractères mais ce ne serait pas pratique. Ce qu’il nous faut, c’est une structure de chaînes de caractères distinctes.
Les objets sont justement utilisés pour structurer des choses. On pourrait très bien créer un objet comme celui-ci :
var archiveDeMessages = {"le premier courriel": "Mon cher neveu, …", "le deuxième courriel": "…" /* et ainsi de suite… */};
Mais parcourir les courriels du début à la fin serait difficile ― comment le programme peut-il deviner le nom de ces propriétés ? La solution est d’utiliser des noms de propriétés plus prévisibles :
var archiveDeMessages = {0: "Mon cher neveu, … (courriel numéro 1)", 1: "(courriel numéro 2)", 2: "(courriel numéro 3)"}; for (var courant = 0; courant in archiveDeMessages; courant++) print("Traitement du courriel #", courant, ": ", archiveDeMessages[courant]);
La chance veut qu’il existe un type d’objet particulier qui corresponde
exactement à ce type de besoin. Ce sont les tableaux et ils
fournissent des commodités très utiles, comme length
(longueur), une propriété qui contient le nombre d’éléments dans le
tableau et bien d’autres fonctions utiles pour ce type de structure.
Pour créer de nouveaux tableaux on utilise des crochets ([ et ]):
var archiveDeMessages = ["courriel un", "courriel deux", "courriel trois"]; for (var courant = 0; courant < archiveDeMessages.length; courant++) print("Traitement du courriel #", courant, ": ", archiveDeMessages[courant]);
Dans cet exemple, le nombre d’éléments n’est plus spécifié explicitement. Le premier a automatiquement le numéro 0, le deuxième le numéro 1 et ainsi de suite.
Pourquoi commencer à 0 ? Dans la vie courante on compte d’habitude à partir de 1. Aussi étrange que cela paraisse, la numérotation à partir de 0 est souvent plus pratique pour programmer. Faites avec pour l’instant, vous allez vous y faire.
Commencer par l’élément 0 veut aussi dire que dans une structure qui
a X éléments, le dernier élément sera trouvé à la position X -
1. C’est pourquoi la boucle for dans notre exemple teste la valeur
courant < archiveDeMessages.length. Il n’y a pas d’élément à la position
archiveDeMessages.length, donc dès que courant atteint cette valeur, on
arrête la boucle.
Écrivez une fonction nommée serie qui prend un argument, un nombre
positif et retourne un tableau contenant chaque nombre de 0 jusqu’au nombre
donné en paramètre inclus.
Un tableau vide peut être créé en tapant simplement []. Souvenez-vous que
pour ajouter une propriété à un tableau ou à un objet, il suffit
d’affecter une valeur à cette propriété avec l’opérateur =. La propriété
length est mise à jour automatiquement quand des éléments sont ajoutés.
function serie(max) { var resultat = []; for (var i = 0; i <= max; i++) resultat[i] = i; return resultat; } show(serie(4));
Au lieu de nommer la variable de boucle compteur ou courant, comme je l’ai
fait jusqu’à présent, elle s’appelle désormais simplement i. L’utilisation
d’une seule lettre, habituellement i, j ou k pour les variables de boucle
est une habitude très répandue en programmation. Son origine tient presque
à de la paresse : on préfère taper un caractère que sept et des noms
comme compteur et courant ne donnent pas forcément plus d’informations
sur la variable.
Si un programme utilise trop souvent des variables à un seul caractère, sans
explication, il peut devenir très difficile à comprendre. Dans mes propres
programmes, j’essaie de me limiter à quelques cas de figures seulement. Les
petites boucles font partie de ces cas. Si la boucle contient une autre
boucle et que celle-ci utilise aussi une variable appelée i, la boucle
intérieure va modifier la variable dont se sert la première boucle, et rien
ne va fonctionner. Ou pourrait utiliser j pour la boucle intérieure, mais
en général, lorsque le corps d’une boucle est grand, vous devriez utiliser
un nom de variable ayant une signification utile pour la compréhension.
Les objets chaînes de caractères et tableaux contiennent tous
deux, outre la propriété length, un certain nombre d’autres propriétés
qui font référence à des fonctions.
var doh = "Doh"; print(typeof doh.toUpperCase); print(doh.toUpperCase());
Chaque chaîne de caractères a une propriété toUpperCase. Lorsqu’elle
est appelée, elle retourne une copie de la chaîne, transformée avec chaque
lettre en majuscule. Il y a aussi l’équivalent toLowerCase. Devinez
le résultat…
Remarquez que même si l’appel de toUpperCase se fait sans arguments,
la fonction a malgré tout accès au contenu de la chaîne de caractères
"Doh", la valeur dont elle est une propriété. La façon dont cela
fonctionne est décrite dans le chapitre 8.
Les propriétés qui se comportent comme des fonctions sont généralement
appelées méthodes, ainsi, toUpperCase est une méthode des
objets chaînes de caractères.
var flipper = []; flipper.push("Flipper"); flipper.push("le"); flipper.push("dauphin"); show(flipper.join(" ")); show(flipper.pop()); show(flipper);
La méthode push, associée aux tableaux, peut être utilisée pour
ajouter des valeurs à ceux-ci. Nous aurions pu l’utiliser dans l’exercice
précédent, à la place de resultat[i] = i. Il y a aussi la méthode
pop, complémentaire de push : elle supprime le dernier élément
d’un tableau et retourne sa valeur. join construit une seule chaîne de
caractères à partir d’un tableau de chaînes de caractères. Le paramètre
utilisé avec cette méthode sera inséré entre chaque valeur du tableau,
avant l’assemblage de la chaîne de caractères finale.
Revenons à nos chats : nous savons maintenant qu’utiliser un tableau
serait une bonne idée pour ranger les archives des courriels. Sur cette page,
la fonction recupererLesMessages sera utilisée pour récupérer (magiquement)
ce tableau. Parcourir les courriels qu’il contient pour les traiter un par un
devient simple comme un jeu d’enfant :
var archiveDeMessages = recupererLesMessages(); for (var i = 0; i < archiveDeMessages.length; i++) { var message = archiveDeMessages[i]; print("Traitement du courriel #", i); // Faire plus de choses… }
Nous avons également décidé d’une manière de représenter un ensemble
de chats vivants. Le problème qui reste à traiter, cependant, est celui
de détecter des paragraphes d’un courriel qui contiennent "Est né le" ou
"Décédé le".
La première question qui vient à l’esprit est de savoir ce qu’est un paragraphe au juste. Dans ce cas, la valeur de la chaîne elle-même n’est pas d’une grande utilité : le concept du texte en JavaScript ne va guère plus loin que l’idée de « suite de caractères », si bien que nous devons définir les paragraphes de cette façon.
Nous avons vu plus haut qu’il existe une chose qui s’appelle un caractère de fin de ligne. C’est ce que la plupart des gens utilisent pour séparer les paragraphes. Nous considérons donc un paragraphe comme une partie du courriel qui commence par un caractère saut de ligne ou au début du contenu du message et se termine au caractère saut de ligne suivant ou bien à la fin du contenu.
Et nous n’avons même pas à écrire nous-mêmes l’algorithme pour scinder
une chaîne en paragraphes. Les chaînes ont déjà une méthode appelée
split, qui est (pratiquement) l’inverse de la méthode join pour les
tableaux. Elle découpe une chaîne en un tableau en utilisant la chaîne
fournie comme argument pour déterminer à quel endroit opérer les divisions
en paragraphes.
var mots = "Les villes de l’arrière-pays"; show(mots.split(" "));
Ainsi, découper avec des caractères saut de ligne ("\n") est une méthode
utilisable pour diviser un courriel en paragraphes.
split et join ne sont pas exactement l’inverse l’une de
l’autre. string.split(x).join(x) produit toujours la valeur originale,
mais pas array.join(x).split(x). Pouvez-vous donner un exemple de tableau
dans lequel .join(" ").split(" ") produit une valeur différente ?
var tableau = ["a", "b", "c d"]; show(tableau.join(" ").split(" "));
Les paragraphes qui ne commencent ni par « Né le » ni par
« Décédé le » peuvent être ignorés par le programme. Comment peut-on
tester si une chaîne commence par un mot particulier ? On peut utiliser
la méthode charAt pour obtenir un caractère particulier dans une chaîne.
x.charAt(0) donne le premier caractère, 1 est le deuxième et ainsi de
suite. Voici une façon de vérifier si une chaîne commence par « Né
le» :
var paragraphe = "Est né le 15/11/2003 (mère, Spot) : Croc Blanc"; show(paragraphe.charAt(0) == "E" && paragraphe.charAt(1) == "s" && paragraphe.charAt(2) == "t" && paragraphe.charAt(3) == " " && paragraphe.charAt(4) == "n" && paragraphe.charAt(5) == "é" && paragraphe.charAt(6) == " " && paragraphe.charAt(7) == "l" && paragraphe.charAt(8) == "e");
Mais cela devient un peu pénible ― imaginez que vous devez vérifier la présence d’un mot de 10 caractères. On peut cependant en tirer une leçon utile : si une instruction est démesurément longue, on peut l’étendre sur plusieurs lignes. Le résultat peut être plus facile à lire en alignant le début de la nouvelle ligne avec le premier élément similaire de la première ligne.
Les chaînes possèdent également une méthode nommée slice. Elle permet
de copier un morceau de la chaîne de caractères, en commençant par le
caractère à la position donnée par le premier argument, et se terminant
avant le caractère (non inclus) à la position donnée par le second
argument. Cela permet de vérifier une chaîne de caractères en peu de lignes.
show(paragraphe.slice(0, 9) == "Est né le");
Écrivez une fonction nommée chaineCommencePar qui prend deux arguments, tous les
deux des chaînes de caractères. Elle renvoie true quand le premier argument
commence par les caractères du second argument, sinon elle renvoie false.
function chaineCommencePar(chaine, motif) { return chaine.slice(0, motif.length) == motif; } show(chaineCommencePar("rotation", "rot"));
Que se passe-t-il quand charAt ou slice sont utilisés pour prendre un
fragment de chaîne qui n’existe pas ? Est-ce que la fonction chaineCommencePar que j’ai
montrée va encore fonctionner si la chaîne recherchée est plus longue
que celle dans laquelle on cherche ?
show("Ouai".charAt(250)); show("Nan".slice(1, 10));
charAt va renvoyer "" s’il n’existe pas de caractère à la position
donnée et slice va tout simplement laisser tomber la partie de la nouvelle
chaîne qui n’existe pas.
Cela confirme que cette version de chaineCommencePar fonctionne. Quand la fonction
chaineCommencePar("Idiots","Mes très chers collègues") est appelée, l’appel
à slice renverra toujours une chaîne plus courte que motif, parce
que le premier argument, chaine ne comporte pas assez de caractères. C’est pour cette raison
que la comparaison avec == renverra false, ce qui est correct.
C’est une bonne idée de toujours consacrer un moment pour prendre en considération les entrées aberrantes (mais valides) dans un programme. On les appelle en général des cas imprévus et il est très fréquent qu’un programme qui tourne à merveille avec toutes les entrées « normales » se plante complètement avec des cas imprévus.
La seule partie de notre problème de chats qui ne soit pas encore résolue est l’extraction des noms d’un paragraphe. L’algorithme était le suivant :
- Trouver le deux-points (:) dans le paragraphe.
- Prendre la partie après ce signe.
- Dans cette partie, séparer les noms en cherchant les virgules.
Il faut reproduire cela à la fois pour les paragraphes qui commencent par
Décès le et ceux qui commencent par Est né le. Ce serait une bonne idée
de le mettre dans une fonction, de sorte que les deux parties de code qui
gèrent les différentes sortes de paragraphes puissent l’utiliser.
Savez-vous écrire une fonction nomDesChats qui prenne un paragraphe comme
argument et renvoie un tableau de noms ?
Les chaînes ont une méthode indexOf que l’on peut utiliser pour trouver la
(première) position d’un caractère ou une sous-chaîne à l’intérieur d’une
chaîne. De même si on ne donne qu’un seul argument à slice, elle renverra
la partie de la chaîne depuis la première position jusqu’à son extrémité.
Il peut être pratique d’utiliser la console pour « explorer » les
fonctions. Par exemple, tapez "foo: bar".indexOf(":") et voyez ce qui
se passe. (NdT: les mots foo et bar n’ont pas de signification précise,
et illustrent parfois des exemples de code).
function nomDesChats(paragraphe) { var deuxPoints = paragraphe.indexOf(":"); return paragraphe.slice(deuxPoints + 2).split(", "); } show(nomDesChats("Est né le 20/09/2004 (mère, Bess la Jaune): " + "Docteur Hobbles II, Kaïra"));
La partie la plus délicate, qui n’a pas été mentionnée lors la description originale
de l’algorithme, est le traitement des espaces après les deux-points
et les virgules. Le +2, utilisé pour le découpage de chaînes, est
nécessaire pour laisser de côté le deux-points lui-même et l’espace qui
le suit. L’argument pour split contient à la fois une virgule et un espace,
parce que ce sont les séparateurs de noms, plutôt que par une simple virgule.
Cette fonction n’effectue aucune vérification de problèmes éventuels. Nous faisons comme si, dans ce cas précis, l’entrée était toujours correcte.
Tout ce qui nous reste à faire maintenant, c’est de rassembler les pièces du puzzle. Voici une façon de s’y prendre :
var archiveDeMessages = recupererLesMessages(); var chatsVivants = {"Spot": true}; for (var message = 0; message < archiveDeMessages.length; message++) { var paragraphes = archiveDeMessages[message].split("\n"); for (var paragraphe = 0; paragraphe < paragraphes.length; paragraphe++) { if (chaineCommencePar(paragraphes[paragraphe], "Est né le")) { var noms = nomDesChats(paragraphes[paragraphe]); for (var nom = 0; nom < noms.length; nom++) chatsVivants[noms[nom]] = true; } else if (chaineCommencePar(paragraphes[paragraphe], "Décédé le")) { var noms = nomDesChats(paragraphes[paragraphe]); for (var nom = 0; nom < noms.length; noms++) delete chatsVivants[noms[nom]]; } } } show(chatsVivants);
Voilà un bloc de code assez copieux et dense. Nous allons voir tout de suite comment l’alléger un peu. Mais d’abord jetons un coup d’œil aux résultats. Nous savons comment vérifier si un chat particulier a survécu :
if ("Spot" in chatsVivants) print("Spot est vivant !"); else print("Ce bon vieux Spot, qu’il repose en paix.");
Mais comment allons-nous faire pour dresser la liste de tous les chats
vivants ? Le mot-clé in a une signification légèrement différente
lorsqu’il est utilisé avec for :
for (var chat in chatsVivants) print(chat);
Une boucle comme celle-là va parcourir les noms des propriétés d’un objet, ce qui nous permettra d’énumérer tous les noms de notre ensemble.
Certaines parties de code ressemblent à une jungle impénétrable. L’exemple de solution pour le problème des chats souffre de ce défaut. Une façon de ménager des clairières consiste tout simplement à ajouter des lignes vides. Cela améliore la lisibilité, mais ne résout pas véritablement le problème.
Ce qu’il nous faut ici, c’est casser le code. Nous avons déjà écrit deux
fonctions d’aide, chaineCommencePar et nomDesChats, qui toutes deux résolvent
une petite partie du problème de façon compréhensible. Continuons sur
cette lancée.
function ajouterAuSet(set, valeurs) { for (var i = 0; i < valeurs.length; i++) set[valeurs[i]] = true; } function enleverDuSet(set, valeurs) { for (var i = 0; i < valeurs.length; i++) delete set[valeurs[i]]; }
Ces deux fonctions traitent de l’ajout et de la suppression des noms dans l’ensemble. Ce qui supprime déjà les deux plus importantes boucles internes de la solution :
var chatsVivants = {Spot: true}; for (var message = 0; message < archiveDeMessages.length; message++) { var paragraphes = archiveDeMessages[message].split("\n"); for (var paragraphe = 0; paragraphe < paragraphes.length; paragraphe++) { if (chaineCommencePar(paragraphes[paragraphe], "Est né le")) ajouterAuSet(chatsVivants, nomDesChats(paragraphes[paragraphe])); else if (chaineCommencePar(paragraphes[paragraphe], "Décédé le")) enleverDuSet(chatsVivants, nomDesChats(paragraphes[paragraphe])); } }
C’est un sacré progrès, si je peux me permettre.
Pourquoi ajouterAuSet et enleverDuSet prennent-ils l’ensemble comme
argument ? Ils pourraient utiliser la variable chatsVivants directement,
s’ils le voulaient. La raison, c’est que de cette façon elles ne sont pas
totalement liées à notre problème. Si ajouterAuSet changeait directement
chatsVivants, il faudrait l’appeler ajouterChatsDansEnsembleDeChats
ou quelque chose comme ça. Tel que nous l’utilisons,
c’est un outil utile pour des cas plus généraux.
Même si nous ne devions jamais utiliser ces fonctions pour quoi que ce
soit d’autre, ce qui est très probable, il est utile de les décrire de
cette façon. Car elles se « suffisent à elles-même », on peut les
lire et les comprendre, sans avoir besoin de connaître une variable externe
nommée chatsVivants.
Ces fonctions ne sont pas pures : elles modifient l’objet set qui a été passé
en premier argument. Cela rend les choses un peu plus délicates
qu’avec des fonctions pures mais c’est déjà beaucoup moins perturbant que
des fonctions qui perdent les pédales et modifient les valeurs de variables
comme ça leur chante.
Nous continuons à découper l’algorithme en petites unités :
function trouverChatsVivants() { var archiveDeMessages = recupererLesMessages(); var chatsVivants = {"Spot": true}; function traiterParagraphe(paragraphe) { if (chaineCommencePar(paragraphe, "Est né le")) ajouterAuSet(chatsVivants, nomDesChats(paragraphe)); else if (chaineCommencePar(paragraphe, "Décédé le")) enleverDuSet(chatsVivants, nomDesChats(paragraphe)); } for (var message = 0; message < archiveDeMessages.length; message++) { var paragraphes = archiveDeMessages[message].split("\n"); for (var i = 0; i < paragraphes.length; i++) traiterParagraphe(paragraphes[i]); } return chatsVivants; } var combien = 0; for (var chat in trouverChatsVivants()) combien++; print("Il y a ", combien, " chats.");
La totalité de l’algorithme est encapsulée dans une fonction. Cela signifie
qu’elle ne laisse rien traîner en vrac derrière elle après exécution :
chatsVivants est maintenant une variable locale dans la fonction et non plus
une variable globale, si bien qu’elle n’existe que pendant que la fonction
s’exécute. Le code qui a besoin de cet ensemble peut appeler trouverChatsVivants
et utiliser la valeur qu’il renvoie.
Il me semble que faire de traiterParagraphe une fonction distincte peut
aussi clarifier les choses. Mais celle-ci est si étroitement liée à
l’algorithme des chats qu’elle n’aurait aucun sens dans une autre situation. De
plus, elle a besoin d’accéder à la variable chatsVivants. C’est donc
une candidate parfaite pour devenir une fonction à l’intérieur d’une
fonction. Quand elle existe à l’intérieur de trouverChatsVivants, il est
clair qu’elle n’est pertinente que là et qu’elle a accès aux variables de
sa fonction parente.
Cette solution est en fait plus grande que la précédente. Mais elle est plus propre et j’espère que vous reconnaîtrez qu’elle est plus lisible.
Le programme ignore encore un grand nombre d’informations qui sont incluses dans les courriels. Il s’agit des dates de naissance, de mort et des noms des mères.
Commençons par les dates : quelle pourrait être la meilleure façon
de stocker une date ? Nous pourrions créer un objet avec ces trois
propriétés, year (année), month (mois), et day (jour) et stocker ensuite des nombres
à l’intérieur.
var quand = {year: 1980, month: 2, day: 1};
Mais JavaScript fournit déjà une sorte d’objet pour cela.
Un tel objet peut être créé en utilisant le mot-clé new :
var quand = new Date(1980, 1, 1); show(quand);
Tout comme la notation avec les accolades et les deux-points que nous avons
déjà vue, new est une façon de créer des valeurs d’un objet. Au lieu
de préciser tous les noms de propriétés et les valeurs, une fonction est
utilisée pour créer l’objet. Cela rend possible de définir une sorte de
procédure standard pour créer des objets. Les fonctions comme celle-là
s’appellent constructeurs et nous verrons comment les écrire dans chapitre 8.
Le constructeur Date peut être utilisé de différentes manières
show(new Date()); show(new Date(1980, 1, 1)); show(new Date(2007, 2, 30, 8, 20, 30));
Comme vous pouvez le voir, ces objets peuvent enregistrer l’heure d’un jour aussi bien qu’une date. Quand aucun argument n’est précisé, un objet représentant l’heure et la date actuelles est créé. Des arguments peuvent être précisés pour stocker une heure et une date précises. L’ordre des arguments est l’année, le mois, le jour, l’heure, la minute, la seconde puis la milliseconde. Les quatre derniers arguments sont optionnels et définis à 0 s’ils ne sont pas précisés.
Pour décrire les mois, on utilise la numérotation de 0 à 11, qui peut provoquer une confusion. Surtout que les nombres définissant les jours commencent eux à 1.
Le contenu de l’objet Date peut être inspecté avec un nombre de
méthodes get….
var aujourdHui = new Date(); print("Année : ", aujourdHui.getFullYear(), ", mois : ", aujourdHui.getMonth(), ", jour : ", aujourdHui.getDate()); print("Heure : ", aujourdHui.getHours(), ", minutes : ", aujourdHui.getMinutes(), ", secondes: ", aujourdHui.getSeconds()); print("Jour de la semaine : ", aujourdHui.getDay());
Tous ces éléments, excepté la méthode getDay, ont une variable set…
qui peut être utilisée pour modifier la valeur de l’objet date.
Dans l’objet, une date est représentée par la somme de millisecondes cumulée depuis le 1er Janvier 1970. Vous pouvez imaginer que c’est un nombre assez impressionnant.
var aujourdHui = new Date(); show(aujourdHui.getTime());
Une chose très utile à faire avec les dates, c’est de les comparer.
var chuteDuMur = new Date(1989, 10, 9); var premiereGuerreDuGolf = new Date (1990, 6, 2); show(chuteDuMur < premiereGuerreDuGolf); show(chuteDuMur == chuteDuMur); // mais show(chuteDuMur == new Date(1989, 10, 9));
Comparer les dates avec <, >, <= et >= remplit exactement l’office
que nous voulons en faire. Quand un objet date est comparé avec lui-même,
le résultat est true, ce qui est bien également. Mais quand == est
utilisé pour comparer un objet date à un autre objet date distinct mais
de même valeur, on obtient false. Étrange, non ?
Comme précisé plus tôt, == retournera la valeur false
var chuteDuMur1 = new Date(1989, 10, 9), chuteDuMur2 = new Date(1989, 10 ,9); show(chuteDuMur1.getTime() == chuteDuMur2.getTime());
Au-delà de la date et l’heure, l’objet Date contient aussi des informations
sur le fuseau horaire. Quand il est une heure à Amsterdam, en fonction
de la période de l’année il peut être midi à Londres et sept heures du
matin à New York. De telles heures ne peuvent être rapprochées que si vous
prenez les fuseaux horaires en compte. La fonction getTimezoneOffset
d’une Date peut être utilisée pour trouver de combien de minutes elle
s’éloigne du GMT (Heure du méridien de Greenwich)
var maintenant = new Date(); print(maintenant.getTimezoneOffset());
"Décédé le 27/04/2006 : Black Leclère"
La partie date est toujours exactement à la même place du paragraphe. Comme
c’est pratique. Écrivez une fonction extraireDate qui prend un tel paragraphe
pour argument, extrait la date et la renvoie sous la forme d’un objet date.
function extraireDate(paragraphe) { function nombreEnPosition(position, longueur) { return Number(paragraphe.slice(position, position + longueur)); } return new Date(nombreEnPosition(16, 4), nombreEnPosition(13, 2) - 1, nombreEnPosition(10, 2)); } show(extraireDate("Décédé le 27-04-2006 : Black Leclère"));
Cela marcherait sans les appels à Number, mais comme je l’ai
expliqué plus haut, je préfère ne pas utiliser de chaînes comme si elles
étaient des nombres. La fonction interne a été introduite pour éviter
d’avoir à répéter trois fois les parties Number et slice.
Notez le -1 pour le numéro du mois. Comme la plupart des gens, tante Émilie
compte les mois à partir de 1, nous devons donc ajuster cette valeur avant
de la donner au constructeur Date (le numéro du jour ne relève pas du
même problème, puisque les objets Date compte les jours de la façon
humaine habituelle).
Dans le chapitre 10, nous verrons une façon plus pratique et plus sûre d’extraire des parties de chaînes qui ont une structure déterminée.
Stocker des chats est une opération qui va se dérouler différemment
à partir de maintenant. Au lieu de simplement mettre la valeur true sur
l’ensemble, nous stockons un objet avec les informations sur le chat. Lorsqu’un
chat meurt, nous ne le supprimons pas de l’ensemble, nous ajoutons simplement
la propriété deces à l’objet pour stocker la date à laquelle le pauvre
animal a trépassé.
Cela signifie que nos fonctions ajouterAuSet et enleverDuSet sont devenues
inutiles. Quelque chose de comparable est nécessaire, mais il s’agit de
stocker aussi les dates de naissance et par la suite, les noms des mères.
function enregistrementChat(nom, dateNaissance, mere) { return {nom: nom, naissance: dateNaissance, mere: mere}; } function ajouterChats(set, noms, dateNaissance, mere) { for (var i = 0; i < noms.length; i++) set[noms[i]] = enregistrementChat(noms[i], dateNaissance, mere); } function chatsDecedes(set, noms, dateDeces) { for (var i = 0; i < noms.length; i++) set[noms[i]].deces = dateDeces; }
enregistrementChat est une fonction distincte pour créer ces objets de stockage. Elle
pourrait être utile dans d’autres situations, telles que la création d’un
objet pour Spot. « Record » (« enregistrement » en français)
est le terme qu’on emploie couramment pour des objets de ce type, qui sont
utilisés pour regrouper un nombre limité de valeurs.
Essayons donc maintenant d’extraire les noms des mamans chats qui se trouvent dans des paragraphes.
"Est né le 15/11/2003 (mère, Spot): Croc Blanc"
Voici un moyen d’obtenir cela…
function extraireNomMere(paragraphe) { var start = paragraphe.indexOf("(mère, ") + "(mère, ".length; var end = paragraphe.indexOf(")"); return paragraphe.slice(start, end); } show(extraireNomMere("Est né le 15/11/2003 (mère, Spot): Croc Blanc"));
Notez comment la position de départ a dû être ajustée à la longueur de
"(mère, ", parce que indexOf renvoie la position initiale de la chaîne
et non la finale.
Ce que fait extraireNomMere peut être exprimé d’une façon plus
générale. Écrivez une fonction extraireChaineEntre qui prend trois arguments,
qui seront tous des chaînes. Elle renverra la partie du premier argument
qui apparaît entre les chaînes fournies par le deuxième et le troisième
argument.
Ainsi, extraireChaineEntre("Est né le 15/11/2003 (mère, Spot): Croc Blanc", "(mère, ",
")") donne "Spot".
extraireChaineEntre("bu ] boo [ bah ] gzz", "[ ", " ]") renvoie "bah".
Pour faire marcher ce deuxième exemple, il peut être utile de savoir qu’on
peut attribuer à indexOf un second paramètre facultatif qui précise à
partir de quel point doit commencer la recherche.
function extraireChaineEntre(chaine, debut, fin) { var indexDebut = chaine.indexOf(debut) + debut.length; var indexFin = chaine.indexOf(fin, indexDebut); return chaine.slice(indexDebut, indexFin); } show(extraireChaineEntre("bu ] boo [ bah ] gzz", "[ ", " ]"));
Avoir la fonction extraireChaineEntre rend possible l’expression de extraireNomMere de
façon plus simple :
function extraireNomMere(paragraphe) { return extraireChaineEntre(paragraphe, "(mère, ", ")"); }
Le nouvel algorithme à chats amélioré ressemble maintenant à ça :
function trouverChats() { var archiveDeMessages = recupererLesMessages(); var chats = {"Spot": enregistrementChat("Spot", new Date(1997, 2, 5), "inconnue")}; function traiterParagraphe(paragraphe) { if (chaineCommencePar(paragraphe, "Est né le")) ajouterChats(chats, nomDesChats(paragraphe), extraireDate(paragraphe), extraireNomMere(paragraphe)); else if (chaineCommencePar(paragraphe, "Décédé le")) chatsDecedes(chats, nomDesChats(paragraphe), extraireDate(paragraphe)); } for (var message = 0; message < archiveDeMessages.length; message++) { var paragraphes = archiveDeMessages[message].split("\n"); for (var i = 0; i < paragraphes.length; i++) traiterParagraphe(paragraphes[i]); } return chats; } var tousLesChats = trouverChats();
Avoir ces données supplémentaires nous permet d’avoir finalement une idée plus précise des chats dont parle tante Émilie. Une fonction comme celle-ci pourrait être utile :
function formatDate(date) { return date.getDate() + "/" + (date.getMonth() + 1) + "/" + date.getFullYear(); } function renseignementSurChat(data, nom) { if (!(nom in data)) return "Aucun chat s’appelant " + nom + " n’a été trouvé."; var chat = data[nom]; var message = nom + ", est né le " + formatDate(chat.naissance) + " de la mère " + chat.mere; if ("deces" in chat) message += ", décédé le " + formatDate(chat.deces); return message + "."; } print(renseignementSurChat(tousLesChats, "Gros Igor"));
La première instruction return dans renseignementSurChat est utilisée comme issue de
secours. Si aucune donnée n’est fournie sur un chat particulier, le reste
de la fonction est dépourvu de sens, nous renvoyons donc immédiatement
une valeur qui empêche le reste du code de s’exécuter.
Dans le passé, certains groupes de programmeurs considéraient comme
malsaines les fonctions contenant de multiples instructions return. Selon
eux, cela rendait difficile de voir quel code était exécuté et quel code
ne l’était pas.
D’autres techniques, qui seront abordées dans le chapitre 5, ont rendu
cet argument plus ou moins obsolète, mais vous pouvez toujours tomber à
l’occasion sur quelqu’un qui critiquera l’utilisation de raccourcis avec
l’instruction return.
La fonction formatDate utilisée par renseignementSurChat n’ajoute pas de zéro avant
la partie mois et jour quand ce sont des nombres à un seul chiffre. Écrivez
une nouvelle version qui fera cela.
function formatDate(date) { function pad(nombre) { if (nombre < 10) return "0" + nombre; else return nombre; } return pad(date.getDate()) + "/" + pad(date.getMonth() + 1) + "/" + date.getFullYear(); } print(formatDate(new Date(2000, 0, 1)));
Écrivez une fonction lePlusVieuxChat qui, étant donné un objet ayant des
chats comme arguments, renvoie le nom du plus vieux chat vivant.
function lePlusVieuxChat(data) { var lePlusVieux = null; for (var nom in data) { var chat = data[nom]; if (!("deces" in chat) && (lePlusVieux == null || lePlusVieux.naissance > chat.naissance)) lePlusVieux = chat; } if (lePlusVieux == null) return null; else return lePlusVieux.nom; } print(lePlusVieuxChat(tousLesChats));
La condition donnée avec la commande if pourrait paraître un peu intimidante.
On peut la lire comme : « ne stocker le chat en cours dans la variable
lePlusVieux que s’il n’est pas mort, et si lePlusVieux est soit null soit un chat
qui est né après le chat en cours ».
Notez que cette fonction renvoie null quand il n’existe aucun chat vivant
dans data. Que fait votre solution à l’exercice dans ce cas ?
Maintenant que vous êtes familiarisé avec les tableaux, je peux vous montrer
quelque chose de lié. Quel que soit le nom d’une fonction, une variable
spéciale nommée arguments est ajoutée à l’environnement dans lequel
le corps de la fonction tourne. Cette variable se réfère à un objet qui
ressemble à un tableau. Il a la propriété 0 pour le premier argument,
1 pour le second, et ainsi de suite pour chaque argument donné par la
fonction. Il possède également une propriété length.
Cependant, cet objet n’est pas véritablement un tableau, il ne possède pas
de méthodes telles que push et il ne met pas pas à jour automatiquement sa
propriété length quand vous lui ajoutez quelque chose. Pourquoi n’est-ce
pas le cas ? Je n’ai jamais vraiment compris l’utilité de tout cela, mais
c’est quelque chose dont vous devez avoir connaissance.
function compteurArgument() { print("Vous m’avez donné ", arguments.length, " arguments."); } compteurArgument("Mort", "Famine", "Fléau");
Certaines fonctions peuvent prendre nombre quelconque d’arguments, comme par
exemple la fonction print. Cette fonction particulière opère une boucle sur
les valeurs des arguments d’un objet pour en faire quelque chose. D’autres
peuvent prendre des arguments de manière optionnelle qui sont initialisés
à une valeur par défaut sensée si l’utilisateur ne fournit pas de valeur.
function ajouter(nombre, combien) { if (arguments.length < 2) combien = 1; return nombre + combien; } show(ajouter(6)); show(ajouter(6, 4));
Étendez la fonction serie de l’exercice 4.2 pour prendre un second argument,
optionnel. Si un seul argument est donné à la fonction, elle se comporte
comme précédemment et produit une série commençant à 0 jusqu’au nombre
donné. Si deux arguments sont donnés, le premier indique le début de la
série, le second la fin.
function serie(debut, fin) { if (arguments.length < 2) { fin = debut; debut = 0; } var resultat = []; for (var i = debut; i <= fin; i++) resultat.push(i); return resultat; } show(serie(4)); show(serie(2, 4));
L’argument optionnel ne fonctionne pas exactement comme celui de
l’exemple ajouter ci-dessus. Quand il n’est pas précisé, le premier argument
prend le rôle de fin et debut devient 0.
Vous devez vous rappeler la ligne de code citée en introduction :
print(somme(serie(1, 10)));
Nous avons la fonction serie maintenant. Tout ce que nous avons besoin
pour faire fonctionner cette ligne est une fonction somme. Cette fonction
prend un tableau de nombre en arguments et retourne leur somme. Écrivez-la,
ce devrait être simple.
function somme(nombres) { var total = 0; for (var i = 0; i < nombres.length; i++) total += nombres[i]; return total; } print(somme(serie(1, 10)));
Le chapitre 2 nous a permis d’étudier les fonctions Math.max
et Math.min. Avec ce que vous connaissez maintenant, vous pourrez noter
que max et min sont déjà les propriétés d’un objet enregistré sous
le nom de Math. Voici un autre rôle que les objets peuvent jouer :
celui d’entrepôt pour un grand nombre de valeurs liées.
Il y a beaucoup de valeurs dans Math, si elles avaient été placées
directement dans l’environnement global, elles l’auraient, comme on dit,
pollué. Plus il y a de noms utilisés, plus il est probable d’écraser par
accident la valeur d’une variable. Par exemple, il n’est pas incongru de
vouloir nommer une variable max.
La plupart des langages vous arrêteront, ou du moins vous alerteront, quand vous définirez une variable avec un nom déjà utilisé par l’environnement. Pas JavaScript.
De toute façon, on peut trouver tout un ensemble de fonctions mathématiques
et de constantes dans Math. Toutes les fonctions trigonométriques sont
présentes : cos, sin, tan, acos, asin et atan. π et e, qui sont
écrits en capitales (PI et E), ce qui était à une époque une façon
très à la mode d’indiquer que quelque chose est une constante. pow est un
bon moyen de substitution des fonctions puissance que nous avons écrites,
il accepte les exposants négatifs et fractionnels. sqrt extrait la racine
carrée d’un nombre. max et min peuvent donner le maximum ou le minimum de
deux valeurs. round, floor,
et ceil vont respectivement arrondir un nombre à l’entier le plus proche,
à l’entier inférieur et supérieur le plus proche.
Il existe un grand nombre d’autres valeurs dans Math, mais ce texte est
une introduction, pas une référence. Les références sont ce que vous
consultez lorsque vous soupçonnez qu’il existe quelque chose dans un langage,
mais avez besoin de savoir comment ça s’appelle ou comment ça marche au
juste. Malheureusement, il n’existe aucune référence totalement exhaustive
pour le JavaScript. C’est essentiellement parce que sa forme courante est la
résultante d’un processus chaotique pendant lequel différents navigateurs
lui ont ajouté diverses extensions à différentes périodes. Le document
standard ECMA, mentionné dans l’introduction, fournit une solide documentation
du langage de base, mais il est plus ou moins lisible.
Pour la plupart de vos questions, vous pouvez compter sur le Mozilla Developer
Network.
Vous avez peut-être déjà pensé à un moyen de découvrir ce qui est
disponible avec l’objet Math :
for (var nom in Math) print(nom);
Mais hélas, rien n’apparaît. De même, quand vous faites ceci :
for (var nom in ["Huey", "Dewey", "Loui"]) print(nom);
Vous ne voyez que 0, 1, et 2, pas length, ni push, ou join,
qui s’y trouvent pourtant bel et bien. Apparemment, certaines propriétés
des objets sont cachées. Il y a une bonne raison à
ça : tous les objets ont quelques méthodes, par exemple toString
qui convertit l’objet en une sorte de chaîne pertinente, mais vous ne
souhaiterez sûrement pas les voir quand vous êtes par exemple, à la
recherche des chats que vous avez stockés dans l’objet.
Pourquoi les propriétés de Math sont-elles cachées ? Ce n’est pas
très clair pour moi. Il y a sûrement quelqu’un qui a voulu en faire un
type d’objet mystérieux.
Toutes les propriétés que vos programmes ajoutent aux objets sont
visibles. Il n’y a pas moyen de les cacher, ce qui est regrettable parce que,
comme vous le verrez dans le chapitre 8, il serait sympa d’ajouter des méthodes
aux objets sans avoir à les rendre visibles dans des boucles for/in.
Certaines propriétés sont en lecture seule, vous pouvez récupérer leur valeur mais pas la modifier. Par exemple, les propriétés d’une valeur de chaîne sont toutes en lecture seule.
D’autres propriétés sont "actives". Modifier leur valeur a des conséquences. Par exemple, le fait de diminuer la longueur d’un tableau provoque la disparition des éléments en trop:
var tableau = ["Ciel", "Terre", "Homme"]; tableau.length = 2; show(tableau);
- Il y a quelques problèmes subtils avec cette approche dont nous parlerons et que nous résoudrons dans le chapitre 8. On ne s’en occupera pas pour ce chapitre.