EINFÜHRUNG |
Manchmal musst du Werte speichern, die zusammen gehören, aber deren genaue Anzahl du beim Erstellen des Programms nicht kennst. Du brauchst also eine Datenstruktur, in der du mehrere Werte speichern kannst. Die Struktur sollte so flexibel sein, dass sie auch die Reihenfolge der hinzugefügten Werte berücksichtigt. Es ist naheliegend, eine Aneinanderreihung von einfachen Behältern dafür einzusetzen, von der du bereits gehört hast, nämlich eine Liste. Hier erfährst du nun ausführlich, wie man mit Listen umgeht. Eine Liste mit 5 Elementen
Eine Liste besteht aus einzelnen Elementen, die nacheinander angeordnet sind. Im Gegensatz zu einer unstrukturierten Menge von Elementen gibt es also ein erstes und ein letztes Element und alle anderen, ausser dem letzten, haben einen Nachfolger. Listen (und ähnliche Container) sind für die Programmierung enorm wichtig. Die möglichen Operationen mit Listen sind sehr anschaulich. Die wichtigsten sind:
In Python kannst du in Listen beliebige Daten speichern, also nicht nur Zahlen. Die einzelnen Elemente können sogar einen unterschiedlichen Typ haben, du kannst also beispielsweise Zahlen und Buchstaben in der gleichen Liste speichern. |
NOTENLISTE |
Eine Liste kannst du wie eine Variable auffassen. Sie hat also auch einen Namen und einen Wert, nämlich ihre Elemente. Du erzeugst sie mit einem eckigen Klammerpaar, z.B. erzeugt li = [1, 2, 3] eine Liste mit den Elementen 1, 2 und 3. Eine Liste kann auch leer sein. Du definierst eine leere Liste mit li = []. Eine typische Verwendung von Listen ist ein Notenbüchlein, wo du die Noten in einem bestimmten Schulfach eingetragen hast, sagen wir einmal Biologienoten. Zu Beginn des Semesters hast du eine leere Liste, in Python ausgedrückt bioNoten = []. Das Hineinschreiben von Noten entspricht dem Hinzufügen von Listenelementen, was du in Python mit dem Befehl append() machst, für eine Note 5 also: bioNoten.append(5). Du kannst die Liste jederzeit mit einem print-Befehl ansehen, du schreibst einfach print bioNoten. Willst du den Notendurchschnitt berechnen, so musst du die Liste durchlaufen. Das kannst du sehr einfach und elegant mit einer for-Schleife machen, denn kopiert der Reihe nach jeden Listenwert in die Variable note und du kannst diese im Schleifenkörper verwenden. bioGrades = [] bioGrades.append(5.5) print(bioGrades) bioGrades.append(5) print(bioGrades) bioGrades.append(5.5) print(bioGrades) bioGrades.append(6) print(bioGrades) count = 0 for note in bioGrades: count += note print("Average: " + str(count / len(bioGrades))) |
MEMO |
Mit der Methode append() fügst du neue Elemente am Ende der Liste an. Die eingebaute Funktion len() gibt dir die aktuelle Länge der Liste zurück. Beachte den interessanten Trick mit der Variable count, wie man die Summe bildet, um dann den Durchschnitt zu berechnen. Du kannst aber auch mit der eingebauten Funktion sum(bioNoten) die Summe direkt erhalten. |
LISTE MIT FESTER ANZAHL VON ELEMENTEN |
Oft ist dir bereits bei der Erstellung des Programms bekannt, wie lang ein Listenbehälter sein muss und dass alle Elemente denselben Datentyp haben. In vielen Programmiersprachen nennt man eine solche Datenstruktur einen Array. Auf die einzelnen Elemente greift man üblicherweise über ihren Index zu. In Python gibt es diesen Datentyp mit festerer Länge nicht und man verwendet dazu eine Liste.
from gpanel import * pA = [0, -3] pB = [5, -3] pC = [0, 7] pD = [-5, 7] makeGPanel(-10, 10, -10, 10) line(-10, 0, 10, 0) line(0, -10, 0, 10) polygon = [0] * 4 # list with 4 elements, initialized with 0 polygon[0] = pA polygon[1] = pB polygon[2] = pC polygon[3] = pD for i in range(4): k = i + 1 if k == 4: k = 0 line(polygon[i], polygon[k]) |
MEMO |
Wenn du bei der Erstellung des Programms die Listenlänge bereits kennst, erzeugst du eine Liste mit den Initialisierungswerten 0 und greifst dann über den Index auf die Elemente zu. Merke dir, dass der Index des ersten Elements 0 und der Index des letzten Elments len(list) - 1 ist. |
ELEMENTE EINFÜGEN UND LÖSCHEN |
Das Programm zeigt, wie eine Textverarbeitung funktioniert. Die eingegebenen Buchstaben werden in eine Buchstabenliste eingefügt. Es ist klar, dass man zu Beginn nicht weiss, wieviele Buchstaben du eingeben wirst, also ist eine Liste die ideale Datenstruktur. Zudem siehst du einen Textcursor, der mit einem Mausklick an irgend eine Stelle des Text gesetzt werden kann.
from gpanel import * BS = 8 SPACE = 32 DEL = 127 def showInfo(key): text = "List length = " + str(len(letterList)) if key != "": text += ". Last key code = " + str(ord(key)) setStatusText(text) def updateGraphics(): clear() for i in range(len(letterList)): text(i, 2, letterList[i], Font("Courier", Font.PLAIN, 24), "blue", "light gray") line(cursorPos - 0.2, 1.7, cursorPos - 0.2, 2.7) def onMousePressed(x, y): setCursor(x) updateGraphics() def setCursor(x): global cursorPos pos = int(x + 0.7) if pos <= len(letterList): cursorPos = pos makeGPanel(-1, 30, 0, 12, mousePressed = onMousePressed) letterList = [] cursorPos = 0 addStatusBar(30) setStatusText("Enter Text. Backspace to delete. Mouse to set cursor.") lineWidth(3) while True: delay(10) key = getKey() if key == "": continue keyCode = ord(key) if keyCode == BS: # backspace if cursorPos > 0: cursorPos -= 1 letterList.pop(cursorPos) elif keyCode >= SPACE and keyCode != DEL: letterList.insert(cursorPos, key) cursorPos += 1 updateGraphics() showInfo(key) |
MEMO |
Du hast bereits früher gelernt, dass du auf einzelne Elemente einer Liste über den Listenindex zugreifen kannst, der bei Null beginnt. Du verwendest dazu die eckigen Klammern, also letterList[i]. Der Index muss immer im Bereich 0 und Listenlänge - 1 liegen. Verwendest du eine for in range()-Struktur, so ist der Stoppwert gerade die Länge der Liste. Mit dem Index darfst du aber nie auf ein Element zugreifen, dass es gar nicht gibt. Fehler mit ungültigen Listenindizes gehören zu den häufigsten Programmierfehlern. Passt du nicht auf, so kriegst du Programme, die unter Umständen manchmal funktionieren und manchmal absterben. Um zu testen, welche Taste gedrückt wurde, kannst du getKey() verwenden. Diese Funktion kehrt nach dem Aufruf sofort zurück und liefert den Buchstaben der zuletzt gedrückten Taste als String oder einen leeren String, falls keine Taste gedrückt wurde. |
BEREITS EIN PROFIPROGRAMM |
Mit deinem Wissen bist nun bereits in der Lage, einen Grafen zu visualisieren [mehr... Graph Drawing ist ein ganzes Themengebiet der Informatik] Die Aufgabenstellung (auch "Pflichtenheft" genannt) ist die Folgende: In einem Grafikfenster kannst du mit rechtem Mausklick gefüllte Kreise erzeugen, die als Knoten eines Grafen aufgefasst werden, wo die Knoten untereinander mit Strecken, Kanten genannt, verbunden sind. Fährst du mit der Maus auf einen Knoten, so kannst du diesen mit einem linken Maus-Press und nachfolgendem Drag herumziehen und der Graf wird ständig aktualisiert. Klickst du mit mit einem rechten Mausklick auf einen bereits bestehenden Knoten, so wird dieser entfernt.
Die Knoten selbst sind Punkte mit zwei Koordinaten P(x, y), die du - wie bereits früher - mit einer Punktliste [x, y] modellierst. Es liegt also eine Liste vor, die als Elemente wiederum Listen (allerdings mit fester Länge 2) enthält. Das Verbinden der Knoten erreichst du über eine doppelte for-Schleife, wobei du acht geben musst, dass die Knoten nur einmal verbunden werden. from gpanel import * def drawGraph(): clear() for pt in graph: move(pt) fillCircle(2) for i in range(len(graph)): for k in range(i, len(graph)): line(graph[i], graph[k]) def onMousePressed(x, y): pt = [x, y] graph.append(pt) drawGraph() graph = [] makeGPanel(0, 100, 0, 100, mousePressed = onMousePressed) |
from gpanel import * def drawGraph(): clear() for i in range(len(graph)): move(graph[i]) if i == iNode: setColor("red") else: setColor("green") fillCircle(2) setColor("blue") for i in range(len(graph)): for k in range(i, len(graph)): line(graph[i], graph[k]) def onMousePressed(x, y): global iNode if isLeftMouseButton(): iNode = near(x, y) if isRightMouseButton(): index = near(x, y) if index != -1: graph.pop(index) iNode = -1 else: pt = [x, y] graph.append(pt) drawGraph() def onMouseDragged(x, y): if isLeftMouseButton(): if iNode == -1: return graph[iNode] = [x, y] drawGraph() def onMouseReleased(x, y): global iNode if isLeftMouseButton(): iNode = -1 drawGraph() def near(x, y): for i in range(len(graph)): p = graph[i] d = (p[0] - x) * (p[0] - x) + (p[1] - y) * (p[1] - y) if d < 10: return i return -1 graph = [] iNode = -1 makeGPanel(0, 100, 0, 100, mousePressed = onMousePressed, mouseDragged = onMouseDragged, mouseReleased = onMouseReleased) addStatusBar(20) setStatusText("Right mouse button to set nodes, left button to drag") |
MEMO |
Das Programm ist voll eventgesteuert. Der Hauptblock definiert lediglich zwei globale Variablen und initialisiert das Grafikfenster. Bei jeder Aktion wird das ganze Grafikfenster gelöscht und mit der aktuellen Situation des Grafen neu aufgebaut. Die wichtigsten Operationen mit Listen
Die Notation mit den eckigen Klammern nennt man Slice-Operation. start und end sind Indizes der Liste. Die Slice-Operation funktioniert ähnlich wie die Parameter von range(). |
AUFGABEN |
|
ZUSATZSTOFF: |
VERÄNDERLICHE UND UNVERÄNDERLICHE DATENTYPEN |
In Python werden alle Datentypen als Objekte gespeichert, also auch die numerischen Typen (integer, float, long, complex). Wie du weisst, greifst du auf ein Objekt über seinen Namen zu. Man sagt auch, dass der Name auf das Objekt verweist oder an das Objekt gebunden wird. Eine solche Variable heisst darum auch Referenzvariable. Auf ein bestimmtes Objekt kann mehr als ein Name verweisen. Man nennt einen weiteren Namen auch ein Alias. Wie du damit umgehst, zeigt das folgende Beispiel.
from gpanel import * makeGPanel(-10, 10, -10, 10) a = [0, 0] a_alias = a b = [0, 5] c = [5, 0] fillTriangle(a, b, c) a[0] = 1 setColor("green") fillTriangle(a_alias, b, c) |
from gpanel import * makeGPanel(-10, 10, -10, 10) xA = 0 yA = 0 xA_alias = xA yA_alias = yA xB = 0 yB = 5 xC = 5 yC = 0 fillTriangle(xA, yA, xB, yB, xC, yC) xA = 1 setColor("green") fillTriangle(xA_alias, yA_alias, xB, yB, xC, yC) |
Was ist die Erklärung dafür? Der Grund liegt darin, dass Zahlen unveränderliche Objekte sind und die Anweisung xA = 1 eine neue Zahl erzeugt. xA_alias ist also immer noch 0. Der Unterschied zwischen unveränderlichen und veränderlichen Datentypen zeigt sich auch bei der Parameterübergabe an Funktionen. Wird ein unveränderliches Objekt übergeben, so kann im Innern der Funktion das übergebene Objekt nicht verändert werden. Wird hingegeben ein veränderliches Objekt übergeben, so kann die Funktion das Objekt verändern. Eine solche Veränderung nennt man einen Neben- oder Seiteneffekt. Es gehört zum guten Programmierstil, Seiteneffekte nur sparsam einzusetzen, weil sie zu schwer auffindbaren Fehlverhalten führen können.
from gpanel import * def translate(pA, pB, pC): pA[0] = pA[0] + 5 pA[1] = pA[1] + 2 pB[0] = pB[0] + 5 pB[1] = pB[1] + 2 pC[0] = pC[0] + 5 pC[1] = pC[1] + 2 makeGPanel(-10, 10, -10, 10) a = [0, 0] b = [0, 5] c = [5, 0] fillTriangle(a, b, c) translate(a, b, c) setColor("green") fillTriangle(a, b, c) |
MEMO |
In Python werden alle Daten als Objekte gespeichert, aber gewisse Objekte gelten als unveränderlich (immutable). Es sind dies: numerische Datentypen, string, byte, tuple. Alle anderen Datentypen sind veränderlich (mutable). Weist man einer Variablen eines unveränderlichen Datentyp einen neuen Wert zu, so wird ein neues Objekt erzeugt. Werden einer Funktion veränderliche Objekte übergeben, so kann die Funktion die Objekte verändern, unveränderliche Objekte sind aber vor solchen Veränderungen geschützt. |