Aller au contenu principal
Voir
Chapitre 12 / 16Enumerable
1. Les variables et les types de données
2. Les chaînes et les symboles
3. Les tableaux
4. Les tables associatives
5. Les conditions
6. Les boucles et les itérateurs
7. Les méthodes et les blocs
8. Les classes et les objets
9. L'héritage
10. Les modules
11. Étendre du code avec prepend
12. Enumerable
13. Pour aller plus loin
14. La gestion des erreurs
15. Lire et écrire des fichiers
16. Les fonctionnalités avancées des classes

Enumerable

Ce chapitre présente Enumerable, un module qui donne plus de 50 méthodes de manipulation de collections. On découvre aussi Comparable, qui permet de comparer des objets entre eux.

Principe

On a déjà utilisé .map, .select, .reject sur les Array. Ce ne sont pas des méthodes d'Array à proprement parler : elles viennent du module Enumerable qu'Array inclut automatiquement.

Le principe est simple : si une classe définit la méthode each et inclut Enumerable, elle hérite de toutes ces méthodes gratuitement. each est la seule brique nécessaire — Ruby construit tout le reste dessus.

C'est exactement ce qu'on a appris au chapitre 10 avec les mixins : include Enumerable injecte des dizaines de méthodes dans la classe.

Inclure Enumerable dans une classe

class Registry
include Enumerable

def initialize
@pokemon_list = []
end

def add(pokemon)
@pokemon_list << pokemon
return self
end

# La seule méthode obligatoire pour Enumerable
def each(&block)
@pokemon_list.each(&block)
end
end
  • include Enumerable donne accès à map, select, sort_by, reduce, etc.
  • each est le contrat : Enumerable l'utilise comme base pour toutes ses méthodes.
  • &block capture le bloc passé à each et le transmet à @pokemon_list.each.
  • return self dans add permet de chaîner : registry.add(a).add(b).

Transformer : map et flat_map

map transforme chaque élément et retourne un nouvel Array :

names = registry.map { |pokemon| pokemon.name }
# => ["Pikachu", "Dracaufeu", "Tortank"]

flat_map fait la même chose, puis aplatit le résultat d'un niveau. C'est utile quand chaque élément produit un Array :

# Chaque Pokémon peut avoir plusieurs types
all_types = registry.flat_map { |pokemon| pokemon.types }
# => [:electric, :fire, :flying, :water]

# Sans flat_map, on obtiendrait un Array d'Arrays
registry.map { |pokemon| pokemon.types }
# => [[:electric], [:fire, :flying], [:water]]
  • flat_map = map + flatten(1). C'est un raccourci très courant.

Filtrer : select, reject, find

On les a déjà vus au chapitre 3, mais ils viennent d'Enumerable :

# Garder les Pokémon de type Feu
fire_pokemon = registry.select { |pokemon| pokemon.types.include?(:fire) }

# Exclure les Pokémon KO
alive = registry.reject { |pokemon| pokemon.ko? }

# Trouver le premier Pokémon Eau
first_water = registry.find { |pokemon| pokemon.types.include?(:water) }
  • select et reject retournent un Array. find retourne un seul élément ou nil.

Réduire : reduce

reduce (aussi appelé inject) accumule une valeur en parcourant la collection :

# Additionner tous les niveaux
total = registry.reduce(0) { |sum, pokemon| sum + pokemon.level }

Comment ça fonctionne, étape par étape :

  1. sum commence à 0 (la valeur initiale passée à reduce)
  2. Premier tour : sum = 0 + 25sum vaut 25
  3. Deuxième tour : sum = 25 + 36sum vaut 61
  4. Et ainsi de suite... Le résultat final est la dernière valeur de sum.

Il existe aussi des raccourcis :

# sum est un raccourci pour reduce(0) { |s, p| s + p.level }
total = registry.sum { |pokemon| pokemon.level }

# reduce avec un symbole (quand on travaille directement sur des nombres)
levels = [25, 36, 40]
total = levels.reduce(:+) # => 101
  • sum est plus lisible que reduce pour les additions simples.
  • reduce(:+) appelle la méthode + entre chaque paire d'éléments.

Trier : sort_by, min_by, max_by

sort_by trie selon un critère extrait par le bloc :

# Trier par niveau croissant
by_level = registry.sort_by { |pokemon| pokemon.level }

# Trier par niveau décroissant (nier la valeur)
strongest = registry.sort_by { |pokemon| -pokemon.level }

min_by et max_by trouvent l'élément extrême sans trier toute la collection :

strongest = registry.max_by { |pokemon| pokemon.level }
weakest = registry.min_by { |pokemon| pokemon.level }

# Les deux à la fois
weakest, strongest = registry.minmax_by { |pokemon| pokemon.level }
  • sort_by retourne un nouvel Array trié.
  • min_by/max_by sont plus performants que sort_by.first/sort_by.last car ils ne parcourent la collection qu'une seule fois.

Regrouper : group_by et tally

group_by organise les éléments en sous-groupes selon un critère :

by_type = registry.group_by { |pokemon| pokemon.types.first }
# => { :electric => [...], :fire => [...], :water => [...] }

tally compte les occurrences de chaque valeur dans un Array :

# Compter combien de Pokémon de chaque type
census = registry.flat_map { |pokemon| pokemon.types }.tally
# => { :electric => 2, :fire => 3, :water => 1, :flying => 1 }
  • group_by retourne un Hash dont les clés sont les valeurs retournées par le bloc.
  • tally est un raccourci très pratique pour les comptages.

Compter : count et sum

# Combien de Pokémon au total ?
total = registry.count

# Combien de Pokémon de type Feu ?
fire_count = registry.count { |pokemon| pokemon.types.include?(:fire) }

# Somme de tous les niveaux
total_levels = registry.sum { |pokemon| pokemon.level }
  • count sans bloc retourne le nombre total. Avec un bloc, il compte ceux qui satisfont la condition.

Prédicats : any?, all?, none?

Ces méthodes retournent true ou false :

# Y a-t-il au moins un Pokémon Eau ?
registry.any? { |pokemon| pokemon.types.include?(:water) } # => true

# Tous les Pokémon sont-ils au-dessus du niveau 10 ?
registry.all? { |pokemon| pokemon.level > 10 } # => true

# Aucun Pokémon n'est KO ?
registry.none? { |pokemon| pokemon.ko? } # => true

Découper : take et drop

# Les 3 premiers Pokémon
first_three = registry.take(3)

# Tous sauf les 2 premiers
without_first_two = registry.drop(2)

Chaîner les appels

On peut enchaîner plusieurs méthodes Enumerable pour construire des requêtes complexes :

# Les 3 noms des Pokémon de plus haut niveau
top_three = registry
.sort_by { |pokemon| -pokemon.level }
.take(3)
.map { |pokemon| pokemon.name }
  • Chaque méthode retourne un Array, ce qui permet d'appeler la suivante dessus.
  • L'ordre des opérations compte : filtrer avant de trier est plus performant.

Le module Comparable

Comparable fonctionne sur le même principe qu'Enumerable : on inclut le module et on définit une seule méthode, l'opérateur <=> (spaceship). En retour, on obtient <, >, <=, >=, between? et clamp.

  • <=> retourne -1 si self est inférieur, 0 si égal, 1 si supérieur.
  • Une fois <=> défini, .sort fonctionne automatiquement sans bloc.

Conclusion

  • Inclure Enumerable et définir each donne accès à plus de 50 méthodes.
  • map transforme, flat_map transforme et aplatit.
  • select/reject filtrent, find trouve le premier.
  • reduce accumule une valeur. sum est un raccourci pour les additions.
  • sort_by trie par critère. min_by/max_by trouvent les extrêmes.
  • group_by regroupe, tally compte les occurrences.
  • count compte, any?/all?/none? vérifient des conditions.
  • Inclure Comparable et définir <=> donne <, >, <=, >=, between?, clamp.
  • On peut chaîner les appels Enumerable pour des requêtes complexes.