Outils pour utilisateurs

Outils du site


cours:informatique:dev:programmation_objet_pharo:330_iterateurs

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentesRévision précédente
Prochaine révision
Révision précédente
cours:informatique:dev:programmation_objet_pharo:330_iterateurs [2022/08/22 09:23] yoanncours:informatique:dev:programmation_objet_pharo:330_iterateurs [2022/08/22 13:48] (Version actuelle) yoann
Ligne 75: Ligne 75:
 </code> </code>
  
-"Je prends la collection, je vais construire une collection de résultats et je vais parcourir de 1 à la taille de la collection, collection size. Je vais utiliser un do. Et puis je vais parcourir la collection et à chaque fois ajouter dans la collection le résultat. " On peut écrire tout ça et c'est complètement équivalent à ça. A vous de choisir votre campest-ce que vous voulez écrire quelque chose de simple ou quelque chose de plus compliqué? En gros, la question est là. Personnellement, je préfère clairement cette première solution. Il existe dans la hiérarchie des collections Pharo quelque chose de fondamental, c'est que toutes les collections sont polymorphiques et héritent de la classe collection et on a une API commune. L'avantage, c'est que les itérateurs aussi vont fonctionner sur la plupart des collections. L'idée, c'est vraiment de penser objet. On demande à la collection d'itérer sur ces éléments pour nous, puisque on ne sait pas si on est en train de manipuler un dictionnaire ou on n'a pas envie de savoir comment sont représentées les clefs, les valeurs, et cetera. Donc on va vraiment demander "Collection, parcours-toi et applique ce traitement sur tes éléments. " Il existe beaucoup d'itérateurs qui vont permettre de faire ça. On a vu le do, le collect. On en a d'autres: select, rejet, detectet caeteraon va en voir quelques uns dans la suite de cette séquence. Le do, c'est l'itérateur le plus simple qui permet de parcourir chacun des éléments de ma collection et je les affiche ici dans le Transcript. Celui-là, on l'a déjà vu plein de fois dans les autres exemples. Un nouvel itérateur qui est select. Je veux récupérer tous les éléments de la collection qui répondent à un critère donné. Ici, je veux tous les éléments impairs de la collection. J'envoie "selectà la collection, je passe un block et à chaque fois que ce bloc va s'évaluer à "Vrai", l'élément en question sera ajouté dans la collection résultats. C'est complètement équivalent à select: odd. Quand j'ai un un bloc ici ou ce qui constitue seulement un envoi de message à l'élément de la collection, le paramètre du block, je peux mettre directement sous la forme d'un symbole le nom du message à envoyer. C'est encore plus concis et ça ne marche que pour les messages unaires. On peut utiliser d'autres types d'itérateurs comme reject. Je veux éliminer tous les éléments de la collection qui sont impairs et donc, dans la collection résultats, il ne va me rester que des éléments pairs. Ou alors, je veux faire un detectje veux détecter le premier élément de la collection qui répond à un critère donné pour lequel l'évaluation du bloc va être vraie. Je veux le premier élément de la collection qui est impair, donc 11. Dans certains cas, on veut détecter le premier élément de la collection qui répond à un critère, mais s'il n'y en a pas alors on aimerait bien avoir une valeur par défaut. C'est "detect: ifNone", donc s'il n'y a aucun élément c'est l'évaluation de ce block là qui va être effectuée, et donc ça va bien nous renvoyer 0. Il y a d'autres itérateurs encore qui vont encore faciliter la vie du programmeur. Par exemple, le "anySatisfy"Est-ce qu'il existe dans la collection un élément qui répond à ce critère-là? Je veux tous les éléments d'une collection qui répondent à un critère donné. Je veux parcourir la collection à l'envers en partant de la fin vers le début. Je veux parcourir la collection en ayant un curseur d'index. Je veux parcourir les éléments de la collection 2 à 2. Je veux parcourir toutes les permutations circulaires possibles des éléments d'une collection. Il y en a beaucoup des itérateurs et on peut en construire des nouveaux, d'ailleurs. Ici, je veux parcourir une collection 123 couplée avec une deuxième collection. Dans mon bloc do, j'aurai x et y, 2 paramètres. Le premier x, ça sera un élément de la première collection, et y un élément de la deuxième collection. Donc je vais pouvoir multiplier ces éléments entre eux. On obtient bien 10, 40 et 90. Et bien évidemment, il faut absolument que les 2 collections aient la même taille avec cet itérateur-là. On peut avoir d'autres types de parcours. Ici, j'utilise le "do separatedBy". J'ai une collection, je vais parcourir chacun des éléments et à chaque fois que j'ai parcouru un élément, je vais évaluer un bloc ici qui correspond à l'affichage d'une virgule. Ça va me permettre de parcourir le A, afficher une virgule, afficher le B, afficher une virgule, afficher le C. A chaque fois entre chaque élément, je vais avoir effectué une action. Ici, j'ai un itérateur qui est "GroupBy", qui me permet de grouper les éléments d'une collection en fonction d'un critère. J'envoie ce message à la collection 123456ici et je récupère en résultat un dictionnaire. Donc tous les éléments qui ont répondu "Faux" à ce critère, le critère c'était "even" c'est les éléments pairs, donc on voit que ça contient bien une collection de tous les éléments impairs. Et tout ce qui a répondu "Vrai", c'est tous les éléments pairs. Quand on a une collection souvent quand on fait des calculs on a tendance à imbriquer des collections dans des collection, et on se retrouve avec des niveaux d'imbrication qui peuvent être importants. Ici, vous avez un exemple construit à la main où on a des collections qui sont imbriquées dans des collections. Ce qu'on aimerait c'est arriver à aplatir la collection, à mettre tous les éléments au même niveau. Pour ça, on a quelque chose de facile en Pharo, on a un itérateur qui s'appelle "flatCollect". C'est-à-dire que je vais parcourir les éléments et construire une nouvelle collection dans laquelle j'ai tout aplati. Donc on obtient bien la collection 123456 dans laquelle on a enlevé tous les niveaux d'imbrication. Le secret, ce n'est pas de vous présenter tous les itérateurs disponibles dans Pharo, ce serait long et fastidieux. L'idée, c'est vraiment de vous montrer qu'il en existe plein et que vous pouvez découvrir vraiment les vôtres en allant lire les méthodes qui existent sur les classes des collections, en allant découvrir ces méthodes. Un exemple simple, c'est de commencer par les itérateurs que vous connaissez. Par exemple, se poser la question "Comment est-ce qu'est implémenté le do?" Je cherche le do dans la hiérarchie des collections, je vais voir qu'il est implémenté dans "SequenceableCollection"toutes les collections séquençables. Donc la méthode "do", elle prend en paramètre un block. Et voici l'implémentation de cette collection. 1 a la taille de la collection do et j'ai un block. Je vais évaluer le block qui est passé en paramètre en lui passant l'élément à l'indice I. Donc c'est tout simple. Les itérateurs sont extrêmement puissants en Pharo, comme on a pu le voir. Toutes les collections supportent ces itérateurs de façon polymorphique. Du point de vue programmeur, on utilise des itérateurs et puis c'est chacune des classes de collections qui vont les implémenter de façon adéquate par rapport à la collection qu'ils représentent. On peut en définir des nouveauxextrêmement intéressants. Je peux définir mes propres itérateurs si j'en ai envie sur les classes de collections. Il y a une subtilité. Pour ceux qui connaissent le Design pattern iterator, en fait la différence c'est que le développeur ne contrôle pas quand il passe à l'élément suivant. C'est la collection, qui en interne, décide de passer à l'élément suivant. On n'envoie pas explicitement le message "next" à l'itérateur. C'est une subtilité pour ceux qui connaissent le Design pattern iterator. En résumé, les itérateurs sont vraiment puissants et un allié fort du programmeur qui vont faciliter l'écriture des programmes. On l'a vu, ça permet d'écrire du code concis, simple et élégant et ça permet de garantir l' encapsulation des données au sein d'une collection.+Je prends la collection, je vais construire une collection de résultats et je vais parcourir de 1 à la taille de la collection, ''%%aCol size%%''. Je vais utiliser un ''do:''. Et puis je vais parcourir la collection et à chaque fois ajouter dans la collection le résultat. L'ensemble de ce code est équivalent au précédent: la concision et la clarté du code présenté initialement est clairement un avantage. 
 + 
 +===== Hiérarchie des collections ===== 
 + 
 + 
 +Il existe dans la hiérarchie des collections Pharo quelque chose de fondamental, c'est que toutes les collections sont polymorphiques et héritent de la classe collection et on a une API commune. 
 + 
 +{{pharo_hierarchie_des_collections.png}} 
 + 
 +L'avantage, c'est que les itérateurs aussi vont fonctionner sur la plupart des collections. L'idée, c'est vraiment de penser objet. On demande à la collection d'itérer sur ces éléments pour nous, puisque on ne sait pas si on est en train de manipuler un dictionnaire ou on n'a pas envie de savoir comment sont représentées les clefs, les valeurs, etc. 
 + 
 +Donc on va vraiment demander "Collection, parcours-toi et applique ce traitement sur tes éléments." Il existe beaucoup d'itérateurs qui vont permettre de faire ça. 
 + 
 +  * ''do:'' (itérer sur les éléments) 
 +  * ''collect:'' (itérer et récupérer les résultats) 
 +  * ''select:'' (itérer et sélectionner certains éléments) 
 +  * ''reject:'' (itérer et rejeter certains éléments) 
 +  * ''detect:'' (itérer et retourner le premier élément correspondant au critère) 
 +  * ''detect:ifNone:'' (itérerretourner le premier élément correspondant au critère ou définir une valeur par défaut) 
 +  * ''includes:'' (effectuer un test d'inclusion/présence) 
 +  * etc 
 + 
 +===== do: agir sur chaque élément ===== 
 + 
 +Le message ''do:'', c'est l'itérateur le plus simple qui permet de parcourir chacun des éléments de la collection
 + 
 +<code smalltalk> 
 +#( 16 11 68 19 ) do: [ :each | Transcript show: each; cr ] 
 +</code> 
 + 
 +Ici j' affiche les éléments de la collection littérale dans le Transcript. 
 + 
 +===== select: récupérer les éléments selon un critère ===== 
 + 
 +Un nouvel itérateur qui est ''select:''. Je veux récupérer tous les éléments de la collection qui répondent à un critère donné. 
 + 
 +<code smalltalk [enable_line_numbers="true"]> 
 +#( 16 11 68 19 ) select: [ :i | i odd ] 
 +"> #(11 19)" 
 + 
 + 
 +# Équivalent 
 +#( 16 11 68 19 ) select: #odd 
 +"> #(11 19)" 
 +</code> 
 + 
 +Ici, je veux tous les éléments impairs de la collection. J'envoie ''select:'' à la collection. Je passe un block et à chaque fois que ce bloc va s'évaluer à "Vrai", l'élément en question sera ajouté dans la collection résultats. 
 + 
 +Les lignes 1 et 6 sont complètement équivalentes. Quand j'ai un un bloc (lige 1) ou ce qui constitue seulement un envoi de message à l'élément de la collection, le paramètre du block, je peux mettre directement sous la forme d'un symbole le nom du message à envoyer (ligne 6). C'est encore plus concis mais ça ne fonctionne que pour les messages unaires. 
 + 
 +===== reject: rejeter selon un critère ===== 
 + 
 +On peut utiliser d'autres types d'itérateurs comme reject. Je veux éliminer tous les éléments de la collection qui sont impairs
 + 
 +<code smalltalk> 
 +#( 16 11 68 19 ) reject: [ :i | i odd ] 
 +"> #(16 68)" 
 + 
 + 
 +# Équivalent 
 +#( 16 11 68 19 ) reject: #odd 
 +"> #(16 68)" 
 +</code> 
 + 
 +Dans la collection résultat, il ne va me rester que des éléments pairs. 
 + 
 +===== detect: sélectionner le premier élément correspondant au critère ===== 
 + 
 +Lorsque je veux détecter le premier élément de la collection qui répond à un critère donné pour lequel l'évaluation du bloc va être vraie, je peux utiliser l'itérateur ''detect:'' 
 + 
 +<code smalltalk> 
 +#( 16 11 68 19 ) detect: [ :i | i odd ] 
 +"> 11" 
 + 
 + 
 +# Équivalent 
 +#( 16 11 68 19 ) detect: #odd 
 +"> 11" 
 +</code> 
 + 
 +Je veux le premier élément de la collection qui est impair, donc 11. Ici ''detect:'' retourne bien un élément, pas une collection. 
 + 
 + 
 +Dans certains cas, on veut détecter le premier élément de la collection qui répond à un critère, mais s'il n'y en a pas alors on aimerait bien avoir une valeur par défaut. C'est l'itérateur ''detect:ifNone:'' qu'on peut alors utiliser. 
 + 
 +<code smalltalk> 
 +#( 16 12 68 20 ) detect: #odd ifNone: [ 0 ] 
 +</code> 
 + 
 +S'il n'y a aucun élément c'est l'évaluation de ce block là qui va être effectuée, et donc ça va bien nous renvoyer 0. 
 + 
 +===== Autres itérateurs usuels ===== 
 + 
 +Il y a d'autres itérateurs encore qui vont encore faciliter la vie du programmeur. Par exemple
 + 
 +  * ''anySatisfy:'' Est-ce qu'il existe dans la collection un élément qui répond à ce critère-là? 
 +  * ''allSatisfy:'' lorsque je veux tous les éléments d'une collection qui répondent à un critère donné. 
 +  * ''reverseDo:'' lorsque je veux parcourir la collection à l'envers en partant de la fin vers le début. 
 +  * ''doWithIndex:'' lorsque je veux parcourir la collection en ayant un curseur d'index. 
 +  * ''pairsDo:'' lorsque je veux parcourir les éléments de la collection 2 à 2. 
 +  * ''permutationsDo:'' lorsque je veux parcourir toutes les permutations circulaires possibles des éléments d'une collection. 
 + 
 +Il y en a beaucoup des itérateurs et on peut en construire des nouveaux, d'ailleurs. 
 + 
 +===== Itérer sur deux structures ===== 
 + 
 +Avec l'itérateur ''with:do:'' 
 + 
 +<code smalltalk> 
 +#( 1 2 3) 
 +  with: #( 10 20 30 ) 
 +  do: [ :x :y | Transcript show: (y*x); cr ] 
 +</code> 
 + 
 +Ici, je veux parcourir une collection #( 1 2 3couplée avec une deuxième collection. J'envoie le message ''with:do:'' à la première collection. Dans mon bloc ''do:'', j'aurai x et y, 2 paramètres. Le premier x, ça sera un élément de la première collection, et y un élément de la deuxième collection. Donc je vais pouvoir multiplier ces éléments entre eux. On obtient dans le Transcript 10, 40 et 90. Et bien évidemment, il faut absolument que les 2 collections aient la même taille avec cet itérateur-là. 
 + 
 +===== do:separatedBy: introduire des séparateurs ===== 
 + 
 +On peut avoir d'autres types de parcours: ici, j'utilise le ''doseparatedBy:''. 
 + 
 +<code smalltalk> 
 +String streamContents: [ :s | 
 +  #( 'a', 'b', 'c' ) do: [ :each | s << each ] 
 +    separatedBy: [ ', ' ] 
 +
 + 
 +</code> 
 + 
 +J'ai une collection, je vais parcourir chacun des éléments et à chaque fois que j'ai parcouru un élément, je vais évaluer un bloc ici qui correspond à l'affichage d'une virgule. 
 + 
 +Ça va me permettre de parcourir le 'a', afficher une virgule, afficher le 'b', afficher une virgule, afficher le 'c'. A chaque fois entre chaque élément, je vais avoir effectué une action. 
 + 
 +===== groupedBy: regrouper des éléments ===== 
 + 
 +Ici, j'ai un itérateur qui est ''groupedBy:'', qui me permet de grouper les éléments d'une collection en fonction d'un critère. 
 + 
 +<code smalltalk> 
 +#( 1 2 3 4 5 6 7 ) groupedBy: #even  
 + 
 +"> an OrderedDictionary(false->#(1 3 5 7) true->#(2 4 6)) " 
 +</code> 
 + 
 +J'envoie le message à la collection #( 1 2 3 4 5 6 7et je récupère en résultat un dictionnaire. Donc tous les éléments qui ont répondu "Faux" à ce critère, le critère c'était "#even" (éléments pairs), donc on voit que ça contient bien une collection de tous les éléments impairs. Et tout ce qui a répondu "Vrai", c'est tous les éléments pairs. 
 + 
 +===== flatCollect: aplatir la collection ===== 
 + 
 +Quand on a une collection souvent quand on fait des calculs on a tendance à imbriquer des collections dans des collection, et on se retrouve avec des niveaux d'imbrication qui peuvent être importants. 
 + 
 +<code smalltalk [enable_lines_numbers="true"]> 
 +#( #(1 2) #(3) #(4) #(5 6)) collect: [ :each | each ] 
 +"> #(#(1 2) #(3) #(4) #(5 6)) " 
 + 
 +#( #(1 2) #(3) #(4) #(5 6)) flatCollect: [ :each | each ] 
 +"> #(1 2 3 4 5 6) " 
 +</code> 
 + 
 +Ici, vous avez un exemple construit à la main où on a des collections qui sont imbriquées dans des collections. Ce qu'on aimerait c'est arriver à aplatir la collection, à mettre tous les éléments au même niveau. 
 + 
 +Pour ça, on a quelque chose de facile en Pharo, on a un itérateur qui s'appelle ''flatCollect:''. C'est-à-dire que je vais parcourir les éléments et construire une nouvelle collection dans laquelle j'ai tout aplati. Donc on obtient bien la collection #(1 2 3 4 5 6) ligne 6 dans laquelle on a enlevé tous les niveaux d'imbrication. 
 + 
 +===== Ouvrir la boîte ===== 
 + 
 +Le secret, ce n'est pas de vous présenter tous les itérateurs disponibles dans Pharo, ce serait long et fastidieux. L'idée, c'est vraiment de vous montrer qu'il en existe plein et que vous pouvez découvrir ceux qui vous seront utiles en allant lire les méthodes qui existent sur les classes des collections, en allant découvrir ces méthodes. 
 + 
 +Un exemple simple, c'est de commencer par les itérateurs que vous connaissez. Par exemple, se poser la question "Comment est-ce qu'est implémenté le ''do:''?" Je cherche le ''do:'' dans la hiérarchie des collections, je vais voir qu'il est implémenté dans "SequenceableCollection" comprenant toutes les collections séquençables.  
 + 
 +Donc la méthode ''do:'', elle prend en paramètre un block. Et voici l'implémentation de cette collection. 
 + 
 +<code smalltalk [enable_line_numbers ="true"]> 
 +do: aBlock  
 +    "Refer to the comment in Collection|do:." 
 +    to: self size do: 
 +        [:index | aBlock value: (self at: index)] 
 +</code> 
 + 
 +Pour chaque élément de la collection (ligne 3) je vais évaluer le block qui est passé en paramètre en lui passant l'élément à l'indice ''index'' (ligne 4). 
 + 
 +===== Résumé ===== 
 + 
 +Les itérateurs sont extrêmement puissants en Pharo, comme on a pu le voir. 
 +  * Toutes les collections supportent ces itérateurs de façon polymorphique. Du point de vue programmeur, on utilise des itérateurs et puis c'est chacune des classes de collections qui vont les implémenter de façon adéquate par rapport à la collection qu'ils représentent. 
 +  * On peut en définir des nouveaux. C'est extrêmement intéressants: je peux définir mes propres itérateurs si j'en ai envie sur les classes de collections. 
 +  * Il y a une subtilité. Pour ceux qui connaissent le Design pattern iterator, en fait la différence c'est que le développeur ne contrôle pas quand il passe à l'élément suivant. C'est la collection, qui en interne, décide de passer à l'élément suivant. On n'envoie pas explicitement le message "next" à l'itérateur. C'est une subtilité pour ceux qui connaissent le Design pattern iterator. 
 + 
 +En résumé, les itérateurs sont vraiment puissants et un allié fort du programmeur qui vont faciliter l'écriture des programmes. On l'a vu, ça permet d'écrire du code concis, simple et élégant et ça permet de garantir l' encapsulation des données au sein d'une collection.
  
cours/informatique/dev/programmation_objet_pharo/330_iterateurs.1661160206.txt.gz · Dernière modification : 2022/08/22 09:23 de yoann