Im Kapitel zu Schleifen in Ebene 1 haben wir die verschiedenen Schleifentypen, die Lua anbietet, eingeführt. Im Kapitel zu 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 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:
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 (String) und einer Hausnummer (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 ( 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.
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 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.
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.
Im Artikel zur 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 ( link einfügen) werden wir sehen, dass die Implementierung von pairs
tatsächlich auf next
basiert.
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.
Voriges Kapitel: Die Lua-Standardbibliothek
Nächstes Kapitel: Weiteres zu Funktionen
Zurück nach oben