[ruby-core:108346] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerables
From:
"Eregon (Benoit Daloze)" <noreply@...>
Date:
2022-04-21 10:37:51 UTC
List:
ruby-core #108346
Issue #18685 has been updated by Eregon (Benoit Daloze).
My performance concern was not about Ruby vs C, writing in C would have the same issues.
What I'm saying is this:
```ruby
(1..3).each do |i|
["A", "B"].each do |c|
puts "#{i}-#{c}"
end
end
```
will always be faster than:
```ruby
Enumerator.product(1..3, ["A", "B"]).each do |i, c|
puts "#{i}-#{c}"
end
```
because the second has a generic/cannot-do-sensible-inline-cache loop and lots of array allocation and splatting.
So Enumerator.product makes sense when one doesn't know how many `enums` to combine, or a large number of them, but for 2-3 it's better performance-wise (and maybe also for clarity) to just use nested `each`.
----------------------------------------
Feature #18685: Enumerator.product: Cartesian product of enumerables
https://bugs.ruby-lang.org/issues/18685#change-97371
* Author: knu (Akinori MUSHA)
* Status: Open
* Priority: Normal
* Target version: 3.2
----------------------------------------
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, &block)
# TODO: size should be calculated if possible
return to_enum(__method__, *enums) 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
--
https://bugs.ruby-lang.org/
Unsubscribe: <mailto:ruby-core-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>