Ruby 3.2 introduces Enumerator::product
Learn how to use the Enumerator::product method
Table of contents
It is that time of the year again when a new version of Ruby is released. Let us take a closer look at a new Enumerator method: ::product
.
Method Syntax
Here is the thread for this change: https://bugs.ruby-lang.org/issues/18685
To understand what this new Enumerator::product
method does, imagine you want to get a Cartesian product from two arrays like so:
array_1 = [1, 2, 3]
array_2 = [:a, :b, :c]
# Cartesian product
[[1, :a], [1, :b], [1, :c], [2, :a], [2, :b], [2, :c], [3, :a], [3, :b], [3, :c]]
To be able to do this before Ruby 3.2, one would need to nest the arrays. Here is a solution taken from this StackOverflow answer:
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]]
Beginning in Ruby 3.2, we can do this instead:
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]]
Practical Applications
Let's take a look at how we might use this method. Imagine we are building an e-commerce application, and we need to create an array of options for a T-shirt.
The T-shirt would have the following options:
Size (Small, Medium, Large)
Color (Blue, Red, Green, Yellow)
Fabric (Cotton, Nylon)
Using Enumerator::product
, we can quickly construct an array of options like so:
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"],
# ...
# ]
Nice! Now we don't need to iterate through all the different combinations to come up with the right options.
Digging Deeper
Some readers may also notice that recent Ruby versions already include an Array#product
method. For this method, it is defined here: 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
However, the newer Enumerable::product
method's implementation is different and is implemented in C. That code is too long to paste in here, so here is the link: https://github.com/ruby/ruby/blob/v3_2_0_rc1/enumerator.c#L3382.