Ruby 3.2 introduce Enumerator::product
Es esa época del año de nuevo cuando se lanza una nueva versión de Ruby. Echemos un vistazo más de cerca a un nuevo método de Enumerator: ::product.
Sintaxis del Método
Aquí está el hilo para este cambio: https://bugs.ruby-lang.org/issues/18685
Para entender qué hace este nuevo método Enumerator::product, imagina que quieres obtener un producto cartesiano de dos arrays así:
array_1 = [1, 2, 3]
array_2 = [:a, :b, :c]
# Producto cartesiano
[[1, :a], [1, :b], [1, :c], [2, :a], [2, :b], [2, :c], [3, :a], [3, :b], [3, :c]]
Para poder hacer esto antes de Ruby 3.2, uno necesitaría anidar los arrays. Aquí hay una solución tomada de esta respuesta de 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]]
A partir de Ruby 3.2, podemos hacer esto en su lugar:
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]]
Aplicaciones Prácticas
Veamos cómo podríamos usar este método. Imagina que estamos construyendo una aplicación de comercio electrónico y necesitamos crear un array de opciones para una camiseta.
La camiseta tendría las siguientes opciones:
- Talla (Pequeña, Mediana, Grande)
- Color (Azul, Rojo, Verde, Amarillo)
- Tela (Algodón, Nylon)
Usando Enumerator::product, podemos construir rápidamente un array de opciones así:
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"],
# ...
# ]
¡Genial! Ahora no necesitamos iterar a través de todas las diferentes combinaciones para obtener las opciones correctas.
Profundizando
Algunos lectores también pueden notar que las versiones recientes de Ruby ya incluyen un método Array#product. Para este método, está definido aquí: 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
Sin embargo, la implementación del método Enumerable::product más nuevo es diferente y está implementado en C. Ese código es demasiado largo para pegarlo aquí, así que aquí está el enlace: https://github.com/ruby/ruby/blob/v3_2_0_rc1/enumerator.c#L3382.