# Inside Enumerator, redefine those Enumerable methods which return an Array
# so that they return another Enumerator instead. This allows Enumerator
# chaining, where the calls go 'horizontally' without creating any
# intermediate arrays, including processing of infinite enumerations. e.g.
#
#   class Fib
#     def initialize(a=1,b=1)
#       @a, @b = a, b
#     end
#     def each
#       a, b = @a, @b
#       yield a
#       while true
#         yield b
#         a, b = b, a+b
#       end
#     end
#   end
#   
#   Fib.new.to_enum.select { |i| i % 2 == 0 }.map { |i| "<#{i}>" }.
#     each_with_index { |i,cnt| puts i; break if cnt >= 20 }
#
# Note: not all methods have been implemented here, just some sample ones.

class Enumerator
  def map(&blk) 
    self.class.new do |y|
      each do |e|
        y << blk[e]
      end
    end
  end

  def select(&blk)
    self.class.new do |y|
      each do |e|
        y << e if blk[e]
      end
    end
  end

  def take(n)
    self.class.new do |y|
      count = 0
      each do |e|
        break if n <= count
        y << e
        count += 1
      end
    end
  end
  
  def skip(n)
    self.class.new do |y|
      count = 0
      each do |e|
        y << e unless count <= n
        count += 1
      end
    end
  end
end

# This reported separately as Feature #666.
# This could go in Enumerator, but it makes sense in Enumerable too IMO.
module Enumerable
  def to_hash
    each_with_object({}) { |(k,v),o| o[k] = v }
  end
end

if __FILE__ == $0
  big = (1..1_000_000_000_000).to_enum
  big.select { |i| i % 2 == 1 }.map { |i| i + 100 }.skip(5).take(10).
      each { |i| puts i }

  # Normal version:
  h = {1=>2, 3=>4}
  p h.select { true }
  # This is the chainable Enumerator version:
  p h.to_enum.select { true }.to_hash
end
