Die Programmiersprache Ruby (German Edition)
einfach das Gegenteil von
select
; sie gibt ein Array von Elementen zurück, für die der Block
nil
oder
false
zurückliefert, zum Beispiel:
odds = (1..10).reject {|x| x%2 == 0} # => [1,3,5,7,9]
Die Methode
inject
ist ein wenig komplizierter als die anderen. Sie ruft den zugehörigen Block mit zwei Argumenten auf. Das erste Argument ist ein akkumulierter Wert irgendeiner Art aus früheren Iterationen. Das zweite Argument ist das nächste Element des Aufzählungsobjekts. Der Rückgabewert des Blocks wird zum ersten Blockargument für die nächste Iteration oder zum Rückgabewert des Iterators nach der letzten Iteration. Der Anfangswert der Akkumulatorvariablen ist entweder das Argument von
inject
, falls vorhanden, oder das erste Element des Aufzählungsobjekts. (In diesem Fall wird der Block für die ersten beiden Elemente nur einmal aufgerufen.) Beispiele machen
inject
klarer:
data = [2, 5, 3, 4]
sum = data.inject {|sum, x| sum + x } # => 14 (2+5+3+4)
floatprod = data.inject(1.0) {|p,x| p*x } # => 120.0 (1.0*2*5*3*4)
max = data.inject {|m,x| m>x ? m : x } # => 5 (größtes Element)
Siehe „9.5.1 Enumerierbare Objekte“ für weitere Details über das Modul
Enumerable
und seine Iteratoren.
5.3.3 Benutzerdefinierte Iteratoren schreiben
Das definierende Feature einer Iteratormethode ist, dass sie einen Codeblock aufruft, der mit dem Methodenaufruf verknüpft ist. Sie erledigen das mit der Anweisung
yield
. Die folgende Methode ist ein trivialer Iterator, der seinen Block einfach zweimal aufruft:
def twice
yield
yield
end
Um Argumentwerte an den Block zu übergeben, setzen Sie eine kommaseparierte Liste von Ausdrücken hinter die
yield
-Anweisung. Genau wie bei Methodenaufrufen können die Argumentwerte optional von Klammern umschlossen werden. Der folgende einfache Iterator zeigt eine Verwendung von
yield
:
# Diese Methode erwartet einen Block. Sie erzeugt n Werte in der
# Form m * i + c für i von 0..n-1 und übergibt sie nacheinander
# an den verknüpften Block.
def sequence(n, m, c)
i = 0
while(i < n) # Schleife n-mal
yield m * i + c # Block aufrufen und ihm einen Wert übergeben
i += 1 # i jedes Mal vermindern
end
end
# Hier ein Aufruf dieser Methode mit einem Block
# Er gibt die Werte 1, 6 und 11 aus.
sequence(3, 5, 1) {|y| puts y }
----
Nomenklatur: yield und Iteratoren
Je nach Ihrem Programmierhintergrund finden Sie die Begriffe »yield« und »Iterator« möglicherweise verwirrend. Die oben gezeigte Methode
sequence
ist ein recht klares Beispiel dafür, warum
yield
(zu Deutsch etwa »abgeben« oder »übergeben«) diesen Namen trägt. Nach der Berechnung jeder Zahl in der Sequenz übergibt die Methode die Kontrolle (und die errechnete Zahl) an den Block, so dass der Block damit arbeiten kann. Dies ist jedoch nicht immer so klar; in manchem Code mag es so aussehen, als sei es der Block, der ein Ergebnis zurück an die Methode übergibt, die ihn aufgerufen hat.
Eine Methode wie
sequence
, die einen Block erwartet und ihn mehrmals aufruft, wird als Iterator bezeichnet, da sie so aussieht und sich so verhält wie eine Schleife. Das kann verwirrend sein, wenn Sie mit Sprachen wie Java vertraut sind, in denen Iteratoren Objekte sind. In Java besitzt der Clientcode, der den Iterator nutzt, die Kontrolle und »zieht« Werte aus dem Iterator, wenn er sie benötigt. In Ruby hat die Iteratormethode die Kontrolle und »schiebt« Werte in den Block, der sie braucht.
Diese Nomenklaturfrage hat mit dem Unterschied zwischen »internen Iteratoren« und »externen Iteratoren« zu tun, der weiter unten in diesem Abschnitt besprochen wird.
----
Hier ein anderes Beispiel eines Ruby-Iterators; er übergibt zwei Argumente an seinen Block. Es lohnt sich, darauf hinzuweisen, dass die Implementierung dieses Iterators intern einen weiteren Iterator nutzt:
# n Punkte erzeugen, die gleichmäßig auf dem Umfang eines
# Kreises mit dem Radius r und dem Zentrum (0,0) verteilt sind.
# Die x- und y-Koordinaten jedes Punkts an den verknüpften
# Block übergeben.
def circle(r,n)
n.times do |i| # Beachten: Methode ist mit Block implementiert.
angle = Math::PI * 2 * i / n
yield r*Math.cos(angle), r*Math.sin(angle)
end
end
# Dieser Aufruf des Iterators gibt aus:
# (1.00, 0.00) (0.00, 1.00) (-1.00, 0.00) (-0.00, −1.00)
circle(1,4) {|x,y| printf "(%.2f, %.2f) ", x, y }
Die Verwendung des Schlüsselworts
yield
hat wirklich viel mit einem Methodenaufruf gemeinsam. (Siehe Kapitel 6 für die vollständigen Details über
Weitere Kostenlose Bücher