类 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
这允许您将 Enumerators 连接在一起。例如,您可以将列表的元素映射到包含索引和元素作为字符串的字符串,方法是
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
是唯一使用外部迭代的方法(以及 Array#zip(Enumerable-not-Array)
,它在内部使用 next
)。
这些方法不会影响其他内部枚举方法,除非底层迭代方法本身具有副作用,例如 IO#each_line
。
如果这些方法针对冻结的枚举器调用,则会引发 FrozenError
。由于 rewind
和 feed
也更改外部迭代的状态,因此这些方法也可能引发 FrozenError
。
外部迭代与内部迭代显著不同,因为它使用了一个 Fiber
-
与内部枚举相比,
Fiber
会增加一些开销。 -
堆栈跟踪只会包含来自
Enumerator
的堆栈,而不会包含上面的堆栈。 -
在
Enumerator
Fiber
中,不会继承 Fiber 本地变量,而是从没有 Fiber 本地变量开始。 -
Fiber
存储变量会被继承,并且被设计用来处理Enumerator
Fibers。给Fiber
存储变量赋值只会影响当前的Fiber
,所以如果你想改变Enumerator
Fiber
的调用者Fiber
中的状态,你需要使用额外的间接引用(例如,在Fiber
存储变量中使用一些对象,并修改它的某个实例变量)。
具体来说
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
公共类方法
创建一个新的 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); }
从任何代码块创建一个无限枚举器,代码块被反复调用。前一次迭代的结果被传递给下一次迭代。如果提供了 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
循环提供基于 Enumerator 的替代方案。
# 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); }
生成一个新的枚举器对象,该对象生成给定枚举对象的笛卡尔积。这等效于 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 = (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)); }
根据该 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); }
与 Enumerator#with_index(0)
相同,即没有起始偏移量。
如果没有给出代码块,则返回一个新的 Enumerator
,其中包含索引。
static VALUE enumerator_each_with_index(VALUE obj) { return enumerator_with_index(0, NULL, 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; }
设置 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; }
创建 e 的可打印版本。
static VALUE enumerator_inspect(VALUE obj) { return rb_exec_recursive(inspect_enumerator, obj, 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(VALUE obj) { VALUE vs = enumerator_next_values(obj); return ary2sv(vs, 0); }
以数组形式返回枚举器中的下一个对象,并将内部位置向前移动。当位置到达末尾时,将引发 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_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
。
参见关于外部迭代器的类级别说明。
示例¶ ↑
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); }
返回下一个对象作为数组,类似于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”方法,则会调用该方法。
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; }
返回枚举器的尺寸,如果无法延迟计算,则返回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; }
为每个元素迭代给定的块,并使用从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)); }
对每个元素使用任意对象 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