Enumerator::Lazy
is a special type of Enumerator
, 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 of Enumerable
methods so that they just construct another lazy enumerator.
Enumerator::Lazy
can be constructed from any Enumerable
with the Enumerable#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-redefined Enumerable
method is called, like Enumerable#first
or Enumerable#to_a
(the latter is aliased as force
for more semantic code):
lazy.first(2)
#=> [21, 23]
lazy.force
#=> [21, 23, 25, 27, 29]
Note that most Enumerable
methods that could be called with or without a block, on Enumerator::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 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)
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.
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)
- #
-
- _enumerable_collect,
- _enumerable_collect_concat,
- _enumerable_drop,
- _enumerable_drop_while,
- _enumerable_filter,
- _enumerable_filter_map,
- _enumerable_find_all,
- _enumerable_flat_map,
- _enumerable_grep,
- _enumerable_grep_v,
- _enumerable_map,
- _enumerable_reject,
- _enumerable_select,
- _enumerable_take,
- _enumerable_take_while,
- _enumerable_uniq,
- _enumerable_zip
- C
- D
- E
- F
- G
- L
- M
- N
- R
- S
- T
- U
- W
- Z
Class Public methods
Lazy.new(obj, size=nil) { |yielder, *values| block } Link
Creates a new Lazy
enumerator. When the enumerator is actually enumerated (e.g. by calling force
), obj
will be enumerated and each value passed to the given block. The block can yield values back using yielder
. For example, to create a “filter+map” enumerator:
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]
Source: show
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; }
Instance Public methods
_enumerable_collect() Link
Like Enumerable#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]
_enumerable_collect_concat() Link
Returns a new lazy enumerator with the concatenated results of running block
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 value x
returned by block
is decomposed if either of the following conditions is true:
-
x
responds to both each and force, which means thatx
is a lazy enumerator. -
x
is 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}]
_enumerable_drop(p1) Link
Like Enumerable#drop
, but chains operation to be lazy-evaluated.
_enumerable_drop_while() Link
Like Enumerable#drop_while
, but chains operation to be lazy-evaluated.
_enumerable_filter() Link
Like Enumerable#select
, but chains operation to be lazy-evaluated.
_enumerable_filter_map() Link
Like Enumerable#filter_map
, but chains operation to be lazy-evaluated.
(1..).lazy.filter_map { |i| i * 2 if i.even? }.first(5)
#=> [4, 8, 12, 16, 20]
_enumerable_find_all() Link
Like Enumerable#select
, but chains operation to be lazy-evaluated.
_enumerable_flat_map() Link
Returns a new lazy enumerator with the concatenated results of running block
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 value x
returned by block
is decomposed if either of the following conditions is true:
-
x
responds to both each and force, which means thatx
is a lazy enumerator. -
x
is 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}]
_enumerable_grep(p1) Link
Like Enumerable#grep
, but chains operation to be lazy-evaluated.
_enumerable_grep_v(p1) Link
Like Enumerable#grep_v
, but chains operation to be lazy-evaluated.
_enumerable_map() Link
Like Enumerable#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]
_enumerable_reject() Link
Like Enumerable#reject
, but chains operation to be lazy-evaluated.
_enumerable_select() Link
Like Enumerable#select
, but chains operation to be lazy-evaluated.
_enumerable_take(p1) Link
Like Enumerable#take
, but chains operation to be lazy-evaluated.
_enumerable_take_while() Link
Like Enumerable#take_while
, but chains operation to be lazy-evaluated.
_enumerable_uniq() Link
Like Enumerable#uniq
, but chains operation to be lazy-evaluated.
_enumerable_zip(*args) Link
Like Enumerable#zip
, but chains operation to be lazy-evaluated. However, if a block is given to zip, values are enumerated immediately.
chunk(*args) Link
Like Enumerable#chunk
, but chains operation to be lazy-evaluated.
Source: show
static VALUE lazy_super(int argc, VALUE *argv, VALUE lazy) { return enumerable_lazy(rb_call_super(argc, argv)); }
chunk_while(*args) Link
Like Enumerable#chunk_while
, but chains operation to be lazy-evaluated.
lazy.collect { |obj| block } → lazy_enumerator Link
Like Enumerable#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]
lazy.collect_concat { |obj| block } → a_lazy_enumerator Link
Returns a new lazy enumerator with the concatenated results of running block
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 value x
returned by block
is decomposed if either of the following conditions is true:
-
x
responds to both each and force, which means thatx
is a lazy enumerator. -
x
is 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}]
lazy.compact → lazy_enumerator Link
Like Enumerable#compact
, but chains operation to be lazy-evaluated.
Source: show
static VALUE lazy_compact(VALUE obj) { return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_compact_funcs); }
lazy.drop(n) → lazy_enumerator Link
Like Enumerable#drop
, but chains operation to be lazy-evaluated.
Source: show
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); }
lazy.drop_while { |obj| block } → lazy_enumerator Link
Like Enumerable#drop_while
, but chains operation to be lazy-evaluated.
Source: show
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); }
lzy.eager → enum Link
Returns a non-lazy Enumerator
converted from the lazy enumerator.
Source: show
static VALUE lazy_eager(VALUE self) { return enumerator_init(enumerator_allocate(rb_cEnumerator), self, sym_each, 0, 0, lazy_eager_size, Qnil, 0); }
lzy.enum_for(method = :each, *args) → lazy_enum
lzy.enum_for(method = :each, *args) {|*args| block } → lazy_enum
Link
Similar to Object#to_enum
, except it returns a lazy enumerator. This makes it easy to define Enumerable
methods that will naturally remain lazy if called from a lazy enumerator.
For example, continuing from the example in 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]
lazy.filter { |obj| block } → lazy_enumerator Link
Like Enumerable#select
, but chains operation to be lazy-evaluated.
lazy.filter_map { |obj| block } → lazy_enumerator Link
Like Enumerable#filter_map
, but chains operation to be lazy-evaluated.
(1..).lazy.filter_map { |i| i * 2 if i.even? }.first(5)
#=> [4, 8, 12, 16, 20]
Source: show
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); }
lazy.find_all { |obj| block } → lazy_enumerator Link
Like Enumerable#select
, but chains operation to be lazy-evaluated.
lazy.flat_map { |obj| block } → a_lazy_enumerator Link
Returns a new lazy enumerator with the concatenated results of running block
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 value x
returned by block
is decomposed if either of the following conditions is true:
-
x
responds to both each and force, which means thatx
is a lazy enumerator. -
x
is 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: show
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); }
lazy.grep(pattern) → lazy_enumerator
lazy.grep(pattern) { |obj| block } → lazy_enumerator
Link
Like Enumerable#grep
, but chains operation to be lazy-evaluated.
Source: show
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); }
lazy.grep_v(pattern) → lazy_enumerator
lazy.grep_v(pattern) { |obj| block } → lazy_enumerator
Link
Like Enumerable#grep_v
, but chains operation to be lazy-evaluated.
Source: show
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); }
enum.lazy → lazy_enumerator Link
Returns self.
Source: show
static VALUE lazy_lazy(VALUE obj) { return obj; }
lazy.map { |obj| block } → lazy_enumerator Link
Like Enumerable#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: show
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); }
lazy.reject { |obj| block } → lazy_enumerator Link
Like Enumerable#reject
, but chains operation to be lazy-evaluated.
Source: show
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); }
lazy.select { |obj| block } → lazy_enumerator Link
Like Enumerable#select
, but chains operation to be lazy-evaluated.
Source: show
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); }
slice_after(*args) Link
Like Enumerable#slice_after
, but chains operation to be lazy-evaluated.
slice_before(*args) Link
Like Enumerable#slice_before
, but chains operation to be lazy-evaluated.
slice_when(*args) Link
Like Enumerable#slice_when
, but chains operation to be lazy-evaluated.
lazy.take(n) → lazy_enumerator Link
Like Enumerable#take
, but chains operation to be lazy-evaluated.
Source: show
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); }
lazy.take_while { |obj| block } → lazy_enumerator Link
Like Enumerable#take_while
, but chains operation to be lazy-evaluated.
Source: show
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.to_a → array Link
Expands lazy
enumerator to an array. See Enumerable#to_a
.
Source: show
static VALUE lazy_to_a(VALUE self) { }
lzy.to_enum(method = :each, *args) → lazy_enum
lzy.to_enum(method = :each, *args) {|*args| block } → lazy_enum
Link
Similar to Object#to_enum
, except it returns a lazy enumerator. This makes it easy to define Enumerable
methods that will naturally remain lazy if called from a lazy enumerator.
For example, continuing from the example in 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]
Source: show
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; }
lazy.uniq → lazy_enumerator
lazy.uniq { |item| block } → lazy_enumerator
Link
Like Enumerable#uniq
, but chains operation to be lazy-evaluated.
Source: show
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); }
lazy.with_index(offset = 0) {|(*args), idx| block }
lazy.with_index(offset = 0)
Link
If a block is given, returns a lazy enumerator that will iterate over the given block for each element with an index, which starts from offset
, 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 from offset
.
offset
-
the starting index to use
Source: show
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); }
lazy.zip(arg, ...) → lazy_enumerator
lazy.zip(arg, ...) { |arr| block } → nil
Link
Like Enumerable#zip
, but chains operation to be lazy-evaluated. However, if a block is given to zip, values are enumerated immediately.
Source: show
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); }