====== Schleifen ======
Schleifen werden dazu benutzt, einen bestimmten Teil eines Skripts wiederholt auszuführen. Ähnlich zu [[ scripting:tutorials:level1:branches |Verzweigungen]] wird der Inhalt einer Schleife so lange ausgeführt, wie eine bestimmte Bedingung zutrifft. Auch bei Schleifen wird diese Bedingung mittels eines [[ scripting:tutorials:level1:variables#booleans |Booleans]] ausgedrückt. Es gibt in Lua meherere Möglichkeiten, eine Schleife zu formulieren. Die wollen wir im Folgenden nacheinander durchgehen.
----
===== while-Schleife =====
Die **while**-Schleife ist die intuitivste der Schleifen in Lua. Als Satz formuliert setzt sie folgende Logik um:\\
"Solange eine bestimmte Bedingung erfüllt ist, führe den folgenden Block aus."
Eine **while**-Schleife beginnt mit dem Schlüsselwort ''while''. Darauf folgt die Bedingung, die geprüft werden soll. Mit ''do'' wird die Definition der Bedingung abgeschlossen und der auszuführende Block beginnt. ''end'' schließt wie aus Verzweigungen oder Funktionen gewohnt die Schleife ab.
Dieses Prinzip zeigt das folgende einfache Beispiel:
MyNumber = 1
while MyNumber < 42 do
MyNumber = MyNumber + 1
end
Als Satz formuliert: "Solange MyNumber kleiner als 42 ist, erhöhe MyNumber um 1."
Die Schleifenbedingung kann dabei jeder mögliche Boolean sein, zum Beispiel auch das Ergebnis einer Funktion:
-- Dieser Code hat genau die gleiche Funktion wie unser Beispiel oben
function IsNumberLargeEnough(_Number)
return _Number >= 42
end
MyNumber = 1
while not IsNumberLargeEnough(MyNumber) do
MyNumber = MyNumber + 1
end
Die Bedingung, ob der Inhalt der Schleife ausgeführt werden soll, wird **immer im Voraus** geprüft. Das heißt, wenn im Beispiel oben ''MyNumber'' zu Beginn mit 42 definiert wird, wird die Zahl kein einziges Mal um 1 erhöht werden.
----
**Hinweis**: Unsere Beispiele in diesem und den folgenden Kapiteln können besser nachvollzogen werden, wenn man die Programmzeilen nebenbei ausprobieren kann. Dazu kann man diese Webseite hier verwenden:
[[ https://www.lua.org/demo.html ]]
Um dabei bei der Ausgabe ein Ergebnis zu sehen, kann man die Fnuktion ''print'' verwenden. Will man beispielsweise die Variable ''MyNumber'' ansehen, kann man
print(MyNumber)
schreiben. Wenn die Variable einen anderen Namen hat, muss man diesen dementsprechend austauschen.
----
Um die Möglichkeiten der **while**-Schleife besser zeigen zu können, wollen wir den Rest einer ganzzahligen Division berechnen. Diese Operation wird auch //Modulo// genannt. Ziel ist also für zwei Zahlen A und B herauszufinden, wie viel Rest beim Berechnen von ''A/B'' übrig bleibt. Die Strategie unseres Algorithmus ist es, B so lange von A abzuziehen, bis B nicht mehr in A "passt". Der Rest muss folglich der Rest der Division sein:
function Modulo(_A, _B)
while _A > _B do
_A = _A - _B
end
return _A
end
Ein klassisches Beispiel für die Verwendung einer **while**-Schleife ist die Berechnung des größten gemeinsamen Teilers zweier Zahlen, sodass beispielsweise ein Bruch gekürzt werden kann. Dazu verwenden wir den [[ https://de.wikipedia.org/wiki/Euklidischer_Algorithmus |Euklidischen Algorithmus]]:
function GreatestCommonDivisor(_A, _B)
if _A == 0 then
return _B
else
while _B ~= 0 do
if _A > _B then
_A = _A - _B
else
_B = _B - _A
end
end
end
return _A
end
Wer Lust hat, kann sich auf der verlinkten Wikipediaseite anschauen, warum dieser Algorithmus funktioniert.
**Achtung**: Stelle bei jeder Formulierung einer **while**-Schleife sicher, dass die Schleifenbedingung auch wirklich irgendwann **false** wird! Andernfalls wird dein Programm die Schleife nie verlassen und darin "gefangen" sein (**Endlosschleife**). Leicht modifiziert, wird das Beispiel von oben so zu einer Endlosschleife:
MyNumber = 1
while MyNumber < 42 do
MyNumber = MyNumber - 1
end
Jetzt wird ''MyNumber'' nicht mehr um 1 erhöht, sondern reduziert. Dadurch entfernt sich die Zahl immer weiter von der 42, weshalb die Schleife niemals enden kann. Die Lua-Demo wird das Programm abbrechen, aber das Siedler-Spiel wird sich aufhängen, sodass es nur mit dem Task Manager beendet werden kann!
----
===== for-Schleife =====
Die **for**-Schleife wird dazu benutzt, den eingesetzten Code-Block für eine feste Anzahl an Wiederholungen auszuführen. Die Anzahl der Wiederholungen (**Iterationen**) wird durch eine Zähler angegeben, der einen Startwert, einen Zielwert und eine Schrittweite hat. Beim Startwert beginnend, geht der Zähler gemäß der Schrittweite auf den Zielwert zu. Bei jedem Schritt wird der Block in der Schleife ausgeführt.
Eingeleitet wird die **for**-Schleife mit dem Schlüsselwort ''for''. Danach wird der Name einer **lokalen** Zählvariable angegeben, die innerhalb der Schleife benutzt werden kann. Nach ''do'' beginnt wie gewohnt der Schleifenblock und endet mit ''end''.
-- Dieser Code addiert alle ungeraden Zahlen von 1 bis 10
Sum = 0
-- Counter ist der Name der Zählvariable
-- Die erste Zahl nach dem = ist der Startwert
-- Die zweite Zahl ist der Zielwert, getrennt durch ein Komma
-- Die dritte Zahl ist die Schrittweite, getrennt durch ein Komma
for Counter = 1, 10, 2 do
-- In jeder Iteration wird der aktuelle Wert der Zählvariable addiert
Sum = Sum + Counter
end
Wenn die Schrittweite der Zählvarable 1 sein soll, kann man sie einfach weglassen:
-- Dieser Code erstellt einen String aller Zahlen von 1 bis 10
NumberString = ""
-- Die Schrittweite soll hier 1 sein, also brauchen wir sie nicht extra aufzuschreiben
for Counter = 1, 10 do
NumberString = NumberString..Counter
-- In allen außer der letzten Iteration wollen wir ein Komma, um die Zahlen zu trennen
if Counter < 10 then
NumberString = NumberString..", "
end
end
Der Variablenname ''Counter'' ist selbstverständlich willkürlich gewählt und kann durch jeden güligen Variablennamen ersetzt werden. Viele Programmierer benutzen als Zählvariable schlicht ''i''.
Wir geben noch ein Beispiel, um zu zeigen, dass **for**-Schleifen selbstverständlich auch rückwärts zählen können, wenn die Schrittweite negativ ist:
PerfectlyBalanced = 0
-- Hier werden wieder alle Zahlen in der Zählschleife addiert
-- Da die gleichen positiven und negativen Zahlen addiert werden,
-- wird das Ergebnis 0 sein
for i = 10, -10, -1 do
PerfectlyBalanced = PerfectlyBalanced + i
end
Es können auch mehrere Schleifen ineinander geschachtelt werden. Dabei ist es **wichtig**, dass die Zählvariablen alle unterschiedliche Namen haben!
-- Dieser Code addiert alle Zahlen a und b, wenn a und b zwischen 1 und 10 liegen
-- Die Summen werden als String angezeigt
for a = 1, 10 do
-- Jede "Reihe" muss mit einem leeren String beginnen
NumberString = ""
for b = 1, 10 do
-- Die einzelnen Zahlen werden mit einem Leerzeichen getrennt
NumberString = NumberString..(a + b).." "
end
print(NumberString)
end
**Hinweis**: Innerhalb der Schleife solltest du die Zählvariable niemals verändern:
for i = 1, 10 do
i = i + 1
end
Es ist zwar prinzipiell möglich, das zu tun, führt aber meistens nur zu Problemen. Besser ist es, einfach eine andere Schrittweite zu setzen. Falls die Schrittweite sich verändern soll, bietet sich eine **while**-Schleife an, die eine maßgeschneiderte Zählvariable führt.
----
===== repeat-until-Schleife =====
Die **repeat-until**-Schleife ist im Prinzip nur eine "umgekehrte" **while**-Schleife und findet nur selten Verwendung. Der Vollständigkeit halber wollen wir sie aber hier erklären.
Der Schleifenblock wird mit ''repeat'' eingeleitet und mit ''until'' beendet. Auf ''until'' folgt die Bedingung, mit der die Schleife abbricht:
MyNumber = 1
repeat
MyNumber = MyNumber + 1
until MyNumber >= 42
Wir sehen, dass diese Art der Schleife auf zwei verschiedene Arten die **while**-Schleife umkehrt:
- Die angegebene Bedingung in der **repeat-until** Schleife gibt nicht an, unter welcher Bedingung die Schleife fortgesetzt wird, sondern unter welcher Bedingung sie beendet wird. Will man eine **while**-Schleife in eine **repeat-until**-Schleife umbauen, muss man die Bedingung also genau umkehren
- Der Schleifenblock wird **immer mindestens 1 mal** ausgeführt. Diese Eigenschaft wird in der Syntax dadurch ausgedrückt, dass die Bedingung **unten** statt **oben** in der Schleife steht. Das Programm führt also zuerst den Block aus und prüft dann, ob eine weitere Iteration stattfinden soll.
Der letzte Punkt kann zu subtilen Unterschieden im Codeverhalten führen. Vergleiche:
MyNumber = 42
while MyNumber < 42 do
MyNumber = MyNumber + 1
end
und
MyNumber = 42
repeat
MyNumber = MyNumber + 1
until MyNumber >= 42
In der ersten Variante hat ''MyNumber'' zum Schluss den Wert ''42'', in der zweiten den Wert ''43''! Dies ist das Resultat davon, dass die **repeat-until**-Schleife immer mindestens 1 mal ausgeführt wird. Obwohl die beiden Schleifenbedingungen äquivalent sind, sind die Ergebnisse unterschiedlich.
Es gibt einige Fälle, in denen eine **repeat-until**-Schleife sinnvoll ist. In den meisten Fällen kannst du aber davon ausgehen, dass eine **while**-Schleife ausreicht.
Auch mit **repeat-until** sind Endlosschleifen möglch. Stelle deshalb sicher, dass die Schleifenbedingung immer **true** werden kann!
----
===== Abgrenzung zu Funktionen =====
Im Artikel zu [[ scripting:tutorials:level1:functions_blocks |Funktionen]] haben wir beschrieben, dass Funktionen dazu da sind, Codeteile wiederverwertbar und wiederholt ausführbar zu machen. Schleifen scheinen eine ähnliche Funktion zu erfüllen, da auch hier kleine Codeteile wiederholt ausgeführt werden. Warum braucht man also beides?
Funktionen dienen primär dazu, Teilen von Code einen **Namen** zu geben. Dadurch erhält der Code Struktur und wird besser lesbar. Funktionen können außerdem mit Parametern arbeiten und ein Ergebnis zurückgeben (''return''), was mit Schleifen nur über Umwege geht.\\
Schleifen hingegen ersparen dem Programmierer Schreibarbeit, da der Inhalt der Schleife nicht mehrmals, sondern nur einmal aufgeschrieben werden muss. Auch laufen viele Berechnungsvorschriften, wie das Beispiel oben mit dem größten gemeinsamen Teiler, **bis sie fertig sind** und keine feste Anzahl an Schritten.
Tatsächlich haben Schleifen und Funktionen aber einen gemeinsamen historischen Ursprung. Wer möchte, kann sich dazu den [[ https://en.wikipedia.org/wiki/Goto |Wikipedia-Artikel zu Goto]] anschauen. Lua bietet keine Funktionalität für Goto (und das ist auch gut so).
----
Mit sogenannten //Tables// (= Tabellen) werden im nächsten Kapitel eine mächtige und flexible Datenstruktur vorgestellt.
[[ scripting:tutorials:level1:variable_scope | Voriges Kapitel: Lokale und Globale Variablen ]]\\
[[ scripting:tutorials:level1:tables | Nächstes Kapitel: Tables ]]\\
[[ scripting:tutorials:level1:loops | Zurück nach oben ]]