Ruby 3.2 introduit Enumerator::product

C’est à nouveau cette période de l’année où une nouvelle version de Ruby est publiée. Examinons de plus près une nouvelle méthode Enumerator : ::product.

Syntaxe de la Méthode

Voici le fil de discussion pour ce changement : https://bugs.ruby-lang.org/issues/18685

Pour comprendre ce que fait cette nouvelle méthode Enumerator::product, imaginez que vous voulez obtenir un produit cartésien de deux tableaux comme ceci :

array_1 = [1, 2, 3]
array_2 = [:a, :b, :c]

# Produit cartésien
[[1, :a], [1, :b], [1, :c], [2, :a], [2, :b], [2, :c], [3, :a], [3, :b], [3, :c]]

Pour pouvoir faire cela avant Ruby 3.2, il fallait imbriquer les tableaux. Voici une solution tirée de cette réponse StackOverflow :

class CartesianProduct
  include Enumerable

  def initialize(xs, ys)
    @xs = xs
    @ys = ys
  end

  def each
    return to_enum unless block_given?

    @xs.each do |x|
      @ys.each { |y| yield [x, y] }
    end
  end
end

products = CartesianProduct.new(array_1, array_2).each.to_a
# [[1, :a], [1, :b], [1, :c], [2, :a], [2, :b], [2, :c], [3, :a], [3, :b], [3, :c]]

À partir de Ruby 3.2, nous pouvons faire ceci à la place :

products = Enumerator.product(array_1, array_2).to_a
# [[1, :a], [1, :b], [1, :c], [2, :a], [2, :b], [2, :c], [3, :a], [3, :b], [3, :c]]

Applications Pratiques

Voyons comment nous pourrions utiliser cette méthode. Imaginez que nous construisons une application e-commerce et que nous devons créer un tableau d’options pour un t-shirt.

Le t-shirt aurait les options suivantes :

  • Taille (Petit, Moyen, Grand)
  • Couleur (Bleu, Rouge, Vert, Jaune)
  • Tissu (Coton, Nylon)

En utilisant Enumerator::product, nous pouvons rapidement construire un tableau d’options comme ceci :

colors = ['blue', 'red', 'green', 'yellow']
sizes = ['small', 'medium', 'large']
fabrics = ['cotton', 'nylon']

variants = Enumerator.product(colors, sizes, fabrics).to_a
# [
#   ["blue", "small", "cotton"],
#   ["blue", "small", "nylon"],
#   ["blue", "medium", "cotton"],
#   ["blue", "medium", "nylon"],
#   ["blue", "large", "cotton"],
#   ...
# ]

Génial ! Maintenant nous n’avons pas besoin d’itérer à travers toutes les différentes combinaisons pour obtenir les bonnes options.

Creusons Plus Profond

Certains lecteurs remarqueront peut-être aussi que les versions récentes de Ruby incluent déjà une méthode Array#product. Pour cette méthode, elle est définie ici : https://github.com/ruby/ruby/blob/v3_2_0_rc1/tool/transcode-tblgen.rb#L10-L20

def product(*args)
  if args.empty?
    self.map {|e| [e] }
  else
    result = []
    self.each {|e0|
      result.concat args.first.product(*args[1..-1]).map {|es| [e0, *es] }
    }
    result
  end
end

Cependant, l’implémentation de la nouvelle méthode Enumerable::product est différente et implémentée en C. Ce code est trop long pour être collé ici, donc voici le lien : https://github.com/ruby/ruby/blob/v3_2_0_rc1/enumerator.c#L3382.