É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.