ruby-core@ruby-lang.org archive (unofficial mirror)
 help / color / mirror / Atom feed
* [ruby-core:108198] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerators
@ 2022-04-08  7:20 knu (Akinori MUSHA)
  2022-04-08 10:52 ` [ruby-core:108199] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerables Eregon (Benoit Daloze)
                   ` (9 more replies)
  0 siblings, 10 replies; 11+ messages in thread
From: knu (Akinori MUSHA) @ 2022-04-08  7:20 UTC (permalink / raw)
  To: ruby-core

Issue #18685 has been reported by knu (Akinori MUSHA).

----------------------------------------
Feature #18685: Enumerator.product: Cartesian product of enumerators
https://bugs.ruby-lang.org/issues/18685

* 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 from enumerable objects.
- 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, **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




-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [ruby-core:108199] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerables
  2022-04-08  7:20 [ruby-core:108198] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerators knu (Akinori MUSHA)
@ 2022-04-08 10:52 ` Eregon (Benoit Daloze)
  2022-04-21  7:59 ` [ruby-core:108334] " matz (Yukihiro Matsumoto)
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Eregon (Benoit Daloze) @ 2022-04-08 10:52 UTC (permalink / raw)
  To: ruby-core

Issue #18685 has been updated by Eregon (Benoit Daloze).


Isn't it significantly slower than doing it manually due to all these extra block and method calls etc in between?

----------------------------------------
Feature #18685: Enumerator.product: Cartesian product of enumerables
https://bugs.ruby-lang.org/issues/18685#change-97183

* 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, **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




-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [ruby-core:108334] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerables
  2022-04-08  7:20 [ruby-core:108198] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerators knu (Akinori MUSHA)
  2022-04-08 10:52 ` [ruby-core:108199] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerables Eregon (Benoit Daloze)
@ 2022-04-21  7:59 ` matz (Yukihiro Matsumoto)
  2022-04-21  8:04 ` [ruby-core:108335] " knu (Akinori MUSHA)
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: matz (Yukihiro Matsumoto) @ 2022-04-21  7:59 UTC (permalink / raw)
  To: ruby-core

Issue #18685 has been updated by matz (Yukihiro Matsumoto).


Looping class method is an unusual pattern in Enumerable. So I first hesitated, but actually it's the only way to go if we want to introduce `product'.
And the method seems to be convenient. Thus, I accept.
@eregon If the performance matters, we can re-implement it in C.

Matz.


----------------------------------------
Feature #18685: Enumerator.product: Cartesian product of enumerables
https://bugs.ruby-lang.org/issues/18685#change-97359

* 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/

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [ruby-core:108335] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerables
  2022-04-08  7:20 [ruby-core:108198] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerators knu (Akinori MUSHA)
  2022-04-08 10:52 ` [ruby-core:108199] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerables Eregon (Benoit Daloze)
  2022-04-21  7:59 ` [ruby-core:108334] " matz (Yukihiro Matsumoto)
@ 2022-04-21  8:04 ` knu (Akinori MUSHA)
  2022-04-21 10:37 ` [ruby-core:108346] " Eregon (Benoit Daloze)
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: knu (Akinori MUSHA) @ 2022-04-21  8:04 UTC (permalink / raw)
  To: ruby-core

Issue #18685 has been updated by knu (Akinori MUSHA).


Thanks.  Performance monsters can also special-case arrays and ranges and omit calls to each_entry to boost performance. 😂

----------------------------------------
Feature #18685: Enumerator.product: Cartesian product of enumerables
https://bugs.ruby-lang.org/issues/18685#change-97360

* 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/

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [ruby-core:108346] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerables
  2022-04-08  7:20 [ruby-core:108198] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerators knu (Akinori MUSHA)
                   ` (2 preceding siblings ...)
  2022-04-21  8:04 ` [ruby-core:108335] " knu (Akinori MUSHA)
@ 2022-04-21 10:37 ` Eregon (Benoit Daloze)
  2022-04-21 10:40 ` [ruby-core:108347] " Eregon (Benoit Daloze)
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Eregon (Benoit Daloze) @ 2022-04-21 10:37 UTC (permalink / raw)
  To: ruby-core

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/

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [ruby-core:108347] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerables
  2022-04-08  7:20 [ruby-core:108198] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerators knu (Akinori MUSHA)
                   ` (3 preceding siblings ...)
  2022-04-21 10:37 ` [ruby-core:108346] " Eregon (Benoit Daloze)
@ 2022-04-21 10:40 ` Eregon (Benoit Daloze)
  2022-04-23 19:26 ` [ruby-core:108387] " shan (Shannon Skipper)
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Eregon (Benoit Daloze) @ 2022-04-21 10:40 UTC (permalink / raw)
  To: ruby-core

Issue #18685 has been updated by Eregon (Benoit Daloze).


And between writing `Enumerator.product` in Ruby or C I'd prefer a lot in Ruby because it's a million times more readable, and it can be reused by other Ruby implementations :)

----------------------------------------
Feature #18685: Enumerator.product: Cartesian product of enumerables
https://bugs.ruby-lang.org/issues/18685#change-97372

* 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/

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [ruby-core:108387] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerables
  2022-04-08  7:20 [ruby-core:108198] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerators knu (Akinori MUSHA)
                   ` (4 preceding siblings ...)
  2022-04-21 10:40 ` [ruby-core:108347] " Eregon (Benoit Daloze)
@ 2022-04-23 19:26 ` shan (Shannon Skipper)
  2022-04-24  2:36 ` [ruby-core:108388] " knu (Akinori MUSHA)
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: shan (Shannon Skipper) @ 2022-04-23 19:26 UTC (permalink / raw)
  To: ruby-core

Issue #18685 has been updated by shan (Shannon Skipper).


It might also be nice to require at least one `enum` argument, since `Enumerator.product #=> [nil]` seems a bit odd. Here's a stab at lazy size:

``` ruby
def Enumerator.product(*enums, **nil, &block)
  raise ArgumentError, 'wrong number of arguments (given 0, expected 1..)' if enums.empty?

  unless block_given?
    return to_enum(__method__, *enums) do
      enums.reduce(1) do |acc, enum|
        enum_size = enum.size
        break unless enum_size

        acc * enum_size
      end
    end
  end

  enums.reverse.reduce(block) do |inner, enum|
    lambda do |*values|
      enum.each_entry do |value|
        inner.call(*values, value)
      end
    end
  end.call
end
```


----------------------------------------
Feature #18685: Enumerator.product: Cartesian product of enumerables
https://bugs.ruby-lang.org/issues/18685#change-97421

* 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/

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [ruby-core:108388] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerables
  2022-04-08  7:20 [ruby-core:108198] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerators knu (Akinori MUSHA)
                   ` (5 preceding siblings ...)
  2022-04-23 19:26 ` [ruby-core:108387] " shan (Shannon Skipper)
@ 2022-04-24  2:36 ` knu (Akinori MUSHA)
  2022-04-26  7:02 ` [ruby-core:108405] " sawa (Tsuyoshi Sawada)
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: knu (Akinori MUSHA) @ 2022-04-24  2:36 UTC (permalink / raw)
  To: ruby-core

Issue #18685 has been updated by knu (Akinori MUSHA).


That's actually not a mathematical idea.  The 0-ary Cartesian product of sets should be defined as a singleton set for theoretical and practical reasons.  It's just like 2^0 equals to 1.

Python's itertools.product aligns with this theory.

```python
import itertools

for i in itertools.product(range(3), range(3)):
  print("2-ary: " + repr(i))

for i in itertools.product(range(3)):
  print("1-ary: " + repr(i))

for i in itertools.product():
  print("0-ary: " + repr(i))
```
Output:
```
2-ary: (0, 0)
2-ary: (0, 1)
2-ary: (0, 2)
2-ary: (1, 0)
2-ary: (1, 1)
2-ary: (1, 2)
2-ary: (2, 0)
2-ary: (2, 1)
2-ary: (2, 2)
1-ary: (0,)
1-ary: (1,)
1-ary: (2,)
0-ary: ()
```




----------------------------------------
Feature #18685: Enumerator.product: Cartesian product of enumerables
https://bugs.ruby-lang.org/issues/18685#change-97422

* 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/

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [ruby-core:108405] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerables
  2022-04-08  7:20 [ruby-core:108198] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerators knu (Akinori MUSHA)
                   ` (6 preceding siblings ...)
  2022-04-24  2:36 ` [ruby-core:108388] " knu (Akinori MUSHA)
@ 2022-04-26  7:02 ` sawa (Tsuyoshi Sawada)
  2022-07-29  8:29 ` [ruby-core:109362] " knu (Akinori MUSHA)
  2022-11-17  6:42 ` [ruby-core:110790] " hsbt (Hiroshi SHIBATA)
  9 siblings, 0 replies; 11+ messages in thread
From: sawa (Tsuyoshi Sawada) @ 2022-04-26  7:02 UTC (permalink / raw)
  To: ruby-core

Issue #18685 has been updated by sawa (Tsuyoshi Sawada).


Related to #6499 #7444 #8970 #14399

----------------------------------------
Feature #18685: Enumerator.product: Cartesian product of enumerables
https://bugs.ruby-lang.org/issues/18685#change-97440

* 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/

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [ruby-core:109362] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerables
  2022-04-08  7:20 [ruby-core:108198] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerators knu (Akinori MUSHA)
                   ` (7 preceding siblings ...)
  2022-04-26  7:02 ` [ruby-core:108405] " sawa (Tsuyoshi Sawada)
@ 2022-07-29  8:29 ` knu (Akinori MUSHA)
  2022-11-17  6:42 ` [ruby-core:110790] " hsbt (Hiroshi SHIBATA)
  9 siblings, 0 replies; 11+ messages in thread
From: knu (Akinori MUSHA) @ 2022-07-29  8:29 UTC (permalink / raw)
  To: ruby-core

Issue #18685 has been updated by knu (Akinori MUSHA).


https://github.com/ruby/ruby/pull/6197

----------------------------------------
Feature #18685: Enumerator.product: Cartesian product of enumerables
https://bugs.ruby-lang.org/issues/18685#change-98506

* 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/

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [ruby-core:110790] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerables
  2022-04-08  7:20 [ruby-core:108198] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerators knu (Akinori MUSHA)
                   ` (8 preceding siblings ...)
  2022-07-29  8:29 ` [ruby-core:109362] " knu (Akinori MUSHA)
@ 2022-11-17  6:42 ` hsbt (Hiroshi SHIBATA)
  9 siblings, 0 replies; 11+ messages in thread
From: hsbt (Hiroshi SHIBATA) @ 2022-11-17  6:42 UTC (permalink / raw)
  To: ruby-core

Issue #18685 has been updated by hsbt (Hiroshi SHIBATA).

Status changed from Open to Closed

https://github.com/ruby/ruby/pull/6197 has been merged for Ruby 3.2

----------------------------------------
Feature #18685: Enumerator.product: Cartesian product of enumerables
https://bugs.ruby-lang.org/issues/18685#change-100142

* Author: knu (Akinori MUSHA)
* Status: Closed
* 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/

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2022-11-17  6:42 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-04-08  7:20 [ruby-core:108198] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerators knu (Akinori MUSHA)
2022-04-08 10:52 ` [ruby-core:108199] [Ruby master Feature#18685] Enumerator.product: Cartesian product of enumerables Eregon (Benoit Daloze)
2022-04-21  7:59 ` [ruby-core:108334] " matz (Yukihiro Matsumoto)
2022-04-21  8:04 ` [ruby-core:108335] " knu (Akinori MUSHA)
2022-04-21 10:37 ` [ruby-core:108346] " Eregon (Benoit Daloze)
2022-04-21 10:40 ` [ruby-core:108347] " Eregon (Benoit Daloze)
2022-04-23 19:26 ` [ruby-core:108387] " shan (Shannon Skipper)
2022-04-24  2:36 ` [ruby-core:108388] " knu (Akinori MUSHA)
2022-04-26  7:02 ` [ruby-core:108405] " sawa (Tsuyoshi Sawada)
2022-07-29  8:29 ` [ruby-core:109362] " knu (Akinori MUSHA)
2022-11-17  6:42 ` [ruby-core:110790] " hsbt (Hiroshi SHIBATA)

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).