Feature #18685
Updated by Eregon (Benoit Daloze) over 3 years ago
I'd like to add a new Enumerator class method for generating the Cartesian product of given enumerators.
A product here does not mean an accumulated array of arrays, but an enumerator to enumerate all combinations.
```ruby
product = Enumerator.product(1..3, ["A", "B"])
p product.class #=> Enumerator
product.each do |i, c|
puts "#{i}-#{c}"
end
=begin output
1-A
1-B
2-A
2-B
3-A
3-B
=end
```
This can be used to reduce nested blocks and allows for iterating over an indefinite number of enumerable objects.
## Implementation notes
- It should internally use `each_entry` instead of `each` on enumerable objects to make sure to capture all yielded arguments.
- If no enumerable object is given, the block is called once with no argument.
- It should reject a keyword-style hash argument so we can add keyword arguments in the future without breaking existing code.
- Here's an example implementation:
```ruby
# call-seq:
# Enumerator.product(*enums) -> enum
# Enumerator.product(*enums) { |*args| block } -> return value of args[0].each_entry {}
def Enumerator.product(*enums, **nil, **kw, &block)
kw.empty? or raise ArgumentError, "unknown keyword#{"s" if kw.size > 1}: #{kw.keys.map(&:inspect).join(", ")}"
# TODO: size should be calculated if possible
return to_enum(__method__, *enums, **kw) if block.nil?
enums.reverse.reduce(block) { |inner, enum|
->(*values) {
enum.each_entry { |value|
inner.call(*values, value)
}
}
}.call()
end
```
- Not to be confused with `Enumerator.produce`. 😝
## Prior case
- Python: https://docs.python.org/3/library/itertools.html#itertools.product