Issue #6636 has been updated by mame (Yusuke Endoh).

Status changed from Open to Assigned
Assignee changed from mame (Yusuke Endoh) to matz (Yukihiro Matsumoto)
Target version changed from 2.0.0 to next minor


----------------------------------------
Feature #6636: Enumerable#size
https://bugs.ruby-lang.org/issues/6636#change-31763

Author: marcandre (Marc-Andre Lafortune)
Status: Assigned
Priority: Normal
Assignee: matz (Yukihiro Matsumoto)
Category: core
Target version: next minor


Now that it has been made clear that `Enumerable#count` never calls `#size` and that we have `Enumerable#lazy`, let me propose again an API for a lazy way to get the size of an Enumerable: `Enumerable#size`.

* call-seq:
*   enum.size  # => nil, Integer or Float::INFINITY
* 
* Returns the number of elements that will be yielded, without going through
* the iteration (i.e. lazy), or +nil+ if it can't be calculated lazily.
* 
*   perm = (1..100).to_a.permutation(4)
*   perm.size              # => 94109400
*   perm.each_cons(2).size # => 94109399
*   loop.size              # => Float::INFINITY
*   [42].drop_while.size   # => nil

About 66 core methods returning enumerators would have a lazy `size`, like `each_slice`, `permutation` or `lazy.take`.

A few would have `size` return `nil`:
  Array#{r}index, {take|drop}_while
  Enumerable#find{_index}, {take|drop}_while
  IO: all methods

Sized enumerators can also be created naturally by providing a block to `to_enum`/`enum_for` or a lambda to `Enumerator.new`.

Example for `to_enum`:

    class Integer
      def composition
        return to_enum(:composition){ 1 << (self - 1) } unless block_given?
        yield [] if zero?
        downto(1) do |i|
          (self - i).composition do |comp|
            yield [i, *comp]
          end
        end
      end
    end

    4.composition.to_a
    # => [[4], [3, 1], [2, 2], [2, 1, 1], [1, 3], [1, 2, 1], [1, 1, 2], [1, 1, 1, 1]]
    42.composition.size # => 2199023255552

Example for `Enumerator.new`:

    def lazy_product(*enums)
      sizer = ->{
        enums.inject(1) do |product, e|
          break if (size = e.size).nil?
          product * size
        end
      }
      Enumerator.new(sizer) do |yielder|
        # ... generate combinations
      end
    end

    lazy_product(1..4, (1..3).each_cons(2)).size # => 8
    lazy_product(1..4, (1..3).cycle).size # => Float::INFINITY



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