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