類別 Enumerator::Lazy
Enumerator::Lazy
是一種特殊的 Enumerator
類型,它允許建構運算鏈而不用立即評估,並在需要時評估值。為了做到這一點,它重新定義了大部分 Enumerable
方法,以便它們只建構另一個 lazy enumerator。
Enumerator::Lazy
可以使用 Enumerable#lazy
方法從任何 Enumerable
建構。
lazy = (1..Float::INFINITY).lazy.select(&:odd?).drop(10).take_while { |i| i < 30 } # => #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: 1..Infinity>:select>:drop(10)>:take_while>
當呼叫任何未重新定義的 Enumerable
方法(例如 Enumerable#first
或 Enumerable#to_a
)時,會執行實際列舉(後者別名為 force
以獲得更語義化的程式碼)
lazy.first(2) #=> [21, 23] lazy.force #=> [21, 23, 25, 27, 29]
請注意,大多數可以在 Enumerator::Lazy
上使用或不使用區塊呼叫的 Enumerable
方法,都將始終需要一個區塊
[1, 2, 3].map #=> #<Enumerator: [1, 2, 3]:map> [1, 2, 3].lazy.map # ArgumentError: tried to call lazy map without a block
此類別允許對長或無限序列進行慣用語法計算,以及在不建構中間陣列的情況下鏈接計算。
處理緩慢計算序列的範例
require 'open-uri' # This will fetch all URLs before selecting # necessary data URLS.map { |u| JSON.parse(URI.open(u).read) } .select { |data| data.key?('stats') } .first(5) # This will fetch URLs one-by-one, only till # there is enough data to satisfy the condition URLS.lazy.map { |u| JSON.parse(URI.open(u).read) } .select { |data| data.key?('stats') } .first(5)
使用 “.eager” 結束鏈會產生非 lazy enumerator,它適合於傳回或傳遞給預期正常 enumerator 的其他方法。
def active_items groups .lazy .flat_map(&:items) .reject(&:disabled) .eager end # This works lazily; if a checked item is found, it stops # iteration and does not look into remaining groups. first_checked = active_items.find(&:checked) # This returns an array of items like a normal enumerator does. all_checked = active_items.select(&:checked)
公共類別方法
建立新的 Lazy
列舉器。當列舉器實際列舉時(例如,呼叫 force
),將列舉 obj
,並將每個值傳遞給指定的區塊。區塊可以使用 yielder
產生值。例如,建立「篩選+對應」列舉器
def filter_map(sequence) Lazy.new(sequence) do |yielder, *values| result = yield *values yielder << result if result end end filter_map(1..Float::INFINITY) {|i| i*i if i.even?}.first(5) #=> [4, 16, 36, 64, 100]
static VALUE lazy_initialize(int argc, VALUE *argv, VALUE self) { VALUE obj, size = Qnil; VALUE generator; rb_check_arity(argc, 1, 2); if (!rb_block_given_p()) { rb_raise(rb_eArgError, "tried to call lazy new without a block"); } obj = argv[0]; if (argc > 1) { size = argv[1]; } generator = generator_allocate(rb_cGenerator); rb_block_call(generator, id_initialize, 0, 0, lazy_init_block_i, obj); enumerator_init(self, generator, sym_each, 0, 0, 0, size, 0); rb_ivar_set(self, id_receiver, obj); return self; }
公共實體方法
類似 Enumerable#map
,但鏈結操作以延遲評估。
(1..Float::INFINITY).lazy.map {|i| i**2 } #=> #<Enumerator::Lazy: #<Enumerator::Lazy: 1..Infinity>:map> (1..Float::INFINITY).lazy.map {|i| i**2 }.first(3) #=> [1, 4, 9]
傳回新的延遲列舉器,其中包含針對延遲列舉器中每個元素執行一次 block
的串接結果。
["foo", "bar"].lazy.flat_map {|i| i.each_char.lazy}.force #=> ["f", "o", "o", "b", "a", "r"]
如果符合下列任一條件,block
傳回的值 x
會被分解
-
x
對 each 和 force 都有回應,這表示x
是延遲列舉器。 -
x
是陣列或對 to_ary 有回應。
否則,x
會原樣包含在傳回值中。
[{a:1}, {b:2}].lazy.flat_map {|i| i}.force #=> [{:a=>1}, {:b=>2}]
類似 Enumerable#chunk
,但鏈結操作以延遲評估。
endif static VALUE lazy_super(int argc, VALUE *argv, VALUE lazy) { return enumerable_lazy(rb_call_super(argc, argv)); }
類似 Enumerable#map
,但鏈結操作以延遲評估。
(1..Float::INFINITY).lazy.map {|i| i**2 } #=> #<Enumerator::Lazy: #<Enumerator::Lazy: 1..Infinity>:map> (1..Float::INFINITY).lazy.map {|i| i**2 }.first(3) #=> [1, 4, 9]
傳回新的延遲列舉器,其中包含針對延遲列舉器中每個元素執行一次 block
的串接結果。
["foo", "bar"].lazy.flat_map {|i| i.each_char.lazy}.force #=> ["f", "o", "o", "b", "a", "r"]
如果符合下列任一條件,block
傳回的值 x
會被分解
-
x
對 each 和 force 都有回應,這表示x
是延遲列舉器。 -
x
是陣列或對 to_ary 有回應。
否則,x
會原樣包含在傳回值中。
[{a:1}, {b:2}].lazy.flat_map {|i| i}.force #=> [{:a=>1}, {:b=>2}]
類似 Enumerable#compact
,但鏈結操作以延遲評估。
static VALUE lazy_compact(VALUE obj) { return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_compact_funcs); }
類似 Enumerable#drop
,但鏈結操作以延遲評估。
static VALUE lazy_drop(VALUE obj, VALUE n) { long len = NUM2LONG(n); VALUE argv[2]; argv[0] = sym_each; argv[1] = n; if (len < 0) { rb_raise(rb_eArgError, "attempt to drop negative size"); } return lazy_add_method(obj, 2, argv, n, rb_ary_new3(1, n), &lazy_drop_funcs); }
類似 Enumerable#drop_while
,但鏈結操作以延遲評估。
static VALUE lazy_drop_while(VALUE obj) { if (!rb_block_given_p()) { rb_raise(rb_eArgError, "tried to call lazy drop_while without a block"); } return lazy_add_method(obj, 0, 0, Qfalse, Qnil, &lazy_drop_while_funcs); }
傳回從延遲枚舉器轉換而來的非延遲 Enumerator
。
static VALUE lazy_eager(VALUE self) { return enumerator_init(enumerator_allocate(rb_cEnumerator), self, sym_each, 0, 0, lazy_eager_size, Qnil, 0); }
類似 Object#to_enum
,但傳回延遲枚舉器。這讓定義 Enumerable
方法變得容易,如果從延遲枚舉器呼叫,這些方法會自然地維持延遲狀態。
例如,從 Object#to_enum
的範例繼續
# See Object#to_enum for the definition of repeat r = 1..Float::INFINITY r.repeat(2).first(5) # => [1, 1, 2, 2, 3] r.repeat(2).class # => Enumerator r.repeat(2).map{|n| n ** 2}.first(5) # => endless loop! # works naturally on lazy enumerator: r.lazy.repeat(2).class # => Enumerator::Lazy r.lazy.repeat(2).map{|n| n ** 2}.first(5) # => [1, 1, 4, 4, 9]
類似 Enumerable#filter_map
,但鏈結操作以延遲評估。
(1..).lazy.filter_map { |i| i * 2 if i.even? }.first(5) #=> [4, 8, 12, 16, 20]
static VALUE lazy_filter_map(VALUE obj) { if (!rb_block_given_p()) { rb_raise(rb_eArgError, "tried to call lazy filter_map without a block"); } return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_filter_map_funcs); }
傳回新的延遲列舉器,其中包含針對延遲列舉器中每個元素執行一次 block
的串接結果。
["foo", "bar"].lazy.flat_map {|i| i.each_char.lazy}.force #=> ["f", "o", "o", "b", "a", "r"]
如果符合下列任一條件,block
傳回的值 x
會被分解
-
x
對 each 和 force 都有回應,這表示x
是延遲列舉器。 -
x
是陣列或對 to_ary 有回應。
否則,x
會原樣包含在傳回值中。
[{a:1}, {b:2}].lazy.flat_map {|i| i}.force #=> [{:a=>1}, {:b=>2}]
static VALUE lazy_flat_map(VALUE obj) { if (!rb_block_given_p()) { rb_raise(rb_eArgError, "tried to call lazy flat_map without a block"); } return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_flat_map_funcs); }
類似 Enumerable#grep
,但會將作業鏈結為延遲評估。
static VALUE lazy_grep(VALUE obj, VALUE pattern) { const lazyenum_funcs *const funcs = rb_block_given_p() ? &lazy_grep_iter_funcs : &lazy_grep_funcs; return lazy_add_method(obj, 0, 0, pattern, rb_ary_new3(1, pattern), funcs); }
類似 Enumerable#grep_v
,但會將作業鏈結為延遲評估。
static VALUE lazy_grep_v(VALUE obj, VALUE pattern) { const lazyenum_funcs *const funcs = rb_block_given_p() ? &lazy_grep_v_iter_funcs : &lazy_grep_v_funcs; return lazy_add_method(obj, 0, 0, pattern, rb_ary_new3(1, pattern), funcs); }
傳回自身。
static VALUE lazy_lazy(VALUE obj) { return obj; }
類似 Enumerable#map
,但鏈結操作以延遲評估。
(1..Float::INFINITY).lazy.map {|i| i**2 } #=> #<Enumerator::Lazy: #<Enumerator::Lazy: 1..Infinity>:map> (1..Float::INFINITY).lazy.map {|i| i**2 }.first(3) #=> [1, 4, 9]
static VALUE lazy_map(VALUE obj) { if (!rb_block_given_p()) { rb_raise(rb_eArgError, "tried to call lazy map without a block"); } return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_map_funcs); }
類似 Enumerable#reject
,但會將作業鏈結為延遲評估。
static VALUE lazy_reject(VALUE obj) { if (!rb_block_given_p()) { rb_raise(rb_eArgError, "tried to call lazy reject without a block"); } return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_reject_funcs); }
類似 Enumerable#select
,但鏈結操作以延遲評估。
static VALUE lazy_select(VALUE obj) { if (!rb_block_given_p()) { rb_raise(rb_eArgError, "tried to call lazy select without a block"); } return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_select_funcs); }
如同 Enumerable#take
,但鏈結作業以延遲評估。
static VALUE lazy_take(VALUE obj, VALUE n) { long len = NUM2LONG(n); if (len < 0) { rb_raise(rb_eArgError, "attempt to take negative size"); } n = LONG2NUM(len); /* no more conversion */ return lazy_add_method(obj, 0, 0, n, rb_ary_new3(1, n), &lazy_take_funcs); }
如同 Enumerable#take_while
,但鏈結作業以延遲評估。
static VALUE lazy_take_while(VALUE obj) { if (!rb_block_given_p()) { rb_raise(rb_eArgError, "tried to call lazy take_while without a block"); } return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_take_while_funcs); }
將 lazy
枚舉器擴充為陣列。請參閱 Enumerable#to_a
。
static VALUE lazy_to_a(VALUE self) { }
類似 Object#to_enum
,但傳回延遲枚舉器。這讓定義 Enumerable
方法變得容易,如果從延遲枚舉器呼叫,這些方法會自然地維持延遲狀態。
例如,從 Object#to_enum
的範例繼續
# See Object#to_enum for the definition of repeat r = 1..Float::INFINITY r.repeat(2).first(5) # => [1, 1, 2, 2, 3] r.repeat(2).class # => Enumerator r.repeat(2).map{|n| n ** 2}.first(5) # => endless loop! # works naturally on lazy enumerator: r.lazy.repeat(2).class # => Enumerator::Lazy r.lazy.repeat(2).map{|n| n ** 2}.first(5) # => [1, 1, 4, 4, 9]
static VALUE lazy_to_enum(int argc, VALUE *argv, VALUE self) { VALUE lazy, meth = sym_each, super_meth; if (argc > 0) { --argc; meth = *argv++; } if (RTEST((super_meth = rb_hash_aref(lazy_use_super_method, meth)))) { meth = super_meth; } lazy = lazy_to_enum_i(self, meth, argc, argv, 0, rb_keyword_given_p()); if (rb_block_given_p()) { RB_OBJ_WRITE(lazy, &enumerator_ptr(lazy)->size, rb_block_proc()); } return lazy; }
如同 Enumerable#uniq
,但鏈結作業以延遲評估。
static VALUE lazy_uniq(VALUE obj) { const lazyenum_funcs *const funcs = rb_block_given_p() ? &lazy_uniq_iter_funcs : &lazy_uniq_funcs; return lazy_add_method(obj, 0, 0, Qnil, Qnil, funcs); }
如果提供區塊,則傳回一個延遲列舉器,它會對每個元素執行指定的區塊,並從 offset
開始編號,而且傳回一個延遲列舉器,它會產生相同的數值(不含索引)。
如果未提供區塊,則傳回一個新的延遲列舉器,它會包含從 offset
開始的索引。
offset
-
要使用的起始索引
static VALUE lazy_with_index(int argc, VALUE *argv, VALUE obj) { VALUE memo; rb_scan_args(argc, argv, "01", &memo); if (NIL_P(memo)) memo = LONG2NUM(0); return lazy_add_method(obj, 0, 0, memo, rb_ary_new_from_values(1, &memo), &lazy_with_index_funcs); }
如同 Enumerable#zip
,但鏈結作業以延遲評估。不過,如果提供區塊給 zip,則會立即列舉數值。
static VALUE lazy_zip(int argc, VALUE *argv, VALUE obj) { VALUE ary, v; long i; const lazyenum_funcs *funcs = &lazy_zip_funcs[1]; if (rb_block_given_p()) { return rb_call_super(argc, argv); } ary = rb_ary_new2(argc); for (i = 0; i < argc; i++) { v = rb_check_array_type(argv[i]); if (NIL_P(v)) { for (; i < argc; i++) { if (!rb_respond_to(argv[i], id_each)) { rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (must respond to :each)", rb_obj_class(argv[i])); } } ary = rb_ary_new4(argc, argv); funcs = &lazy_zip_funcs[0]; break; } rb_ary_push(ary, v); } return lazy_add_method(obj, 0, 0, ary, ary, funcs); }
私人實例方法
對每個元素執行指定的區塊,並從 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)); }