class Enumerator::Lazy
Enumerator::Lazy is a special type ofEnumerator, that allows constructing chains of operations without evaluating them immediately, and evaluating values on as-needed basis. In order to do so it redefines most ofEnumerable methods so that they just construct another lazy enumerator.
Enumerator::Lazy can be constructed from anyEnumerable with theEnumerable#lazy method.
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>
The real enumeration is performed when any non-redefinedEnumerable method is called, likeEnumerable#first orEnumerable#to_a (the latter is aliased asforce for more semantic code):
lazy.first(2)#=> [21, 23]lazy.force#=> [21, 23, 25, 27, 29]
Note that mostEnumerable methods that could be called with or without a block, onEnumerator::Lazy will always require a block:
[1,2,3].map#=> #<Enumerator: [1, 2, 3]:map>[1,2,3].lazy.map# ArgumentError: tried to call lazy map without a block
This class allows idiomatic calculations on long or infinite sequences, as well as chaining of calculations without constructing intermediate arrays.
Example for working with a slowly calculated sequence:
require'open-uri'# This will fetch all URLs before selecting# necessary dataURLS.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 conditionURLS.lazy.map {|u|JSON.parse(URI.open(u).read) } .select {|data|data.key?('stats') } .first(5)
Ending a chain with “.eager” generates a non-lazy enumerator, which is suitable for returning or passing to another method that expects a normal enumerator.
defactive_itemsgroups .lazy .flat_map(&:items) .reject(&:disabled) .eagerend# 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)
Public Class Methods
Source
static VALUElazy_initialize(int argc, VALUE *argv, VALUE self){ VALUE obj, size = Qnil; VALUE generator; rb_check_arity(argc, 1, 2); LAZY_NEED_BLOCK(new); 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;}Creates a newLazy enumerator. When the enumerator is actually enumerated (e.g. by callingforce),obj will be enumerated and each value passed to the given block. The block can yield values back usingyielder. For example, to create a “filter+map” enumerator:
deffilter_map(sequence)Lazy.new(sequence)do|yielder,*values|result =yield*valuesyielder<<resultifresultendendfilter_map(1..Float::INFINITY) {|i|i*iifi.even?}.first(5)#=> [4, 16, 36, 64, 100]
Public Instance Methods
LikeEnumerable#map, but chains operation to be lazy-evaluated.
(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]
Returns a new lazy enumerator with the concatenated results of runningblock once for every element in the lazy enumerator.
["foo","bar"].lazy.flat_map {|i|i.each_char.lazy}.force#=> ["f", "o", "o", "b", "a", "r"]
A valuex returned byblock is decomposed if either of the following conditions is true:
xresponds to both each and force, which means thatxis a lazy enumerator.xis an array or responds to to_ary.
Otherwise,x is contained as-is in the return value.
[{a:1}, {b:2}].lazy.flat_map {|i|i}.force#=> [{:a=>1}, {:b=>2}]LikeEnumerable#select, but chains operation to be lazy-evaluated.
LikeEnumerable#select, but chains operation to be lazy-evaluated.
Source
endifstatic VALUElazy_super(int argc, VALUE *argv, VALUE lazy){ return enumerable_lazy(rb_call_super(argc, argv));}LikeEnumerable#chunk, but chains operation to be lazy-evaluated.
LikeEnumerable#chunk_while, but chains operation to be lazy-evaluated.
LikeEnumerable#map, but chains operation to be lazy-evaluated.
(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]
Returns a new lazy enumerator with the concatenated results of runningblock once for every element in the lazy enumerator.
["foo","bar"].lazy.flat_map {|i|i.each_char.lazy}.force#=> ["f", "o", "o", "b", "a", "r"]
A valuex returned byblock is decomposed if either of the following conditions is true:
xresponds to both each and force, which means thatxis a lazy enumerator.xis an array or responds to to_ary.
Otherwise,x is contained as-is in the return value.
[{a:1}, {b:2}].lazy.flat_map {|i|i}.force#=> [{:a=>1}, {:b=>2}]Source
static VALUElazy_compact(VALUE obj){ return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_compact_funcs);}LikeEnumerable#compact, but chains operation to be lazy-evaluated.
Source
static VALUElazy_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);}LikeEnumerable#drop, but chains operation to be lazy-evaluated.
Source
static VALUElazy_drop_while(VALUE obj){ LAZY_NEED_BLOCK(drop_while); return lazy_add_method(obj, 0, 0, Qfalse, Qnil, &lazy_drop_while_funcs);}LikeEnumerable#drop_while, but chains operation to be lazy-evaluated.
Source
static VALUElazy_eager(VALUE self){ return enumerator_init(enumerator_allocate(rb_cEnumerator), self, sym_each, 0, 0, lazy_eager_size, Qnil, 0);}Returns a non-lazyEnumerator converted from the lazy enumerator.
Similar toObject#to_enum, except it returns a lazy enumerator. This makes it easy to defineEnumerable methods that will naturally remain lazy if called from a lazy enumerator.
For example, continuing from the example inObject#to_enum:
# See Object#to_enum for the definition of repeatr =1..Float::INFINITYr.repeat(2).first(5)# => [1, 1, 2, 2, 3]r.repeat(2).class# => Enumeratorr.repeat(2).map{|n|n**2}.first(5)# => endless loop!# works naturally on lazy enumerator:r.lazy.repeat(2).class# => Enumerator::Lazyr.lazy.repeat(2).map{|n|n**2}.first(5)# => [1, 1, 4, 4, 9]
LikeEnumerable#select, but chains operation to be lazy-evaluated.
Source
static VALUElazy_filter_map(VALUE obj){ LAZY_NEED_BLOCK(filter_map); return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_filter_map_funcs);}LikeEnumerable#filter_map, but chains operation to be lazy-evaluated.
(1..).lazy.filter_map {|i|i*2ifi.even? }.first(5)#=> [4, 8, 12, 16, 20]
LikeEnumerable#select, but chains operation to be lazy-evaluated.
Source
static VALUElazy_flat_map(VALUE obj){ LAZY_NEED_BLOCK(flat_map); return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_flat_map_funcs);}Returns a new lazy enumerator with the concatenated results of runningblock once for every element in the lazy enumerator.
["foo","bar"].lazy.flat_map {|i|i.each_char.lazy}.force#=> ["f", "o", "o", "b", "a", "r"]
A valuex returned byblock is decomposed if either of the following conditions is true:
xresponds to both each and force, which means thatxis a lazy enumerator.xis an array or responds to to_ary.
Otherwise,x is contained as-is in the return value.
[{a:1}, {b:2}].lazy.flat_map {|i|i}.force#=> [{:a=>1}, {:b=>2}]Source
static VALUElazy_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);}LikeEnumerable#grep, but chains operation to be lazy-evaluated.
Source
static VALUElazy_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);}LikeEnumerable#grep_v, but chains operation to be lazy-evaluated.
Source
static VALUElazy_map(VALUE obj){ LAZY_NEED_BLOCK(map); return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_map_funcs);}LikeEnumerable#map, but chains operation to be lazy-evaluated.
(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]
Source
static VALUElazy_reject(VALUE obj){ LAZY_NEED_BLOCK(reject); return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_reject_funcs);}LikeEnumerable#reject, but chains operation to be lazy-evaluated.
Source
static VALUElazy_select(VALUE obj){ LAZY_NEED_BLOCK(select); return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_select_funcs);}LikeEnumerable#select, but chains operation to be lazy-evaluated.
LikeEnumerable#slice_after, but chains operation to be lazy-evaluated.
LikeEnumerable#slice_before, but chains operation to be lazy-evaluated.
LikeEnumerable#slice_when, but chains operation to be lazy-evaluated.
Source
static VALUElazy_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);}LikeEnumerable#take, but chains operation to be lazy-evaluated.
Source
static VALUElazy_take_while(VALUE obj){ LAZY_NEED_BLOCK(take_while); return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_take_while_funcs);}LikeEnumerable#take_while, but chains operation to be lazy-evaluated.
Source
static VALUElazy_to_a(VALUE self){}Expandslazy enumerator to an array. SeeEnumerable#to_a.
Source
static VALUElazy_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;}Similar toObject#to_enum, except it returns a lazy enumerator. This makes it easy to defineEnumerable methods that will naturally remain lazy if called from a lazy enumerator.
For example, continuing from the example inObject#to_enum:
# See Object#to_enum for the definition of repeatr =1..Float::INFINITYr.repeat(2).first(5)# => [1, 1, 2, 2, 3]r.repeat(2).class# => Enumeratorr.repeat(2).map{|n|n**2}.first(5)# => endless loop!# works naturally on lazy enumerator:r.lazy.repeat(2).class# => Enumerator::Lazyr.lazy.repeat(2).map{|n|n**2}.first(5)# => [1, 1, 4, 4, 9]
Source
static VALUElazy_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);}LikeEnumerable#uniq, but chains operation to be lazy-evaluated.
Source
static VALUElazy_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);}If a block is given, returns a lazy enumerator that will iterate over the given block for each element with an index, which starts fromoffset, and returns a lazy enumerator that yields the same values (without the index).
If a block is not given, returns a new lazy enumerator that includes the index, starting fromoffset.
offsetthe starting index to use
Source
static VALUElazy_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);}LikeEnumerable#zip, but chains operation to be lazy-evaluated. However, if a block is given to zip, values are enumerated immediately.
Private Instance Methods
Source
static VALUEenumerator_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));}Iterates the given block for each element with an index, which starts fromoffset. If no block is given, returns a newEnumerator that includes the index, starting fromoffset
offsetthe starting index to use