Offizielle Ruby FAQ
Wenn Sie Fehler melden oder Verbesserungen für diese FAQ vorschlagen möchten, besuchen Sie bitte unser GitHub-Repository und öffnen Sie ein Issue oder einen Pull Request.
Iteratoren
Was ist ein Iterator?
Ein Iterator ist eine Methode, die einen Block oder ein Proc-Objekt akzeptiert. In der Quelldatei wird der Block unmittelbar nach dem Aufruf der Methode platziert. Iteratoren werden verwendet, um benutzerdefinierte Kontrollstrukturen zu erzeugen - insbesondere Schleifen.
Betrachten wir ein Beispiel, um zu sehen, wie dies funktioniert. Iteratoren werden oft verwendet, um dieselbe Aktion auf jedem Element einer Sammlung zu wiederholen, wie hier:
data = [1, 2, 3]
data.each do |i|
puts i
end
Erzeugt
1
2
3
Die Methode each des Arrays data erhält den do ... end-Block und führt ihn wiederholt aus. Bei jedem Aufruf werden dem Block nacheinander Elemente des Arrays übergeben.
Sie können Blöcke mit { ... } anstelle von do ... end definieren.
data = [1, 2, 3]
data.each { |i|
puts i
}
Erzeugt
1
2
3
Dieser Code hat die gleiche Bedeutung wie das letzte Beispiel. In einigen Fällen führen jedoch Vorrangigkeitsprobleme dazu, dass do ... end und { ... } unterschiedlich wirken.
foobar a, b do ... end # foobar is the iterator.
foobar a, b { ... } # b is the iterator.
Dies liegt daran, dass { ... } enger an den vorhergehenden Ausdruck gebunden ist als ein do ... end-Block. Das erste Beispiel ist äquivalent zu foobar(a, b) do ... end, während das zweite foobar(a, b { ... }) ist.
Wie kann ich einen Block an einen Iterator übergeben?
Sie platzieren den Block einfach nach dem Iteratoraufruf. Sie können auch ein Proc-Objekt übergeben, indem Sie ein & vor den Variablen- oder Konstantenamen stellen, der auf das Proc verweist.
Wie wird ein Block in einem Iterator verwendet?
Dieser Abschnitt oder Teile davon könnten veraltet oder nicht bestätigt sein.
Es gibt drei Möglichkeiten, einen Block von einer Iterator-Methode auszuführen: (1) die yield-Kontrollstruktur; (2) Aufrufen eines Proc-Arguments (aus einem Block erstellt) mit call; und (3) Verwenden von Proc.new, gefolgt von einem Aufruf.
Die yield-Anweisung ruft den Block auf und übergibt ihm optional ein oder mehrere Argumente.
def my_iterator
yield 1, 2
end
my_iterator {|a, b| puts a, b }
Erzeugt
1
2
Wenn eine Methodendefinition ein Blockargument hat (dem letzten formalen Parameter ist ein kaufmännisches Und (&) vorangestellt), erhält sie den angehängten Block, konvertiert in ein Proc-Objekt. Dieses kann mit prc.call(args) aufgerufen werden.
def my_iterator(&b)
b.call(1, 2)
end
my_iterator {|a, b| puts a, b }
Erzeugt
1
2
Proc.new (oder die äquivalenten proc- oder lambda-Aufrufe) nimmt, wenn es in einer Iteratordefinition verwendet wird, den Block, der der Methode als Argument übergeben wird, und erzeugt daraus ein Prozedurobjekt. (proc und lambda sind praktisch Synonyme.)
[Update erforderlich: lambda verhält sich geringfügig anders und gibt eine Warnung aus: tried to create Proc object without a block.]
def my_iterator
Proc.new.call(3, 4)
proc.call(5, 6)
lambda.call(7, 8)
end
my_iterator {|a, b| puts a, b }
Erzeugt
3
4
5
6
7
8
Vielleicht überraschend ist, dass Proc.new und ähnliche keine Blöcke, die an die Methode angehängt sind, in irgendeiner Weise "verbrauchen" – jeder Aufruf von Proc.new erzeugt ein neues Prozedurobjekt aus demselben Block.
Sie können erkennen, ob einer Methode ein Block zugeordnet ist, indem Sie block_given? aufrufen.
Was tut Proc.new ohne Block?
Proc.new ohne Block kann kein Prozedurobjekt erzeugen und es tritt ein Fehler auf. In einer Methodendefinition impliziert Proc.new ohne Block jedoch die Existenz eines Blocks zum Zeitpunkt des Methodenaufrufs, sodass kein Fehler auftritt.
Wie kann ich Iteratoren parallel ausführen?
Hier eine Übernahme einer Lösung von Matz in [ruby-talk:5252], die Threads verwendet.
require "thread"
def combine(*iterators)
queues = []
threads = []
iterators.each do |it|
queue = SizedQueue.new(1)
th = Thread.new(it, queue) do |i, q|
send(i) {|x| q << x }
end
queues << queue
threads << th
end
loop do
ary = []
queues.each {|q| ary << q.pop }
yield ary
iterators.size.times do |i|
return if !threads[i].status && queues[i].empty?
end
end
end
def it1
yield 1; yield 2; yield 3
end
def it2
yield 4; yield 5; yield 6
end
combine(:it1, :it2) do |x|
# x is [1, 4], then [2, 5], then [3, 6]
end