IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

ÉLOQUENT JAVASCRIPT

3 e Édition


précédentsommairesuivant

V. Chapitre 4 : structure des données - Objets et Tableaux

À deux reprises, on m'a demandé : « Priez, M. Babbage, si vous mettez dans la machine des chiffres erronés, les bonnes réponses seront-elles données ? » [...] Je ne suis pas en mesure d'appréhender correctement le genre de confusion d'idées susceptible de provoquer une telle question.Charles Babbage, Passages de la vie d'un philosophe (1864)

Nombres, booléens et chaînes de caractères sont les atomes à partir desquels les structures des données sont construites. Cependant, de nombreuses informations requièrent plus d’un atome. Les Objets nous permettent de grouper des valeurs, y compris d’autres objets, pour construire des structures plus complexes.

Les programmes que nous avons conçus jusqu’à présent étaient limités par le fait qu’ils ne géraient que des données de types simples. Ce chapitre va introduire les bases des structures de données. En fin de chapitre, vous en connaîtrez suffisamment pour écrire des programmes utiles.

Le chapitre va se développer au travers d’un exemple de programme plus ou moins réaliste, introduisant les concepts au fur et à mesure qu’ils apparaissent dans le problème. Le code d’exemple se construira souvent sur des fonctions et des liens introduits précédemment dans le texte.

V-A. L’écureuil-garou

Régulièrement, entre 8 et 10 heures du matin, Jacques se retrouve transformé en un furieux petit rongeur à la queue touffue.

D’un côté, Jacques est heureux de ne pas être transformé en trop classique lycanthrope : se transformer en écureuil lui cause moins de problèmes que de se transformer en loup. Au lieu de s’inquiéter d’avoir éventuellement mangé le voisin (ça, ce serait gênant), il doit s’inquiéter de ne pas se faire manger par le chat du voisin. Après s’être réveillé par deux fois sur une fine petite branche à la cime d’un arbre, nu et désorienté, il s’est mis à barricader portes et fenêtres de sa chambre le soir et à placer quelques noix sur le sol pour se tenir occupé. Cela résout le problème du chat et de l’arbre. Mais Jacques voudrait être complètement débarrassé de cette situation. La survenance aléatoire de la transformation le pousse à soupçonner que cela pourrait être déclenché par quelque chose. Pendant un temps, il a cru que cela n’arrivait que les jours où il avait été en contact avec des chênes. Mais le fait d’éviter les chênes n’a rien changé au problème. Passant à une approche plus scientifique, Jacques a commencé à tenir un journal quotidien de tout ce qu’il fait et des moments où il change de forme. Avec ces données, il espère cerner les conditions qui déclenchent les transformations.

La première chose dont il a besoin c’est d’une structure de données pour stocker l’information.

V-B. Ensemble de données

Pour travailler avec un ensemble de données numériques, nous devons d’abord trouver un moyen de représenter cela dans la mémoire de notre machine. Ainsi, nous voulons représenter la collection des nombres 2, 3, 5, 7 et 11.

Nous pouvons être créatifs et travailler avec des chaînes de caractères (string) car après tout une chaîne de caractères peut être de n’importe quelle longueur et donc, nous pourrions utiliser la chaîne « 2 3 5 7 11 » dans notre exemple. Mais c’est maladroit. Pour les utiliser, vous devriez à chaque fois en extraire les différents nombres et les transformer en valeurs numériques.

Heureusement, JavaScript fournit un type de données spécifique pour stocker une série de valeurs. Ce type de données est appelé Array (tableau) et il est écrit comme une liste de valeurs entre crochets séparées par des virgules.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
let listOfNumbers = [2, 3, 5, 7, 11];
console.log(listOfNumbers[2]);
// → 5
console.log(listOfNumbers[0]);
// → 2
console.log(listOfNumbers[2 - 1]);
// → 3

La notation qui permet de récupérer des éléments à l’intérieur d’un tableau utilise également les crochets. Une paire de crochets immédiatement après une expression, incluant une autre expression, pointera sur l’élément de l‘expression de gauche correspondant à l’index donné par l’expression entre crochets.

Le premier index d’un tableau est zéro, pas un. Donc le premier élément de la liste est retrouvé par listeDeNombres[0]. Le comptage partant de zéro a une longue tradition dans la technologie et il est assez logique d’un certain point de vue, mais il faut un certain temps pour s’y habituer. Imaginez l’index comme le nombre d’éléments à laisser tomber en partant du début du tableau.

V-C. Propriétés

Nous avons vu plusieurs expressions suspectes comme myString.length (pour récupérer la longueur d’une chaîne de caractères) et Math.max (la fonction maximum) dans les chapitres précédents. Ce sont des expressions qui permettent d’accéder à une propriété de certaines valeurs. Dans le premier cas, nous accédons à la propriété longueur (length) de la valeur dans myString. Dans le second cas, nous accédons à la propriété appelée max de l’objet Math (qui est une collection de constantes et fonctions mathématiques).

À peu près toutes les valeurs en JavaScript ont des propriétés. Les seules exceptions sont null et undefined. Si vous tentez d’accéder à une propriété d’une de ces non-valeurs, vous obtenez une erreur.

 
Sélectionnez
null.length;
// → TypeError: null has no properties

Les deux principaux moyens d’accéder aux propriétés en JavaScript sont le point et les crochets. valeur.x et valeur[x] accèdent à une propriété de valeur (mais pas forcément à la même propriété). La différence est la manière dont x est interprété. En utilisant un point, le mot après le point est le nom littéral deense la propriété. En utilisant les crochets, l’expression entre crochets est évaluée pour récupérer le nom de la propriété. Alors que valeur.x va chercher la propriété de la valeur nommée « x », valeur[x] tente d’évaluer l’expression x et utilise le résultat, converti en chaîne de caractères, comme nom de la propriété. Donc, si vous savez que la propriété qui vous intéresse s’appelle couleur, vous dites valeur.couleur. Si vous souhaitez extraire la propriété nommée d’après la valeur contenue dans la constante i, vous dites valeur[i]. Les noms des propriétés sont des chaînes de caractères. Ils peuvent être n’importe quelle chaîne de caractères, mais la notation avec point ne fonctionne que si le nom ressemble à un nom de variable valide. Donc si vous souhaitez accéder à une propriété nommée 2 ou Pierre Carré, vous devez utiliser les crochets: valeur[2] ou valeur["Pierre Carré"].

Les éléments dans un tableau (Array) sont stockés comme des propriétés de ce tableau, utilisant des nombres comme nom de propriété. Parce que vous ne pouvez utiliser la notation point avec des nombres et que vous souhaitez néanmoins utiliser la constante qui contient l’index, vous devez utiliser la notation entre crochets pour les récupérer. La propriété length (longueur) d’un tableau nous indique le nombre d’éléments qu’il contient. Ce nom de propriété est une constante valide et nous connaissons ce nom à l’avance, donc pour trouver la longueur d’un tableau, vous pouvez écrire array.length parce que c’est aussi plus facile que d’écrire array["length"].

V-D. Méthodes

Les objets string et array contiennent, en plus de la propriété length, un certain nombre de propriétés qui contiennent des valeurs de fonctions.

 
Sélectionnez
1.
2.
3.
4.
5.
let doh = "Doh";
console.log(typeof doh.toUpperCase);
// → function
console.log(doh.toUpperCase());
// → DOH

Chaque chaîne de caractères possède une propriété toUpperCase. Lorsqu’elle est appelée, elle va renvoyer une copie de la chaîne de caractères dans laquelle toutes les lettres ont été mises en majuscule. Il existe aussi la propriété toLowerCase, pour faire le contraire (mettre en minuscule).

Fait intéressant, même si l’appel de la fonction toUppercase ne passe aucun argument, la fonction a accès à la chaîne de caractères « Doh », la valeur de la propriété que nous appelons. Le fonctionnement de ceci est expliqué au Chapitre 6

Les propriétés qui contiennent des fonctions sont généralement appelées méthodes de la variable à laquelle elles appartiennent. « toUpperCase » est une méthode d’une chaîne de caractères.

Cet exemple montre deux méthodes que vous pouvez utiliser pour manipuler un tableau :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
let sequence = [1, 2, 3];
sequence.push(4);
sequence.push(5);
console.log(sequence);
// → [1, 2, 3, 4, 5]
console.log(sequence.pop());
// → 5
console.log(sequence);
// → [1, 2, 3, 4]

La méthode push ajoute des valeurs à la fin d’un tableau et la méthode pop fait le contraire, supprimant la dernière valeur du tableau et retournant celui-ci.

Ces noms quelque peu ridicules sont les termes traditionnels pour les opérations sur un empilement. Un empilement, en programmation, est une structure de données dans laquelle vous pouvez pousser des valeurs et les ressortir dans l’ordre opposé de sorte que le dernier élément ajouté est le premier retiré. C’est assez commun en programmation. Vous pourriez vous souvenir de la fonction call stack du <chapitre précédent> qui est un autre exemple de la même idée.

V-E. Objets

Retour à l’écureuil-garou. Un ensemble d’entrées quotidiennes du journal peut être représenté comme un tableau. Mais chaque entrée n’est pas juste un nombre ou une chaîne de caractères. Chaque entrée doit stocker une liste d’activités et une valeur booléenne qui indique si Jacques s’est transformé en écureuil ou pas. Idéalement, nous souhaiterions regrouper tout cela en une seule variable et mettre toutes ces variables dans un tableau du journal d’entrées.

Les variables de type Object sont une collection arbitraire de propriétés. Une manière de créer un objet est d’utiliser des accolades comme dans une expression.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
let day1 = {
  squirrel: false,
  events: ["work", "touched tree", "pizza", "running"]
};
console.log(day1.squirrel);
// → false
console.log(day1.wolf);
// → undefined
day1.wolf = false;
console.log(day1.wolf);
// → false

Dans les accolades, il y a une liste de propriétés séparées par des virgules. Chaque propriété a un nom suivi de deux points et d’une valeur. Quand un objet est écrit sur plusieurs lignes, l’indentation, comme dans l’exemple, aide à la lisibilité. Les propriétés qui ne seraient pas des noms de constante valides ou des nombres valides doivent être mises entre guillemets.

 
Sélectionnez
1.
2.
3.
4.
let descriptions = {
  work: "Went to work",
  "touched tree": "Touched a tree"
};

Ceci veut dire que les accolades ont deux significations en JavaScript. En début d’une déclaration, elles démarrent le bloc d’instruction. Dans toute autre position, elles décrivent un objet. Heureusement, il est rarement utile de commencer une instruction avec un objet dans des accolades, donc cette ambiguïté n’est pas réellement un problème.

La lecture d’une propriété inexistante vous donnera une valeur indéfinie (undefined).

Il est possible d'attribuer une valeur à une propriété avec l’opérateur « = ». Ceci remplacera la valeur de la propriété si elle existe déjà ou créera une nouvelle propriété sur l’objet si elle n’existe pas encore.

Revenons brièvement à notre modèle tentaculaire de liaisons : les liaisons de propriétés sont similaires. Elles saisissent des valeurs, mais d'autres liaisons et propriétés peuvent conserver ces mêmes valeurs. Vous pouvez penser à des objets comme des pieuvres avec un nombre quelconque de tentacules portant chacun un nom tatoué.

L'opérateur delete coupe un tentacule de la pieuvre. C'est un opérateur unaire qui, lorsqu'il est appliqué à une propriété d'objet, supprimera la propriété nommée de l'objet. Ce n'est pas une chose que l’on fait couramment, mais c'est possible.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
let anObject = {left: 1, right: 2};
console.log(anObject.left);
// → 1
delete anObject.left;
console.log(anObject.left);
// → undefined
console.log("left" in anObject);
// → false
console.log("right" in anObject);
// → true

L’opérateur binaire in, quand il se rapporte à une chaîne de caractères ou à un objet, indique si l’objet à une propriété avec ce nom. La différence entre définir une propriété comme indéfinie (undefined) et la supprimer est que dans le premier cas, l’objet a toujours la propriété (elle n’a simplement pas une valeur très intéressante), alors que dans le second cas, la propriété n’existe plus et cela renverra une valeur « faux ».

Pour trouver les propriétés qu’un objet possède, vous pouvez utiliser la fonction Object.keys. Vous lui donnez un objet et elle renvoie un tableau de chaînes de caractères avec les noms des propriétés de l’objet.

 
Sélectionnez
console.log(Object.keys({x: 0, y: 0, z: 2}));
// → ["x", "y", "z"]

Il existe une fonction Object.assign qui copie toutes les propriétés d’un objet vers un autre.

 
Sélectionnez
1.
2.
3.
4.
let objectA = {a: 1, b: 2};
Object.assign(objectA, {b: 3, c: 4});
console.log(objectA);
// → {a: 1, b: 3, c: 4}

Les tableaux (arrays) sont donc un type d’objet spécialisé pour stocker une série de choses. Si vous testez typeof [], il produira « object ». Vous pouvez vous les représenter comme de longues pieuvres plates avec tous leurs tentacules bien alignés, étiquetés avec un numéro.

Nous allons représenter le journal tenu par Jacques comme un tableau d’objets.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
let journal = [
  {events: ["work", "touched tree", "pizza",
            "running", "television"],
   squirrel: false},
  {events: ["work", "ice cream", "cauliflower",
            "lasagna", "touched tree", "brushed teeth"],
   squirrel: false},
  {events: ["weekend", "cycling", "break", "peanuts",
            "beer"],
   squirrel: true},
  /* et ainsi de suite... */
];

V-F. Mutabilité

Nous allons bientôt rentrer dans la vraie programmation. Mais avant cela, il y a encore un point de théorie à comprendre.

Nous avons vu que les valeurs d’un objet peuvent être modifiées. Les types de variables ont été étudiés dans les chapitres précédents comme nombres, chaînes de caractères, booléens et sont immuables (il n’est pas possible de changer des variables de ces types).

Vous pouvez les combiner, déduire de nouvelles valeurs à partir d’elles, mais quand vous prenez une chaîne de valeur spécifique, cette valeur restera toujours la même. Le texte à l’intérieur ne peut être changé. Si vous avez une chaîne de caractères qui contient « chat », ce n’est pas possible pour un autre code de modifier deux caractères dans la chaîne pour faire « rat ».

Les objets fonctionnent différemment. Vous pouvez changer leurs propriétés de sorte qu’une seule variable peut avoir des contenus différents à des moments différents.

Quand nous avons deux nombres, 120 et 120, nous pouvons les considérer comme étant précisément le même nombre, qu’ils se réfèrent ou pas aux mêmes octets physiques. Avec des objets, il y a une différence entre avoir deux références au même objet et avoir deux objets qui contiennent la même propriété. Voyez le code suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
let objet1 = {value: 10};
let objet2 = object1;
let objet3 = {value: 10};

console.log(objet1 == objet2);
// → true
console.log(objet1 == objet3);
// → false

objet1.value = 15;
console.log(objet2.value);
// → 15
console.log(objet3.value);
// → 10

L’objet1 et l’objet2 liés contiennent le même objet, c’est pourquoi une modification de l’objet1 change également la valeur de l’objet2. On dit qu’ils ont la même identité. Par contre, l’objet3 pointe sur un objet différent qui au départ contient la même propriété que l’objet1 mais vit une vie séparée.

Les liens peuvent être modifiables ou constants, mais ceci est indépendant de la façon dont leurs valeurs se comportent. Même si les valeurs numériques ne changent pas, vous pouvez utiliser une variable let pour suivre l’évolution d’un nombre en modifiant la valeur vers laquelle pointe ce let. De même, une constante liée à un objet ne peut pas être changée et continuera de pointer vers le même objet, le contenu de l’objet peut changer.

 
Sélectionnez
1.
2.
3.
4.
5.
const score = {visitors: 0, home: 0};
// ceci est correct
score.visitors = 1;
// ceci n’est pas permis
score = {visitors: 1, home: 1};

Quand vous comparez des objets JavaScript avec l’opérateur « == », il le compare par identité : il produira « vrai » uniquement si les objets ont exactement la même valeur. En comparant différents objets, il retournera « faux » même si leurs propriétés sont identiques. En JavaScript, il n’existe pas d’opération de comparaison en « profondeur » qui comparerait des objets au niveau de leur contenu, mais il est possible d’écrire cela vous-même (c’est l’objet d’un des exercices à la fin de ce chapitre).

V-F-1. Le journal du lycanthrope

Ainsi, Jacques démarre son interpréteur JavaScript et met en place l’environnement dont il a besoin pour tenir son journal.

 
Sélectionnez
let journal = [];

function addEntry(events, squirrel) {
  journal.push({events, squirrel});
}

Notez bien que les objets ajoutés au journal paraissent un peu étranges. Au lieu de déclarer des propriétés comme events:events, cela donne juste un nom de propriété. Ceci est un raccourci qui signifie la même chose : si le nom de la propriété dans une notation entre parenthèses n’est pas suivi d’une valeur, cette valeur est reprise du lien de même nom.

Ainsi donc, chaque soir vers 10 heures, ou parfois le matin après être descendu du dessus de son étagère, Jacques enregistre sa journée.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
addEntry(["work", "touched tree", "pizza", "running",
          "television"], false);
addEntry(["work", "ice cream", "cauliflower", "lasagna",
          "touched tree", "brushed teeth"], false);
addEntry(["weekend", "cycling", "break", "peanuts",
          "beer"], true);

Une fois qu’il a assez de données, il tente d’utiliser des statistiques pour trouver quel événement entraîne les « écureuillifications ».

La Corrélation est la mesure de la dépendance entre des variables statistiques. Une variable statistique n’est pas tout à fait la même chose qu’une variable en programmation. En statistiques, vous avez un ensemble de mesures et chaque variable est mesurée pour chaque mesure : la corrélation entre variables est le plus souvent exprimée par une valeur comprise entre -1 et 1. Une corrélation de zéro signifie que les variables ne sont pas du tout liées. Une corrélation de 1 indique que les deux variables sont parfaitement liées, quand vous avez l’une, vous avez l’autre. Une corrélation de -1 indique que les variables sont parfaitement liées mais opposées : quand l’une est vraie, l’autre est fausse.

Pour calculer la mesure de corrélation entre deux variables booléennes, nous pouvons utiliser le coefficient phi (ϕ). Il s’agit d’une formule dont l’entrée est une table de fréquence contenant le nombre de fois que les différentes variables ont été observées. Le résultat de la formule est un nombre compris entre -1 et 1 qui décrit la corrélation.

Nous pourrions prendre l’événement « manger une pizza » et avoir une table de fréquence comme ceci où chaque nombre indique le nombre de fois que la combinaison est apparue dans nos mesures :

Si nous appelons cette table n, nous pouvons calculer ϕ en utilisant la formule suivante:

kitxmlcodelatexdvpΦ = $ \frac{ n_{11}n_{00} - n_{10}n_{01}} {\sqrt{ n_{1}\cdot n_{0} \cdot n_{1}\cdot n_{0} }}finkitxmlcodelatexdvp

(Si à ce point, vous laissez tomber le livre pour revenir sur un terrible retour en arrière sur vos cours de mathématiques du secondaire, attendez ! Je ne souhaite pas vous torturer avec des pages entières de notation cryptiques, c’est juste une formule. Et tout ce que nous allons en faire c’est la transformer en JavaScript).

La notation n01 indique le nombre de mesures où la première variable (écureuillisation - squirrel) est fausse (0) et la seconde variable (pizza) est vraie (1). Dans la table pizza, n01 est égal à 9.

La valeur n1• fait référence à la somme de toutes les mesures pour lesquelles la première variable est vraie, ce qui correspond à 5 dans notre exemple. De même, n•0 fait référence à la somme des mesures pour lesquelles la seconde variable est fausse.

Donc, pour la table pizza, la partie supérieure de la division (le dividende) serait 1×76−4×9 = 40, et la partie en dessous (le diviseur) serait la racine carrée de 5×85×10×80 ou √340000. Ce qui ne fait que ϕ ≈ 0.069, ce qui est très petit. Manger des pizzas ne semble pas avoir d’influence sur les transformations.

V-G. Calcul des corrélations

Nous pouvons représenter un tableau de 2 sur 2 en JavaScript avec quatre éléments de type Array ([76, 9, 4, 1]). Nous pouvons aussi utiliser d’autres représentations comme un tableau contenant deux doubles éléments de type Array ([[76, 9], [4, 1]]) ou un objet avec des noms de propriétés comme « 11 » et « 01 », mais le simple Array est facile et permet d’utiliser des expressions d’accès à la table agréablement courtes.

Nous allons interpréter les index du tableau comme des nombres binaires à 2 bits où le chiffre le plus à gauche (plus signifiant) fait référence à la variable squirrel et celui le plus à droite (le moins signifiant) fait référence à la variable event. Par exemple, le nombre binaire 10 fait référence au cas où Jacques s’est transformé en écureuil, mais l’événement (disons, « pizza ») n’a pas eu lieu. C’est arrivé 4 fois. Et puisque 10 en binaire est 2 en décimal, nous allons enregistrer le nombre à l’index 2 du tableau.

Ceci est une fonction qui calcule le coefficient ϕ d’un tel tableau :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
function phi(table) {
  return (table[3] * table[0] - table[2] * table[1]) /
    Math.sqrt((table[2] + table[3]) *
              (table[0] + table[1]) *
              (table[1] + table[3]) *
              (table[0] + table[2]));
}
console.log(phi([76, 9, 4, 1]));
// → 0.06859943405700354


C’est une traduction directe de la formule ϕ en JavaScript. Math.sqrt est la fonction racine carrée telle que fournie par l’objet Math dans un environnement JavaScript standard. Nous devons ajouter deux champs de la table pour obtenir des champs comme n1• parce que la somme des lignes et des colonnes n’est pas stockée directement dans notre structure de données.

Jacques a tenu son journal pendant trois mois. Les données qui en résultent sont disponibles dans le <bac à sable de codage> relatif à ce chapitre où il est stocké dans l’élément JOURNAL et dans un fichier <téléchargeable>.

Pour extraire un tableau de deux sur deux du journal, nous devons boucler sur toutes les entrées et indiquer combien de fois l’événement survient en relation avec les transformations en écureuil.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
function tableFor(event, journal) {
  let table = [0, 0, 0, 0];
  for (let i = 0; i < journal.length; i++) {
    let entry = journal[i], index = 0;
    if (entry.events.includes(event)) index += 1;
    if (entry.squirrel) index += 2;
    table[index] += 1;
  }
  return table;
}

console.log(tableFor("pizza", JOURNAL));
// → [76, 9, 4, 1]

Les tableaux ont une méthode intégrée qui permet de vérifier si une valeur existe dans le tableau. La fonction ci-dessus utilise cela pour déterminer si l’événement qui nous intéresse fait partie de la liste des événements pour un jour donné.

Le corps de la boucle dans tableFor analyse dans quelle boîte de la table, chaque entrée du journal arrive, en vérifiant si l’entrée contient l’événement spécifique qui l’intéresse et quels événements arrivent en même temps que l’incident de l’écureuil. La boucle ajoute ensuite un (1) dans la boîte correspondante de la table.

Nous avons maintenant les outils dont nous avons besoin pour calculer les corrélations individuelles. La dernière étape étant de trouver une corrélation pour tout type d’événement qui a été enregistré et de voir si quelque chose en ressort.

V-H. Boucle dans un tableau

Dans la fonction tableFor, Il y a une boucle comme celle-ci :

 
Sélectionnez
1.
2.
3.
4.
for (let i = 0; i < JOURNAL.length; i++) {
  let entry = JOURNAL[i];
  // Fais quelque chose de cette entrée
}

Ce type de boucle est commun en JavaScript classique : passer en revue les éléments d’une table l’un après l’autre est quelque chose de fréquent et pour le faire, vous devez lancer un compteur de la longueur du tableau et prendre les éléments un par un.

Il y a un moyen plus simple d’écrire ce genre de boucle dans le JavaScript moderne.

 
Sélectionnez
1.
2.
3.
for (let entry of JOURNAL) {
  console.log(`${entry.events.length} events.`);
}

Quand une instruction for boucle comme ceci avec le mot of après la définition d’une variable, elle va boucler sur les éléments de la valeur donnée après le of. Cela ne fonctionne pas uniquement pour des tableaux, mais également pour les chaînes de caractères et certaines autres structures de données. Nous discuterons de ce fonctionnement dans le Chapitre 6.

V-I. L’analyse finale

Nous devons calculer la corrélation pour tout type d’événements qui arrivent dans l’ensemble des données. Pour faire cela, nous devons d’abord découvrir tous les types d’événements.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
function journalEvents(journal) {
  let events = [];
  for (let entry of journal) {
    for (let event of entry.events) {
      if (!events.includes(event)) {
        events.push(event);
      }
    }
  }
  return events;
}

console.log(journalEvents(JOURNAL));
// → ["carrot", "exercise", "weekend", "bread", …]

En passant en revue tous les événements et en ajoutant ceux qui ne sont pas encore dans le tableau des événements, la fonction collecte tous les types d’événements.

En utilisant cela, nous pouvons voir toutes les corrélations.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
for (let event of journalEvents(JOURNAL)) {
  console.log(event + ":", phi(tableFor(event, JOURNAL)));
}
// → carrot:   0.0140970969
// → exercise: 0.0685994341
// → weekend:  0.1371988681
// → bread:   -0.0757554019
// → pudding: -0.0648203724
// et ainsi de suite...

La plupart des corrélations semblent proches de zéro. Manger des carottes, du pain ou du pudding ne déclenche apparemment pas l’écureuil-lycanthropie. Mais il semble se passer quelque chose de plus fréquent le week-end. Filtrons les résultats pour n’avoir que les corrélations plus grandes que 0.1 et moins que -0.1.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
for (let event of journalEvents(JOURNAL)) {
  let correlation = phi(tableFor(event, JOURNAL));
  if (correlation > 0.1 || correlation < -0.1) {
    console.log(event + ":", correlation);
  }
}
// → weekend:        0.1371988681
// → brushed teeth: -0.3805211953
// → candy:          0.1296407447
// → work:          -0.1371988681
// → spaghetti:      0.2425356250
// → reading:        0.1106828054
// → peanuts:        0.5902679812

Aha! Il y a deux facteurs avec une corrélation qui est clairement plus forte que les autres. Manger des cacahuètes a un effet positif fort sur les chances de se transformer en écureuil alors que se laver les dents à un effet négatif significatif.

Intéressant. Essayons quelque chose.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
for (let entry of JOURNAL) {
  if (entry.events.includes("peanuts") &&
     !entry.events.includes("brushed teeth")) {
    entry.events.push("peanut teeth");
  }
}
console.log(phi(tableFor("peanut teeth", JOURNAL)));
// → 1

Voici un résultat fort. Le phénomène apparaît précisément quand Jacques mange des cacahuètes et oublie de se brosser les dents. Si seulement l’hygiène dentaire lui tenait à cœur, il n’aurait jamais connu cette affection.

Sachant cela, Jacques arrête complètement de manger des cacahuètes et découvre que ses transformations ne reviennent pas.

Pendant quelques années, tout va bien pour Jacques. Mais à un moment il perd son travail. Parce qu’il vit dans un pays pourri où ne pas avoir de travail signifie ne pas jouir de services médicaux, il est forcé de se produire dans un cirque où il présente l’incroyable homme-écureuil, emplissant sa bouche de beurre de cacahuète avant chaque représentation.

Un jour, n’en pouvant plus de cette misérable existence, Jacques oublie de se retransformer en humain, saute à travers un trou dans la toile du chapiteau et disparaît dans la forêt. On ne l’a plus revu.

V-J. Plus loin avec les tableaux

Avant de terminer ce chapitre, je voudrais introduire quelques autres concepts orientés objet. Je vais commencer par introduire quelques méthodes des tableaux généralement très utiles.

Nous avons vu plus hautMéthodes dans ce chapitre, push et pop, qui ajoutent et enlèvent des éléments à la fin d’un tableau. Les méthodes correspondantes pour ajouter et retirer un élément au début du tableau sont appelées unshift et shift.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
let todoList = [];
function remember(task) {
  todoList.push(task);
}
function getTask() {
  return todoList.shift();
}
function rememberUrgently(task) {
  todoList.unshift(task);
}

Ce programme gère des files de tâches. Vous pouvez ajouter des tâches à la fin de la liste en faisant appel à remember("groceries") et quand vous êtes prêts à faire quelque chose, vous appelez getTask() pour récupérer (et supprimer) le premier élément de la file. La fonction rememberUrgently ajoute également une tâche mais l’ajoute au début plutôt qu’à la fin de la liste des tâches.

Pour rechercher une valeur spécifique, les tableaux fournissent une méthode indexOf. La méthode recherche dans le tableau, du début à la fin, et renvoie l’index auquel se trouve la valeur recherchée (ou -1 s’il ne l’a pas trouvée). Pour rechercher à partir de la fin plutôt qu’à partir du début, il y a une méthode similaire appelée lastIndexOf.

 
Sélectionnez
1.
2.
3.
4.
console.log([1, 2, 3, 2, 1].indexOf(2));
// → 1
console.log([1, 2, 3, 2, 1].lastIndexOf(2));
// → 3

Les deux fonctions indexOf et lastIndexOf acceptent un second argument optionnel qui indique à partir d’où entamer la recherche.

Une autre méthode fondamentale est slice, qui récupère les index de début et de fin et en fait un tableau qui ne reprend que les éléments compris entre eux. L’index de début est inclusif, celui de fin est exclusif.

 
Sélectionnez
1.
2.
3.
4.
console.log([0, 1, 2, 3, 4].slice(2, 4));
// → [2, 3]
console.log([0, 1, 2, 3, 4].slice(2));
// → [2, 3, 4]

Quand l’index de fin n’est pas donné, slice reprend tous les éléments après l’index de début. Vous pouvez également omettre l’index de début pour copier l’ensemble du tableau.

La méthode concat peut être utilisée pour coller ensemble des tableaux et créer un nouveau tableau comme l’opérateur + le fait pour des chaînes de caractères.

L’exemple suivant montre concat et slice en action : il prend un tableau et un index et il renvoie un nouveau tableau qui est une copie du tableau original, avec l’élément de l’index donné qui a été supprimé.

 
Sélectionnez
function remove(array, index) {
  return array.slice(0, index)
    .concat(array.slice(index + 1));
}
console.log(remove(["a", "b", "c", "d", "e"], 2));
// → ["a", "b", "d", "e"]

Si vous passez à concat un argument qui n’est pas un tableau, cette valeur sera ajoutée à un nouveau tableau comme s’il s’agissait d’un tableau à un seul élément.

V-K. Chaînes de caractères et leurs propriétés

Nous pouvons lire les propriétés comme length et toUpperCase à partir de valeurs de type chaînes de caractères. Mais si vous essayez d’ajouter une nouvelle propriété, cela ne fonctionne pas.

 
Sélectionnez
1.
2.
3.
4.
let kim = "Kim";
kim.age = 88;
console.log(kim.age);
// → undefined

Les valeurs de type chaînes de caractères, nombres et booléens ne sont pas des objets et même si le langage ne rouspète pas, si vous essayez de leur ajouter de nouvelles propriétés, il ne les enregistre pas réellement.

Comme nous l’avons mentionné plus haut, de telles valeurs sont immuables.

Mais ces types ont des propriétés prédéfinies. Chaque chaîne de caractères a un certain nombre de méthodes. Slice  et indexOf sont deux méthodes très utiles qui ressemblent aux méthodes des tableaux avec le même nom.

 
Sélectionnez
1.
2.
3.
4.
console.log("coconuts".slice(4, 7));
// → nut
console.log("coconut".indexOf("u"));
// → 5

Une différence est que le indexOf d’une chaîne de caractères peut rechercher une chaîne qui contient plus qu’un caractère alors que la méthode correspondante du tableau recherche un seul élément.

 
Sélectionnez
1.
2.
console.log("one two three".indexOf("ee"));
// → 11

La méthode trim supprime les espaces (espaces, passages à la ligne, tabulations et autres caractères semblables) au début et à la fin de la chaîne de caractères.

 
Sélectionnez
1.
2.
console.log("  okay \n ".trim());
// → okay

La fonction zeroPad du <chapitre précédent> existe également comme méthode. Elle est appelée padStart et utilise la longueur désirée et le caractère de remplissage comme arguments.

 
Sélectionnez
1.
2.
console.log(String(6).padStart(3, "0"));
// → 006

Vous pouvez couper une chaîne de caractères sur chaque occurrence d’une autre chaîne avec split et relier à nouveau les éléments avec join.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
let sentence = "Secretarybirds specialize in stomping";
let words = sentence.split(" ");
console.log(words);
// → ["Secretarybirds", "specialize", "in", "stomping"]
console.log(words.join(". "));
// → Secretarybirds. specialize. in. stomping

Une chaîne de caractères peut être répétée avec la méthode repeat qui crée une nouvelle chaîne contenant des copies multiples de la chaîne originale collées ensemble.

 
Sélectionnez
1.
2.
console.log("LA".repeat(3));
// → LALALA

Nous avons déjà vu la propriété  length des chaînes de caractères. Accéder à chaque caractère d’une chaîne ressemble à accéder aux éléments d’un tableau (avec un avertissement dont nous discuterons au Chapitre 5).

 
Sélectionnez
1.
2.
3.
4.
5.
let string = "abc";
console.log(string.length);
// → 3
console.log(string[1]);
// → b

V-L. Le paramètre Rest

Il peut être utile pour une fonction d’accepter n’importe quel nombre d’arguments. Par exemple, la fonction max de l’objet Math calcule le maximum dans l’ensemble des arguments qui lui sont passés.

Pour écrire une telle fonction, vous ajoutez trois points avant le dernier paramètre de la fonction comme ceci :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
function max(...numbers) {
  let result = -Infinity;
  for (let number of numbers) {
    if (number > result) result = number;
  }
  return result;
}
console.log(max(4, 1, 9, -2));
// → 9

Quand une telle fonction est appelée, le paramètre rest est lié à un tableau contenant tous les autres arguments. S’il y a d’autres paramètres devant celui-ci, leur valeur ne fait pas partie du tableau. Quand, comme dans max, c’est le seul paramètre, il contiendra tous les arguments.

Vous pouvez utiliser la même notation avec les 3 points pour appeler une fonction avec un tableau d’arguments.

 
Sélectionnez
1.
2.
3.
let numbers = [5, 1, 7];
console.log(max(...numbers));
// → 7

Ceci étend l’appel de la fonction à l’ensemble du tableau passant chaque élément comme un argument isolé. Il est possible d’ajouter un tableau comme cela avec d’autres arguments comme dans :

 
Sélectionnez
max(9, ...numbers, 2).

La notation des tableaux avec des crochets permet également l’utilisation de l’opérateur 3 points pour étendre un tableau dans un autre.

 
Sélectionnez
let words = ["never", "fully"];
console.log(["will", ...words, "understand"]);
// → ["will", "never", "fully", "understand"]

V-M. L’objet Math

Comme nous l’avons vu, Math est un grand sac avec un grand nombre de fonctions bien utiles comme Math.max (maximum), Math.min (minimum) et Math.sqrt (racine carrée).

L’objet Math est utilisé comme un contenant pour regrouper un tas de fonctions connexes. Il n’y a qu’un seul objet Math et il n’est quasiment jamais utile comme variable. Voyons-le plutôt comme un espace de noms qui évite de devoir lier ces différentes fonctions et valeurs. Devoir lier globalement trop d’éléments « pollue » l’espace de noms. Plus vous donnez de noms, plus vous risquez d’écraser une valeur existante. Par exemple, il n’est pas improbable de vouloir nommer quelque chose max dans vos programmes. Comme la fonction max est rangée de manière sécurisée dans l’objet Math, il n’y a pas de crainte de l’écraser.

Beaucoup de langages vous arrêteront ou au moins vous avertiront quand vous nommerez une variable avec un nom qui est déjà donné. JavaScript le fait pour des variables déclarées avec let ou const mais pas pour des variables standards ou des variables déclarées avec var ou function.

Revenons à l’objet Math. Si vous devez faire de la trigonométrie, Math peut vous aider. Il contient cos (cosinus), sin (sinus) et tan (tangente) ainsi que leurs fonctions inverses, respectivement, acos, asin  et atan. Le nombre π (pi) (ou en tout cas une bonne approximation qui correspond aux nombres en JavaScript) est disponible via Math.PI. Il y a une ancienne tradition de nommer les constantes en lettres capitales.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
function randomPointOnCircle(radius) {
  let angle = Math.random() * 2 * Math.PI;
  return {x: radius * Math.cos(angle),
          y: radius * Math.sin(angle)};
}
console.log(randomPointOnCircle(2));
// → {x: 0.3667, y: 1.966}

Si les sinus et cosinus ne vous sont pas familiers, ne vous inquiétez pas. Quand ils seront utilisés dans ce livre, au Chapitre 14, je les expliquerai.

L’exemple suivant utilise Math.random. Cette fonction renvoie un nouveau nombre quasi aléatoire compris entre zéro (inclus) et un (exclus) chaque fois qu’elle est appelée.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
console.log(Math.random());
// → 0.36993729369714856
console.log(Math.random());
// → 0.727367032552138
console.log(Math.random());
// → 0.40180766698904335

Bien que les ordinateurs soient des machines déterministes (ils réagissent toujours de la même manière si la même information entrante est donnée), il est possible pour eux de produire des nombres qui apparaissent aléatoires. Pour le faire, la machine garde une variable cachée et chaque fois que vous demandez un nouveau nombre aléatoire, de complexes calculs sont faits sur cette variable cachée pour créer une nouvelle valeur. Elle est stockée comme une nouvelle variable et renvoie un nombre qui en dérive. Ainsi, elle produit à chaque fois un nouveau nombre difficilement prédictible qui semble aléatoire.

Si nous souhaitons un nombre entier plutôt qu’une fraction, nous pouvons utiliser Math.floor (qui arrondit vers le bas vers le nombre entier le plus proche) sur le résultat de Math.random.

 
Sélectionnez
console.log(Math.floor(Math.random() * 10));
// → 2

En multipliant un nombre aléatoire par 10, cela nous donne un nombre plus grand ou égal à zéro et en dessous de 10. Comme Math.floor arrondit vers le bas, cette expression produira avec une même probabilité n’importe quel nombre de 0 à 9.

Il existe aussi les fonctions Math.ceil (arrondit vers le haut au premier entier), Math.round (au plus proche entier) et Math.abs qui prend la valeur absolue d’un nombre, ce qui signifie qu'il nie les valeurs négatives, mais laisse les positives comme elles sont.

V-N. Déstructuration

Revenons un instant à la fonction phi.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
function phi(table) {
  return (table[3] * table[0] - table[2] * table[1]) /
    Math.sqrt((table[2] + table[3]) *
              (table[0] + table[1]) *
              (table[1] + table[3]) *
              (table[0] + table[2]));
}

Une des raisons pour lesquelles cette fonction est étrange à lire, c’est que nous avons une variable pointant sur notre tableau, mais nous aurions préféré avoir des variables pointant sur les éléments du tableau, c’est  let n00 = table[0] et ainsi de suite. Heureusement, il y a un moyen rapide de faire cela en JavaScript.

 
Sélectionnez
1.
2.
3.
4.
5.
function phi([n00, n01, n10, n11]) {
  return (n11 * n00 - n10 * n01) /
    Math.sqrt((n10 + n11) * (n00 + n01) *
              (n01 + n11) * (n00 + n10));
}

Cela fonctionne également pour des variables créées avec let, var et const. Si vous savez que la valeur de la variable est un tableau, vous pouvez utiliser les crochets pour « regarder à l’intérieur » de la variable, en pointant sur son contenu.

Un truc équivalent existe pour les objets, en utilisant des accolades à la place des crochets.

 
Sélectionnez
1.
2.
3.
let {name} = {name: "Faraji", age: 23};
console.log(name);
// → Faraji

Notez bien que si vous tentez de déstructurer un null ou undefined, vous recevez un message d’erreur comme si vous essayiez d’accéder à une propriété de ces variables.

V-O. JSON

Parce que les propriétés ne saisissent que les valeurs, plutôt que le contenu, les objets et tableaux sont stockés dans la mémoire de l’ordinateur comme des séquences de bits contenant les adresses (la place dans la mémoire) de leur contenu. Donc, un tableau qui contient un autre tableau est (au minimum) une zone de mémoire pour le tableau interne et une autre pour le tableau extérieur contenant, entre autres choses, un nombre binaire qui représente la position du tableau interne.

Si vous souhaitez sauvegarder les données dans un fichier pour plus tard ou les envoyer vers un autre ordinateur sur le réseau, vous devez en quelque sorte convertir ce fouillis d’adresses mémoire en une description qui peut être stockée ou envoyée. Vous devriez envoyer toute la mémoire de votre ordinateur avec l’adresse de la valeur qui vous intéresse, mais cela ne semble pas être la meilleure approche… Ce que nous pouvons faire, c’est sérialiser les données. Cela signifie les convertir en une description à plat. Un format assez populaire de sérialisation est appelé JSON (prononcez « Jason »), qui signifie JavaScript Object Notation. Ce format est largement utilisé comme système de stockage et de communication de données sur le Web, même dans d’autres langages que JavaScript.

JSON ressemble au mode d’écriture de tableaux et d’objets en JavaScript avec quelques restrictions. Tous les noms des propriétés doivent être entourés de guillemets et seules des expressions de données simples sont permises (pas d’appels de fonctions, de variables ou quoi que ce soit qui implique un réel calcul). Les commentaires ne sont pas admis en JSON.

Une entrée du journal ressemble à ceci quand elle est représentée en données JSON :

 
Sélectionnez
{
  "squirrel": false,
  "events": ["work", "touched tree", "pizza", "running"]
}

JavaScript nous propose les fonctions JSON.stringify et JSON.parse pour convertir des données de et vers ce format. La première prend une valeur JavaScript et renvoie une chaîne encodée JSON. La seconde prend cette chaîne et la convertit dans les valeurs encodées.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
let string = JSON.stringify({squirrel: false,
                             events: ["weekend"]});
console.log(string);
// → {"squirrel":false,"events":["weekend"]}
console.log(JSON.parse(string).events);
// → ["weekend"]

V-P. Résumé

Objets et tableaux (qui sont une forme spécifique d’objets) fournissent des moyens de regrouper plusieurs valeurs en une seule. Conceptuellement, ceci permet de mettre un paquet de choses semblables dans un sac et de s’en aller avec le sac plutôt que de s’encombrer les bras avec plein de choses isolées en tâchant de les garder séparées.

La plupart des valeurs de JavaScript ont des propriétés, les exceptions étant null et undefined. On accède aux propriétés en utilisant valeur.prop ou valeur["prop"]. Les objets ont tendance à utiliser des noms pour leurs propriétés et stockent plus ou moins un ensemble fixe de propriétés. Les tableaux, de leur côté, contiennent généralement un nombre variable de valeurs identiques et utilisent des nombres (en commençant par 0) comme nom de leurs propriétés. Les méthodes sont des fonctions qui vivent dans les propriétés et (généralement) agissent sur leurs valeurs.

Vous pouvez parcourir un tableau un utilisant une sorte de loop...for (élément let d’un tableau)

V-Q. Exercices

V-Q-1. La somme d’un intervalle

<L'introduction> de ce livre faisait allusion à ce qui suit comme une belle méthode pour calculer la somme d’un intervalle de nombres :

 
Sélectionnez
console.log(sum(range(1, 10)));

Écrivez une fonction range qui prend deux arguments, début et fin, et renvoie un tableau contenant tous les nombres depuis le début jusqu’à la fin incluse.

Ensuite, écrivez une fonction somme qui prend un tableau de nombres et renvoie la somme de ces nombres. Lancez le programme et vérifiez qu’il renvoie bien 55.

En mission bonus, modifiez votre fonction range pour prendre un troisième argument optionnel indiquant la valeur « étape » quand on construit le tableau. Si aucune étape n’est donnée, l’élément avance par incrément de un correspondant à l’ancien comportement. La fonction appelée range(1, 10, 2) devrait retourner [1, 3, 5, 7, 9]. Soyez sûr que cela fonctionne aussi avec des étapes négatives de sorte que range(5, 2, -1) produit[5, 4, 3, 2].

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
// Votre code ici.

console.log(range(1, 10));
// → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(range(5, 2, -1));
// → [5, 4, 3, 2]
console.log(sum(range(1, 10)));
// → 55

La construction d'un tableau se fait plus facilement en initialisant d'abord une liaison à [] (un nouveau tableau vide) et en appelant à plusieurs reprises sa méthode push pour ajouter une valeur. N'oubliez pas de retourner le tableau à la fin de la fonction.

Puisque la limite finale est inclusive, vous devrez utiliser l'opérateur <= plutôt que < pour vérifier la fin de votre boucle.

Le paramètre step peut être un paramètre optionnel qui prend la valeur 1 par défaut (à l'aide de l'opérateur =).

Pour que range puisse comprendre les valeurs négatives de step, il est probablement mieux de faire deux boucles séparées - une pour compter vers le haut et une pour compter vers le bas - parce que la comparaison qui vérifie si la boucle est terminée doit être >= plutôt que <= pour compter vers le bas.

Il peut également être utile d'utiliser un autre paramètre step par défaut, à savoir -1, lorsque la fin de la plage est inférieure au début. De cette façon, range(5, 2) renvoie quelque chose de significatif, plutôt que de rester coincé dans une boucle infinie. Il est possible de se référer aux paramètres précédents dans la valeur par défaut d'un paramètre.

V-Q-2. Renverser un tableau

Les tableaux ont une méthode reverse qui change le tableau en intervertissant l’ordre dans lequel les éléments apparaissent. Pour cet exercice, écrivez deux fonctions, reverseArray et reverseArrayInPlace. La première, reverseArray, prend un tableau comme argument et produit un nouveau tableau qui contient les mêmes éléments dans l’ordre inverse. La seconde, reverseArrayInPlace, fait ce que la méthode reverse fait : elle modifie le tableau donné en argument en renversant ses éléments. Aucune ne peut utiliser la méthode reverse standard.

En repensant aux notes sur les effets secondaires et les fonctions pures du <chapitre précédent>, quelle variante croyez-vous qui sera la plus utile dans le plus de situations ? Laquelle ira le plus vite ?

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
// Votre code ici.

console.log(reverseArray(["A", "B", "C"]));
// → ["C", "B", "A"];
let arrayValue = [1, 2, 3, 4, 5];
reverseArrayInPlace(arrayValue);
console.log(arrayValue);
// → [5, 4, 3, 2, 1]

Il y a deux façons évidentes d'implémenter le reverseArray. La première est de passer simplement en revue le tableau d'entrée de l'avant vers l'arrière et d'utiliser la méthode unshift sur le nouveau tableau pour insérer chaque élément à son début. La seconde est de boucler le tableau d'entrée vers l'arrière et d'utiliser la méthode push. Itérer à l'envers sur un tableau nécessite une spécification (quelque peu gênante), comme (let i = array.length - 1 ; i >= 0 ; i--).


Inverser le tableau en place est plus difficile. Vous devez faire attention à ne pas écraser les éléments dont vous aurez besoin par la suite. Utiliser reverseArray ou copier le tableau entier (array.slice(0) est un bon moyen de copier un tableau) fonctionne.

L'astuce consiste à échanger le premier et le dernier élément, puis le deuxième et l'avant-dernier, et ainsi de suite. Vous pouvez le faire en bouclant sur la moitié de la longueur du tableau (utilisez Math.floor pour arrondir vers le bas - vous n'avez pas besoin de toucher l'élément central d'un tableau ayant un nombre impair d'éléments) et en inversant l'élément à la position i avec celui de position array.length - 1 - i. Vous pouvez utiliser une liaison locale pour maintenir momentanément un des éléments, remplacer celui-ci par son image miroir, puis placer la valeur de la liaison locale dans l'emplacement où se trouvait l'image miroir.

V-Q-3. Une liste

Les objets, comme ensemble générique de valeurs, peuvent être utilisés pour construire n’importe quelle sorte de structure de données. Une structure de donnée très commune est la liste (à ne pas confondre avec le tableau). Une liste est un ensemble de données imbriquées avec le premier objet contenant une référence au second, le second au troisième et ainsi de suite.

 
Sélectionnez
let list = {
  value: 1,
  rest: {
    value: 2,
    rest: {
      value: 3,
      rest: null
    }
  }
};

L’objet qui en résulte forme une chaîne comme ceci :

Un point intéressant à propos des chaînes est qu’elles peuvent partager une partie de leur structure. Par exemple, si je crée deux nouvelles valeurs {value: 0, rest: list} et {value: -1, rest: list} (avec list faisant référence à une variable définie précédemment), elles forment deux listes indépendantes, mais elles partagent la structure qui constitue leurs trois derniers éléments. La liste originale est également une liste valide à trois éléments.

Écrivez une fonction arrayToList qui construit une structure de liste comme celle montée quand on lui fait passer [1, 2, 3] comme arguments. Écrivez également une fonction listToArray qui produit un tableau à partir de la liste. Ajoutez ensuite une fonction auxiliaire prepend qui prend un élément et une liste et crée une nouvelle liste qui ajoute l’élément au début de la liste d’entrée. Créez également nth qui prend une liste et un nombre et renvoie l’élément à une position spécifique de la liste (avec zéro faisant référence au premier élément) ou undefined quand il n’y a pas cet élément.

Si vous en l’avez pas encore fait, écrivez également une version récursive de nth.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
// Votre code ici.

console.log(arrayToList([10, 20]));
// → {value: 10, rest: {value: 20, rest: null}}
console.log(listToArray(arrayToList([10, 20, 30])));
// → [10, 20, 30]
console.log(prepend(10, prepend(20, null)));
// → {value: 10, rest: {value: 20, rest: null}}
console.log(nth(arrayToList([10, 20, 30]), 1));
// → 20

Il est plus facile d'établir une liste lorsqu'elle est faite de l'arrière vers l'avant. Ainsi, arrayToList pourrait itérer sur le tableau vers l'arrière (voir l'exercice précédent) et, pour chaque élément, ajouter un objet à la liste. Vous pouvez utiliser un lien local pour tenir la partie de la liste qui a été construite jusqu'à présent et utiliser une affectation comme liste = {valeur : X, rest : list} pour ajouter un élément.

Pour exécuter une liste (dans listToArray et nth), une spécification de boucle comme celle-ci peut être utilisée :

 
Sélectionnez
1.
for (let node = list ; node ; node ; node = node.rest) {}

Voyez-vous comment ça fonctionne ? À chaque itération de la boucle, le nœud pointe vers la sous-liste courante, et le corps peut lire sa propriété de valeur pour obtenir l'élément courant. À la fin d'une itération, le nœud passe à la sous-liste suivante. Quand c'est nul, nous avons atteint la fin de la liste, et la boucle est terminée.

Votre test pour savoir si vous avez affaire à un objet réel ressemblera à quelque chose comme typeof x === "object" && x != null. Veillez à ne comparer les propriétés que lorsque les deux arguments sont des objets. Dans tous les autres cas, vous pouvez simplement retourner immédiatement le résultat de la demande ====.

Utilisez les touches Object.keys pour passer en revue les propriétés. Vous devez tester si les deux objets ont le même jeu de noms de propriétés et si ces propriétés ont des valeurs identiques. Une façon de le faire est de s'assurer que les deux objets ont le même nombre de propriétés (les longueurs des listes de propriétés sont les mêmes). Et puis, lorsque vous mettez en boucle les propriétés d'un objet pour les comparer, assurez-vous d'abord que l'autre possède bien une propriété portant ce nom. S'ils ont le même nombre de propriétés et que toutes les propriétés de l'une existent aussi dans l'autre, ils ont le même ensemble de noms de propriétés.

V-Q-4. Comparaison profonde

L’opérateur == compare deux objets par identité. Mais parfois, vous préféreriez comparer la valeur de leurs propriétés.

Écrivez une fonction deepEqual qui prend deux valeurs et renvoie true uniquement si elles sont de même valeur ou sont des objets avec la même propriété où les valeurs des propriétés sont égales quand elles sont comparées par un appel récursif à deepEqual.

Pour savoir si des valeurs doivent être comparées directement (utilisez l’opérateur === pour cela) ou si leurs propriétés doivent être comparées, vous pouvez utiliser l’opérateur typeof. S’il retourne « object » pour les deux valeurs, vous devez effectuer une comparaison en profondeur. Mais vous devez prendre en compte une stupide exception : à cause d’un accident historique, typeof null produit également « object ».

La fonction Object.keys sera utile quand vous devrez passer dans les propriétés des objets pour les comparer.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
// Votre code ici.

let obj = {here: {is: "an"}, object: 2};
console.log(deepEqual(obj, obj));
// → true
console.log(deepEqual(obj, {here: 1, object: 2}));
// → false
console.log(deepEqual(obj, {here: {is: "an"}, object: 2}));
// → true

Votre test pour savoir si vous avez affaire à un objet réel ressemblera à quelque chose comme typeof x == "object" && x != null. Veillez à ne comparer les propriétés que lorsque les deux arguments sont des objets. Dans tous les autres cas, vous pouvez simplement retourner immédiatement le résultat de la demande ===.

Utilisez les touches Object.keys pour passer en revue les propriétés. Vous devez tester si les deux objets ont le même jeu de noms de propriétés et si ces propriétés ont des valeurs identiques. Une façon de le faire est de s'assurer que les deux objets ont le même nombre de propriétés (les longueurs des listes de propriétés sont les mêmes). Et puis, lorsque vous mettez en boucle les propriétés d'un objet pour les comparer, assurez-vous d'abord que l'autre possède bien une propriété portant ce nom. S'ils ont le même nombre de propriétés et que toutes les propriétés de l'une existent aussi dans l'autre, ils ont le même ensemble de noms de propriétés.

La meilleure façon de retourner la valeur correcte de la fonction est de renvoyer immédiatement false lorsqu'une inadéquation est trouvée et de renvoyer true à la fin de la fonction.


précédentsommairesuivant

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2019 Marijn Haverbeke. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.