Benutzer-Werkzeuge

Webseiten-Werkzeuge


scripting:tutorials:level1:loops

Schleifen

Schleifen werden dazu benutzt, einen bestimmten Teil eines Skripts wiederholt auszuführen. Ähnlich zu Verzweigungen wird der Inhalt einer Schleife so lange ausgeführt, wie eine bestimmte Bedingung zutrifft. Auch bei Schleifen wird diese Bedingung mittels eines 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 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:

  1. 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
  2. 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 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 Wikipedia-Artikel zu Goto anschauen. Lua bietet keine Funktionalität für Goto (und das ist auch gut so).

scripting/tutorials/level1/loops.txt · Zuletzt geändert: 2023/05/28 14:18 von fritz_98