Outils pour utilisateurs

Outils du site


cours:informatique:dev:programmation_objet_pharo:325_principales_collections

Ceci est une ancienne révision du document !


Vue générale des principales collections

Dans cette séquence nous allons voir les éléments essentiels de la hiérarchie des collections en Pharo.

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. On verra également la différence entre les collections littérales et les collections dynamiques.

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 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:

  • OrderedCollection (extensible dynamiquement)
  • Array (taille fixe, accès direct)
  • Set (pas de doublons)
  • Dictionnary (table de hachage, accès à clé/valeur)

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.

Voici un extrait de la hiérarchie des collections:

C'est seulement un extrait, il est plus riche que ça en Pharo. Vous pouvez voir qu'il y a plein de classes. Elles héritent toutes de Collection, 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.

Il existe une API commune, je vous disais, répartie en 7 points, avec des méthodes spécifiques pour:

  1. La création: with:, with:with:, withAll: qu'on va envoyer plutôt aux classes des collections;
  2. Accès: size:, at:, at:put: 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.
  3. Tests: isEmpty, includes:, contains: savoir est-ce que la collection est vide ou pas.
  4. Ajout: add:, addAll: ajout et de retrait d'éléments;
  5. Suppression: remove:, remove:ifAbsent:, removeAll:
  6. Enumération: do:, collect:, select:, reject:, detect: permettant de parcourir l'ensemble des éléments et de savoir si un élément existe, est présent dans une collection ou pas.
  7. Convertir: asBag, asSet, asOrderedCollection, asSortedCollection, asArray pour passer d'un type de collection à un autre.

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.

"Instanciation d'une collection via new"
Array new
 
"On peut spécifier la taille de la collection avec new:"
Array new: 4
 
Array new: 2
 
OrderedCollection new: 1000

Donc premier cas de figure, j'utilise new.

Deuxième cas de figure, en spécifiant la taille de la collection. Typiquement Array je peux lui envoyer new: 4, donc je fais un tableau de taille 4 ou un tableau de taille 2, ça marche également sur les OrderedCollection je pourrais faire un OrderedCollection de taille 1000.

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.

OrderedCollection withAll: #(7 7 3 33)

Une collection littérale commence par un #( . Une nouvelle instance de la classe OrderedCollection est créée 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 dans un Set on ne peut pas avoir d'objets doublons.

Set withAll: #(7 7 3 33)
 
"> a Set(7 3 33)"

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.

Il y a d'autres sortes de messages qu'on peut envoyer aux classes collection pour les initialiser. Ici j'en ai un autre exemple.

OrderedCollection new: 5 withAll: 'a'
 
"> an OrderedCollection('a' 'a' 'a' 'a' 'a')"

Avec le message new:withAll:, 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 String d'un seul élément 'a'.

Petite subtilité en Pharo toutes les collections commencent à l'indice 1.

#('Calvin' 'hates' 'Suzie') at: 2
 
"> 'hates' "

Si je demande à cette collection de 3 éléments de me rendre l'élément à l'indice 2, c'est bien 'hates'.

Même chose pour les OrderedCollection, si je convertis cette collection en OrderedCollection et je lui demande son élément indice 2:

#('Calvin' 'hates' 'Suzie') asOrderedCOllection at: 2
 
"> 'hates' "

Le code retourne 'hates' la même chose.

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.

"Collection de type Array contenant des objets différents"
#('calvin' #(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.

cours/informatique/dev/programmation_objet_pharo/325_principales_collections.1661071214.txt.gz · Dernière modification : 2022/08/21 08:40 de yoann