======Weiteres zu Schleifen======
Im [[ scripting:tutorials:level1:loops |Kapitel zu Schleifen in Ebene 1]] haben wir die verschiedenen Schleifentypen, die Lua anbietet, eingeführt. Im Kapitel zu [[ scripting:tutorials:level1:tables#nutzung_von_schleifen |Tables]] haben wir eine **for**-Schleife benutzt, um durch ein numerisches Table zu iterieren.
In diesem Kapitel wollen wir unser Verständnis von Schleifen erweitern und mit ihrer Hilfe auch [[ scripting:tutorials:level1:tables#tables_als_woerterbuecher |assoziative Tables]] durchlaufen. Außerdem besprechen wir das Schlüsselwort ''break'', das zusätzliche Kontrolle in Schleifen erlaubt.
----
**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 ]]
----
=====For-Schleifen mit Iteratoren=====
Neben der bereits bekannten //numerischen// **for**-Schleife bietet Lua eine sogenannte //generische// **for**-Schleife an. Generische **for**-Schleifen zeichnen sich dadurch aus, dass nicht nur Zahlenreihen, sondern beliebige "Abfolgen" von Werten durchlaufen werden. Diese Abfolgen können ihrerseits nicht nur aus einzelnen Werten pro Iteration, sondern aus mehreren gleichzeitig bestehen.
Das wird beispielsweise notwendig, wenn man mehrere Adressen aufzählen will: Eine Adresse besteht nicht aus einzelnen Zahlen/Werten wie z.B. ''1, 2, 3'' usw, sondern aus einer Straße ([[ scripting:tutorials:level1:variables#strings |String]]) und einer Hausnummer ([[ scripting:tutorials:level1:variables#zahlen |Zahl]]). Um durch alle gültige Adressen wie ''Eichhornweg 1-10, 20-25'' und ''Gartenstraße 1-10, 20-25'' zu iterieren, müssen demnach in jeder Iteration zwei Werte entstehen, die sogar von unterschiedlichem Datentyp sind.
Mit der Möglichkeit, beliebige Abfolgen zu durchlaufen, obliegt es allerdings dem Programmierer, diese Abfolgen zu definieren, da Lua unmöglich für alle denkbaren Anwendungszwecke eine zusätzliche Schreibweise wie die der numerischen Schleife bereitstellen kann. Deshalb müssen generische Schleifen mittels sogenannter //Iteratoren// definiert werden. Da Iteratoren nicht sehr leicht zu durchschauen sind, wird deren Konstruktion erst in Ebene 3 (FIXME link einfügen) genauer beschrieben. Im verlinkten Kapitel schreiben wir einige leichte und etwas komplexere Iteratoren. Für dieses Kapitel sollen die Iteratoren genügen, die bereits in Lua integriert sind.
\\
====pairs====
Der wichtigste Iterator, um assoziative Tables zu durchlaufen, ist ''pairs''. Mit ''pairs'' wird durch ein komplettes Table iteriert und in jedem Schritt, sowohl Key **als auch** Value zurückgegeben.
**for**-Schleifen mit Iteratoren starten mit der Bezeichnung aller lokalen Variablen, die pro Iteration erzeugt werden. Darauf folgt das Schlüsselwort **in** und der Iterator mit allen notwendigen Parametern. In unserem Fall gibt der Iterator zwei Werte, den Key und den Value zurück und braucht als Parameter das Table. In diesem Beispiel verwenden wir das Wörterbuch aus [[ scripting:tutorials:level1:tables#tables_als_woerterbuecher |Ebene 1]]:
Dictionary = {
Computer = "Rechner",
Mouse = "Maus",
Keyboard = "Tastatur",
Screen = "Bildschirm"
}
for Key, Value in pairs(Dictionary) do
print(Key.." bedeutet "..Value)
end
**Wichtig**: Die Reihenfolge, in der die Elemente im Table mit ''pairs'' durchlaufen werden, ist nicht fest vorgegeben und kann jedes Mal unterschiedlich sein! Wenn du in der Lua-Demo wiederholt auf "run" klickst, wird sich der Output im unteren Fenster wahrscheinlich häufig ändern.
\\
====ipairs====
Der zweite Iterator, auf den wir eingehen wollen, ist ''ipairs''. Im Gegensatz zu ''pairs'' wird ''ipairs'' dazu benutzt, numerische Tables zu durchlaufen:
NumberNames = {
[1] = "One",
[2] = "Two",
[3] = "Three",
[4] = "Four"
}
for i, Value in ipairs(NumberNames) do
print(i.." is spelled "..Value)
end
**Wichtig**: Die Reihenfolge der Iterationen bei ''ipairs'' ist fest vorgegeben und erfolgt immer von 1 bis //n//. Allerdings unterliegt dieser Iterator den gleichen "Einschränkungen" wie ''table.getn''! Das heißt, dass die Indexfolge nicht unterbrochen sein darf und nicht-numerische Keys ignoriert werden:
NumberNames = {
[1] = "One",
[2] = "Two",
[4] = "Four"
}
-- Diese Schleife wird Index 4 nicht erreichen, da Index 3 fehlt
for i, Value in ipairs(NumberNames) do
print(i.." is spelled "..Value)
end
NumberNames = {
[1] = "One",
["Pan"] = "Cakes",
[2] = "Two",
[3] = "Three"
}
-- Diese Schleife wird nur die numerischen Indizes beachten
for i, Value in ipairs(NumberNames) do
print(i.." is spelled "..Value)
end
**Hinweis**: Natürlich kann für numerische Tables auch ''pairs'' verwendet werden. Dann ist die Reihenfolge der Iterationen aber wieder undefiniert.
\\
====Äquivalente while-Schleife====
Im Artikel zur [[ scripting:tutorials:level2:lua_library |Lua-Standardbibliothek]] wurde die Funktion ''next'' vorgestellt, die ein nächstes Key-Value-Paar eines assoziativen Tables zurückgibt. Wenn kein weiteres Element existiert, wird **nil** zurückgegeben. ''next'' kann in einer **while**-Schleife so verwendet werden, um die gleiche Funktionalität wie bei der **for**-Schleife mit ''pairs'' zu implementieren:
Dictionary = {
Computer = "Rechner",
Mouse = "Maus",
Keyboard = "Tastatur",
Screen = "Bildschirm"
}
local Key, Value = next(Dictionary)
-- Wenn Value nil ist, wird das von der while-Schleife wie false interpretiert, die
-- nächste Iteration findet in dem Fall also nicht statt
-- Falls das Table leer ist, wird die Schleife komplett übersprungen, weil next sofort nil zurückgibt
while Value do
print(Key.." bedeutet "..Value)
Key, Value = next(Dictionary, Key)
end
Wie bei der **for**-Schleife mit ''pairs'' ist die Reihenfolge, in der diese **while**-Schleife durch das Table geht, nicht vordefiniert. Im Kapitel zu Iteratoren (FIXME link einfügen) werden wir sehen, dass die Implementierung von ''pairs'' tatsächlich auf ''next'' basiert.
----
=====Schleifen abbrechen=====
Mit dem Schlüsselwort **break** kann eine Schleife abgebrochen werden, bevor die Schleifenabbruchbedingung erfüllt ist. Das geschieht normalerweise unter einer Bedingung, die im Schleifenblock definiert ist:
NumberNames = {
[1] = "One",
[2] = "Two",
[3] = "Three",
[4] = "Four"
}
for i = 1, 4 do
print(NumberNames[i])
-- Wenn i == 2 ist...
if i == 2 then
print("Genug gezählt!")
-- ...beende die Schleife vorzeitig
break
end
end
Beachte dabei, dass mit **break** nur die **innerste** Schleife, in der es verwendet wird, abbricht. Falls du geschachtelte Schleifen definierst, laufen alle äußeren Schleifen nach **break** weiter:
-- Dies ist die verschachtelte Schleife aus Ebene 1, die eigentlich alle Summenpaare für 1 <= a, b <= 10 bildet.
for a = 1, 10 do
NumberString = ""
for b = 1, 10 do
-- In diesem Fall wird die innere Schleife abgebrochen, wenn die Summe großer als 7 ist
-- Die äußere Schleife läuft weiter
-- Das hat den Effekt, dass in jeder Zeile nur bis 7 gezählt wird (Abbruchbedingung), jede Zeile
-- aber mit einer höheren Zahl beginnt (äußere Schleife, die a erhöht, läuft weiter)
if (a + b) > 7 then
break
end
NumberString = NumberString..(a + b).." "
end
print(NumberString)
end
**Achtung**: Wie bei **return** muss auch nach **break** immer ein **end** folgen, es dürfen danach keine weiteren Befehle gegeben werden. Die Schleife ist nach **break** zu Ende, jede weitere Instruktion führt zu einem Syntaxfehler!
Schleifen abzubrechen ist beispielsweise nützlich, wenn man ein Table nach einem bestimmten Wert durchsucht. Unser Wörterbuch erlaubt nur Übersetzungen von Englisch nach Deutsch. Falls wir dennoch einmal zu einem deutschen Begriff den englischen suchen, müssen wir das Table nach dem passenden Wort durchsuchen:
Dictionary = {
Computer = "Rechner",
Mouse = "Maus",
Keyboard = "Tastatur",
Screen = "Bildschirm"
}
-- Nach dem Wort in der Variable Item wird gesucht
local Item = "Tastatur"
-- Noch haben wir nichts gefunden, sodass die Übersetzung nil ist
local Translation = nil
for Key, Value in pairs(Dictionary) do
if Value == Item then
Translation = Key
break
end
end
-- Unsere Suche kann gescheitert sein. Falls kein Wert gepasst hat, ist die Variable Translation immer noch nil
-- Das bedeutet, dass es von if wie false interpretiert wird
-- Falls unsere Suche erfolgreich war, ist Translation ein string, der wie true interpretiert wird
if Translation then
print(Item.." bedeutet "..Translation)
else
print("Keine Übersetzung gefunden :(")
end
In diesem Fall ist es nicht unbedingt notwendig, die Schleife vorzeitig abzubrechen. Wenn das Wörterbuch jedoch eine fünfstellige Anzahl Wörter enthält und wir das richtige schon nach 4 Versuchen entdecken, sparen wir uns durch den Abbruch viele Iterationen und damit Rechenleistung.
Schleifen können auch mit **return** abgebrochen werden. Im folgenden Beispiel wollen wir nicht rückwärts übersetzen, sondern nur wissen, ob das Wort im Wörterbuch enthalten ist:
function CanTranslate(_Dictionary, _Word)
-- Für diesen Anwendungsfall brauchen wir den Key nicht zu kennen. Es ist guter Stil, mit dem
-- Unterstrich anzuzeigen, dass dieser Wert ignoriert wird. Auf der Logikebene macht es keinen
-- Unterschied, für den Leser wird aber schneller ersichtlich, welche Variablen von Bedeutung sind
for _, Value in pairs(_Dictionary) do
if Value == _Word then
-- Wenn unser Wort gefunden wurde, gib true zurück
return true
end
end
-- Wenn am Ende der Schleife unser Wort nicht gefunden wurde, ist es im Wörterbuch nicht enthalten
return false
end
print(CanTranslate(Dictionary, "Latzhose"))
----
Im nächsten Kapitel widmen wir uns Funktionen und den vielfältigen Möglichkeiten, die Lua bietet, um sie zu nutzen.
[[ scripting:tutorials:level2:lua_library | Voriges Kapitel: Die Lua-Standardbibliothek]] \\
[[ scripting:tutorials:level2:functions | Nächstes Kapitel: Weiteres zu Funktionen ]] \\
[[ scripting:tutorials:level2:loops | Zurück nach oben ]]