Class: Enumerator

Inherits:
Object show all
Includes:
Enumerable
Defined in:
enumerator.c

Overview

A class which provides a method `each' to be used as an Enumerable object.

An enumerator can be created by following methods.

  • Kernel#to_enum

  • Kernel#enum_for

  • Enumerator.new

Also, most iteration methods without a block returns an enumerator. For example, Array#map returns an enumerator if a block is not given. The enumerator has the with_index method. So ary.map.with_index works as follows.

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

An enumerator object can be used as an external iterator. I.e. Enumerator#next returns the next value of the iterator. Enumerator#next raises StopIteration at end.

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

An external iterator can be used 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
  p yield
  p yield(1)
  p yield(1, 2)
  3
end

# use o.each as an internal iterator directly.
p o.each {|*x| p 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.
p ext_each(o.to_enum) {|*x| p x; [:b, *x] }
#=> [], [:b], [1], [:b, 1], [1, 2], [:b, 1, 2], 3

Defined Under Namespace

Classes: Generator, Yielder

Instance Method Summary collapse

Methods included from Enumerable

#all?, #any?, #chunk, #collect, #collect_concat, #count, #cycle, #detect, #drop, #drop_while, #each_cons, #each_entry, #each_slice, #entries, #find, #find_all, #find_index, #first, #flat_map, #grep, #group_by, #include?, #inject, #map, #max, #max_by, #member?, #min, #min_by, #minmax, #minmax_by, #none?, #one?, #partition, #reduce, #reject, #reverse_each, #select, #slice_before, #sort, #sort_by, #take, #take_while, #to_a, #zip

Constructor Details

#new(obj, method = :each, *args) ⇒ Object #new {|y| ... } ⇒ Object

Creates a new Enumerator object, which is to be used as an Enumerable object iterating in a given way.

In the first form, a generated Enumerator iterates over the given object using the given method with the given arguments passed. Use of this form is discouraged. Use Kernel#enum_for(), alias to_enum, instead.

e = Enumerator.new(ObjectSpace, :each_object)
    #-> ObjectSpace.enum_for(:each_object)

e.select { |obj| obj.is_a?(Class) }  #=> array of all classes

In the second 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, alias <<.

fib = Enumerator.new { |y|
  a = b = 1
  loop {
    y << a
    a, b = b, a + b
  }
}

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

Overloads:

  • #new {|y| ... } ⇒ Object

    Yields:

    • (y)


# File 'enumerator.c'

/*
 *  call-seq:
 *    Enumerator.new(obj, method = :each, *args)
 *    Enumerator.new { |y| ... }
 *
 *  Creates a new Enumerator object, which is to be used as an
 *  Enumerable object iterating in a given way.
 *
 *  In the first form, a generated Enumerator iterates over the given
 *  object using the given method with the given arguments passed.
 *  Use of this form is discouraged.  Use Kernel#enum_for(), alias
 *  to_enum, instead.
 *
 *    e = Enumerator.new(ObjectSpace, :each_object)
 *        #-> ObjectSpace.enum_for(:each_object)
 *
 *    e.select { |obj| obj.is_a?(Class) }  #=> array of all classes
 *
 *  In the second 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, alias +<<+.
 *
 *    fib = Enumerator.new { |y|
 *      a = b = 1
 *      loop {
 *        y << a
 *        a, b = b, a + b
 *      }
 *    }
 *
 *    p fib.take(10) #=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
 */
static VALUE
enumerator_initialize(int argc, VALUE *argv, VALUE obj)
{
    VALUE recv, meth = sym_each;

    if (argc == 0) {
    if (!rb_block_given_p())
        rb_raise(rb_eArgError, "wrong number of argument (0 for 1+)");

    recv = generator_init(generator_allocate(rb_cGenerator), rb_block_proc());
    }
    else {
    recv = *argv++;
    if (--argc) {
        meth = *argv++;
        --argc;
    }
    }

    return enumerator_init(obj, recv, meth, argc, argv);
}

Instance Method Details

#each { ... } ⇒ Object

Iterates the given block using the object and the method specified in the first place. If no block is given, returns self.

Yields:



# File 'enumerator.c'

/*
 *  call-seq:
 *    enum.each {...}
 *
 *  Iterates the given block using the object and the method specified
 *  in the first place.  If no block is given, returns self.
 *
 */
static VALUE
enumerator_each(VALUE obj)
{
    if (!rb_block_given_p()) return obj;
    return enumerator_block_call(obj, 0, obj);
}

#each_with_index {|(*args), idx| ... } ⇒ Object #each_with_indexObject

Same as Enumerator#with_index, except each_with_index does not receive an offset argument.

Overloads:

  • #each_with_index {|(*args), idx| ... } ⇒ Object

    Yields:

    • ((*args), idx)


# File 'enumerator.c'

/*
 *  call-seq:
 *    e.each_with_index {|(*args), idx| ... }
 *    e.each_with_index
 *
 *  Same as Enumerator#with_index, except each_with_index does not
 *  receive an offset argument.
 *
 */
static VALUE
enumerator_each_with_index(VALUE obj)
{
    return enumerator_with_index(0, NULL, obj);
}

#with_object(obj) {|(*args), memo_obj| ... } ⇒ Object #with_object(obj) ⇒ Object

Iterates the given block for each element with an arbitrary object given, and returns the initially given object.

If no block is given, returns an enumerator.

Overloads:

  • #with_object(obj) {|(*args), memo_obj| ... } ⇒ Object

    Yields:

    • ((*args), memo_obj)


# File 'enumerator.c'

/*
 *  call-seq:
 *    e.with_object(obj) {|(*args), memo_obj| ... }
 *    e.with_object(obj)
 *
 *  Iterates the given block for each element with an arbitrary
 *  object given, and returns the initially given object.
 *
 *  If no block is given, returns an enumerator.
 *
 */
static VALUE
enumerator_with_object(VALUE obj, VALUE memo)
{
    RETURN_ENUMERATOR(obj, 1, &memo);
    enumerator_block_call(obj, enumerator_with_object_i, memo);

    return memo;
}

#objnil

Set the value for the next yield in the enumerator returns.

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

This value is cleared after used.

o = Object.new
def o.each
  # (2)
  x = yield
  p x          #=> "foo"
  # (5)
  x = yield
  p x          #=> nil
  # (7)
  x = yield
  # not reached
  p x
end
e = o.to_enum
# (1)
e.next
# (3)
e.feed "foo"
# (4)
e.next
# (6)
e.next
# (8)


# File 'enumerator.c'

/*
 * call-seq:
 *   e.feed obj   -> nil
 *
 * Set the value for the next yield in the enumerator returns.
 *
 * If the value is not set, the yield returns nil.
 *
 * This value is cleared after used.
 *
 *   o = Object.new
 *   def o.each
 *     # (2)
 *     x = yield
 *     p x          #=> "foo"
 *     # (5)
 *     x = yield
 *     p x          #=> nil
 *     # (7)
 *     x = yield
 *     # not reached
 *     p x
 *   end
 *   e = o.to_enum
 *   # (1)
 *   e.next
 *   # (3)
 *   e.feed "foo"
 *   # (4)
 *   e.next
 *   # (6)
 *   e.next
 *   # (8)
 *
 */

static VALUE
enumerator_feed(VALUE obj, VALUE v)
{
    struct enumerator *e = enumerator_ptr(obj);

    if (e->feedvalue != Qundef) {
    rb_raise(rb_eTypeError, "feed value already set");
    }
    e->feedvalue = v;

    return Qnil;
}

#initialize_copyObject

:nodoc:



# File 'enumerator.c'

/* :nodoc: */
static VALUE
enumerator_init_copy(VALUE obj, VALUE orig)
{
    struct enumerator *ptr0, *ptr1;

    ptr0 = enumerator_ptr(orig);
    if (ptr0->fib) {
    /* Fibers cannot be copied */
    rb_raise(rb_eTypeError, "can't copy execution context");
    }

    TypedData_Get_Struct(obj, struct enumerator, &enumerator_data_type, ptr1);

    if (!ptr1) {
    rb_raise(rb_eArgError, "unallocated enumerator");
    }

    ptr1->obj  = ptr0->obj;
    ptr1->meth = ptr0->meth;
    ptr1->args = ptr0->args;
    ptr1->fib  = 0;
    ptr1->lookahead  = Qundef;
    ptr1->feedvalue  = Qundef;

    return obj;
}

#inspectString

Create a printable version of e.



# File 'enumerator.c'

/*
 * call-seq:
 *   e.inspect  -> string
 *
 *  Create a printable version of <i>e</i>.
 */

static VALUE
enumerator_inspect(VALUE obj)
{
    return rb_exec_recursive(inspect_enumerator, obj, 0);
}

#nextObject

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

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 method does not affect other non-external enumeration methods, unless underlying iteration methods itself has side-effect, e.g. IO#each_line.



# File 'enumerator.c'

/*
 * 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.
 *
 *   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 method does not affect other
 * non-external enumeration methods, unless underlying iteration
 * methods itself has side-effect, e.g. IO#each_line.
 *
 */

static VALUE
enumerator_next(VALUE obj)
{
    VALUE vs = enumerator_next_values(obj);
    return ary2sv(vs, 0);
}

#next_valuesArray

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.

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 enumeration sequence by next_values method does not affect other non-external enumeration methods, unless underlying iteration methods itself has side-effect, e.g. IO#each_line.



# File 'enumerator.c'

/*
 * 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 <code>yield</code> and <code>yield nil</code>.
 *
 *   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 enumeration sequence by next_values method does not affect other
 * non-external enumeration methods, unless underlying iteration
 * methods itself has side-effect, e.g. IO#each_line.
 *
 */

static VALUE
enumerator_next_values(VALUE obj)
{
    struct enumerator *e = enumerator_ptr(obj);
    VALUE vs;

    if (e->lookahead != Qundef) {
        vs = e->lookahead;
        e->lookahead = Qundef;
        return vs;
    }

    return get_next_values(obj, e);
}

#peekObject

Returns the next object in the enumerator, but don't move the internal position forward. When the position reached at the end, StopIteration is raised.

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


# File 'enumerator.c'

/*
 * call-seq:
 *   e.peek   -> object
 *
 * Returns the next object in the enumerator, but don't move the internal
 * position forward.  When the position reached at the end, StopIteration
 * is raised.
 *
 *   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
 *
 */

static VALUE
enumerator_peek(VALUE obj)
{
    VALUE vs = enumerator_peek_values(obj);
    return ary2sv(vs, 1);
}

#peek_valuesArray

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

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


# File 'enumerator.c'

/*
 * call-seq:
 *   e.peek_values   -> array
 *
 * Returns the next object as an array in the enumerator,
 * but don't move the internal position forward.
 * When the position reached at the end, StopIteration is raised.
 *
 *   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
 *
 */

static VALUE
enumerator_peek_values_m(VALUE obj)
{
    return rb_ary_dup(enumerator_peek_values(obj));
}

#rewindObject

Rewinds the enumeration sequence by the next method.

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



# File 'enumerator.c'

/*
 * call-seq:
 *   e.rewind   -> e
 *
 * Rewinds the enumeration sequence by the next method.
 *
 * If the enclosed object responds to a "rewind" method, it is called.
 */

static VALUE
enumerator_rewind(VALUE obj)
{
    struct enumerator *e = enumerator_ptr(obj);

    rb_check_funcall(e->obj, id_rewind, 0, 0);

    e->fib = 0;
    e->dst = Qnil;
    e->lookahead = Qundef;
    e->feedvalue = Qundef;
    e->stop_exc = Qfalse;
    return obj;
}

#with_index(offset = 0) {|(*args), idx| ... } ⇒ Object #with_index(offset = 0) ⇒ Object

Iterates the given block for each element with an index, which starts from offset. If no block is given, returns an enumerator.

Overloads:

  • #with_index(offset = 0) {|(*args), idx| ... } ⇒ Object

    Yields:

    • ((*args), idx)


# File 'enumerator.c'

/*
 *  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 an enumerator.
 *
 */
static VALUE
enumerator_with_index(int argc, VALUE *argv, VALUE obj)
{
    VALUE memo;

    rb_scan_args(argc, argv, "01", &memo);
    RETURN_ENUMERATOR(obj, argc, argv);
    memo = NIL_P(memo) ? 0 : (VALUE)NUM2LONG(memo);
    return enumerator_block_call(obj, enumerator_with_index_i, (VALUE)&memo);
}

#with_object(obj) {|(*args), memo_obj| ... } ⇒ Object #with_object(obj) ⇒ Object

Iterates the given block for each element with an arbitrary object given, and returns the initially given object.

If no block is given, returns an enumerator.

Overloads:

  • #with_object(obj) {|(*args), memo_obj| ... } ⇒ Object

    Yields:

    • ((*args), memo_obj)


# File 'enumerator.c'

/*
 *  call-seq:
 *    e.with_object(obj) {|(*args), memo_obj| ... }
 *    e.with_object(obj)
 *
 *  Iterates the given block for each element with an arbitrary
 *  object given, and returns the initially given object.
 *
 *  If no block is given, returns an enumerator.
 *
 */
static VALUE
enumerator_with_object(VALUE obj, VALUE memo)
{
    RETURN_ENUMERATOR(obj, 1, &memo);
    enumerator_block_call(obj, enumerator_with_object_i, memo);

    return memo;
}