Épisode 10 sur des traitements classiques sur les listes. En Ruby on appelle ça Enumerable, mais heureusement les mots sont partagés dans la plupart des bibliothèques et langages.
Traitons d’abord quelques oublis classiques de débutants (et d’étourderies d’experts aussi, ne vous sentez pas ridicules quand ça vous arrive, c’est le jeu) sur des listes et “boucles”.
each
et les itérations
Imaginez-vous dans un emploi qui traite des dossiers un par un.
Votre méthode de travail est de prendre des fiches, et les traiter.
Partons sur un tableau nommé a
et contenant les chiffres de 1 à 5.
a = [1,2,3,4,5] # ou (1..5).to_a
Traiter, ça peut vouloir dire prendre une feuille, noter quelque
chose ailleurs, et les remettre exactement à leur place.
C’est ce que fait each
: le code b = a.each{|x| x * 2}
met effectivement un tableau dans b
, mais [1,2,3,4,5]
et non pas [2,4,6,8,10]
.
map
et les pièges sémantiques
Traiter, ça peut vouloir dire prendre un dossier a
,
ouvrir un nouveau dossier vierge, et mettre dans ce nouveau
dossier le résultat de votre travail.
Le piège, c’est qu’après vous pouvez tout aussi bien mettre
une nouvelle étiquette b
sur ce nouveau dossier, ou
voler l’étiquette du dossier a
pour la coller sur ce
nouveau dossier.
Pire encore, vous pouvez travailler soit avec des photocopies
de chacune des feuilles de a
, soit modifier physiquement
chacune des feuilles : vous avez mis du surligneur, déchiré
un coupon, écrit sur la fiche ou mis un tampon ?
Impossible de retrouver la feuille de départ dans l’état de départ !
En terme de code, ces trois options donneraient cela :
b = a.map {|x| x * 2}
, qui renvoie bien [2,4,6,8,10]
et le stocke dans b
, sans aucunement déranger le tableau a
.
a = a.map {|x| x * 2}
a travaillé sur une copie de a
(sans la nommer) puis finit par lui recoller l’étiquette a
dessus.
On ne parle pas des informations de départ : en ayant redonné
l’étiquette au nouveau dossier, on n’a pas vraiment prévu de moyen
d’aller chercher les anciennes informations.
['bonjour', 'au revoir'].map{|x| x.upcase!}
a vraiment modifié
le contenu, chacune des chaînes de caractères du tableau.
On note d’ailleurs que par convention, les Rubyistes mettront un
point d’exclamation à la fin des noms de méthodes “destructrices”,
c’est à dire qui changent le contenu de départ.
str = "Bonjour" # => "Bonjour" # valeur de départ
str.upcase # => "BONJOUR" # ça n'est pas la même donnée
str # => "Bonjour" # on n'a pas touché à str
str.upcase! # => "BONJOUR" # on a mis en majuscule et on altère str
str # => "BONJOUR" # str a été modifiée
Traitement “en place”
Pareillement pour les dossiers, si je veux trier mon dossier a
je n’ai pas forcément envie de préparer un nouveau dossier pour
conserver les valeurs triées, et jouer ensuite avec les étiquettes.
Je peux vouloir simplement dire que tout se fait dans le tableau a
et que je n’ai pas besoin de faire de copies. Ça s’appelle un
traitement “in-place” et la plupart des rubyistes vont le faire
avec des méthodes finissant par des points d’exclamation.
On n’en voit pas dans Enumerable
, mais on voit beaucoup de paires
dans la classe Array
: sort
et sort!
, rotate
, reverse
, uniq
…
D’une liste à un seul résultat
Je pense qu’il faudra un autre épisode pour toutes ces super fonctions. En attendant, il y a une classe entière de besoins à voir.
Imaginez que votre travail c’est de prendre une liste de fiches, et de ressortir un seul nombre : la somme des paiements, ou le nombre de mauvais payeurs par exemple.
Cette méthode a trois noms.
Ruby l’appelle reduce
: on “réduit” les N fiches à 1 résultat.
Ruby l’appelle aussi inject
: on “injecte” la valeur zéro,
puis on va regarder chaque fiche pour ajouter la somme d’argent
de LA fiche au total “temporaire” et ainsi de suite.
Par exemple, 50 + 100 + 25 + 20 + 5 : vous avez probablement fait
l’addition étape par étape.
[50, 100, 25, 20, 5].inject(0) {|somme, obj| somme + obj}
[100, 25, 20, 5].inject(50) {|somme, obj| somme + obj}
[25, 20, 5].inject(150) {|somme, obj| somme + obj}
[20, 5].inject(175) {|somme, obj| somme + obj}
[5].inject(195) {|somme, obj| somme + obj}
# => 200
Le troisième nom n’est pas très utilisé en Ruby, mais dans le reste
des langages : fold
. Il applique le résultat étape par étape, comme
on vient de voir.
J’aime bien l’image que ce mot donne : si vous avez déjà vu de vieilles
imprimantes avec des piles de papier qui se pliaient et dépliaient,
c’est vraiment pour moi l’acte de replier une très longue liste de
pages en un accordéon qui prend moins de place et affiche en bas
le résultat voulu.
That’s all folks!
Et bien voilà, c’est déjà assez long pour cette fois ! Mon but n’est pas franchement de vous lire la doc Ruby à voix haute, donc allez lire Enumerable, Array, Hash, et aussi String, les opérations sur les chaînes de caractères, pour faire bonne mesure, mais peut-être que je ferai un épisode “rappel” sur quelques-unes de ces méthodes un de ces jours.