класс Enumerator
Класс, позволяющий осуществлять итерацию как внутреннюю, так и внешнюю.
Объект Enumerator может быть создан с помощью следующих методов.
Большинство методов имеют две формы: в форме с блоком, где содержимое оценивается для каждого элемента в перечислении, и в форме без блока, которая возвращает новый Enumerator, оборачивающий итерацию.
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
Это позволяет объединять Enumerator'ы в цепочки. Например, вы можете преобразовать элементы списка в строки, содержащие индекс и элемент в виде строки с помощью:
puts %w[foo bar baz].map.with_index { |w, i| "#{i}:#{w}" }
# => ["0:foo", "1:bar", "2:baz"]
Объект Enumerator также может использоваться в качестве внешнего итератора. Например, Enumerator#next возвращает следующее значение итератора или вызывает исключение StopIteration, если Enumerator достиг конца.
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
Вы можете использовать это для реализации внутреннего итератора следующим образом:
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
Общедоступные методы класса
static VALUE
enumerator_initialize(int argc, VALUE *argv, VALUE obj)
{
VALUE recv, meth = sym_each;
VALUE size = Qnil;
int kw_splat = 0;
if (rb_block_given_p()) {
rb_check_arity(argc, 0, 1);
recv = generator_init(generator_allocate(rb_cGenerator), rb_block_proc());
if (argc) {
if (NIL_P(argv[0]) || rb_respond_to(argv[0], id_call) ||
(RB_TYPE_P(argv[0], T_FLOAT) && RFLOAT_VALUE(argv[0]) == HUGE_VAL)) {
size = argv[0];
}
else {
size = rb_to_int(argv[0]);
}
argc = 0;
}
}
else {
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
rb_warn_deprecated("Enumerator.new without a block", "Object#to_enum");
recv = *argv++;
if (--argc) {
meth = *argv++;
--argc;
}
kw_splat = PASS_KW_SPLAT;
}
return enumerator_init(obj, recv, meth, argc, argv, 0, size, kw_splat);
} Создаёт новый объект 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). Он может быть значением или вызываемым объектом.
Во второй, устаревшей, форме созданный Enumerator итерируется по заданному объекту, используя заданный метод с переданными аргументами.
Использование этой формы не рекомендуется. Используйте Object#enum_for или Object#to_enum вместо неё.
e = Enumerator.new(ObjectSpace, :each_object)
#-> ObjectSpace.enum_for(:each_object)
e.select { |obj| obj.is_a?(Class) } # => array of all classes
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, 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"]
Методы Публичного Экземпляра
static VALUE
enumerator_plus(VALUE obj, VALUE eobj)
{
VALUE enums = rb_ary_new_from_args(2, obj, eobj);
return enum_chain_initialize(enum_chain_allocate(rb_cEnumChain), enums);
} Возвращает объект перечислителя, сгенерированный из этого перечислителя и заданного перечисляемого объекта.
e = (1..3).each + [4, 5] e.to_a #=> [1, 2, 3, 4, 5]
static VALUE
enumerator_each(int argc, VALUE *argv, VALUE obj)
{
if (argc > 0) {
struct enumerator *e = enumerator_ptr(obj = rb_obj_dup(obj));
VALUE args = e->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;
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
static VALUE
enumerator_each_with_index(VALUE obj)
{
return enumerator_with_index(0, NULL, obj);
} Аналогично Enumerator#with_index(0), т.е. нет начального смещения.
Если блок не задан, возвращается новый Enumerator, который включает индекс.
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
static VALUE
enumerator_feed(VALUE obj, VALUE v)
{
struct enumerator *e = enumerator_ptr(obj);
if (e->feedvalue != Qundef) {
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)
static VALUE
enumerator_inspect(VALUE obj)
{
return rb_exec_recursive(inspect_enumerator, obj, 0);
} Создает отображаемую версию e.
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 не влияет на другие методы перечисления, не являющиеся внешними, если только метод базовой итерации не имеет побочного эффекта, например IO#each_line.
static VALUE
enumerator_next_values(VALUE obj)
{
struct enumerator *e = enumerator_ptr(obj);
VALUE vs;
if (e->lookahead != Qundef) {
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]
Обратите внимание, что next_values не влияет на другие методы перечисления, не являющиеся внешними, если только метод базовой итерации не имеет побочного эффекта, например IO#each_line.
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
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
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”, он вызывается.
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 (size != Qundef) return size;
return e->size;
} Возвращает размер перечислителя или nil если его нельзя вычислить лениво.
(1..100).to_a.permutation(4).size # => 94109400 loop.size # => Float::INFINITY (1..100).drop_while.size # => nil
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 -
начальный индекс для использования
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
Ruby Core © 1993–2017 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.