Benutzer-Werkzeuge

Webseiten-Werkzeuge


scripting:tutorials:level2:loops

Weiteres zu Schleifen

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:

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 (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 (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 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 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.

Voriges Kapitel: Die Lua-Standardbibliothek
Nächstes Kapitel: Weiteres zu Funktionen
Zurück nach oben

scripting/tutorials/level2/loops.txt · Zuletzt geändert: 2023/09/07 14:00 von fritz_98