Programmieren lernen mit Lua: Lektion 05a: Tabellen als Listen und Wörterbücher
Übersicht | Vorherige Lektion | Nächste Lektion
ARVE Error: src mismatch
provider: youtube
url: https://www.youtube.com/watch?v=wBqprJgAi4U&t=0s
src in org: https://www.youtube-nocookie.com/embed/wBqprJgAi4U?feature=oembed&width=840&height=1000&discover=1
src in mod: https://www.youtube-nocookie.com/embed/wBqprJgAi4U?width=840&height=1000&discover=1
src gen org: https://www.youtube-nocookie.com/embed/wBqprJgAi4U?start=0
Die fünfte und letzte Lektion unserer kleinen Lua Einführung behandelt das Thema Tabellen. Dieses Thema ist besonders spannend und vielseitig. Tatsächlich wurde die Programmiersprache Lua überhaupt entwickelt, um mit tabellenartigen Daten besonders flexibel umgehen zu können. Wegen des großen Umfangs haben wir die Lektion in zwei Teile, a und b, aufgeteilt.
Tabellen sind das Schweizer Taschenmesser unter den Lua-Datentypen. Die möglichen Anwendungsfälle von Tabellen sind unüberschaubar vielfältig. So können sie verwendet werden, um etwa folgende Dinge zu speichern und in eurem Programm zu repräsentieren:
- Der Inhalt einer Tasche
- Alle in einer Physiksimulation umherfliegenden Partikel
- Alle in einer Pizzeria-Simulation vorhandenen Extra-Beläge
- Alle Funktionen, die für eine Pizzeria-Simulation benötigt werden
- Die Eigenschaften eines Bausteins in Minetest
- Eine Spielerin mit Eigenschaften wie
name
,punktestand
,position
etc… - Alle Funktionen, die das Verhalten der Spielerin steuern, zum Beispiel
kaempfen()
oderposition_wechseln()
Tabellen als Listen oder Arrays
Starten wir mit dem einfachsten Anwendungsfall. Eine Tabelle kann sich wie eine Liste von Werten verhalten. So können wir den Inhalt einer Tasche wie folgt als Tabelle darstellen:
> tasche = {"buch", "brille", "kekse"}
tasche
ist vom Typ table
:
> type(tasche) table
Der Zugriff auf die einzelnen Elemente läuft wie folgt:
> tasche[1] buch > tasche[2] brille > tasche[3] kekse
Die Zahlen 3, 4 und 5 nennen wir in diesem Fall auch Indizes, Einzahl Index: Unter dem Index 1 ist der String "buch"
gespeichert.
Wenn Du ein nicht existierendes Element abfragst, erhältst Du nil
. Wie in dem folgenden Fall – unter dem Index 4 ist nichts gespeichert:
> tasche[4] nil
Lua verhält sich hier genau so wie bei Bezeichnern, die auf nichts verweisen. Zur Erinnerung: nil
steht für „nihil“, das ist lateinisch für „Nichts“. nil
ist der universelle Platzhalter für Bezeichner, denen kein Wert zugewiesen ist.
Du kannst nachträglich ein Element hinzufügen:
> tasche[4] = "bleistift"
Jetzt befindet sich an der vierten Position der Liste nicht mehr nil
, sondern der String "bleistift"
:
> tasche[4] bleistift
Genau so kannst Du ein Element durch ein anderes ersetzen. Nach der Ausführung wurden die Kekse durch einen Apfel ersetzt:
> tasche[3] = "apfel"
Exkurs: Tabellen sind veränderlich
Die letzten Beispiele zeigen: Tabellen sind im Unterschied zu allen anderen Typen in der Programmiersprache Lua veränderlich. An dieser Stelle ist ein kleiner Einschub angesagt, der zeigt, was das bedeutet:
Wenn Du eine Tabelle t anlegst …
> t = {"rhabarber"} > t[1] rhabarber
… und einem Bezeichner t2 die Tabelle t zuweist …
> t2 = t
… und dann t2 veränderst …
> t2[1] = "rhododendron"
… ist auch die Tabelle t verändert:
> t[1] rhododendron
Dieses auf den ersten Blick seltsame Verhalten ist auf den zweiten Blick sehr logisch: t
und t2
sind hier Bezeichner, die auf den selben Wert verweisen. Daher werden Veränderungen auf der Tabelle t
auch auf t2
wirksam und umgekehrt. Es gibt schlichtweg nur eine Tabelle.
Die Bezeichner t und t2 verweisen beide auf dieselbe Tabelle
Die Anzahl der Elemente
Zurück zu den „listenartigen“ Tabellen: Ein vorangestelltes #
liefert die Anzahl der enthaltenen Elemente:
> #tasche 4
Mithilfe dieses Tricks kannst Du Elemente an die jeweils letzte Position anhängen. Wenn tasche
4 Elemente enthält, dann ist #tasche + 1
gleich 5, also die erste freie Position:
> tasche[#tasche + 1] = "block" > tasche[#tasche + 1] = "mineralwasser" > #tasche 6 > tasche[5] block > tasche[6] mineralwasser
Es ist übrigens nicht verboten, folgendes zu machen, also den Index 1000 zu benutzen, auch wenn die Indizes von 7 bis 999 frei sind:
> liste[1000] = "portemonnaie"
Auch negativer Indices, hier -1000, sind erlaubt:
> liste[-1000] = "kühlakku"
Allerdings liefert #tasche
danach immer noch 6, obwohl jetzt eigentlich 8 Sachen in der tasche
sind:
> #tasche 6
Es ist sogar möglich, statt ganzer Zahlen auch Dezimalzahlen, Strings oder gar andere Tabellen als Schlüssel zu verwenden. Die zuletzt genannten „Tricks“ werden in anderen Situationen sehr viel Sinn ergeben. Nicht aber, wenn es um listenartige Tabellen geht.
Listenartige Tabellen haben folgende Eigenschaften:
- Alle Indizes sind positive ganze Zahlen
- Der niedrigste Index ist 1
- Es gibt keine „Lücken“, d.h.: Wenn unter den Indizes 3 und 5 etwas gespeichert ist, dann muss auch unter 4 etwas gespeichert sein.
Übrigens wird sowohl in der englischsprachigen als auch in der deutschen Lua-Literatur gelegentlich von „array-like“ bzw. „array-artigen“ Tabellen u.ä. gesprochen. Wir haben uns hier für den deutschen Begriff „Liste“ entschieden, meinen aber dasselbe.
Den Inhalt von listenartigen Tabellen aufzählen
Zur Erinnerung: In Lektion 03 haben wir gezeigt, wie wir in einer for
-Schleife einen Wertebereich durchlaufen. So läuft in der folgenden Schleife der Index von 1 bis 10 und gibt entsprechend die Zahlen von 1 bis 10 aus:
for index = 1, 10 do print(index) end
Wir können mittels einer for
-Schleife auch den Inhalt einer listenartigen Tabelle aufzählen. In unserem konkreten Fall der Tasche ersetzten wir
index = 1, 10
durch
index, wert in ipairs(tasche)
Ihr könnt euch vorstellen, dass ipairs(tasche)
in jedem Durchgang der for
-Schleife einen Eintrag in die Schleife einspeist. Jeder Eintrag besteht aus einem Index (zum Beispiel 1
) und einem Wert (zum Beispiel "buch"
). In dem oben genannten Fall haben wir dann im Schleifenkörper Zugriff auf Index und Wert über die entsprechenden Bezeichner. Wie wir sie nennen, ist uns überlassen. Wir könnten, wenn das Array zum Beispiel Ufos in einem Weltraumballerspiel enthält, schreiben:
i, ufo in ipairs(alle_ufos)
Im folgenden durchlaufen wir sämtliche Einträge in der Tabelle tasche
. Es gilt:
Beim ersten Durchgang der for
-Schleife ist index
1 und wert
"buch"
Beim zweiten Durchgang ist index
2 und wert
"brille"
Beim dritten Durchgang ist index
3 und wert
"stift"
Da tasche
nur drei Elemente enthält, ist nach dem dritten Durchgang Schluss: Das Programm springt aus der for
-Schleife heraus.
Im Block der for
-Schleife können wir wie gehabt auf index
und wert
zugreifen:
tasche = {"buch", "brille", "stift"} for index, wert in ipairs(tasche) do print(index .. ": " .. wert) end
Die Ausgabe des Programms sieht so aus:
1: buch 2: brille 3: stift
Aufgabe 1.1: Schreibe ein Programm, bei dem die Nutzerin in einer Schleife gefragt wird, was sie in die Tasche tun möchte. Diese Eingaben sollen in eine Tabelle tasche
eingetragen werden. Nach jedem Durchgang der Schleife soll angegeben werden, wie viele Elemente bereits in der Tasche sind:
Was möchtest Du in die Tasche tun? $ Kartoffeln Anzahl der Elemente in der Tasche: 1 Was möchtest Du in die Tasche tun? $ Quark Anzahl der Elemente in der Tasche: 2
tasche = {} while true do print("Was möchtest Du in die Tasche tun?") eingabe = io.read() tasche[#tasche+1] = eingabe print("Anzahl der Elemente in der Tasche: " .. #tasche) end
Aufgabe 1.2: Erweitere die Lösung von Aufgabe 1.1, sodass nach jeder Eingabe nicht mehr die Anzahl der Elemente genannt, sondern alle Elemente aufgezählt werden:
Was möchtest Du in die Tasche tun? $ Schraubendreher In der Tasche befinden sich: Schraubendreher Was möchtest Du in die Tasche tun? $ Hammer In der Tasche befinden sich: Schraubendreher Hammer
tasche = {} while true do print("Was möchtest Du in die Tasche tun?") eingabe = io.read() tasche[#tasche+1] = eingabe print("In der Tasche befinden sich:") for index, wert in ipairs(tasche) do print(wert) end end
Aufgabe 1.3: Erweitere die Lösung von Aufgabe 1.2: Es sollen maximal 5 Sachen in die Tasche passen. Die Aufzählung soll erst erfolgen, wenn die Tasche voll ist. Löse die Aufgabe mit einer Abfrage innerhalb der
while
-Schleife und break
.
Was möchtest Du in die Tasche tun? $ Zitronen Was möchtest Du in die Tasche tun? $ Orangen ... Die Tasche ist nun voll und enthält: Zitronen Orangen ...
tasche = {} while true do print("Was möchtest Du in die Tasche tun?") eingabe = io.read() tasche[#tasche+1] = eingabe if #tasche >= 5 then break end end print("Die Tasche ist nun voll und enthält:") for index, wert in ipairs(tasche) do print(wert) end
Elemente hinzufügen mit table.insert()
Wir haben bereits gezeigt, wie sich ein Element an das Ende der listenartigen Tabelle anhängen lässt:
tabelle[#tabelle+1] = x
Was ist aber, wenn die Reihenfolge (zum Beispiel bei einem Ranking von Lieblingsserien) eine Rolle spielt, und wir an einer anderen Stelle, am Anfang oder mitten drin, ein Element einfügen wollen?
Wir könnten das von Hand machen, indem wir alle Elemente, die nach dem Einfügepunkt kommen, um einen Schritt nach hinten verschieben:
lieblingsserien = {"Fargo", "The Prisoner", "Better call Saul"}
Ein Einfügen an der ersten Position würde dann so aussehen:
lieblingsserien[4] = lieblingsserien[3] lieblingsserien[3] = lieblingsserien[3] lieblingsserien[2] = lieblingsserien[1] lieblingsserien[1] = "WandaVision"
Das ist ziemlich mühsam! Zum Glück erleichtert uns Lua die Aufgabe mit der Funktion table.insert()
. table
ist eine Bibliothek mit Funktionen, die sich auf Tabellen anwenden lassen. table.insert()
erwartet drei Argumente:
table.insert(tabelle, position, wert) tabelle Die Tabelle, in die etwas eingefügt werden soll position Die Position der Einfügung wert Das, was eingefügt werden soll
Das nächste Beispiel zeigt die Anwendung. In diesen und den folgenden Beispielen werden wir zudem eine selbst definierte Funktion drucke_inhalt()
anwenden, die den Inhalt einer Tabelle ausgibt.
function drucke_inhalt(tabelle) for index, wert in ipairs(tabelle) do print(index .. ": " .. wert) end end lieblingsserien = {"Fargo", "The Prisoner", "Better call Saul"} table.insert(lieblingsserien, 1, "WandaVision") drucke_inhalt(lieblingsserien)
Die Ausgabe sieht so aus:
1: WandaVision 2: Fargo 3: The Prisoner 4: Better call Saul
Elemente entfernen mit table.remove()
Wir können Elemente aus einer Tabelle zu entfernen, indem wir ihnen nil
zuweisen. Das bringt bei listenartigen Tabellen aber Probleme mit sich:
aufgaben = {"Abwaschen", "Müll runterbringen", "Regale abstauben", "Saugen"} aufgaben[2] = nil
Wenn wir jetzt die Tabelle aufgaben mit for
und ipairs()
durchlaufen, bricht die Aufzählung bereits nach dem ersten Element ab:
1: Abwaschen
Das liegt daran, dass durch das „Löschen“ der zweiten Position die Tabelle nicht mehr lückenlos ist. Die Schleife bricht an der ersten Position ab, die nil
ist.
Auch hier könnten wir das Problem durch mühsames, schrittweises verschieben der nachfolgenden Elemente lösen. Auch hier bietet Lua eine vereinfachte Lösung an, und zwar mit der Funktion table.remove()
. Sie erwartet zwei Argumente: Die Tabelle, aus der etwas entfernt werden soll und den Index des zu entfernenden Elementes.
aufgaben = {"Abwaschen", "Müll runterbringen", "Regale abstauben", "Saugen"} table.remove(aufgaben, 2) drucke_inhalt(aufgaben)
Jetzt sind die Elemente nach der entfernten Position um 1 aufgerückt:
1: Abwaschen 2: Regale abstauben 3: Saugen
Aufgabe 2.1: Du hast eine Tabelle mit den Namen von Geburtstagsgästen:
gaeste = {"Anna", "Peter", "Michael", "Sabine", "Michaela"}
Mit welchen Code fügst Du an der ersten Stelle den Gast „Theodor“ ein?
table.insert(gaeste, 1, "theodor")
Aufgabe 2.2: Michael hat abgesagt, und Du möchtest in von der Liste streichen. Anstatt mühsam von Hand die entsprechende Position (bzw. Index) zu ermitteln, schreibe einen Code, der die Position automatisch ermittelt und dann das entsprechende Element entfernt. Diese Aufgabe ist etwas knifflig, daher einige Tipps:
- Du benötigst dafür
for
undipairs()
- Mache Dir die Tatsache zunutze, dass
ipairs()
sowohl den Index, als auch den Wert an diefor
-Schleife liefert - Ist in einem Schleifendurchgang
wert == "Michael"
, kannst Du den entsprechenden Index zur Entfernung des Eintrages verwenden
gaeste = {"Anna", "Peter", "Michael", "Sabine", "Maria"} for index, wert in ipairs(gaeste) do if wert == "Michael" then table.remove(gaeste, index) end end
Die Ausgabe sieht so aus, Michael steht nicht mehr in der Liste:
1: Anna 2: Peter 3: Sabine 4: Maria
Eine Tabelle als Wörterbuch
Im vorherigen Abschnitt haben wir uns mit listenartigen Tabellen beschäftigt. Eine wichtige Eigenschaft listenartiger Tabellen ist, dass die Indizes (allgemeiner: Schlüssel) positive ganze Zahlen sind. Das muß aber nicht sein. Im folgenden Beispiel sind die Schlüssel Strings. Eine Tabelle speichert englischen Übersetzungen einiger deutscher Wörter:
> woerterbuch = {} > woerterbuch["sonne"] = "sun" > woerterbuch["stern"] = "star" > woerterbuch["erde"] = "earth"
Wenn Schlüssel Strings ohne Leerzeichen sind, dann gilt folgende, verkürzte Schreibweise:
> woerterbuch.sonne = "sun" > woerterbuch.stern = "star" > woerterbuch.erde = "earth"
Anstatt die Tabelle schrittweise zu befüllen, kannst Du es auch so schreiben – für die angehenden Programmierprofis nebenbei bemerkt: das nennt man die Literal-Schreibweise:
woerterbuch = { sonne = "sun", stern = "star", erde = "earth" }
Die Abfrage der einzelnen Einträge läuft entsprechend so:
> woerterbuch["sonne"] sun
Oder, in der verkürzten Schreibweise, so:
> woerterbuch.sonne sun
Das folgende Programmbeispiel ist ein einfaches Übersetzungsprogramm, bei dem die Nutzerin ein deutsches Wort eingibt, um die englische Übersetzung zu erhalten. Gibt es zu dem eingegebenen deutschen Wort keinen Eintrag, liefert das Programm eine entsprechende Meldung.
woerterbuch = { sonne = "sun", stern = "star", erde = "earth" } while true do print("Bitte gib ein Wort ein.") eingabe = io.read() uebersetzung = woerterbuch[eingabe] if uebersetzung then print(uebersetzung) else print("Das Wort " .. eingabe .. " ist mir unbekannt") end end
Zeile 11: woerterbuch[eingabe]
liefert den Eintrag, falls vorhanden, sonst nil
. Obacht: Die Kurzschreibweise woerterbuch.eingabe
ist hier nicht möglich, weil das würde vom Lua Interpreter als woerterbuch["eingabe"]
verarbeitet werden!
Zeile 13: Nur, wenn uebersetzung
nicht nil
ist …
Zeile 14: … gibt das Programm die uebersetzung
aus …
Zeile 16: … andernfalls meldet das Programm, dass das Wort unbekannt ist
Aufgabe 3: Erweitere das letzte Programmbeispiel, sodass es neue Wörter lernen kann. Es soll, wenn ein Wort unbekannt ist, nach einer Übersetzung fragen und diese in die Tabelle woerterbuch
eintragen. Teste das Programm auf der Konsole. Es soll folgendes Verhalten zeigen:
Bitte gib ein Wort ein. $ buch Das Wort 'buch' ist mir unbekannt. Bitte gib eine Übersetzung ein. $ book Bitte gib ein Wort ein. $ buch book
woerterbuch = { sonne = "sun", stern = "star", erde = "earth" } while true do print("Bitte gib ein Wort ein.") eingabe = io.read() uebersetzung = woerterbuch[eingabe] if uebersetzung then print(uebersetzung) else print("Das Wort '" .. eingabe .. "' ist mir unbekannt. Bitte gib eine Übersetzung ein.") uebersetzung = io.read() woerterbuch[eingabe] = uebersetzung end end
Tabelleninhalte durchlaufen mit pairs()
Listenartige Tabellen können wir mit ipairs()
durchlaufen. Die Funktion pairs()
ist die allgemeinere Variante. Sie funktioniert für jede Art von Schlüssel:
woerterbuch = { sonne = "sun", stern = "star", erde = "earth" } for deutsch, englisch in pairs(woerterbuch) do print(deutsch .. ": " .. englisch) end
Navigation
Wir bedanken uns bei der Peakboard GmbH für die freundliche Unterstützung bei der Entwicklung dieses Kurses.
Peakboard ist eine All-in-One-Lösung aus Soft- und Hardware, mit der Du Daten aus unterschiedlichen Datenquellen erhebst, auswertest und in Echtzeit auf Bildschirmen visualisierst. Mit der kostenlosen Software, dem Peakboard Designer, gestaltest Du Dein individuelles Dashboard und bindest deine Datenschnittstellen an. Die Hardware, die Peakboard Box, verarbeitet und kommuniziert die Datenströme dezentral und damit ressourcenschonend direkt am Industriearbeitsplatz. Damit sorgst Du für mehr Transparenz und optimierst so ganz einfach deine Prozesse.