Alors dans cette séquence nous allons voir les éléments
essentiels de la hiérarchie des collections en Pharo.
Donc vous allez voir que Pharo est riche du point de vue
des différents types de collections, mais il facilite la
vie du programmeur puisqu'ils présentent tous une API commune.
Et on verra également la différence entre les collections
littérales et les collections dynamiques.
Donc là l'API des collections, comme je disais, est
riche, on verra qu'il y a beaucoup de types de collections différents.
Toutes présentent une API commune, on le verra, qui est
assez bien organisée, qui facilite énormément la vie du programmeur.
Petit point particulier en Pharo les collections, les
index des collections, commencent à 1 alors que ça
commence à 0 dans d'autres langages.
Et puis les collections peuvent contenir n'importe quel
type d'objet en Pharo, ce qui n'est pas forcément le cas
dans dans les autres langages.
Donc quelques-unes des collections les plus remarquables
et les plus utilisées, donc OrderedCollection qui est une
collection dynamique dont la taille va grossir à chaque
fois qu'on ajoute des éléments dedans.
Array qui est une collection de taille fixe.
Et puis on va accéder aux éléments en fonction d'un
indice Set, qui va contenir des éléments mais sans doublon.
On ne pourra pas insérer 2 fois le même élément dans un Set.
Et puis les dictionnaires, donc les dictionnaires ce sont
des tables de hachage, à une clé donnée j'associe une valeur.
Donc vous avez ici un extrait de la hiérarchie des
collections, c'est seulement un extrait.
Il est plus riche que ça en Pharo.
Donc voyez qu'il y a plein de classes.
Elles héritent toutes de collections, ce qui nous fournit
une API commune pour l'ensemble des collections.
Et puis on va voir celles qui sont en gras, on voit les
expliciter un peu au fur et à mesure de cette séquence.
Donc il existe une API commune, je vous disais, répartie en 7 points.
On a des méthodes spécifiques pour la création des
collections, qu'on va envoyer plutôt aux classes des collections.
Des méthodes spécifiques pour accéder aux propriétés des
collections, que ce soit accéder par exemple à la taille
d'une collection ou accéder même aux éléments que la collection contient.
Des méthodes de tests, savoir est-ce que la collection est vide ou pas.
Des méthodes d'ajout et de retrait d'éléments, des
méthodes d'énumération des éléments dans une collection.
Donc je parcours l'ensemble des éléments, je voudrais
savoir si un élément existe, est présent dans une collection ou pas.
Et puis on a des méthodes de conversion d'un type de
collection en un autre type.
Commençons par un exemple.
Donc je veux créer une collection en Pharo tout bêtement.
Je vais sélectionner la bonne classe qui m'intéresse et
lui envoyer un message new pour instancier, pour créer un
nouvel instance sur cette classe.
Donc premier cas de figure, j'utilise new.
Deuxième cas de figure.
En fait je ne peux pas faire un new directement où j'ai
envie de spécifier la taille de la collection.
Donc typiquement Array je peux lui envoyer new:4, donc je
fais un tableau de taille 4 ou un tableau de taille 2,
donc ça marche également sur les OrderedCollection je
pourrais faire un OrderedCollection. Donc on a d'autres
types de méthodes pour créer des collections
pré-initialisées, donc avec withAll par exemple où je
vais passer une collection littérale.
Une collection littérale ça commence par un #( je vous rappelle.
Et puis ça va me créer donc une nouvelle instance de la
classe OrderedCollection, donc un nouvel
OrderedCollection qui contiendra bien tous les éléments
qui ont été placés au moment de sa création.
Je peux faire la même chose avec un Set, par contre je
vous rappelle hein dans un Set on ne peut pas avoir d'objets doublons.
Donc le chiffre 7 qu'on avait mis 2 fois dans la
collection littérale il ne peut pas se retrouver 2 fois dans le Set.
Donc il y a d'autres types, d'autres sortes de messages
qu'on peut envoyer aux classes collection pour les initialiser.
Ici j'en ai un autre exemple.
new withAll, donc qui est un message, je veux faire une
collection de taille 5 mais je veux que toutes les
classes soient initialisées avec un certain objet.
Donc en l'occurrence ici une Stream qui contient.
Donc subtilité en Pharo toutes les collections commencent à l'indice 1.
Donc si je demande à cette collection de 3 éléments de me
rendre l'élément à l'indice 2, c'est bien'hates'.
Donc c'est bien celui-là.
Ça c'est 1, ça c'est 2, ça c'est 3.
Donc c'est la même chose pour les OrderedCollection, si
je convertis cette collection en OrderedCollection et je
lui demande son élément indice 2, ça me revient'hates'la même chose.
Donc les collections peuvent contenir toutes sortes d'objets,
comme je l'ai dit, et ici je vous en montre un exemple
cette collection littérale va contenir, dans son premier
élément, la chaîne des caractères'calvin', et dans son
second élément une collection qui contiendra les nombres 1, 2, 3.
Je peux créer par exemple un tableau, donc ici ce tableau
il est composé des éléments 1, 2 ici
et d'un Set à la fin.
Donc l'élément 1 a été ajouté ici, l'élément 2 ici et le Set ici.
Donc on peut maintenant parcourir les éléments d'une
collection, en utilisant le message "do" par exemple.
Donc ici j'ai une collection et je vais lui envoyer le message "do".
Je vais lui passer un block, donc je vous rappelle le
block ici il commence avec le crochet ouvrant, il se
ferme avec le crochet fermant. Le paramètre du block il s'appelle
"each", donc qui commence par un :.
Il est séparé du corps de block par la barre verticale et
on va à chaque tour de boucles, "each" vaudra le premier
élément de la collection, le deuxième et caetera jusqu'à la fin.
Donc on va bien afficher sur le Transcript Calvin hates Suzie.
Donc on a les tableaux.
Donc les tableaux c'est des collections de taille fixe.
Donc on peut demander à un tableau sa taille, j'y envoie le message "size".
On peut accéder directement à un élément d'un tableau en
lui envoyant le message "at:", je veux l'élément 2 de la collection.
On peut modifier l'élément à l'indice 2 dans la
collection en envoyant at:1 put'Calvin'.
Donc je vais insérer la chaîne Calvin dans la case 1.
Et puis je peux demander la taille.
L'élément intéressant ici dans cet exemple c'est qu'on
voit qu'on a construit le même tableau de 2 manières
différentes: une première version littérale et une
deuxième version dynamique ici, où j'ai vraiment
instancié la classe Array à la main et rempli chacune des cases.
Donc on peut envoyer le message "size" à une collection
pour récupérer sa taille. On peut accéder à un élément d'une
collection en lui fournissant la méthode "at:".
Donc ça j'en ai déjà parlé.
Donc il faut faire attention il y a une subtilité.
Quand j'accède à un élément d'une collection en
fournissant un indice, il faut faire attention que cet
indice soit bien dans les bornes acceptées par la
collection, qu'il soit inférieur à la taille de la collection.
Si je demande à cette collection l'élément à l'indice 55
forcément il n'existe pas puisque cette collection a une
taille 3, donc ça va bien me rendre une erreur.
Donc pour modifier les éléments, j'en ai parlé, à l'indice
2 je veux modifier, insérer un nouvel élément dans la
collection, la chaine des caractères'loves'.
Donc ça bien remplacer'hates'par'loves', on le voit bien
ici dans le résultat.
Donc les tableaux littéraux, donc on a ici un exemple de
tableau littéral, ça commence par #( comme je vous l'avais
dit, on peut mettre n'importe quoi dedans donc un nom ou
une chaine de caractères et cetera.
Et puis tous les tableaux littéraux en Pharo sont
instance de la classe Array par défaut.
Donc je peux envoyer le message "class" à un tableau
littéral et ça me rend bien Array.
Donc c'est bien une instance de la classe Array.
Alors les versions dynamiques et les versions littérales
sont exactement équivalentes en Pharo.
C'est juste une version plus concise, donc ça permet d'écrire ça plus vite.
Donc ici vous avez la version littérale d'une collection
et vous avez sa version dynamique où j'instancie vraiment
la classe Array à la main.
Mais c'est complètement équivalent puisque vous voyez
bien que les 2 résultats sont les mêmes.
Donc la classe OrderedCollection définit une collection
particulière, qui est extensible, donc à chaque fois qu'on
va ajouter des éléments elle va s'agrandir.
Donc ici j'instancie la classe OrderedCollection en envoyant un message new.
J'utilise la méthode "add" pour ajouter différents
éléments dans cette collection. J'ai même des variations
je peux faire "addFirst" pour ajouter un élément au début
de la collection, par défaut il s'ajoute à la fin.
Donc vous voyez à chaque fois ce que nous rend la collection.
Donc là on a bien 3 éléments dans la collection: Pharo, Reef et Pharo.
Si je fais "add: Seaside", "Seaside" est bien ajouté à la
fin de la collection.
J'ai des méthodes de conversion entre un type de collection et un autre.
Donc par exemple ici j'utilise une collection littérale,
donc qui va être un Array et le message
asOrderedCollection qui va transformer ce tableau en
collection ordonnée.
Enfin les Set. Donc les Set sont un type de collection sans doublon.
Donc pareil de taille extensible, donc à chaque fois que
va ajouter un élément ça va grandir.
Je peux utiliser une collection littérale que je
transforme en Set et ça me donne bien un Set dans lequel
les doublons ont été retirés.
Ou alors je peux accéder à la version dynamique plutôt qu'une
collection littérale. Set with with qui permet de créer
un Set et de le remplir avec 2 éléments, deux Set à chaque fois.
Donc les méthodes de conversion sont extrêmement
pratiques pour jongler, pour transformer une collection en un autre type.
Elles ont toujours la même forme c'est "as" + le nom de
la collection qu'on voudra avoir.
Les dictionnaires. Donc c'est un type de collection clés, valeurs.
À une clé donnée j'associe une valeur.
Elles sont aussi extensibles à chaque fois qu'on va
ajouter des éléments elles vont grandir, et puis on a une
API un peu particulière sur cette collection,
donc le message "at" qui lui est classique, at:ifAbsent:
c'est-à-dire si je veux accéder à un élément, à une clé
particulière mais qu'elle n'existe pas, qu'est-ce que je dois rendre?
"at: put: donc à une clé particulière je veux insérer une
nouvelle valeur et caetera. Et puis je vais pouvoir
itérer avec des messages tout à fait classiques "do", on
a déjà vu, mais on va avoir des nouveaux messages
"KeysDo" donc je parcours toutes les clés du dictionnaire
ou les clés et les valeurs.
Donc je vous donne un exemple.
Ici j'ai créé l'instance de la classe dictionnaire, et
puis dans ce dictionnaire, donc il faut voir un
dictionnaire un peu comme un tableau, à la clé janvier j'associe
le nombre 31, à la clé février le nombre 28 et à la clé mars le nombre 31.
Donc c'est complètement équivalent à une collection dynamique.
Donc une collection dynamique cette fois c'est créé avec une accolade.
Accolade ouvrante, accolade fermante.
Ici je vous rappelle la flèche c'est pour créer des associations.
Donc ici j'ai un symbole.
Donc au symbole janvier j'associe le nombre 31, donc ici
j'ai bien une collection d'associations que je transforme
en dictionnaire avec à le "Ad dictionary".
Donc ces 2 formes pour créer le dictionnaire sont complètement équivalentes.
Donc si je demande à une association, si je lui demande
sa clé ça va bien nous renvoyer la clé, donc le début.
On voit qu'ici c'est l'équivalent.
Et puis si je demande à une association de me retourner
sa valeur ça ne me retourne que la valeur.
Donc ça c'est une paire ou une association.
Donc les dictionnaires.
Si je veux dans un dictionnaire accéder à une valeur
particulière, il suffit que j'utilise "at" et la clé, je
spécifie la clé pour laquelle je veux la valeur.
Si c'est une clé qui est inexistante forcément je vais
récupérer une erreur en retour.
Et si je veux éviter ça ce que je peux faire c'est
utiliser "at: ifAbsent", donc je fais "at" une clé qui n'existe
pas dans le dictionnaire, et si c'est absent donc je vais
récupérer la valeur qui est ici le 0.
Donc on voit bien cette clé n'existe pas dans le
dictionnaire donc je récupère la valeur 0.
Je peux itérer sur un dictionnaire, donc si je fais un
"do" sur les éléments d'un dictionnaire je récupère en
fait ici que les valeurs du dictionnaire.
On ne voit pas les clés.
Alors pourquoi on peut se demander ça?
C'est un peu bizarre de ne récupérer que les valeurs.
En fait c'est complètement logique puisque si on regarde
dans la classe "dictionnaire" l'implémentation de la
méthode "do", qui prend un block, en fait elle fait à l'intérieur
selfvaluesDo. Donc par défaut quand en fait un "do" sur
un dictionnaire, on ne parcourt que ses valeurs et pas les clés.
Si je veux parcourir les 2 en fait j'ai une méthode
particulière qui s'appelle "KeysAndValuesDo", qui prend
en paramètre un block avec 2 arguments: k et v.
Et donc k ça correspondra bien à une clé et v à la valeur.
Donc cette fois j'ai bien mon dictionnaire complet.
Donc en résumé dans cette séquence on a vu que Pharo
propose énormément de types de collections différents.
Que toutes les collections proposent un vocabulaire
commun, que ce soit pour créer des collections, pour
accéder aux éléments, pour accéder à la taille d'une collection et caetera.
Donc ça, ça facilite la vie du programmeur.
Et c'est simple aussi de convertir un type de collection en un autre type.
Et vous on a même vu quelque chose de supplémentaire, on
a vu que quand on se pose des questions il est facile d'aller
découvrir dans le système, dans Pharo, en allant lire le
code des classes, en allant découvrir de nouvelles classes de collections.