Class: Enumerator

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
mrbgems/mruby-enumerator/mrblib/enumerator.rb,
mrbgems/mruby-enum-lazy/mrblib/lazy.rb,
mrbgems/mruby-enum-chain/mrblib/chain.rb

Overview

A class which allows both internal and external iteration.

An Enumerator can be created by the following methods. - Kernel#to_enum - Kernel#enum_for - Enumerator.new

Most methods have two forms: a block form where the contents are evaluated for each item in the enumeration, and a non-block form which returns a new Enumerator wrapping the iteration.

  enumerator = %w(one two three).each
  puts enumerator.class # => Enumerator

  enumerator.each_with_object("foo") do |item, obj|
    puts "#{obj}: #{item}"
  end

  # foo: one
  # foo: two
  # foo: three

  enum_with_obj = enumerator.each_with_object("foo")
  puts enum_with_obj.class # => Enumerator

  enum_with_obj.each do |item, obj|
    puts "#{obj}: #{item}"
  end

  # foo: one
  # foo: two
  # foo: three

This allows you to chain Enumerators together. For example, you can map a list’s elements to strings containing the index and the element as a string via:

  puts %w[foo bar baz].map.with_index { |w, i| "#{i}:#{w}" }
  # => ["0:foo", "1:bar", "2:baz"]

An Enumerator can also be used as an external iterator. For example, Enumerator#next returns the next value of the iterator or raises StopIteration if the Enumerator is at the end.

  e = [1,2,3].each   # returns an enumerator object.
  puts e.next   # => 1
  puts e.next   # => 2
  puts e.next   # => 3
  puts e.next   # raises StopIteration

You can use this to implement an internal iterator as follows:

  def ext_each(e)
    while true
      begin
        vs = e.next_values
      rescue StopIteration
        return $!.result
      end
      y = yield(*vs)
      e.feed y
    end
  end

  o = Object.new

  def o.each
    puts yield
    puts yield(1)
    puts yield(1, 2)
    3
  end

  # use o.each as an internal iterator directly.
  puts o.each {|*x| puts x; [:b, *x] }
  # => [], [:b], [1], [:b, 1], [1, 2], [:b, 1, 2], 3

  # convert o.each to an external iterator for
  # implementing an internal iterator.
  puts ext_each(o.to_enum) {|*x| puts x; [:b, *x] }
  # => [], [:b], [1], [:b, 1], [1, 2], [:b, 1, 2], 3

Direct Known Subclasses

Lazy

Defined Under Namespace

Classes: Chain, Generator, Lazy, Yielder

Constant Summary

Constants included from Enumerable

Enumerable::NONE

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Enumerable

__update_hash, #all?, #any?, #chain, #collect, #count, #cycle, #detect, #drop, #drop_while, #each_cons, #each_slice, #each_with_object, #entries, #filter_map, #find_all, #find_index, #first, #flat_map, #grep, #group_by, #hash, #include?, #inject, #lazy, #max, #max_by, #min, #min_by, #minmax, #minmax_by, #none?, #one?, #partition, #reject, #reverse_each, #sort, #sort_by, #take, #take_while, #tally, #to_h, #uniq, #zip

Constructor Details

#initialize(obj, method = :each, *args) ⇒ Enumerator

Creates a new Enumerator object, which can be used as an Enumerable.

In the first form, iteration is defined by the given block, in which a “yielder” object, given as block parameter, can be used to yield a value by calling the +yield+ method (aliased as +«+):

fib = Enumerator.new do |y|
  a = b = 1
  loop do
    y << a
    a, b = b, a + b
  end
end

p fib.take(10) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

In the second, deprecated, form, a generated Enumerator iterates over the given object using the given method with the given arguments passed. This form is left only for internal use.

Use of this form is discouraged. Use Kernel#enum_for or Kernel#to_enum instead.



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'mrbgems/mruby-enumerator/mrblib/enumerator.rb', line 117

def initialize(obj=NONE, meth=:each, *args, &block)
  if block
    obj = Generator.new(&block)
  elsif obj == NONE
    raise ArgumentError, "wrong number of arguments (given 0, expected 1+)"
  end

  @obj = obj
  @meth = meth
  @args = args
  @fib = nil
  @dst = nil
  @lookahead = nil
  @feedvalue = nil
  @stop_exc = false
end

Instance Attribute Details

#argsObject

Returns the value of attribute args



133
134
135
# File 'mrbgems/mruby-enumerator/mrblib/enumerator.rb', line 133

def args
  @args
end

#fibObject (readonly)

Returns the value of attribute fib



134
135
136
# File 'mrbgems/mruby-enumerator/mrblib/enumerator.rb', line 134

def fib
  @fib
end

#methObject

Returns the value of attribute meth



133
134
135
# File 'mrbgems/mruby-enumerator/mrblib/enumerator.rb', line 133

def meth
  @meth
end

#objObject

Returns the value of attribute obj



133
134
135
# File 'mrbgems/mruby-enumerator/mrblib/enumerator.rb', line 133

def obj
  @obj
end

Class Method Details

.produce(init = NONE, &block) ⇒ Object

call-seq: Enumerator.produce(initial = nil) { |val| } -> enumerator

Creates an infinite enumerator from any block, just called over and over. Result of the previous iteration is passed to the next one. If +initial+ is provided, it is passed to the first iteration, and becomes the first element of the enumerator; if it is not provided, first iteration receives +nil+, and its result becomes first element of the iterator.

Raising StopIteration from the block stops an iteration.

Examples of usage:

Enumerator.produce(1, &:succ) # => enumerator of 1, 2, 3, 4, ….

Enumerator.produce { rand(10) } # => infinite random number sequence

ancestors = Enumerator.produce(node) { prev node = prev.parent or raise StopIteration }
enclosing_section = ancestors.find { n n.type == :section }

Raises:



580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
# File 'mrbgems/mruby-enumerator/mrblib/enumerator.rb', line 580

def Enumerator.produce(init=NONE, &block)
  raise ArgumentError, "no block given" if block.nil?
  Enumerator.new do |y|
    if init == NONE
      val = nil
    else
      val = init
      y.yield(val)
    end
    begin
      while true
        y.yield(val = block.call(val))
      end
    rescue StopIteration
      # do nothing
    end
  end
end

Instance Method Details

#+(other) ⇒ Object



12
13
14
# File 'mrbgems/mruby-enum-chain/mrblib/chain.rb', line 12

def +(other)
  Chain.new(self, other)
end

#each(*argv, &block) ⇒ Object

call-seq: enum.each { |elm| block } -> obj enum.each -> enum enum.each(appending_args) { |elm| block } -> obj enum.each(appending_args) -> an_enumerator

Iterates over the block according to how this Enumerator was constructed. If no block and no arguments are given, returns self.

=== Examples

Array.new(3) #=> [nil, nil, nil] Array.new(3) { |i| i } #=> [0, 1, 2] Array.to_enum(:new, 3).to_a #=> [0, 1, 2] Array.to_enum(:new).each(3).to_a #=> [0, 1, 2]

obj = Object.new

def obj.each_arg(a, b=:b, *rest) yield a yield b yield rest :method_returned end

enum = obj.to_enum :each_arg, :a, :x

enum.each.to_a #=> [:a, :x, []] enum.each.equal?(enum) #=> true enum.each { |elm| elm } #=> :method_returned

enum.each(:y, :z).to_a #=> [:a, :x, [:y, :z]] enum.each(:y, :z).equal?(enum) #=> false enum.each(:y, :z) { |elm| elm } #=> :method_returned



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'mrbgems/mruby-enumerator/mrblib/enumerator.rb', line 270

def each(*argv, &block)
  obj = self
  if 0 < argv.length
    obj = self.dup
    args = obj.args
    if !args.empty?
      args = args.dup
      args.concat argv
    else
      args = argv.dup
    end
    obj.args = args
  end
  return obj unless block
  enumerator_block_call(&block)
end

#each_with_index(&block) ⇒ Object

call-seq: e.each_with_index {|(*args), idx| … } e.each_with_index

Same as Enumerator#with_index(0), i.e. there is no starting offset.

If no block is given, a new Enumerator is returned that includes the index.



184
185
186
# File 'mrbgems/mruby-enumerator/mrblib/enumerator.rb', line 184

def each_with_index(&block)
  with_index(0, &block)
end

#feed(value) ⇒ Object

call-seq: e.feed obj -> nil

Sets the value to be returned by the next yield inside +e+.

If the value is not set, the yield returns nil.

This value is cleared after being yielded.

# Array#map passes the array’s elements to “yield” and collects the # results of “yield” as an array. # Following example shows that “next” returns the passed elements and # values passed to “feed” are collected as an array which can be # obtained by StopIteration#result. e = [1,2,3].map p e.next #=> 1 e.feed “a” p e.next #=> 2 e.feed “b” p e.next #=> 3 e.feed “c” begin e.next rescue StopIteration p $!.result #=> [“a”, “b”, “c”] end

o = Object.new def o.each x = yield # (2) blocks p x # (5) => “foo” x = yield # (6) blocks p x # (8) => nil x = yield # (9) blocks p x # not reached w/o another e.next end

e = o.to_enum e.next # (1) e.feed “foo” # (3) e.next # (4) e.next # (7) # (10)

Raises:



520
521
522
523
524
# File 'mrbgems/mruby-enumerator/mrblib/enumerator.rb', line 520

def feed(value)
  raise TypeError, "feed value already set" if @feedvalue
  @feedvalue = value
  nil
end

#initialize_copy(obj) ⇒ Object

Raises:



136
137
138
139
140
141
142
143
144
145
146
# File 'mrbgems/mruby-enumerator/mrblib/enumerator.rb', line 136

def initialize_copy(obj)
  raise TypeError, "can't copy type #{obj.class}" unless obj.kind_of? Enumerator
  raise TypeError, "can't copy execution context" if obj.fib
  @obj = obj.obj
  @meth = obj.meth
  @args = obj.args
  @fib = nil
  @lookahead = nil
  @feedvalue = nil
  self
end

#inspectObject



225
226
227
228
229
230
231
232
# File 'mrbgems/mruby-enumerator/mrblib/enumerator.rb', line 225

def inspect
  if @args && @args.size > 0
    args = @args.join(", ")
    "#<#{self.class}: #{@obj.inspect}:#{@meth}(#{args})>"
  else
    "#<#{self.class}: #{@obj.inspect}:#{@meth}>"
  end
end

#nextObject

call-seq: e.next -> object

Returns the next object in the enumerator, and move the internal position forward. When the position reached at the end, StopIteration is raised.

=== Example

a = [1,2,3] e = a.to_enum p e.next #=> 1 p e.next #=> 2 p e.next #=> 3 p e.next #raises StopIteration

Note that enumeration sequence by +next+ does not affect other non-external enumeration methods, unless the underlying iteration methods itself has side-effect



312
313
314
# File 'mrbgems/mruby-enumerator/mrblib/enumerator.rb', line 312

def next
  next_values.__svalue
end

#next_valuesObject

call-seq: e.next_values -> array

Returns the next object as an array in the enumerator, and move the internal position forward. When the position reached at the end, StopIteration is raised.

This method can be used to distinguish yield and yield nil.

=== Example

o = Object.new def o.each yield yield 1 yield 1, 2 yield nil yield [1, 2] end e = o.to_enum p e.next_values p e.next_values p e.next_values p e.next_values p e.next_values e = o.to_enum p e.next p e.next p e.next p e.next p e.next

## yield args next_values next # yield [] nil # yield 1 [1] 1 # yield 1, 2 [1, 2] [1, 2] # yield nil [nil] nil # yield [1, 2] [[1, 2]] [1, 2]

Note that +next_values+ does not affect other non-external enumeration methods unless underlying iteration method itself has side-effect

Raises:

  • (@stop_exc)


360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
# File 'mrbgems/mruby-enumerator/mrblib/enumerator.rb', line 360

def next_values
  if @lookahead
    vs = @lookahead
    @lookahead = nil
    return vs
  end
  raise @stop_exc if @stop_exc

  curr = Fiber.current

  if !@fib || !@fib.alive?
    @dst = curr
    @fib = Fiber.new do
      result = each do |*args|
        feedvalue = nil
        Fiber.yield args
        if @feedvalue
          feedvalue = @feedvalue
          @feedvalue = nil
        end
        feedvalue
      end
      @stop_exc = StopIteration.new "iteration reached an end"
      @stop_exc.result = result
      Fiber.yield nil
    end
    @lookahead = nil
  end

  vs = @fib.resume curr
  if @stop_exc
    @fib = nil
    @dst = nil
    @lookahead = nil
    @feedvalue = nil
    raise @stop_exc
  end
  vs
end

#peekObject

call-seq: e.peek -> object

Returns the next object in the enumerator, but doesn’t move the internal position forward. If the position is already at the end, StopIteration is raised.

=== Example

a = [1,2,3] e = a.to_enum p e.next #=> 1 p e.peek #=> 2 p e.peek #=> 2 p e.peek #=> 2 p e.next #=> 2 p e.next #=> 3 p e.next #raises StopIteration



420
421
422
# File 'mrbgems/mruby-enumerator/mrblib/enumerator.rb', line 420

def peek
  peek_values.__svalue
end

#peek_valuesObject

call-seq: e.peek_values -> array

Returns the next object as an array, similar to Enumerator#next_values, but doesn’t move the internal position forward. If the position is already at the end, StopIteration is raised.

=== Example

o = Object.new def o.each yield yield 1 yield 1, 2 end e = o.to_enum p e.peek_values #=> [] e.next p e.peek_values #=> [1] p e.peek_values #=> [1] e.next p e.peek_values #=> [1, 2] e.next p e.peek_values # raises StopIteration



450
451
452
453
454
455
# File 'mrbgems/mruby-enumerator/mrblib/enumerator.rb', line 450

def peek_values
  if @lookahead.nil?
    @lookahead = next_values
  end
  @lookahead.dup
end

#rewindObject

call-seq: e.rewind -> e

Rewinds the enumeration sequence to the beginning.

If the enclosed object responds to a “rewind” method, it is called.



465
466
467
468
469
470
471
472
473
# File 'mrbgems/mruby-enumerator/mrblib/enumerator.rb', line 465

def rewind
  @obj.rewind if @obj.respond_to? :rewind
  @fib = nil
  @dst = nil
  @lookahead = nil
  @feedvalue = nil
  @stop_exc = false
  self
end

#with_index(offset = 0, &block) ⇒ Object

call-seq: e.with_index(offset = 0) {|(*args), idx| … } e.with_index(offset = 0)

Iterates the given block for each element with an index, which starts from +offset+. If no block is given, returns a new Enumerator that includes the index, starting from +offset+

+offset+:: the starting index to use



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'mrbgems/mruby-enumerator/mrblib/enumerator.rb', line 159

def with_index(offset=0, &block)
  return to_enum :with_index, offset unless block

  if offset.nil?
    offset = 0
  else
    offset = offset.__to_int
  end

  n = offset - 1
  enumerator_block_call do |*i|
    n += 1
    block.call i.__svalue, n
  end
end

#with_object(object, &block) ⇒ Object

call-seq: e.each_with_object(obj) {|(args), obj| … } e.each_with_object(obj) e.with_object(obj) {|(args), obj| … } e.with_object(obj)

Iterates the given block for each element with an arbitrary object, +obj+, and returns +obj+

If no block is given, returns a new Enumerator.

Examples:

to_three = Enumerator.new do |y|
  3.times do |x|
    y << x
  end
end

to_three_with_string = to_three.with_object("foo")
to_three_with_string.each do |x,string|
  puts "#{string}: #{x}"
end

# => foo:0
# => foo:1
# => foo:2


216
217
218
219
220
221
222
223
# File 'mrbgems/mruby-enumerator/mrblib/enumerator.rb', line 216

def with_object(object, &block)
  return to_enum(:with_object, object) unless block

  enumerator_block_call do |i|
    block.call [i,object]
  end
  object
end