类 Enumerator
一个允许内部和外部迭代的类。
可以通过以下方法创建 Enumerator。
大多数方法有两种形式:块形式(其中对枚举中的每个项评估内容)和非块形式(返回一个包装迭代的新 Enumerator)。
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
这允许你将 Enumerator 链接在一起。例如,你可以通过以下方式将列表的元素映射到包含索引和元素作为字符串的字符串:
puts %w[foo bar baz].map.with_index { |w, i| "#{i}:#{w}" } # => ["0:foo", "1:bar", "2:baz"]
外部迭代¶ ↑
Enumerator 也可以用作外部迭代器。例如,Enumerator#next 返回迭代器的下一个值,如果 Enumerator 到达末尾,则引发 StopIteration。
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
next、next_values、peek 和 peek_values 是唯一使用外部迭代的方法(以及内部使用 next 的 Array#zip(Enumerable-not-Array))。
这些方法不影响其他内部枚举方法,除非底层迭代方法本身具有副作用,例如 IO#each_line。
如果对冻结的枚举器调用这些方法,则会引发 FrozenError。由于 rewind 和 feed 也会更改外部迭代的状态,因此这些方法也可能引发 FrozenError。
由于使用了 Fiber,外部迭代与内部迭代有显著不同
-
与内部枚举相比,
Fiber增加了一些开销。 -
堆栈跟踪将仅包括来自
Enumerator的堆栈,而不包括其上方的堆栈。 -
Fiber 局部变量不在
EnumeratorFiber内部继承,它从没有 Fiber 局部变量开始。 -
Fiber存储变量是继承的,并且旨在处理EnumeratorFiber。赋值给Fiber存储变量只会影响当前的Fiber,因此如果你想更改EnumeratorFiber的调用方Fiber中的状态,则需要使用额外的间接层(例如,在Fiber存储变量中使用一些对象并更改其某个 ivar)。
具体来说
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
公共类方法
源代码
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);
}
创建一个新的 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_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);
}
从任何块创建一个无限枚举器,只需重复调用。前一次迭代的结果将传递给下一次迭代。如果提供了 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 }
将 ::produce 与 Enumerable 方法(例如 Enumerable#detect、Enumerable#slice_after、Enumerable#take_while)结合使用可以为 while 和 until 循环提供基于枚举器的替代方案
# 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_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;
}
生成一个新的枚举器对象,该对象生成给定可枚举对象的笛卡尔积。这等效于 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_plus(VALUE obj, VALUE eobj)
{
return new_enum_chain(rb_ary_new_from_args(2, obj, eobj));
}
从该枚举器和给定的可枚举对象生成一个枚举器对象。
e = (1..3).each + [4, 5] e.to_a #=> [1, 2, 3, 4, 5]
源代码
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);
}
根据 Enumerator 的构造方式迭代该块。如果没有给出块和参数,则返回 self。
示例¶ ↑
"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_with_index(VALUE obj)
{
return enumerator_with_index(0, NULL, obj);
}
与 Enumerator#with_index(0) 相同,即没有起始偏移量。
如果没有给出块,则返回一个包含索引的新 Enumerator。
源代码
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;
}
使用任意对象 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_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;
}
设置 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_inspect(VALUE obj)
{
return rb_exec_recursive(inspect_enumerator, obj, 0);
}
创建 e 的可打印版本。
源代码
static VALUE
enumerator_next(VALUE obj)
{
VALUE vs = enumerator_next_values(obj);
return ary2sv(vs, 0);
}
返回枚举器中的下一个对象,并将内部位置向前移动。当位置到达末尾时,会引发 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_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);
}
返回枚举器中的下一个对象作为数组,并将内部位置向前移动。当位置到达末尾时,会引发 StopIteration。
请参阅关于外部迭代器的类级别注释。
此方法可用于区分 yield 和 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]
源代码
static VALUE
enumerator_peek(VALUE obj)
{
VALUE vs = enumerator_peek_values(obj);
return ary2sv(vs, 1);
}
返回枚举器中的下一个对象,但不向前移动内部位置。如果位置已到达末尾,则会引发 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_values_m(VALUE obj)
{
return rb_ary_dup(enumerator_peek_values(obj));
}
返回下一个对象作为数组,类似于 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_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;
}
将枚举序列倒回开头。
如果封闭的对象响应 “rewind” 方法,则调用它。
源代码
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;
}
返回枚举器的大小,如果无法延迟计算,则返回 nil。
(1..100).to_a.permutation(4).size # => 94109400 loop.size # => Float::INFINITY (1..100).drop_while.size # => nil
源代码
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));
}
使用从 offset 开始的索引为每个元素迭代给定的块。如果没有给出块,则返回一个新的包含索引的 Enumerator,该索引从 offset 开始
offset-
要使用的起始索引
使用任意对象 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