class Enumerator
Eine Klasse, die sowohl interne als auch externe Iteration ermöglicht.
Ein Enumerator kann mit den folgenden Methoden erstellt werden.
Die meisten Methoden haben zwei Formen: eine Blockform, bei der der Inhalt für jedes Element der Enumeration ausgewertet wird, und eine Nicht-Blockform, die einen neuen Enumerator zurückgibt, der die Iteration umschließt.
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
Dies ermöglicht es Ihnen, Enumeratoren miteinander zu verketten. Sie können beispielsweise die Elemente einer Liste in Zeichenketten abbilden, die den Index und das Element als Zeichenkette enthalten, über
puts %w[foo bar baz].map.with_index { |w, i| "#{i}:#{w}" } # => ["0:foo", "1:bar", "2:baz"]
Externe Iteration
Ein Enumerator kann auch als externer Iterator verwendet werden. Zum Beispiel gibt Enumerator#next den nächsten Wert des Iterators zurück oder löst StopIteration aus, wenn der Enumerator am Ende ist.
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 und peek_values sind die einzigen Methoden, die externe Iteration verwenden (und Array#zip(Enumerable-not-Array), die intern next verwendet).
Diese Methoden beeinflussen keine anderen internen Enumerationsmethoden, es sei denn, die zugrunde liegende Iterationsmethode selbst hat Nebeneffekte, z. B. IO#each_line.
FrozenError wird ausgelöst, wenn diese Methoden gegen einen gefrorenen Enumerator aufgerufen werden. Da rewind und feed auch den Zustand für die externe Iteration ändern, können diese Methoden ebenfalls FrozenError auslösen.
Externe Iteration unterscheidet sich signifikant von interner Iteration, da sie eine Fiber verwendet
-
Die
Fiberverursacht im Vergleich zur internen Enumeration einen gewissen Mehraufwand. -
Der Stacktrace enthält nur den Stack vom
Enumerator, nicht darüber. -
Fiber-lokale Variablen werden innerhalb der
Enumerator-FaserFibernicht vererbt, die stattdessen ohne Fiber-lokale Variablen beginnt. -
Fiber-Speichervariablen werden vererbt und sind dafür ausgelegt,Enumerator-Fasern zu verwalten. Zuweisungen an eineFiber-Speichervariable wirken sich nur auf die aktuelleFiberaus. Wenn Sie also den Zustand in der aufrufendenFiberderEnumerator-FaserFiberändern möchten, müssen Sie eine zusätzliche Indirektion verwenden (z. B. ein Objekt in derFiber-Speichervariable verwenden und eine Instanzvariable davon ändern).
Konkret
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)
Externe Iteration in interne Iteration umwandeln
Sie können einen externen Iterator verwenden, um einen internen Iterator wie folgt zu implementieren
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
Öffentliche Klassenmethoden
Source
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);
}
Erstellt ein neues Enumerator-Objekt, das als Enumerable verwendet werden kann.
Die Iteration wird durch den gegebenen Block definiert, in dem ein als Blockparameter übergebener "Yielder"-Objekt verwendet werden kann, um einen Wert durch Aufruf der yield-Methode (alias <<) zu erzeugen.
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]
Der optionale Parameter kann verwendet werden, um die Größe auf faule Weise zu berechnen (siehe Enumerator#size). Er kann entweder ein Wert oder ein aufrufbares Objekt sein.
Source
static VALUE
enumerator_s_produce(int argc, VALUE *argv, VALUE klass)
{
VALUE init, producer, opts, size;
ID keyword_ids[1];
if (!rb_block_given_p()) rb_raise(rb_eArgError, "no block given");
keyword_ids[0] = rb_intern("size");
rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS, argc, argv, "01:", &init, &opts);
rb_get_kwargs(opts, keyword_ids, 0, 1, &size);
size = UNDEF_P(size) ? DBL2NUM(HUGE_VAL) : convert_to_feasible_size_value(size);
if (argc == 0 || (argc == 1 && !NIL_P(opts))) {
init = Qundef;
}
producer = producer_init(producer_allocate(rb_cEnumProducer), init, rb_block_proc(), size);
return rb_enumeratorize_with_size_kw(producer, sym_each, 0, 0, producer_size, RB_NO_KEYWORDS);
}
Erstellt einen unendlichen Enumerator aus jedem Block, der immer wieder aufgerufen wird. Das Ergebnis der vorherigen Iteration wird an die nächste übergeben. Wenn initial angegeben ist, wird es an die erste Iteration übergeben und wird zum ersten Element des Enumerators; wenn es nicht angegeben ist, erhält die erste Iteration nil, und ihr Ergebnis wird zum ersten Element des Iterators.
Wenn StopIteration aus dem Block ausgelöst wird, stoppt die Iteration.
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 }
Die Verwendung von ::produce zusammen mit Enumerable-Methoden wie Enumerable#detect, Enumerable#slice_after, Enumerable#take_while kann Enumerator-basierte Alternativen für while- und until-Schleifen bieten.
# 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"]
Das optionale Schlüsselwortargument size gibt die Größe des Enumerators an, die von Enumerator#size abgerufen werden kann. Es kann eine Ganzzahl, Float::INFINITY, ein aufrufbares Objekt (wie ein Lambda) oder nil sein, um eine unbekannte Größe anzuzeigen. Wenn nicht angegeben, ist die Standardgröße Float::INFINITY.
# Infinite enumerator enum = Enumerator.produce(1, size: Float::INFINITY, &:succ) enum.size # => Float::INFINITY # Finite enumerator with known/computable size abs_dir = File.expand_path("./baz") # => "/foo/bar/baz" traverser = Enumerator.produce(abs_dir, size: -> { abs_dir.count("/") + 1 }) { raise StopIteration if it == "/" File.dirname(it) } traverser.size # => 4 # Finite enumerator with unknown size calendar = Enumerator.produce(Date.today, size: nil) { it.monday? ? raise(StopIteration) : it + 1 } calendar.size # => nil
Source
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;
}
Erzeugt ein neues Enumerator-Objekt, das ein kartesisches Produkt der gegebenen aufzählbaren Objekte erzeugt. Dies ist äquivalent zu 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
Wenn ein Block gegeben ist, ruft er den Block mit jedem generierten N-Elemente-Array auf und gibt nil zurück.
Öffentliche Instanzmethoden
Source
static VALUE
enumerator_plus(VALUE obj, VALUE eobj)
{
return new_enum_chain(rb_ary_new_from_args(2, obj, eobj));
}
Gibt ein Enumerator-Objekt zurück, das aus diesem Enumerator und einem gegebenen aufzählbaren Objekt generiert wird.
e = (1..3).each + [4, 5] e.to_a #=> [1, 2, 3, 4, 5]
Source
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);
}
Iteriert gemäß der Erstellung dieses Enumerator über den Block. Wenn kein Block und keine Argumente gegeben sind, wird self zurückgegeben.
Beispiele
"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
Source
static VALUE
enumerator_each_with_index(VALUE obj)
{
return enumerator_with_index(0, NULL, obj);
}
Ähnlich wie Enumerator#with_index(0), d. h. es gibt keinen Startversatz.
Wenn kein Block gegeben ist, wird ein neuer Enumerator zurückgegeben, der den Index enthält.
Source
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;
}
Iteriert für jedes Element mit einem beliebigen Objekt, obj, über den gegebenen Block und gibt obj zurück.
Wenn kein Block gegeben ist, wird ein neuer Enumerator zurückgegeben.
Beispiel
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
Source
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;
}
Setzt den Wert, der vom nächsten yield innerhalb von e zurückgegeben werden soll.
Wenn der Wert nicht gesetzt ist, gibt das yield nil zurück.
Dieser Wert wird nach dem Erzeugen gelöscht.
# 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)
Source
static VALUE
enumerator_inspect(VALUE obj)
{
return rb_exec_recursive(inspect_enumerator, obj, 0);
}
Erstellt eine druckbare Version von e.
Source
static VALUE
enumerator_next(VALUE obj)
{
VALUE vs = enumerator_next_values(obj);
return ary2sv(vs, 0);
}
Gibt das nächste Objekt im Enumerator zurück und bewegt die interne Position vorwärts. Wenn die Position das Ende erreicht, wird StopIteration ausgelöst.
Beispiel
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
Siehe Hinweise auf Klassenebene zu externen Iteratoren.
Source
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);
}
Gibt das nächste Objekt als Array im Enumerator zurück und bewegt die interne Position vorwärts. Wenn die Position das Ende erreicht, wird StopIteration ausgelöst.
Siehe Hinweise auf Klassenebene zu externen Iteratoren.
Diese Methode kann verwendet werden, um zwischen yield und yield nil zu unterscheiden.
Beispiel
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]
Source
static VALUE
enumerator_peek(VALUE obj)
{
VALUE vs = enumerator_peek_values(obj);
return ary2sv(vs, 1);
}
Gibt das nächste Objekt im Enumerator zurück, bewegt aber die interne Position nicht vorwärts. Wenn die Position bereits am Ende ist, wird StopIteration ausgelöst.
Siehe Hinweise auf Klassenebene zu externen Iteratoren.
Beispiel
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
Source
static VALUE
enumerator_peek_values_m(VALUE obj)
{
return rb_ary_dup(enumerator_peek_values(obj));
}
Gibt das nächste Objekt als Array zurück, ähnlich wie Enumerator#next_values, bewegt aber die interne Position nicht vorwärts. Wenn die Position bereits am Ende ist, wird StopIteration ausgelöst.
Siehe Hinweise auf Klassenebene zu externen Iteratoren.
Beispiel
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
Source
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;
}
Spult die Enumerationssequenz zum Anfang zurück.
Wenn das eingeschlossene Objekt auf eine "rewind"-Methode reagiert, wird diese aufgerufen.
Source
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;
}
Gibt die Größe des Enumerators zurück oder nil, wenn sie nicht faul berechnet werden kann.
(1..100).to_a.permutation(4).size # => 94109400 loop.size # => Float::INFINITY (1..100).drop_while.size # => nil
Beachten Sie, dass die Größe des Enumerators ungenau sein kann und eher als Hinweis behandelt werden sollte. Zum Beispiel gibt es keine Prüfung, ob die an ::new übergebene Größe korrekt ist.
e = Enumerator.new(5) { |y| 2.times { y << it} } e.size # => 5 e.to_a.size # => 2
Ein weiteres Beispiel ist ein Enumerator, der von ::produce ohne size-Argument erstellt wurde. Solche Enumeratoren geben Infinity für die Größe zurück, aber dies ist ungenau, wenn der übergebene Block StopIteration auslöst.
e = Enumerator.produce(1) { it + 1 } e.size # => Infinity e = Enumerator.produce(1) { it > 3 ? raise(StopIteration) : it + 1 } e.size # => Infinity e.to_a.size # => 4
Source
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));
}
Iteriert für jedes Element mit einem Index, der bei offset beginnt, über den gegebenen Block. Wenn kein Block gegeben ist, wird ein neuer Enumerator zurückgegeben, der den Index enthält, beginnend bei offset.
offset-
der zu verwendende Startindex
Iteriert für jedes Element mit einem beliebigen Objekt, obj, über den gegebenen Block und gibt obj zurück.
Wenn kein Block gegeben ist, wird ein neuer Enumerator zurückgegeben.
Beispiel
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