класс Enumerator::Lazy
Enumerator::Lazy — это особый тип Enumerator, который позволяет строить цепочки операций без их немедленного выполнения и оценивать значения по мере необходимости. Для этого он переопределяет большинство методов Enumerable таким образом, что они просто создают другой ленивый перечислитель.
Enumerator::Lazy может быть создан из любого Enumerable с помощью метода Enumerable#lazy.
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]
Обратите внимание, что большинство методов Enumerable, которые могут вызываться с блоком или без него, для Enumerator::Lazy всегда требуют блока:
[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» создаёт не ленивый перечислитель, который подходит для возврата или передачи другому методу, ожидающему обычный перечислитель.
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)
Публичные методы класса
Исходный код
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;
} Создаёт новый 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]
Публичные методы экземпляра
Как 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"]
Значение x возвращаемое block декомпозируется, если выполняется одно из следующих условий:
-
xотвечает на both each and force, что означает, чтоxявляется ленивым перечислителем. -
xявляется массивом или отвечает на to_ary.
В противном случае, x содержится как есть в возвращаемом значении.
[{a:1}, {b:2}].lazy.flat_map {|i| i}.force
#=> [{:a=>1}, {:b=>2}]
Как Enumerable#select, но цепочки операций лениво вычисляются.
Как Enumerable#select, но цепочки операций лениво вычисляются.
Исходный код
endif
static VALUE
lazy_super(int argc, VALUE *argv, VALUE lazy)
{
return enumerable_lazy(rb_call_super(argc, argv));
} Как Enumerable#chunk, но цепочки операций лениво вычисляются.
Как Enumerable#chunk_while, но цепочки операций лениво вычисляются.
Как 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"]
Значение x возвращаемое block декомпозируется, если выполняется одно из следующих условий:
-
xотвечает на both each and force, что означает, чтоxявляется ленивым перечислителем. -
xявляется массивом или отвечает на to_ary.
В противном случае, x содержится как есть в возвращаемом значении.
[{a:1}, {b:2}].lazy.flat_map {|i| i}.force
#=> [{:a=>1}, {:b=>2}]
Исходный код
static VALUE
lazy_compact(VALUE obj)
{
return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_compact_funcs);
} Как Enumerable#compact, но цепочки операций лениво вычисляются.
Исходный код
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, но цепочечно выполняет операцию для ленивой оценки.
Исходный код
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);
} Аналогично Enumerable#drop_while, но цепочечно выполняет операцию для ленивой оценки.
Исходный код
static VALUE
lazy_eager(VALUE self)
{
return enumerator_init(enumerator_allocate(rb_cEnumerator),
self, sym_each, 0, 0, lazy_eager_size, Qnil, 0);
} Возвращает неленивый Enumerator, преобразованный из ленивого перечислителя.
Аналогично 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#select, но цепочечно выполняет операцию для ленивой оценки.
Исходный код
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);
} Аналогично Enumerable#filter_map, но цепочечно выполняет операцию для ленивой оценки.
(1..).lazy.filter_map { |i| i * 2 if i.even? }.first(5)
#=> [4, 8, 12, 16, 20]
Аналогично Enumerable#select, но цепочечно выполняет операцию для ленивой оценки.
Исходный код
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);
} Возвращает новый ленивый перечислитель с конкатенированными результатами выполнения block один раз для каждого элемента в ленивом перечислителе.
["foo", "bar"].lazy.flat_map {|i| i.each_char.lazy}.force
#=> ["f", "o", "o", "b", "a", "r"]
Значение x , возвращаемое block, разлагается, если выполняется хотя бы одно из следующих условий:
-
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_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, но цепочечно выполняет операцию для ленивой оценки.
Исходный код
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);
} Аналогично Enumerable#grep_v, но цепочечно выполняет операцию для ленивой оценки.
Исходный код
static VALUE
lazy_lazy(VALUE obj)
{
return obj;
} Возвращает self.
Исходный код
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#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_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#reject, но цепочечно выполняет операцию для ленивой оценки.
Исходный код
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#select, но цепочкой операций для отложенной оценки.
Аналогично Enumerable#slice_after, но цепочкой операций для отложенной оценки.
Аналогично Enumerable#slice_before, но цепочкой операций для отложенной оценки.
Аналогично Enumerable#slice_when, но цепочкой операций для отложенной оценки.
Исходный код
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, но цепочкой операций для отложенной оценки.
Исходный код
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);
} Аналогично Enumerable#take_while, но цепочкой операций для отложенной оценки.
Исходный код
static VALUE
lazy_to_a(VALUE self)
{
} Преобразует lazy перечислитель в массив. См. Enumerable#to_a.
Исходный код
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;
} Аналогично 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_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);
} Аналогично Enumerable#uniq, но цепочкой операций для отложенной оценки.
Исходный код
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);
} Если задан блок, возвращает ленивый перечислитель, который будет итерировать по заданному блоку для каждого элемента с индексом, начинающимся с offset, и возвращает ленивый перечислитель, который возвращает те же значения (без индекса).
Если блок не задан, возвращает новый ленивый перечислитель, который включает индекс, начинающийся со offset.
offset-
начальный индекс для использования
Исходный код
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);
} Аналогично Enumerable#zip, но цепочкой операций для отложенной оценки. Однако, если для zip задан блок, значения перечисляются немедленно.
Закрытые методы экземпляра
Исходный код
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-
начальный индекс для использования
Ruby Core © 1993–2024 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.