类 Enumerator::Lazy

Enumerator::Lazy 是一种特殊的 Enumerator 类型,它允许构建操作链而不立即执行它们,并在需要时按需评估值。为此,它重新定义了大多数 Enumerable 方法,使它们只构建另一个延迟枚举器。

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#firstEnumerable#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”结束一个链会生成一个非延迟枚举器,它适合返回或传递给期望普通枚举器的另一个方法。

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)

公共类方法

new(obj, size=nil) { |yielder, *values| block } 点击切换源代码

创建一个新的 Lazy 枚举器。当枚举器实际被枚举(例如,通过调用 force)时,obj 将被枚举,并将每个值传递给给定的块。该块可以使用 yielder 返回值。例如,要创建一个“filter+map”枚举器

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_collect()

类似于 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]
别名:collect
_enumerable_collect_concat()

返回一个新的延迟枚举器,其中包含对延迟枚举器中的每个元素运行 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}]
别名:collect_concat
_enumerable_drop(p1)

类似于 Enumerable#drop,但将操作链接起来以进行延迟评估。

别名:drop
_enumerable_drop_while()

类似于 Enumerable#drop_while,但将操作链接起来以进行延迟评估。

别名:drop_while
_enumerable_filter()

类似于 Enumerable#select,但将操作链接起来以进行延迟评估。

别名:filter
_enumerable_filter_map()

类似于 Enumerable#filter_map,但将操作链接起来以进行延迟评估。

(1..).lazy.filter_map { |i| i * 2 if i.even? }.first(5)
#=> [4, 8, 12, 16, 20]
别名:filter_map
_enumerable_find_all()

类似于 Enumerable#select,但将操作链接起来以进行延迟评估。

别名:find_all
_enumerable_flat_map()

返回一个新的延迟枚举器,其中包含对延迟枚举器中的每个元素运行 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}]
别名:flat_map
_enumerable_grep(p1)

类似于 Enumerable#grep,但将操作链接起来以进行延迟评估。

别名:grep
_enumerable_grep_v(p1)

类似于 Enumerable#grep_v,但将操作链接起来以进行延迟评估。

别名:grep_v
_enumerable_map()

类似于 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]
别名:map
_enumerable_reject()

类似于 Enumerable#reject,但将操作链接起来以进行延迟评估。

别名:reject
_enumerable_select()

类似于 Enumerable#select,但将操作链接起来以进行延迟评估。

别名:select
_enumerable_take(p1)

类似于 Enumerable#take,但将操作链接起来以进行延迟评估。

别名:take
_enumerable_take_while()

类似于 Enumerable#take_while,但将操作链接起来以进行延迟评估。

别名:take_while
_enumerable_uniq()

类似于 Enumerable#uniq,但将操作链接起来以进行延迟评估。

别名:uniq
_enumerable_zip(*args)

类似于 Enumerable#zip,但将操作链接起来以进行延迟评估。但是,如果给 zip 传递了代码块,则会立即枚举值。

别名:zip
chunk(*args) 点击切换源代码

类似于 Enumerable#chunk,但将操作链接起来以进行延迟评估。

static VALUE
lazy_super(int argc, VALUE *argv, VALUE lazy)
{
    return enumerable_lazy(rb_call_super(argc, argv));
}
chunk_while(*args)

类似于 Enumerable#chunk_while,但将操作链接起来以进行延迟评估。

别名:chunk
collect { |obj| block } → lazy_enumerator

类似于 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]
也称为:_enumerable_collect
别名:map
collect_concat { |obj| block } → a_lazy_enumerator

返回一个新的延迟枚举器,其中包含对延迟枚举器中的每个元素运行 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}]
别名:flat_map
compact → lazy_enumerator 点击切换源代码

类似于 Enumerable#compact,但将操作链接起来以延迟评估。

static VALUE
lazy_compact(VALUE obj)
{
    return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_compact_funcs);
}
drop(n) → lazy_enumerator 点击切换源代码

类似于 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
drop_while { |obj| block } → lazy_enumerator 点击切换源代码

类似于 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);
}
eager → enum 点击切换源代码

从延迟枚举器返回一个非延迟的 Enumerator

static VALUE
lazy_eager(VALUE self)
{
    return enumerator_init(enumerator_allocate(rb_cEnumerator),
                           self, sym_each, 0, 0, lazy_eager_size, Qnil, 0);
}
enum_for(method = :each, *args) → lazy_enum
enum_for(method = :each, *args) {|*args| block } → lazy_enum

类似于 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]
别名:to_enum
filter { |obj| block } → lazy_enumerator

类似于 Enumerable#select,但将操作链接起来以进行延迟评估。

也称为:_enumerable_filter
别名:select
filter_map { |obj| block } → lazy_enumerator 点击切换源代码

类似于 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);
}
find_all { |obj| block } → lazy_enumerator

类似于 Enumerable#select,但将操作链接起来以进行延迟评估。

也称为:_enumerable_find_all
别名:select
flat_map { |obj| block } → a_lazy_enumerator 点击切换源代码

返回一个新的延迟枚举器,其中包含对延迟枚举器中的每个元素运行 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);
}
force → array

lazy 枚举器扩展为数组。参见 Enumerable#to_a

别名:to_a
grep(pattern) → lazy_enumerator 点击切换源代码
grep(pattern) { |obj| block } → lazy_enumerator

类似于 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
grep_v(pattern) → lazy_enumerator 点击切换源代码
grep_v(pattern) { |obj| block } → lazy_enumerator

类似于 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);
}
lazy → lazy_enumerator 点击切换源代码

返回自身。

static VALUE
lazy_lazy(VALUE obj)
{
    return obj;
}
map { |obj| block } → lazy_enumerator 点击切换源代码

类似于 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);
}
reject { |obj| block } → lazy_enumerator 点击切换源代码

类似于 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);
}
select { |obj| block } → lazy_enumerator 点击切换源代码

类似于 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);
}
slice_after(*args)

类似于 Enumerable#slice_after,但将操作链式起来以延迟评估。

别名:chunk
slice_before(*args)

类似于 Enumerable#slice_before,但将操作链式起来以延迟评估。

别名:chunk
slice_when(*args)

类似于 Enumerable#slice_when,但将操作链式起来以延迟评估。

别名:chunk
take(n) → lazy_enumerator 点击切换源代码

类似于 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
take_while { |obj| block } → lazy_enumerator 点击切换源代码

类似于 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);
}
to_a → array 点击切换源代码

lazy 枚举器扩展为数组。参见 Enumerable#to_a

static VALUE
lazy_to_a(VALUE self)
{
}
别名:force
to_enum(method = :each, *args) → lazy_enum 点击切换源代码
to_enum(method = :each, *args) {|*args| block } → lazy_enum

类似于 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;
}
也称为:enum_for
uniq → lazy_enumerator 点击切换源代码
uniq { |item| block } → lazy_enumerator

类似于 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);
}
也称为:_enumerable_uniq
with_index(offset = 0) {|(*args), idx| block } 点击切换源代码
with_index(offset = 0)

如果给定一个代码块,则返回一个延迟枚举器,该枚举器将对每个元素(带索引)执行给定代码块,索引从 offset 开始,并返回一个延迟枚举器,该枚举器产生相同的值(不带索引)。

如果没有给定代码块,则返回一个新的延迟枚举器,该枚举器包含索引,从 offset 开始。

offset

要使用的起始索引

参见 Enumerator#with_index.

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);
}
zip(arg, ...) → lazy_enumerator 点击切换源代码
zip(arg, ...) { |arr| block } → nil

类似于 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);
}
也称为:_enumerable_zip

私有实例方法

with_index(offset = 0) {|(*args), idx| ... } 点击切换源代码
with_index(offset = 0)

对每个元素(带索引)执行给定代码块,索引从 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));
}