Ruby 3.2でEnumerator::productが導入
今年もまた、新しいRubyのバージョンがリリースされる時期がやってきました。新しいEnumeratorメソッド ::product を詳しく見てみましょう。
メソッドの構文
この変更に関するスレッドはこちらです:https://bugs.ruby-lang.org/issues/18685
この新しいEnumerator::productメソッドが何をするかを理解するために、2つの配列からデカルト積を取得したい場合を想像してください:
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]]
実用的な応用
このメソッドをどのように使用できるか見てみましょう。ECサイトを構築していて、Tシャツのオプションの配列を作成する必要があると想像してください。
Tシャツには以下のオプションがあります:
- サイズ(S、M、L)
- カラー(青、赤、緑、黄)
- 生地(コットン、ナイロン)
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。