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 Enumerabledonne accès àmap,select,sort_by,reduce, etc.eachest le contrat : Enumerable l'utilise comme base pour toutes ses méthodes.&blockcapture le bloc passé àeachet le transmet à@pokemon_list.each.return selfdansaddpermet 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) }
selectetrejectretournent un Array.findretourne un seul élément ounil.
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 :
sumcommence à0(la valeur initiale passée àreduce)- Premier tour :
sum = 0 + 25→sumvaut25 - Deuxième tour :
sum = 25 + 36→sumvaut61 - 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
sumest plus lisible quereducepour 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_byretourne un nouvel Array trié.min_by/max_bysont plus performants quesort_by.first/sort_by.lastcar 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_byretourne un Hash dont les clés sont les valeurs retournées par le bloc.tallyest 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 }
countsans 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-1siselfest inférieur,0si égal,1si supérieur.- Une fois
<=>défini,.sortfonctionne automatiquement sans bloc.
Conclusion
- Inclure
Enumerableet définireachdonne accès à plus de 50 méthodes. maptransforme,flat_maptransforme et aplatit.select/rejectfiltrent,findtrouve le premier.reduceaccumule une valeur.sumest un raccourci pour les additions.sort_bytrie par critère.min_by/max_bytrouvent les extrêmes.group_byregroupe,tallycompte les occurrences.countcompte,any?/all?/none?vérifient des conditions.- Inclure
Comparableet définir<=>donne<,>,<=,>=,between?,clamp. - On peut chaîner les appels Enumerable pour des requêtes complexes.