Spec-Zone.ru › Ruby 3.2

класс 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"]

== External Iteration

Перечислитель также может использоваться как внешний итератор. Например, 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.

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

- The Fiber adds some overhead compared to internal enumeration.
- The stacktrace will only include the stack from the Enumerator, not above.
- Fiber-local variables are *not* inherited inside the Enumerator Fiber,
  which instead starts with no Fiber-local variables.
- Fiber storage variables *are* inherited and are designed
  to handle Enumerator Fibers. Assigning to a Fiber storage variable
  only affects the current Fiber, so if you want to change state
  in the caller Fiber of the Enumerator Fiber, you need to use an
  extra indirection (e.g., use some object in the Fiber storage
  variable and mutate some ivar of it).

Конкретно:

 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)

== Convert External Iteration to Internal Iteration

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

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);
}

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

Итерация определяется заданным блоком, в котором объект «выдающего» (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 → enumerator Показать исходный код
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);
        }
        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(obj) {|(*args), obj| ... } Показать исходный код
each_with_object(obj)
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 obj → nil Показать исходный код
static VALUE
enumerator_feed(VALUE obj, VALUE v)
{
    struct enumerator *e = enumerator_ptr(obj);

    if (!UNDEF_P(e->feedvalue)) {
        rb_raise(rb_eTypeError, "feed value already set");
    }
    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;

    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_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 → целое число, 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(obj) {|(*args), obj| ... }
with_object(obj)

Итерирует заданный блок для каждого элемента с произвольным объектом, 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