Ruby 3.2引入Enumerator::product
又到了每年新版本Ruby发布的时候。让我们仔细看看一个新的Enumerator方法:::product。
方法语法
这个更改的讨论线程在这里:https://bugs.ruby-lang.org/issues/18685
要理解这个新的Enumerator::product方法做什么,想象你想从两个数组获取笛卡尔积:
array_1 = [1, 2, 3]
array_2 = [:a, :b, :c]
# 笛卡尔积
[[1, :a], [1, :b], [1, :c], [2, :a], [2, :b], [2, :c], [3, :a], [3, :b], [3, :c]]
在Ruby 3.2之前要做到这一点,需要嵌套数组。这是来自这个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]]
从Ruby 3.2开始,我们可以这样做:
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]]
实际应用
让我们看看如何使用这个方法。假设我们正在构建一个电商应用,需要为T恤创建一个选项数组。
T恤会有以下选项:
- 尺寸(小号、中号、大号)
- 颜色(蓝色、红色、绿色、黄色)
- 面料(棉、尼龙)
使用Enumerator::product,我们可以快速构建一个选项数组:
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"],
# ...
# ]
太棒了!现在我们不需要遍历所有不同的组合来得到正确的选项。
深入了解
一些读者可能也注意到最近的Ruby版本已经包含了Array#product方法。这个方法定义在这里: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
然而,新的Enumerable::product方法的实现是不同的,它用C实现。那段代码太长无法粘贴在这里,所以这是链接:https://github.com/ruby/ruby/blob/v3_2_0_rc1/enumerator.c#L3382。