類別列舉器

允許內部和外部反覆的類別。

可以使用下列方法建立 列舉器

大多數方法有兩種形式:區塊形式,其中會針對列舉中的每個項目評估內容,以及非區塊形式,其中會傳回一個新的 列舉器,將反覆包覆起來。

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

這允許您將列舉器串連在一起。例如,您可以透過以下方式將清單的元素對應到包含索引和元素(字串形式)的字串:

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

外部反覆

也可以將 列舉器 用作外部反覆器。例如,列舉器#next 會傳回反覆器的下一個值,或是在 列舉器 已到末尾時引發 停止反覆

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

nextnext_valuespeekpeek_values 是唯一使用外部反覆的方法(以及在內部使用 next陣列#zip(非陣列列舉))。

這些方法不會影響其他內部列舉方法,除非基礎反覆方法本身有副作用,例如 IO#each_line

如果針對凍結的列舉器呼叫這些方法,會引發 凍結錯誤。由於 rewindfeed 也會變更外部反覆的狀態,因此這些方法也可能會引發 凍結錯誤

外部反覆由於使用纖維,因此與內部反覆有顯著差異

具體來說

Thread.current[:fiber_local] = 1
Fiber[:storage_var] = 1
e = Enumerator.new do |y|
  p Thread.current[:fiber_local] # for external iteration: nil, for internal iteration: 1
  p Fiber[:storage_var] # => 1, inherited
  Fiber[:storage_var] += 1
  y << 42
end

p e.next # => 42
p Fiber[:storage_var] # => 1 (it ran in a different Fiber)

e.each { p _1 }
p Fiber[:storage_var] # => 2 (it ran in the same Fiber/"stack" as the current Fiber)

將外部迭代轉換為內部迭代

你可以使用外部迭代器來實作內部迭代器,如下所示

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

公用類別方法

new(size = nil) { |yielder| ... } 按一下以切換原始碼

建立新的 Enumerator 物件,可用作 Enumerable

迭代由指定的區塊定義,其中「yielder」物件(指定為區塊參數)可用於透過呼叫 yield 方法(別名為 <<)來產生值

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

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

可使用選用參數來指定如何以延遲方式計算大小(請參閱 Enumerator#size)。它可以是值或可呼叫物件。

static VALUE
enumerator_initialize(int argc, VALUE *argv, VALUE obj)
{
    VALUE iter = rb_block_proc();
    VALUE recv = generator_init(generator_allocate(rb_cGenerator), iter);
    VALUE arg0 = rb_check_arity(argc, 0, 1) ? argv[0] : Qnil;
    VALUE size = convert_to_feasible_size_value(arg0);

    return enumerator_init(obj, recv, sym_each, 0, 0, 0, size, false);
}
produce(initial = nil) { |prev| block } → enumerator 按一下以切換原始碼

從任何區塊建立無限列舉器,只需重複呼叫即可。前一次迭代的結果會傳遞給下一次迭代。如果提供 initial,則會傳遞給第一次迭代,並成為列舉器的第一個元素;如果未提供,則第一次迭代會收到 nil,而其結果會成為迭代器的第一個元素。

從區塊引發 StopIteration 會停止迭代。

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 }

::produceEnumerable 方法(例如 Enumerable#detectEnumerable#slice_afterEnumerable#take_while)一起使用,可以提供基於列舉器的 whileuntil 迴圈替代方案

# Find next Tuesday
require "date"
Enumerator.produce(Date.today, &:succ).detect(&:tuesday?)

# Simple lexer:
require "strscan"
scanner = StringScanner.new("7+38/6")
PATTERN = %r{\d+|[-/+*]}
Enumerator.produce { scanner.scan(PATTERN) }.slice_after { scanner.eos? }.first
# => ["7", "+", "38", "/", "6"]
static VALUE
enumerator_s_produce(int argc, VALUE *argv, VALUE klass)
{
    VALUE init, producer;

    if (!rb_block_given_p()) rb_raise(rb_eArgError, "no block given");

    if (rb_scan_args(argc, argv, "01", &init) == 0) {
        init = Qundef;
    }

    producer = producer_init(producer_allocate(rb_cEnumProducer), init, rb_block_proc());

    return rb_enumeratorize_with_size_kw(producer, sym_each, 0, 0, producer_size, RB_NO_KEYWORDS);
}
product(*enums) → enumerator 按一下以切換原始碼
product(*enums) { |elts| ... } → enumerator

產生一個新的列舉器物件,它會產生給定列舉物件的笛卡兒積。這等同於 Enumerator::Product.new

e = Enumerator.product(1..3, [4, 5])
e.to_a #=> [[1, 4], [1, 5], [2, 4], [2, 5], [3, 4], [3, 5]]
e.size #=> 6

當給定一個區塊時,呼叫區塊並傳入每個產生的 N 元素陣列,並傳回 nil

static VALUE
enumerator_s_product(int argc, VALUE *argv, VALUE klass)
{
    VALUE enums = Qnil, options = Qnil, block = Qnil;

    rb_scan_args(argc, argv, "*:&", &enums, &options, &block);

    if (!NIL_P(options) && !RHASH_EMPTY_P(options)) {
        rb_exc_raise(rb_keyword_error_new("unknown", rb_hash_keys(options)));
    }

    VALUE obj = enum_product_initialize(argc, argv, enum_product_allocate(rb_cEnumProduct));

    if (!NIL_P(block)) {
        enum_product_run(obj, block);
        return Qnil;
    }

    return obj;
}

公開實例方法

e + enum → enumerator 按一下以切換原始碼

傳回一個從此列舉器和給定的列舉器產生的列舉器物件。

e = (1..3).each + [4, 5]
e.to_a #=> [1, 2, 3, 4, 5]
static VALUE
enumerator_plus(VALUE obj, VALUE eobj)
{
    return new_enum_chain(rb_ary_new_from_args(2, obj, eobj));
}
each { |elm| block } → obj 按一下以切換原始碼
each → enum
each(*appending_args) { |elm| block } → obj
each(*appending_args) → an_enumerator

根據此 Enumerator 的建構方式,對區塊進行迭代。如果沒有給定區塊和引數,則傳回自身。

範例

"Hello, world!".scan(/\w+/)                     #=> ["Hello", "world"]
"Hello, world!".to_enum(:scan, /\w+/).to_a      #=> ["Hello", "world"]
"Hello, world!".to_enum(:scan).each(/\w+/).to_a #=> ["Hello", "world"]

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
static VALUE
enumerator_each(int argc, VALUE *argv, VALUE obj)
{
    struct enumerator *e = enumerator_ptr(obj);

    if (argc > 0) {
        VALUE args = (e = enumerator_ptr(obj = rb_obj_dup(obj)))->args;
        if (args) {
#if SIZEOF_INT < SIZEOF_LONG
            /* check int range overflow */
            rb_long2int(RARRAY_LEN(args) + argc);
#endif
            args = rb_ary_dup(args);
            rb_ary_cat(args, argv, argc);
        }
        else {
            args = rb_ary_new4(argc, argv);
        }
        RB_OBJ_WRITE(obj, &e->args, args);
        e->size = Qnil;
        e->size_fn = 0;
    }
    if (!rb_block_given_p()) return obj;

    if (!lazy_precheck(e->procs)) return Qnil;

    return enumerator_block_call(obj, 0, obj);
}
each_with_index {|(*args), idx| ... } 按一下以切換原始碼
each_with_index

Enumerator#with_index(0) 相同,亦即沒有起始偏移量。

如果沒有給定區塊,則傳回一個新的 Enumerator,其中包含索引。

static VALUE
enumerator_each_with_index(VALUE obj)
{
    return enumerator_with_index(0, NULL, obj);
}
each_with_object(obj) {|(*args), obj| ... } 按一下以切換原始碼
each_with_object(obj)

對每個元素使用一個任意物件 obj 來迭代給定的區塊,並傳回 obj

如果沒有給定區塊,則傳回一個新的 Enumerator

範例

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
static VALUE
enumerator_with_object(VALUE obj, VALUE memo)
{
    RETURN_SIZED_ENUMERATOR(obj, 1, &memo, enumerator_enum_size);
    enumerator_block_call(obj, enumerator_with_object_i, memo);

    return memo;
}
也別名為:with_object
feed obj → nil 按一下以切換原始碼

設定 e 中下一個 yield 要傳回的值。

如果沒有設定值,則 yield 傳回 nil。

此值在 yield 之後會被清除。

# 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)
static VALUE
enumerator_feed(VALUE obj, VALUE v)
{
    struct enumerator *e = enumerator_ptr(obj);

    rb_check_frozen(obj);

    if (!UNDEF_P(e->feedvalue)) {
        rb_raise(rb_eTypeError, "feed value already set");
    }
    RB_OBJ_WRITE(obj, &e->feedvalue, v);

    return Qnil;
}
inspect → string 按一下以切換原始碼

建立 e 的可列印版本。

static VALUE
enumerator_inspect(VALUE obj)
{
    return rb_exec_recursive(inspect_enumerator, obj, 0);
}
next → 物件 按一下以切換來源

傳回列舉器中的下一個物件,並將內部位置向前移動。當位置到達尾端時,會引發 StopIteration

範例

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

請參閱類別層級的外部反覆運算器備註。

static VALUE
enumerator_next(VALUE obj)
{
    VALUE vs = enumerator_next_values(obj);
    return ary2sv(vs, 0);
}
next_values → 陣列 按一下以切換來源

傳回列舉器中的下一個物件作為陣列,並將內部位置向前移動。當位置到達尾端時,會引發 StopIteration

請參閱類別層級的外部反覆運算器備註。

此方法可用於區分 yieldyield 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]
static VALUE
enumerator_next_values(VALUE obj)
{
    struct enumerator *e = enumerator_ptr(obj);
    VALUE vs;

    rb_check_frozen(obj);

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

    return get_next_values(obj, e);
}
peek → 物件 按一下以切換來源

傳回列舉器中的下一個物件,但不會將內部位置向前移動。如果位置已在尾端,會引發 StopIteration

請參閱類別層級的外部反覆運算器備註。

範例

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.peek   #raises StopIteration
static VALUE
enumerator_peek(VALUE obj)
{
    VALUE vs = enumerator_peek_values(obj);
    return ary2sv(vs, 1);
}
peek_values → 陣列 按一下以切換來源

傳回下一個物件作為陣列,類似於 Enumerator#next_values,但不會將內部位置向前移動。如果位置已在尾端,會引發 StopIteration

請參閱類別層級的外部反覆運算器備註。

範例

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));
}
rewind → e 按一下以切換來源

將列舉順序倒回至開頭。

如果封閉的物件回應「rewind」方法,則會呼叫該方法。

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

    rb_check_frozen(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;
}
size → int、Float::INFINITY 或 nil 按一下以切換來源

傳回列舉器的長度,如果無法延遲計算,則傳回 nil

(1..100).to_a.permutation(4).size # => 94109400
loop.size # => Float::INFINITY
(1..100).drop_while.size # => nil
static VALUE
enumerator_size(VALUE obj)
{
    struct enumerator *e = enumerator_ptr(obj);
    int argc = 0;
    const VALUE *argv = NULL;
    VALUE size;

    if (e->procs) {
        struct generator *g = generator_ptr(e->obj);
        VALUE receiver = rb_check_funcall(g->obj, id_size, 0, 0);
        long i = 0;

        for (i = 0; i < RARRAY_LEN(e->procs); i++) {
            VALUE proc = RARRAY_AREF(e->procs, i);
            struct proc_entry *entry = proc_entry_ptr(proc);
            lazyenum_size_func *size_fn = entry->fn->size;
            if (!size_fn) {
                return Qnil;
            }
            receiver = (*size_fn)(proc, receiver);
        }
        return receiver;
    }

    if (e->size_fn) {
        return (*e->size_fn)(e->obj, e->args, obj);
    }
    if (e->args) {
        argc = (int)RARRAY_LEN(e->args);
        argv = RARRAY_CONST_PTR(e->args);
    }
    size = rb_check_funcall_kw(e->size, id_call, argc, argv, e->kw_splat);
    if (!UNDEF_P(size)) return size;
    return e->size;
}
with_index(offset = 0) {|(*args), idx| ... } 按一下以切換來源
with_index(offset = 0)

對每個元素執行指定的區塊,並附上索引,索引從 offset 開始。如果未指定區塊,則傳回新的 Enumerator,其中包含索引,從 offset 開始

offset

要使用的起始索引

static VALUE
enumerator_with_index(int argc, VALUE *argv, VALUE obj)
{
    VALUE memo;

    rb_check_arity(argc, 0, 1);
    RETURN_SIZED_ENUMERATOR(obj, argc, argv, enumerator_enum_size);
    memo = (!argc || NIL_P(memo = argv[0])) ? INT2FIX(0) : rb_to_int(memo);
    return enumerator_block_call(obj, enumerator_with_index_i, (VALUE)MEMO_NEW(memo, 0, 0));
}
with_object(obj) {|(*args), obj| ... }
with_object(obj)

對每個元素使用一個任意物件 obj 來迭代給定的區塊,並傳回 obj

如果沒有給定區塊,則傳回一個新的 Enumerator

範例

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
別名為: each_with_object