Spec-Zone.ru › Ruby 3.3

класс Enumerator

Родитель:
Объект
Включенные модули:
Enumerable

Класс, позволяющий осуществлять итерацию как внутри, так и снаружи.

Перечислитель можно создать с помощью следующих методов.

  • Object#to_enum

  • Object#enum_for

  • Enumerator.new

Большинство методов имеют две формы: форму с блоком, где содержимое оценивается для каждого элемента перечисления, и форму без блока, которая возвращает новый перечислитель, оборачивающий итерацию.

enumerator = %w(one two three).each
puts enumerator.class # => Enumerator

enumerator.each_with_object("foo") do |item, obj|
  puts "#{obj}: #{item}"
end

# foo: one
# foo: two
# foo: three

enum_with_obj = enumerator.each_with_object("foo")
puts enum_with_obj.class # => Enumerator

enum_with_obj.each do |item, obj|
  puts "#{obj}: #{item}"
end

# foo: one
# foo: two
# foo: three

Это позволяет объединять перечислители в цепочки. Например, вы можете преобразовать элементы списка в строки, содержащие индекс и элемент как строку, следующим образом:

puts %w[foo bar baz].map.with_index { |w, i| "#{i}:#{w}" }
# => ["0:foo", "1:bar", "2:baz"]

Внешняя итерация

Перечислитель также может использоваться как внешний итератор. Например, Enumerator#next возвращает следующее значение итератора или возбуждает исключение StopIteration, если перечислитель достиг конца.

e = [1,2,3].each   # returns an enumerator object.
puts e.next   # => 1
puts e.next   # => 2
puts e.next   # => 3
puts e.next   # raises StopIteration

next, next_values, peek, и peek_values — единственные методы, использующие внешнюю итерацию (и Array#zip(Enumerable-not-Array), который использует next внутри).

Эти методы не влияют на другие методы внутренней итерации, если только сам метод основной итерации не имеет побочных эффектов, например, IO#each_line.

FrozenError будет возбуждено, если эти методы вызываются для замороженного перечислителя. Поскольку rewind и feed также изменяют состояние для внешней итерации, эти методы также могут возбуждать FrozenError.

Внешняя итерация существенно отличается от внутренней из-за использования волокна:

  • Волокно добавляет некоторую накладную стоимость по сравнению с внутренней итерацией.

  • Трассировка стека будет содержать только стек из перечислителя, а не из вышестоящих уровней.

  • Локальные переменные волокна не наследуются внутри перечислителя, который вместо этого начинает без локальных переменных волокна.

  • Переменные хранения волокна наследуются и предназначены для обработки волокон перечислителя. Присвоение переменной хранения волокна влияет только на текущее волокно, поэтому, если вы хотите изменить состояние вызывающего волокна перечислителя, вам нужна дополнительная косвенность (например, используйте некоторый объект в переменной хранения волокна и измените его ivar).

Конкретно:

Thread.current[:fiber_local] = 1
Fiber[:storage_var] = 1
e = Enumerator.new do |y|
  p Thread.current[:fiber_local] # for external iteration: nil, for internal iteration: 1
  p Fiber[:storage_var] # => 1, inherited
  Fiber[:storage_var] += 1
  y << 42
end

p e.next # => 42
p Fiber[:storage_var] # => 1 (it ran in a different Fiber)

e.each { p _1 }
p Fiber[:storage_var] # => 2 (it ran in the same Fiber/"stack" as the current Fiber)

Преобразование внешней итерации во внутреннюю итерацию

Внешний итератор можно использовать для реализации внутреннего итератора следующим образом:

def ext_each(e)
  while true
    begin
      vs = e.next_values
    rescue StopIteration
      return $!.result
    end
    y = yield(*vs)
    e.feed y
  end
end

o = Object.new

def o.each
  puts yield
  puts yield(1)
  puts yield(1, 2)
  3
end

# use o.each as an internal iterator directly.
puts o.each {|*x| puts x; [:b, *x] }
# => [], [:b], [1], [:b, 1], [1, 2], [:b, 1, 2], 3

# convert o.each to an external iterator for
# implementing an internal iterator.
puts ext_each(o.to_enum) {|*x| puts x; [:b, *x] }
# => [], [:b], [1], [:b, 1], [1, 2], [:b, 1, 2], 3

Методы публичного класса

new(size = nil) { |yielder| ... } Показать исходный код
static VALUE
enumerator_initialize(int argc, VALUE *argv, VALUE obj)
{
    VALUE iter = rb_block_proc();
    VALUE recv = generator_init(generator_allocate(rb_cGenerator), iter);
    VALUE arg0 = rb_check_arity(argc, 0, 1) ? argv[0] : Qnil;
    VALUE size = convert_to_feasible_size_value(arg0);

    return enumerator_init(obj, recv, sym_each, 0, 0, 0, size, false);
}

Создаёт новый объект Enumerator, который можно использовать как Enumerable.

Итерация определяется заданным блоком, в котором объект «yielder», переданный в качестве параметра блока, может использоваться для получения значения путём вызова метода yield (алиас <<):

fib = Enumerator.new do |y|
  a = b = 1
  loop do
    y << a
    a, b = b, a + b
  end
end

fib.take(10) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

Необязательный параметр может использоваться для указания способа вычисления размера ленивым способом (см. Enumerator#size). Он может быть значением или вызываемым объектом.

produce(initial = nil) { |prev| block } → enumerator Показать исходный код
static VALUE
enumerator_s_produce(int argc, VALUE *argv, VALUE klass)
{
    VALUE init, producer;

    if (!rb_block_given_p()) rb_raise(rb_eArgError, "no block given");

    if (rb_scan_args(argc, argv, "01", &init) == 0) {
        init = Qundef;
    }

    producer = producer_init(producer_allocate(rb_cEnumProducer), init, rb_block_proc());

    return rb_enumeratorize_with_size_kw(producer, sym_each, 0, 0, producer_size, RB_NO_KEYWORDS);
}

Создаёт бесконечный перечислитель из любого блока, просто вызывая его снова и снова. Результат предыдущей итерации передаётся следующей. Если initial предоставлен, он передаётся в первую итерацию и становится первым элементом перечислителя; если он не предоставлен, первая итерация получает nil, и её результат становится первым элементом итератора.

Возбуждение StopIteration из блока останавливает итерацию.

Enumerator.produce(1, &:succ)   # => enumerator of 1, 2, 3, 4, ....

Enumerator.produce { rand(10) } # => infinite random number sequence

ancestors = Enumerator.produce(node) { |prev| node = prev.parent or raise StopIteration }
enclosing_section = ancestors.find { |n| n.type == :section }

Использование ::produce вместе с методами Enumerable, такими как Enumerable#detect, Enumerable#slice_after, Enumerable#take_while, может предоставить перечислительные альтернативы циклам while и until:

# Find next Tuesday
require "date"
Enumerator.produce(Date.today, &:succ).detect(&:tuesday?)

# Simple lexer:
require "strscan"
scanner = StringScanner.new("7+38/6")
PATTERN = %r{\d+|[-/+*]}
Enumerator.produce { scanner.scan(PATTERN) }.slice_after { scanner.eos? }.first
# => ["7", "+", "38", "/", "6"]
product(*enums) → enumerator Показать исходный код
product(*enums) { |elts| ... } → enumerator
static VALUE
enumerator_s_product(int argc, VALUE *argv, VALUE klass)
{
    VALUE enums = Qnil, options = Qnil, block = Qnil;

    rb_scan_args(argc, argv, "*:&", &enums, &options, &block);

    if (!NIL_P(options) && !RHASH_EMPTY_P(options)) {
        rb_exc_raise(rb_keyword_error_new("unknown", rb_hash_keys(options)));
    }

    VALUE obj = enum_product_initialize(argc, argv, enum_product_allocate(rb_cEnumProduct));

    if (!NIL_P(block)) {
        enum_product_run(obj, block);
        return Qnil;
    }

    return obj;
}

Генерирует новый объект перечислителя, который генерирует декартово произведение заданных перечислимых объектов. Это эквивалентно Enumerator::Product.new.

e = Enumerator.product(1..3, [4, 5])
e.to_a #=> [[1, 4], [1, 5], [2, 4], [2, 5], [3, 4], [3, 5]]
e.size #=> 6

Когда задан блок, вызывается блок с каждым N-элементным массивом и возвращается nil.

Общедоступные методы экземпляров

e + enum → перечислитель Показать исходный код
static VALUE
enumerator_plus(VALUE obj, VALUE eobj)
{
    return new_enum_chain(rb_ary_new_from_args(2, obj, eobj));
}

Возвращает объект перечислителя, сгенерированный из этого перечислителя и заданного перечисляемого объекта.

e = (1..3).each + [4, 5]
e.to_a #=> [1, 2, 3, 4, 5]
each { |elm| блок } → obj Показать исходный код
each → enum
each(*дополнительные_аргументы) { |elm| блок } → obj
each(*дополнительные_аргументы) → перечислитель
static VALUE
enumerator_each(int argc, VALUE *argv, VALUE obj)
{
    struct enumerator *e = enumerator_ptr(obj);

    if (argc > 0) {
        VALUE args = (e = enumerator_ptr(obj = rb_obj_dup(obj)))->args;
        if (args) {
#if SIZEOF_INT < SIZEOF_LONG
            /* check int range overflow */
            rb_long2int(RARRAY_LEN(args) + argc);
#endif
            args = rb_ary_dup(args);
            rb_ary_cat(args, argv, argc);
        }
        else {
            args = rb_ary_new4(argc, argv);
        }
        RB_OBJ_WRITE(obj, &e->args, args);
        e->size = Qnil;
        e->size_fn = 0;
    }
    if (!rb_block_given_p()) return obj;

    if (!lazy_precheck(e->procs)) return Qnil;

    return enumerator_block_call(obj, 0, obj);
}

Итерирует по блоку в соответствии со способом построения этого Enumerator. Если блок и аргументы не заданы, возвращает self.

Примеры

"Hello, world!".scan(/\w+/)                     #=> ["Hello", "world"]
"Hello, world!".to_enum(:scan, /\w+/).to_a      #=> ["Hello", "world"]
"Hello, world!".to_enum(:scan).each(/\w+/).to_a #=> ["Hello", "world"]

obj = Object.new

def obj.each_arg(a, b=:b, *rest)
  yield a
  yield b
  yield rest
  :method_returned
end

enum = obj.to_enum :each_arg, :a, :x

enum.each.to_a                  #=> [:a, :x, []]
enum.each.equal?(enum)          #=> true
enum.each { |elm| elm }         #=> :method_returned

enum.each(:y, :z).to_a          #=> [:a, :x, [:y, :z]]
enum.each(:y, :z).equal?(enum)  #=> false
enum.each(:y, :z) { |elm| elm } #=> :method_returned
each_with_index {|(*args), idx| ... } Показать исходный код
each_with_index
static VALUE
enumerator_each_with_index(VALUE obj)
{
    return enumerator_with_index(0, NULL, obj);
}

То же, что и Enumerator#with_index(0), т.е. нет начального смещения.

Если блок не задан, возвращается новый Enumerator, который включает индекс.

each_with_object(объект) {|(*args), объект| ... } Показать исходный код
each_with_object(объект)
static VALUE
enumerator_with_object(VALUE obj, VALUE memo)
{
    RETURN_SIZED_ENUMERATOR(obj, 1, &memo, enumerator_enum_size);
    enumerator_block_call(obj, enumerator_with_object_i, memo);

    return memo;
}

Итерирует заданный блок для каждого элемента с произвольным объектом, obj, и возвращает obj

Если блок не задан, возвращается новый Enumerator.

Пример

to_three = Enumerator.new do |y|
  3.times do |x|
    y << x
  end
end

to_three_with_string = to_three.with_object("foo")
to_three_with_string.each do |x,string|
  puts "#{string}: #{x}"
end

# => foo: 0
# => foo: 1
# => foo: 2
Также алиас: with_object
feed объект → nil Показать исходный код
static VALUE
enumerator_feed(VALUE obj, VALUE v)
{
    struct enumerator *e = enumerator_ptr(obj);

    rb_check_frozen(obj);

    if (!UNDEF_P(e->feedvalue)) {
        rb_raise(rb_eTypeError, "feed value already set");
    }
    RB_OBJ_WRITE(obj, &e->feedvalue, v);

    return Qnil;
}

Устанавливает значение, которое будет возвращено следующим yield внутри e.

Если значение не установлено, yield возвращает nil.

Это значение очищается после того, как оно было передано.

# Array#map passes the array's elements to "yield" and collects the
# results of "yield" as an array.
# Following example shows that "next" returns the passed elements and
# values passed to "feed" are collected as an array which can be
# obtained by StopIteration#result.
e = [1,2,3].map
p e.next           #=> 1
e.feed "a"
p e.next           #=> 2
e.feed "b"
p e.next           #=> 3
e.feed "c"
begin
  e.next
rescue StopIteration
  p $!.result      #=> ["a", "b", "c"]
end

o = Object.new
def o.each
  x = yield         # (2) blocks
  p x               # (5) => "foo"
  x = yield         # (6) blocks
  p x               # (8) => nil
  x = yield         # (9) blocks
  p x               # not reached w/o another e.next
end

e = o.to_enum
e.next              # (1)
e.feed "foo"        # (3)
e.next              # (4)
e.next              # (7)
                    # (10)
inspect → строка Показать исходный код
static VALUE
enumerator_inspect(VALUE obj)
{
    return rb_exec_recursive(inspect_enumerator, obj, 0);
}

Создаёт удобочитаемую версию e.

next → объект Показать исходный код
static VALUE
enumerator_next(VALUE obj)
{
    VALUE vs = enumerator_next_values(obj);
    return ary2sv(vs, 0);
}

Возвращает следующий объект в перечислителе и перемещает внутреннюю позицию вперёд. Когда позиция достигает конца, поднимается исключение StopIteration.

Пример

a = [1,2,3]
e = a.to_enum
p e.next   #=> 1
p e.next   #=> 2
p e.next   #=> 3
p e.next   #raises StopIteration

См. заметки на уровне класса об внешних итераторах.

next_values → массив Показать исходный код
static VALUE
enumerator_next_values(VALUE obj)
{
    struct enumerator *e = enumerator_ptr(obj);
    VALUE vs;

    rb_check_frozen(obj);

    if (!UNDEF_P(e->lookahead)) {
        vs = e->lookahead;
        e->lookahead = Qundef;
        return vs;
    }

    return get_next_values(obj, e);
}

Возвращает следующий объект как массив в перечислителе и перемещает внутреннюю позицию вперёд. Когда позиция достигает конца, поднимается исключение StopIteration.

См. заметки на уровне класса об внешних итераторах.

Этот метод может использоваться для различения yield и yield nil.

Пример

o = Object.new
def o.each
  yield
  yield 1
  yield 1, 2
  yield nil
  yield [1, 2]
end
e = o.to_enum
p e.next_values
p e.next_values
p e.next_values
p e.next_values
p e.next_values
e = o.to_enum
p e.next
p e.next
p e.next
p e.next
p e.next

## yield args       next_values      next
#  yield            []               nil
#  yield 1          [1]              1
#  yield 1, 2       [1, 2]           [1, 2]
#  yield nil        [nil]            nil
#  yield [1, 2]     [[1, 2]]         [1, 2]
peek → объект Показать исходный код
static VALUE
enumerator_peek(VALUE obj)
{
    VALUE vs = enumerator_peek_values(obj);
    return ary2sv(vs, 1);
}

Возвращает следующий объект в перечислителе, но не перемещает внутреннюю позицию вперёд. Если позиция уже в конце, поднимается исключение StopIteration.

См. заметки на уровне класса об внешних итераторах.

Пример

a = [1,2,3]
e = a.to_enum
p e.next   #=> 1
p e.peek   #=> 2
p e.peek   #=> 2
p e.peek   #=> 2
p e.next   #=> 2
p e.next   #=> 3
p e.peek   #raises StopIteration
peek_values → массив Показать исходный код
static VALUE
enumerator_peek_values_m(VALUE obj)
{
    return rb_ary_dup(enumerator_peek_values(obj));
}

Возвращает следующий объект как массив, аналогично Enumerator#next_values, но не перемещает внутреннюю позицию вперёд. Если позиция уже в конце, поднимается исключение StopIteration.

См. заметки на уровне класса об внешних итераторах.

Пример

o = Object.new
def o.each
  yield
  yield 1
  yield 1, 2
end
e = o.to_enum
p e.peek_values    #=> []
e.next
p e.peek_values    #=> [1]
p e.peek_values    #=> [1]
e.next
p e.peek_values    #=> [1, 2]
e.next
p e.peek_values    # raises StopIteration
rewind → e Показать исходный код
static VALUE
enumerator_rewind(VALUE obj)
{
    struct enumerator *e = enumerator_ptr(obj);

    rb_check_frozen(obj);

    rb_check_funcall(e->obj, id_rewind, 0, 0);

    e->fib = 0;
    e->dst = Qnil;
    e->lookahead = Qundef;
    e->feedvalue = Qundef;
    e->stop_exc = Qfalse;
    return obj;
}

Перематывает последовательность перечисления в начало.

Если заключённый объект отвечает методу «rewind», он вызывается.

size → int, Float::INFINITY или nil Показать исходный код
static VALUE
enumerator_size(VALUE obj)
{
    struct enumerator *e = enumerator_ptr(obj);
    int argc = 0;
    const VALUE *argv = NULL;
    VALUE size;

    if (e->procs) {
        struct generator *g = generator_ptr(e->obj);
        VALUE receiver = rb_check_funcall(g->obj, id_size, 0, 0);
        long i = 0;

        for (i = 0; i < RARRAY_LEN(e->procs); i++) {
            VALUE proc = RARRAY_AREF(e->procs, i);
            struct proc_entry *entry = proc_entry_ptr(proc);
            lazyenum_size_func *size_fn = entry->fn->size;
            if (!size_fn) {
                return Qnil;
            }
            receiver = (*size_fn)(proc, receiver);
        }
        return receiver;
    }

    if (e->size_fn) {
        return (*e->size_fn)(e->obj, e->args, obj);
    }
    if (e->args) {
        argc = (int)RARRAY_LEN(e->args);
        argv = RARRAY_CONST_PTR(e->args);
    }
    size = rb_check_funcall_kw(e->size, id_call, argc, argv, e->kw_splat);
    if (!UNDEF_P(size)) return size;
    return e->size;
}

Возвращает размер перечислителя или nil , если его нельзя вычислить лениво.

(1..100).to_a.permutation(4).size # => 94109400
loop.size # => Float::INFINITY
(1..100).drop_while.size # => nil
with_index(смещение = 0) {|(*args), idx| ... } Показать исходный код
with_index(смещение = 0)
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

начальный индекс для использования

with_object(объект) {|(*args), объект| ... }
with_object(объект)

Итерирует заданный блок для каждого элемента с произвольным объектом, obj, и возвращает obj

Если блок не задан, возвращается новый Enumerator.

Пример

to_three = Enumerator.new do |y|
  3.times do |x|
    y << x
  end
end

to_three_with_string = to_three.with_object("foo")
to_three_with_string.each do |x,string|
  puts "#{string}: #{x}"
end

# => foo: 0
# => foo: 1
# => foo: 2
Псевдоним для: each_with_object

Ruby Core © 1993–2022 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API