Die Programmiersprache Ruby (German Edition)
internen Iteratoren ist es praktisch unmöglich ... Andererseits sind interne Iteratoren einfacher zu verwenden, weil sie die Iterationslogik für Sie definieren.
In Ruby sind Iteratormethoden wie
each
interne Iteratoren; sie steuern die Iteration und »schieben« Werte in den Codeblock, der mit dem Methodenaufruf verknüpft ist. Enumeratoren haben eine
each
-Methode zur internen Iteration, aber in Ruby 1.9 und neuer funktionieren sie auch als externe Iteratoren – Clientcode kann Werte mit
next
sequenziell aus einem Enumerator »ziehen«.
----
Um Schleifen mit externen Iteratoren zu vereinfachen, enthält die Methode
Kernel.loop
(in Ruby 1.9) eine implizite
rescue
-Klausel und endet korrekt, wenn
StopIteration
ausgelöst wird. Daher könnte der oben gezeigte Countdown-Code folgendermaßen einfacher geschrieben werden:
iterator = 9.downto(1)
loop do # Schleife, bis StopIteration ausgelöst wird
print iterator.next # Nächstes Element ausgeben
end
puts "... Abflug!"
Viele externe Iteratoren können durch einen Aufruf der Methode
rewind
neu gestartet werden. Beachten Sie jedoch, das
rewind
nicht mit allen Enumeratoren funktioniert. Wenn ein Enumerator auf einem Objekt wie etwa einem
File
basiert, das Zeilen sequenziell liest, startet ein Aufruf von
rewind
die Iteration nicht neu. Allgemein gilt: Wenn neue Aufrufe von
each
für das zugrunde liegende
Enumerable
-Objekt die Iteration nicht neu starten, dann tut ein Aufruf von
rewind
das auch nicht.
Nachdem eine externe Iteration gestartet wurde (d.h. nachdem zum ersten Mal
next
aufgerufen wurde), kann ein Enumerator nicht geklont oder dupliziert werden. Es ist im Allgemeinen möglich, einen Enumerator vor dem Aufruf von
next
zu klonen, oder aber nachdem
StopIteration
ausgelöst oder
rewind
aufgerufen wurde.
Normalerweise werden Enumeratoren mit
next
-Methoden aus
Enumerable
-Objekten erzeugt, die eine
each
-Methode besitzen. Wenn Sie aus irgendeinem Grund eine Klasse definieren, die eine
next
-Methode zur externen Iteration statt einer
each
-Methode zur internen Iteration bereitstellt, können Sie each leicht mithilfe von
next
implementieren. Aus einer extern iterierbaren Klasse, die
next
implementiert, eine
Enumerable
-Klasse zu machen, ist in der Tat nicht schwieriger, als ein Modul als Mixin einzufügen (mit
include
– siehe „7.5 Module“ ):
module Iterable
include Enumerable # Iteratoren auf each aufbauen
def each # Und each durch next definieren
loop { yield self.next }
end
end
Eine andere Möglichkeit, einen externen Iterator zu verwenden, besteht darin, ihn an eine interne Iteratormethode wie diese zu übergeben:
def iterate(iterator)
loop { yield iterator.next }
end
iterate(9.downto(1)) {|x| print x }
Das Zitat aus Design Patterns weiter oben machte auf eines der wichtigsten Features externer Iteratoren aufmerksam: Sie lösen das Problem der parallelen Iteration. Angenommen, Sie haben zwei
Enumerable
-Sammlungen und müssen in Paaren über ihre Werte iterieren: Das erste Element jeder der beiden Sammlungen, dann jeweils das zweite Argument und so weiter. Ohne einen externen Iterator müssen Sie eine der Sammlungen in ein Array konvertieren (mit der von Enumerable definierten Methode
to_a
), so dass Sie auf ihre Elemente zugreifen können, während Sie mit
each
über die andere Sammlung iterieren.
Listing 5.1 zeigt die Implementierung von drei Iteratormethoden. Alle drei akzeptieren eine beliebige Anzahl von
Enumerable
-Objekten und iterieren auf unterschiedliche Weise über diese. Die erste ist eine einfache, sequenzielle Iteration, die nur interne Iteratoren verwendet; die beiden anderen sind parallele Iterationen und können nur mithilfe der externen Iterationsfeatures von Enumeratoren durchgeführt werden.
# Nacheinander die each-Methode jeder Sammlung aufrufen.
# Dies ist keine parallele Iteration, die keine Enumeratoren
# benötigt.
def sequence(*enumerables, &block)
enumerables.each do |enumerable|
enumerable.each(&block)
end
end
# Über die angegebenen Sammlungen iterieren und ihre Elemente
# verschachteln. Das ist ohne externe Iteratoren nicht effizient
# möglich. Beachten Sie die Verwendung der unüblichen else-
# Klausel in begin/rescue.
def interleave(*enumerables)
# Enumerable-Sammlungen in Enumeratoren-Array konvertieren.
enumerators = enumerables.map {|e| e.to_enum }
# Schleife, bis wir keine Enumeratoren mehr haben.
until enumerators.empty?
begin
e = enumerators.shift # Ersten Enumerator nehmen
yield e.next #
Weitere Kostenlose Bücher