Project

General

Profile

Feature #18685

Updated by Eregon (Benoit Daloze) over 2 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 

Back