Kapitel 6: Anweisungen Grundlagen der Programmierung 1 Holger Karl Wintersemester 2016/2017 Inhaltsverzeichnis Inhaltsverzeichnis 1 Abbildungsverzeichnis 2 Liste von Definitionen u.ä. 6.1 Überblick . . . . . . . . . 6.2 Programm . . . . . . . . 6.3 Bedingte Ausführung: if 6.4 Schleifen: while . . . . . 6.5 Schleifen: for . . . . . . 6.6 List comprehensions . . 6.7 Geschachtelte Schleifen . . . . . . . . . . . . . . . . . . . . . 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 3 3 4 12 22 32 34 6.8 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . 41 Abbildungsverzeichnis 6.1 6.2 6.3 Flussdiagramm einer einfachen While-Schleife . . . . . . . . . goto considered harmful . . . . . . . . . . . . . . . . . . . . . . Flussdiagramm einer While-Schleife mit continue , break und else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zuweisung an Aufzählung während while-Schleife . . . . . . . Zwischenstand bei einer Iteration über ein Dict D.items() . . Selection Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.4 6.5 6.6 13 21 23 25 29 38 Liste von Definitionen u.ä. 6.1 6.2 6.3 6.1 6.2 6.4 6.3 6.5 6.6 Definition (pass) . . . . . . . . . . . . . . Definition (Anweisung: if/elif/else) Definition (Block) . . . . . . . . . . . . . Achtung (Nicht pythonisch) . . . . . . . . Achtung (Bedingung genau beachten) . . Definition (while) . . . . . . . . . . . . . Achtung (Keine korrekte Schleife!) . . . . Definition (for-Schleife) . . . . . . . . . Definition (Hilfsfunktion: range) . . . . 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 12 12 15 19 22 23 24 29 6.1. Überblick 6.1 6.1.1 Überblick Was bisher geschah • Wir können bis jetzt – Werte aufschreiben, was Objekte erzeugt – aus Objekten Ausdrücke für neue Objekte bilden * mit Aufruf von Funktionen als Beispiel – mit Namen auf Objekte referenzieren (zuweisen) * mit der Definition einer Funktion als Sonderfall • Also: Folgen von Zuweisungen – Die eine nach der anderen ausgeführt werden Aber: Kein Einfluss auf Reihenfolge der Ausführung! 6.1.2 Dieses Kapitel • Wir nehmen Einfluss auf die Reihenfolge der Ausführung von Anweisungen • Dazu brauchen wir weitere Anweisungen – Bedingungen, Schleifen • Wir verallgemeinern die Vorstellung, was eine Anweisung ist 6.2 6.2.1 Programm Ein Programm • Ein Programm ist eine Folge von Anweisungen – Ausgeführt entsprechend des Ausführungsmodells – Bis jetzt: Sequentiell, Funktionsaufruf • Um die Reihenfolge der Anweisungen zu beeinflussen, müssen wir das Ausführungsmodell erweitern 6.2.2 Anweisungen – bis jetzt • Die Zuweisung = • Die Funktionsdefinition def • Die Rückkehr aus einer Funktion return Und die Folge von Anweisungen 3 4 Liste von Definitionen u.ä. • Erinnerung: Eine Zeile, eine Anweisung! 6.2.3 Bedingte Folge von Anweisungen Idee: • Programms nicht immer bei der nächsten Anweisung fortsetzen • Sondern die Wahl der nächsten auszuführenden Anweisung von dem Wert eines Ausdrucks abhängig machen • Alternativen im Programm vorsehen 6.2.4 Schleifen von Anweisungen Idee: • In der Fortsetzung des Programms erlauben, zu vorherigen Anweisungen zurückzukehren • Meist (nicht unbedingt) abhängig vom Wert eines Ausdrucks • Wiederholungen oder Schleifen erlauben 6.3 6.3.1 Bedingte Ausführung: if Neue Anweisung: if • Die Anweisung if besteht aus zwei Teilen: – Einem Ausdruck, der auf wahr oder falsch ausgewertet wird – Eine Folge von Anweisungen – der so genannte block • Semantik: die angegebene Folge von Anweisungen nur ausführen, wenn der Ausdruck zum Zeitpunkt der Ausführung den Wert wahr hat – Auswertung des Ausdrucks einmal vor Ausführung des Blocks 6.3.2 1 2 3 4 if: Syntax if Ausdruck: Anweisung1 Anweisung2 usw. 5 6 7 # if hier zu Ende Anweisung nach if 6.3. Bedingte Ausführung: if Beobachtung • Das Ende des Ausdrucks wird durch Doppelpunkt : markiert • Die durch if kontrollierten Anweisungen werden unterhalb von if eingerückt aufgeschrieben • Der Block endet dort, wo die Einrückung endet 6.3.3 if: Syntax im Vergleich mit def • Syntax ist der von def sehr ähnlich – Schlüsselwort, dann ein Ausdruck, dann ein Doppelpunkt – Dann eingerückt die kontrollierten Anweisungen – Ende der Einrückung = Ende des Blocks (if, def, etc.) • Typisches Muster der Syntax von Python! 6.3.4 Warum Einrückung? • Einrückung ist kompakt, leicht lesbar • Erzwingt ordentliche Struktur des Codes – Keine Variationsmöglichkeiten wie in anderen Sprachen • Readability counts (PEP 20) Vergleich mit anderen Sprachen Wenn Sie andere Sprachen wie C oder Java kennen, wird Ihnen die Kompaktheit dieser Formulierung auffallen. Keine runden Klammern um den Ausdruck erforderlich, keine geschweiften Klammen, um den Beginn und das Ende der kontrollierten Anweisungen zu kontrollieren. Kein überflüssiges Wort then, was in manchen Sprachen vorkommt. Insgesamt ist die Syntax von Python sehr knapp und frugal. 6.3.5 Alternative: Wahr oder falsch? • Erweiterung: Zwei Code-Blöcke, für den Wahr- und den Falsch-Fall 1 2 3 4 5 6 7 8 9 if Ausdruck: # Block für wahr: Anweisung1 Anweisung2 ... else: # Block für falsch: Anweisung1 Anweisung2 5 6 Liste von Definitionen u.ä. ... 10 11 12 13 14 # Anweisungen nach if/else Anweisung1 ... Achtung Doppelpunkt nach else! 6.3.6 Alternative: Semantik • Der Ausdruck wird einmal ausgewertet und auf wahr oder falsch überprüft • Danach wird entweder der Wahr-Block oder der Falsch-Block ausgeführt 6.3.7 1 2 if: Beispiele if True: print("Ja!") Ja! 1 2 3 4 if not True: print("Ja!") else: print("Nein!") Nein! 6.3.8 if: Beispiel Betrag Wie Code ergänzen, um den Betrag einer Zahl zu bestimmen? 1 2 3 4 5 6 def betrag(x): """Liefere Absolutbetrag von x""" if (???): return ??? else: return ??? 6.3. Bedingte Ausführung: if 7 8 9 6.3.9 print("Betrag von 5: ", betrag(5)) print("Betrag von -3: ", betrag(-3)) Ausflug: Code testen Wie sind Sie sicher, dass Ihre Lösung stimmt? • Erste Idee: Testfälle ausprobieren • Von Hand? Automatisch! • Insbesondere: Randfälle Aber: sicher? Nein. . . 6.3.10 Test-Anweisung: assert assert: Anweisung, der ein Ausdruck folgt • Wenn Ausdruck wahr, weitermachen • Wenn Ausdruck falsch, mit Fehler abbrechen 1 2 3 4 5 def betrag(x): if (x>0): return x else: return -1 * x 6 7 8 9 10 6.3.11 assert assert assert assert 5 == betrag(5) 7 == betrag(-7) 0 == betrag(0) "abc" == betrag("abc") Tests für quersumme3 Was wären gute assert-Anweisungen für die Funktion quersumme3 aus vorherigem Kapitel? 1 2 def quersumme3(t): return t[0] + t[1] + t[2] 3 4 5 # Nuetzliche asserts? assert True 7 8 Liste von Definitionen u.ä. 6.3.12 Geschachtelte if/else Beobachtung: • if/else ist eine Anweisung • Im Block von if bzw. else dürfen Anweisungen stehen Darf dann da auch ein if/else stehen ? • Die Logik der Syntax gibt das her • Also: JA! 6.3.13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 6.3.14 Geschachtelte if/else: Allgemeiner Fall if Bedingung1: Teilblock1 if Bedingung2: Teilblock2 else: Teilblock3 # Ende des inneren if Teilblock4 else: Teilblock5 if Bedingung3: Teilblock6 else: Teilblock7 # Ende des zweiten inneren if Teilblock8 # Ende von if Geschachtelte if/else: Einfachere Fälle • Die ganzen Teilblöcke dürfen auch leer sein • Leerer Teilblock nach else? – Dann kann man das else auch weglassen und nur das if schreiben 6.3.15 Leerer Teilblock nach if? Würde das gehen? 6.3. Bedingte Ausführung: if 1 2 3 if Bedingung: else: Anweisung Kein leerer Block nach if! Nein, nach if muss Anweisung folgen! • Aber wenn da nichts zu tun ist? – Weil Code noch kommt, . . . ? • Sonderanweisung: pass 6.3.16 pass: Beispiel Angenommen, Sie wollen Wert der Variable laenge auf mindestens 100 aber höchstens 200 eingrenzen: 1 2 3 4 5 6 7 8 9 laenge = 252 if laenge >= 100: if laenge <= 200: pass else: laenge = 200 else: laenge = 100 print(laenge) 200 6.3.17 Anweisung: pass Definition 6.1 (pass). pass ist die leere Anweisung: Sie verändert den Zustand des Programms nicht und geht zur folgenden Anweisung über. pass wird typischerweise benutzt • wo die Syntax eine Anweisung vorschreibt aber keine sinnvolle Handlung möglich ist oder • als ein Platzhalter für noch zu ergänzenden Code. 6.3.18 Eindeutigkeit von else? Ist immer eindeutig klar, zu welchem if ein else gehört? 9 10 Liste von Definitionen u.ä. Beispiel 1 1 2 3 4 5 if Bedingung1: if Bedingung 2: Block1 else: Block2 Beispiel 2 1 2 3 4 5 if Bedingung1: if Bedingung 2: Block1 else: Block2 Unproblematisch Dank Einrückung ist die Syntax eindeutig Verschachtelte if/else in anderen Sprachen Auch hier wieder: in anderen Sprachen werden die Blöcke häufig durch Konstrukte wie geschweifte Klammern markiert. Das ist zwar semantisch ebenfalls eindeutig, für einen Menschen aber ggf. viel schwerer zu lesen und verwirrend, wenn die optische Einrückung nicht mit der Klammerstruktur übereinstimmt. Wir werden uns im Java-Teil der Vorlesung hier noch einige unschöne Beispiele anschauen. 6.3.19 Häufiger Fall: Nach else wieder ein if • Szenario: Wenn nicht das, ist dann das der Fall? Und wenn das nicht, dann das? . . . ? • Mit if/else ausdrückbar, aber schrecklich lesbar 1 2 3 4 5 6 7 if Bedingung1: Anweisung1 else: if Bedingung2: Anweisung2 else: if Bedingung3: 6.3. Bedingte Ausführung: if 8 9 10 6.3.20 Anweisung3 else: Anweisung4 Allgemeine Form: if/elif/else Gleicher Effekt, aber knapper: elif 1 2 3 4 5 6 7 8 if Bedingung1: Anweisung1 elif Bedingung2: Anweisung2 elif Bedingung3: Anweisung3 else: Anweisung4 Doppelpunkt Auf Doppelpunkt nach elif-Bedingung achten! 6.3.21 if/elif/else: Beispiel 1 2 3 4 5 6 7 8 9 L = [1, 2, 17, 42] if len(L) < 3: print("zu kurz") elif len(L) > 5: print("zu lang") elif len(L) == 4: print("ausgereizt") else: print("knapp") ausgereizt 11 12 Liste von Definitionen u.ä. 6.3.22 Definition: if/elif/else Definition 6.2 (Anweisung: if/elif/else). Die Anweisung if/elif/else ermöglichen die alternative Ausführung einer Folge von Anweisungen – eines Blocks – in Abhängigkeit des Wahrheitswertes eines Ausdrucks (if) bzw. mehrerer Ausrücke (if und folgende elif Ausdrücke). Dem if kann optional ein oder mehrere elif und optional höchstens ein else-Teil folgen. Die Blöcke werden durch Einrückung den jeweiligen Bedingungen bzw. dem else zugeordnet. Definition: Block Definition 6.3 (Block). Ein Block ist eine Folge beliebiger Anweisungen (mindestens eine Anweisung). Die Zustandsänderung, die ein Block bewirkt, ist die Zustandsänderung, die sich durch die sequentielle Ausführung der Anweisungen des Blocks ergibt. Sequentiell bezieht sich hier auf die Reihenfolge der Anweisung im Programmcode. 6.4 6.4.1 Schleifen: while Allgemeine Schleifen • Idee: Wiederhole einen Block solange, wie eine Bedingung wahr ist • Typisch: Anweisungen im Block werden Einfluss auf den Test haben • Grundform: 1 2 6.4.2 while Test Block while: Flussdiagramm Abbildung 6.1 zeigt eine erste Vorstellung einer while-Schleife in Python als Flussdiagramm. 6.4.3 while: Beispiel Zählschleife 6.4. Schleifen: while Abbildung 6.1: Flussdiagramm einer einfachen While-Schleife 1 2 3 4 5 6.4.4 a=0 b=5 while a < b: print(a, end=" ") a += 1 Beobachtung: Sichtbarkeit von Variablen • Vorheriges Beispiel: Auf Variable a wurde innerhalb der Schleife zugegriffen • Offenbar unproblematisch • Regel: Schon vor der Schleife bekannte Variablen bleiben innerhalb des Blocks sichtbar – Sie sind im gleichen Scope (und auch im gleichen Namensraum) 6.4.5 Beobachtung: Sichtbarkeit von Variablen (2) • Was ist umgekehrt? Variable wird erst in Schleife erzeugt? 1 2 3 i = 0 while i < 2: i += 1 13 14 Liste von Definitionen u.ä. 4 5 x = 2*i print(x) 4 Variable nach Schleife sichtbar • Variable bleibt nach der Schleife erhalten und zeigt auf entsprechenden Wert 6.4.6 Schleifen: Terminierung Gefahr: Schleifenbedingung stets wahr • Schleife wird nicht verlassen • Programm in Endlosschleife 6.4.7 Einfache Regeln für Terminierung Schleife terminiert bestimmt, wenn es • eine Variable gibt, die in jedem Schleifendurchlauf streng monoton wächst und • die Variable diskret wächst und • die Schleifenbedingung eine obere Schranke darstellt Analog: Variable schrumpft, untere Schranke 6.4.8 Terminierung ist kritisch! Q. How did the programmer die in the shower? A. He read the shampoo bottle instructions: Lather. Rinse. Repeat. 6.4.9 Beispiel: GGT Bestimme den größten gemeinsamen Teiler 1 2 3 4 5 6 7 a = 18 b = 12 while (a != b): if a>b: a = a-b else: b = b-a 6.4. Schleifen: while Terminiert für alle Eingaben? 6.4.10 while: Schleife über Feld Beispiel: Werte in einer Liste aufaddieren 1 2 3 4 5 6 7 L = [1, 2, 3, 4] i = 0 sum = 0 while i < len(L): sum = sum + L[i] i = i + 1 print(sum) 10 Achtung 6.1 (Nicht pythonisch). Das ist nicht Python-Style (pythonic). Hier nur als didaktischer Zwischenschritt! 6.4.11 while: Beispiel Suchschleife Szenario: • Wir haben eine Liste (oder Tuple) • Wir suchen ein Element mit einer bestimmten Eigenschaft – Allgemeiner als mit index machbar: da nur Identität als Eigenschaft möglich Idee: • Mit einer Schleife die Elemente ablaufen • Testen, ob Eigenschaft erfüllt – Merken, dass gefunden 6.4.12 while: Beispiel Suchschleife (2) 1 2 3 4 5 6 7 # ist eine ungerade Zahl in L? def ungerade_in_liste(L): gefunden = False i = 0 while i < len(L): if L[i] % 2 == 1: gefunden = True 15 16 Liste von Definitionen u.ä. 8 9 i = i + 1 return gefunden 10 11 12 print(ungerade_in_liste([2, 18, 6, 5, 9])) print(ungerade_in_liste([2, 18, 6, 10, 22])) True False Beobachtung • Alle Elemente untersuchen? – Nein, nach erstem Finden aufhören? 6.4.13 while: Beispiel Suchschleife (3) Zusätzliches Abbruchkriterium: not gefunden 1 2 3 4 5 6 7 8 9 # ist eine ungerade Zahl in L? def ungerade_in_liste(L): gefunden = False i = 0 while (not gefunden) and (i < len(L)): if L[i] % 2 == 1: gefunden = True i = i + 1 return gefunden 10 11 12 print(ungerade_in_liste([2, 18, 6, 5, 9])) print(ungerade_in_liste([2, 18, 6, 10, 22])) True False 6.4.14 Schleifenbedingungen finden? Inspiration? Oder strukturiertes Vorgehen? • Idee: Nach der Schleife soll irgendeine Zielbedingung gelten • Während der Schleife gilt die Schleifenbedingung • Schön wäre: Negation der Schleifenbedingung ist Zielbedingung? – Oder: impliziert Zielbedingung? 6.4. Schleifen: while 1 2 while SCHLEIFENBEDINGUNG == True: ... 3 4 6.4.15 # Es gilt: ZIELBEDINGUNG == True Schleifenbedingungen finden? (2) Oft praktisch: Schleifenbedingung konstruieren aus zwei Teilen • Die Zielbedingung an sich: Was wollen wir nach der Schleife erreicht haben? – Oder eine stärkere Variante davon • Eine Invariante: Gilt vor, während, nach der Schleife – Stellt sicher, dass alle Anweisungen innerhalb der Anweisung korrekt ausgeführt werden können – Beispiel: Index für Listenzugriffe gültig Schleifenbedingung zusammensetzen Schleifenbedingung = (not Zielbedingung) and Invariante • Invariante kann oft entfallen, aber hilfreich zum Nachdenken 6.4.16 Schleifenbedingung finden? (3) Mit Schleifenbedingung = (not Zielbedingung) and Invariante nach Schleife gilt: Zielbedingung or (not Invariante) Was gilt also? D.h.: Nach Schleife nicht klar, was eigentlich der Fall ist!? • Es könnte die Zielbedingung gelten • Es könnte aber auch die Invariante verletzt sein Also unklar! 6.4.17 Nach Schleife: Prüfen! Wenn also unklar ist, weswegen die Schleife beendet wurde • also wegen Erreichen der Zielbedingung • oder Verletzten der Invariante Dann muss man ggf. nach der Schleife nachprüfen! • Häufiges Muster: Zielbedingung durch Variable explizit darstellen 17 18 Liste von Definitionen u.ä. • Wie im Beispiel oben: gefunden 6.4.18 1 2 3 Beispiel umformuliert # ist eine ungerade Zahl in L? def ungerade_in_liste(L): i = 0 4 5 6 ZIELBEDINGUNG = False INVARIANTE = i < len(L) 7 8 9 10 11 while (not ZIELBEDINGUNG) and INVARIANTE: ZIELBEDINGUNG = (L[i] % 2 == 1) i = i + 1 INVARIANTE = i < len(L) 12 13 return ZIELBEDINGUNG 14 15 16 print(ungerade_in_liste([2, 18, 6, 5, 9])) print(ungerade_in_liste([2, 18, 6, 10, 22])) True False 6.4.19 Schleifen: Hilfskonstrukte Innerhalb einer Schleife kann viel passieren • Wirklich jede Iteration komplett durchlaufen? • Eine Schleife abbrechen? 6.4.20 Schleifeniterationen nicht komplett durchlaufen Typisches Muster: Umständlich 1 2 3 4 5 6 7 while Schleifenbedingung: Anweisungen if Andere_Bedingung: # Lasse den Rest der Schleife aus pass else: Restliche Anweisungen 6.4. Schleifen: while 6.4.21 Schleifeniterationen nicht komplett durchlaufen: continue Gleich, aber knapper: 1 2 3 4 5 6.4.22 while Schleifenbedingung: Anweisungen if Andere_Bedingung: continue Restliche Anweisungen Schleife abbrechen Typisches Muster: Umständlich 1 2 3 4 5 6 6.4.23 while (not Abbruchsbedingung) and WeitereBedingung: Anweisungen if Abbruchsbedingung: pass else: Restliche Anweisungen Schleife abbrechen: break Gleich, aber knapper: 1 2 3 4 5 while WeitereBedingung: Anweisungen if Abbruchsbedingung: break Restliche Anweisungen Achtung 6.2 (Bedingung genau beachten). Vorsicht, das Weglassen der Abbruchsbedingung aus der Schleifenbedingung im Einzelfall genau überlegen. 6.4.24 Reguläre Beendigung vs. break: Reaktion Unterschiedliches Beenden einer Schleife kann unterschiedliche Reaktionen erfordern – siehe oben Unterstützung: else für Schleife! 19 20 Liste von Definitionen u.ä. 6.4.25 else bei Schleifen Beispiel: 1 2 3 4 5 6 7 8 9 10 def is_prime(n): """Ist n eine Primzahl?""" x = n // 2 while x > 1: if n % x == 0: print(n, ’hat Teiler’, x) break x -= 1 else: print(n, ’ist eine Primzahl’) # For some n > 1 # Remainder # Skip else # Normal exit 11 12 13 14 15 print("Ist 5 eine Primzahl?") is_prime(5) print("Ist 9 eine Primzahl?") is_prime(9) 6.4.26 else bei Schleifen (2) • Vorteil: Keine Test-Variable nötig – Vereinfacht Schleifenbedingung – Vereinfacht Anweisungen in der Schleife – Wohldefinierte Art, Anweisungen für einen typischen Fall zu platzieren – leichte Lesbarkeit durch Konvention 6.4.27 while: Suchschleife mit else 1 2 3 4 5 6 7 8 9 10 # ist eine ungerade Zahl in L? def ungerade_in_liste(L): i = 0 while (i < len(L)): if L[i] % 2 == 1: break i = i + 1 else: print("nix ungerades!") return False 11 12 13 return True 6.4. Schleifen: while 14 15 print(ungerade_in_liste([2, 18, 6, 5, 9])) print(ungerade_in_liste([2, 18, 6, 10, 22])) True nix ungerades! False while und else: Sinnvoll? In den hier gezeigten, kleinen Beispielen ist der Einsatz von else möglicherweise nicht vollständig überzeugend. Der Nutzen wird wirklich klar, wenn im else-Teil einer Schleife nicht-triviale Operationen durchlaufen werden müssen, zum Beispiel eine Fehlerbehandlung, weitere Verarbeitungsschritte, etc. Wir kommen in den Übungsaufgaben darauf zurück. 6.4.28 Stil: continue, break ? Ist das überhaupt guter Stil? Wird das Programm unstrukturiert? • Das ist beinahe Glaubenssache • Aber break und continue haben klar definiertes Verhalten • Insbesondere in Verbindung mit else alle Fälle eines Schleifenendes im Programmcode klar identifizierbar Also: Ja, das ist ok! Mit Verständnis! 6.4.29 Stil: goto • In alten Sprachen (und in Maschinensprache): goto – Springe an beliebige Stelle im Programm • Aber: Goto considered harmful Abbildung 6.2: goto considered harmful 21 22 Liste von Definitionen u.ä. 6.4.30 Definition: while Definition 6.4 (while). Eine while-Schleife besteht aus einem Ausdruck als Test (Schleifenbedingung) und einem Rumpf (Schleifenrumpf, Schleifenkörper). Beim Einreichen der while-Anweisung wird der Test-Ausdruck ausgewertet und mit dem booleschen Wert True verglichen. Ist der Test-Ausdruck wahr, wird der Rumpf einmal ausgeführt. Das Testen des Ausdrucks und eine Ausführung des Rumpfes werden als (Schleifen-)Iteration bezeichnet. Die Veränderungen des Zustands durch die Anweisungen des Rumpfs sind (natürlich) wirksam. Danach wird wie beim Erreichen der while-Anweisung verfahren: Der Test wird (mit den neuen Variablen-Werten) ausgewertet und ggf. der Rumpf ausgeführt. Der Rest einer Schleifeniteration kann durch die Anweisung continue übersprungen werden und mit der folgenden Iteration begonnen werden. Eine Schleife kann mit der Anweisung break abgebrochen werden. Am Ende einer regulär (nicht durch break) beendeten Schleife wird ein ggf. vorhandener Block nach der Anweisung else ausgeführt. Bei Beendung der Schleife durch break wird dieser Block ignoriert. 6.4.31 while: Komplettes Flussdiagramm Abbildung 6.3 zeigt ein vollständiges Flussdiagramm einer while-Schleife in Python. 6.5 6.5.1 Schleifen: for Häufiger Fall: Schleife über Aufzählung Häufig: Eine Aufzählung (Liste, Tuple, usw) Element für Element verarbeiten Skizze: 1 2 3 4 5 6 7 L = [1, 2, 3, 4, 5] while L is not []: aktuellesElement = L[0] # mache etwas mit aktuellem Element # ... # betrachte die restlichte Liste: L = L[1:] 6.5. Schleifen: for Abbildung 6.3: Flussdiagramm einer While-Schleife mit continue , break und else Achtung 6.3 (Keine korrekte Schleife!). Dies ist keine korrekte Schleife; sie hat mindestens zwei Probleme. Siehe Details in der Übungsaufgabe. 6.5.2 Häufige Fälle verdienen eigene Anweisung: for Schleifen über Aufzählungen vereinfacht schreiben 1 2 3 4 5 6 # Sei A eine Referenz auf # ein Objekt eines Aufzählungsdatentyp for element in A: Anweisung1 Anweisung2 ... 7 8 9 # Beispiel: L = [1, 2, 3] 23 24 Liste von Definitionen u.ä. 10 11 6.5.3 for l in L: print(l, end=" ") for: Definition Definition 6.5 (for-Schleife). • Mit jeder Iteration einer for Schleife wird das jeweils nächste Element der gegeben Aufzählung der Schleifenvariable (auch Laufvariable) zugewiesen (im Beispiel: element; Name ist frei wählbar) • Nach Bearbeitung des letzten Elements der Aufzählung wird die Schleife verlassen • Ist die Aufzählung leer (z.B. []), so wird die Schleife nicht betreten und keine Iteration ausgeführt • Die Anweisungen break, continue und else verhalten sich genau wie bei while-Schleifen 6.5.4 Zuweisung an Aufzählung? Was passiert, wenn der Aufzählung einer neuer Wert zugewiesen wird? • Ändert das den Schleifenverlauf? • Beispiel: 1 2 3 4 6.5.5 L = [1, 2, 3] for l in L: print(l, end=" ") L = [17, 18, 19] Zuweisung an Aufzählung – es passiert gar nicht! • Tatsächlich kann an die Aufzählung, die für die Schleife verwendet wird, gar nicht zugewiesen werden • Zu Beginn der for-Schleife wird eine weitere, verborgene Variable für die Aufzählung angelegt – Anhand dieser wird iteriert • Weißt man also einer Variable, die im Schleifenkopf benutzt wird, einen neuen Wert zu, so beeinflusst das nicht den Schleifenablauf – Der ursprüngliche Wert steht ja weiterhin zur Verfügung Visualisierung Die Visualisierung dieses Effekts durch unsere üblichen Diagramme gelingt leider nicht gut (Abbildung 6.4). Der verborgene Name für die Aufzählung 6.5. Schleifen: for wird durch Pythontutor leider nicht explizit dargestellt. Dennoch hilft dieses Diagramm vielleicht ein wenig. Abbildung 6.4: Zuweisung an Aufzählung während while-Schleife 6.5.6 Zuweisung an Elemente der Aufzählung? Was passiert also hier? 1 2 3 4 6.5.7 L = [1, 2, 3] for l in L: print(l, end=" ") L[1] = 17 Zuweisung an Schleifenvariable Was passiert, wenn der Schleifenvariable etwas zugewiesen wird? • Ändert das die Aufzählung? Den Schleifenverlauf? • Beispiel: 1 2 3 4 L = [1, 2, 3, 4, 5] for x in L: x += 1 print(L) [1, 2, 3, 4, 5] 25 26 Liste von Definitionen u.ä. Nein! Auch hier ist die ursprüngliche Aufzählung nicht betroffen • Die Schleifenvariable x ist ein Name für den Wert; zeigt nicht in die Aufzählungsstruktur • x wird bei erneutem Betreten der Schleife einfach den nächsten Wert aus der Aufzählung zugewiesen 6.5.8 Schleifenvariable nach Schleifenende? Bleibt Schleifenvariable erhalten nach Schleifenende? 1 2 3 for i in [1, 2, 3]: print("in schleife") print("Variable i nach Schleife: ", i) in schleife in schleife in schleife (’Variable i nach Schleife: ’, 3) Ja • Mit Wert des letzten Durchlaufs 6.5.9 for-Schleifen über andere Aufzählungen • Beispiele bisher: for über Liste • Laut Kapitel 5: Andere Aufzählungen sind str, tuple, set – Und eigentlich auch dict • for-Schleifen darüber? 6.5.10 for-Schleifen über andere Aufzählungen – Beispiele 1 2 3 1 2 3 s = "Hallo GP1" for ss in s: print(ss, end=" ") t = (1, 2, 3, 4) for tt in t: print(tt, end=" ") 6.5. Schleifen: for 6.5.11 for-Schleifen über andere Aufzählungen – Beispiele • Überraschend? Nein! – Principle of least surprise 1 2 3 s = {1, 2, 3, 4, 2, 3, 4} for ss in s: print(ss, end=" ") • Überraschend? Nein! – Semantik einer Menge 6.5.12 for-Schleife über verschachtelte Datentypen Beispiel: Liste von Tupeln 1 2 3 L = [ (1,2), (3, 4), (5, 6)] for t in L: print(t, end=" ") 6.5.13 for-Schleife mit erweiterter Zuweisung? • Eigentlich macht ja eine for-Schleife jeweils eine Zuweisung an die Laufvariable • Python kennt erweiterte Zuweisungen und unpacking: x, y = (1, 2) • Funktioniert das bei for? 1 2 3 4 L = [ (1,2), (3, 4), (5, 6)] for a, b in L: print(a, b) print("---") (1, 2) --(3, 4) --(5, 6) --- 27 28 Liste von Definitionen u.ä. 6.5.14 for-Schleife über dict ? • Iterieren über ein dict liefert die Schlüssel 1 2 3 D = {"a": 1, "b": 2, "c": 3, "d": "abc"} for key in D: print(key, end=" ") 6.5.15 for-Schleife über dict – Key und Value? Wir bekommen eine Liste von Schlüssel-/Wert-Tuple eines dicts mit Aufruf von items • Mit implizitem unpacking der Tuple in separate Variablen k (für key) und v (für value) 1 2 3 4 D = {"a": 1, "b": 2, "c": 3, "d": "abc"} for k, v in D.items(): print(k, v) print("----") (’a’, ---(’c’, ---(’b’, ---(’d’, ---- 1) 3) 2) ’abc’) Beobachtung: Reihenfolge • Es gibt keine Garantie für die Reihenfolge in D.items()! Visualisierung Zur Illustration (Abbildung 6.5) hier ein Schnappschuss aus dem zweiten Durchlauf der Schleife. Die beiden Schleifenvariablen key und value referenzieren dabei Werte aus dem Dictionary D. 6.5.16 for über Folge von Zahlen: range • Häufig: Liste über eine Folge Zahlen 6.5. Schleifen: for Abbildung 6.5: Zwischenstand bei einer Iteration über ein Dict D.items() – Z.B. 1 . . . 10, 1 . . . n für Variable n, usw • Mit for so nicht unmittelbar darstellbar – Mit while schon, aber umständlich Definition 6.6 (Hilfsfunktion: range). range(stop) liefert eine Aufzählung der Zahlen von 0 bis (ausschliesslich) stop 6.5.17 range: Beispiel 1 2 for i in range(11): print(i, end=" ") 6.5.18 range: Wie viele Durchläufe? Fencepost-Problem: Wie viele Pfähle braucht man für einen 100m langen Zaun mit Pfählen im Abstand von 10m? • 9, 10, 11? 6.5.19 Terminierung von for und range Terminiert das hier? 29 30 Liste von Definitionen u.ä. 1 2 for i in range(5): i -= 1 Terminierung von range Natürlich terminiert das. Der Schleifenstruktur ist egal, was innerhalb der Schleife mit dem Namen i gemacht wird (genauer: auf welchen Wert der Name i referenziert). Am Ende der Schleife wird der nächste Wert aus der Aufzählung verwendet. 6.5.20 range: Aufzählung verändern Erinnerung: Das hier verändert die Aufzählung L nicht 1 2 3 4 L = [1, 2, 3, 4, 5] for x in L: x += 1 print(L) [1, 2, 3, 4, 5] L verändern? Was aber, wenn Schleife doch L verändern soll? 6.5.21 range: Aufzählung verändern Dann brauchen wir eine for-Schleife mit Zugriff auf L durch den Index 1 2 3 4 L = [1, 2, 3, 4, 5] for i in range(len(L)): L[i] += 1 print(L) [2, 3, 4, 5, 6] Muster Typisches Muster für den Einsatz von range und len in for-Schleifen • Durch Struktur der for-Schleife Indiz an Leser des Codes! 6.5. Schleifen: for 6.5.22 31 Aufzählung in for – duck typing? Im Kopf der for-Schleife: • Muss es eine Aufzählung sein? • Oder – nach duck typing-Prinzip – reicht etwas, dass sich wie eine Aufzählung benimmt? – Dafür haben wir noch kein Beispiel – aber vielleicht ein interessanter Gedanke! 6.5.23 Beispiel: Sieb des Erathostenes • Bestimme alle Primzahlen bis zu einer gegebenen Obergrenze n • Idee: – Schreibe alle Zahlen von 2 bis n – Nimm die erste Zahl (die 2); streiche alle Vielfachen dieser Zahl aus der Tabelle – Nimm die nächste noch nicht gestrichene Zahl und wiederhole Prozedur – Bis keine nicht gestrichene Zahl mehr übrig 6.5.24 Eratostenes: Umsetzung • Wie Tabelle repräsentieren? – Als Liste der Zahlen: [2, 3, 4, 5, ... ] * Möglich, aber dann Suche nach Vielfachen aufwändig – Als Liste von gestrichen/nicht gestrichen Werte, mit Zugriff per Index * nicht/gestrichen als True oder False repräsentieren 6.5.25 1 2 3 4 5 6 7 8 Erathostenes: Code n = 20 prim = [True] * n for i in range(n): if i==0 or i==1: prim[i] = False elif prim[i] == True: for streich in range(2*i, n, i): prim[streich] = False 9 10 print(prim) [False, False, True, True, False, True, False, True, False, False, False, True 32 Liste von Definitionen u.ä. range mit drei Parametern Parameter: Start, Stop, Schrittweite! 6.5.26 while oder for ? • Ist eine Aufzählung abzuarbeiten; ist eine feste Anzahl an Durchläufen bekannt? Dann for • Ist nur eine Abbruchbedingung bekannt, bei unklarer Anzahl an Durchläufen? Dann while 6.6 6.6.1 List comprehensions Listen in Schleife konstruieren • Häufiges Muster: – Eine Schleife produziert Ergebnis pro Druchlauf – Ergebnisse werden in Liste aufgesammelt • Einfachster Fall: Einen Ausdruck für jedes Element der Aufzählung berechnen 1 2 3 4 6.6.2 # leere Liste zum Ergebnisse sammeln: ergebnis = [] for x in aufzaehlung: ergebnis.append(Ausdruck ueber x) Listen in Schleife konstruieren – Beispiel Beispiel: Wir verdoppeln jeden Eintrag der Liste 1 2 3 4 ergebnis = [] for x in [1, 2, 3, "abc", "xyz"]: ergebnis.append(x * 2) print(ergebnis) [2, 4, 6, ’abcabc’, ’xyzxyz’] 6.6. List comprehensions 6.6.3 Listen in Schleifen bedingt konstruieren • Auch häufig: Pro Durchlauf Ergbnis nur bei bestimmer Bedingung konstruieren 1 2 3 4 5 6.6.4 # leere Liste zum Ergebnisse sammeln: ergebnis = [] for x in aufzaehlung: if x erfuellt Bedingung: ergebnis.append(Ausdruck ueber x) Listen in Schleifen bedingt konstruieren – Beispiel Verdopple Eintrag nur wenn gerade Zahl: 1 2 3 4 5 ergebnis = [] for x in [1, 2, 3, 17, 42, 99]: if x % 2 == 0: ergebnis.append(x * 2) print(ergebnis) [4, 84] 6.6.5 List comprehensions Wie üblich: Häufige Muster verdienen eigene Syntax! List comprehension: • Konstruieren einer Liste aus einer anderen Liste • mit einer kompakten for-Formulierung 1 [Ausdruck ueber x for x in Liste if x erfuellt Bedingung] Schleifenvariable Natürlich auch andere Variablennamen als x möglich 6.6.6 List comprehension – Beispiel 1 Verdopple Eintrag nur wenn gerade Zahl: 33 34 Liste von Definitionen u.ä. 1 2 3 4 5 6.6.7 # Zeilenumbruch nur für Lesbarkeit ergebnis = [x * 2 for x in [1, 2, 3, 17, 42, 99] if x % 2 == 0] print(ergebnis) Beispiel 2 • Liste der Noten von Austauschstudierenden 1 2 3 4 5 6 7 # Liste mit Matrikelnummern: austausch = [11, 22, 33] # Dict mit Matrikelnummer: Note Abbildung noten = {11: 2.0, 17: 1.7, 20: 3.3, 22: 1.0, 27: 5.0, 33: 2.3} # Liste der Noten der Austauschstudierenden: austauschnoten = [noten[a] for a in austausch] print(austauschnoten) [2.0, 1.0, 2.3] Fehler? Was passiert, wenn Matrikelnummer fehlt? 6.7 6.7.1 Geschachtelte Schleifen Geschachtelte Schleifen • • • • 6.7.2 Schleifenrumpf: Folge von Anweisungen Schleifen (while und for) sind Anweisungen Also: Schleifen in Schleifen möglich!? Na klar! Paarweise Operation • Angenommen, wir wollen Elemente zweier Listen paarweise miteinander vergleichen – Schleife 1 (äußere Schleife): Iteriere über eine Liste 6.7. Geschachtelte Schleifen 35 – Schleife 2 (innere Schleife): In jeder Iteration der äußeren Schleife, iteriere über die zweite Schleife * Führe Operation durch 1 2 3 6.7.3 for l1 in liste1: for l2 in liste2: operation auf l1, l2 Paarweise Operation: Beispiel 1 – Summe der Produkte Berechne Summe der Produkte jedes Elements von Liste 1 mit jedem Element von Liste 2 Plen(L )−1 Plen(L2 )−1 • Also: i=0 1 L1 [i]L2 [j] j=0 1 2 3 4 5 6 7 L1 = [1, 2, 3] L2 = [17, 18, 19] summe = 0 for l1 in L1: for l2 in L2: summe += l1 * l2 print(summe) 324 6.7.4 Paarweise Operation: Beispiel 2 – Test auf enthalten Welche Elemente von tests sind in der Liste items? • Innere Schleife abbrechen wenn Ergebnis feststeht 1 2 items = ["aaa", 111, (4, 5), 2.01] tests = [(4, 5), 3.14] # Eine Liste von Daten # Wonach suchen wir? 3 4 5 6 7 8 9 10 11 for key in tests: # Fuer alle zu suchenden Daten for item in items: # Fuer alle vorhandenen Daten if item == key: # Gleich? print(key, "gefunden in Daten") break # Brich die innere Schleife ab else: print(key, "nicht in Daten") # Nur ausgefuehrt wenn innere Schleife # nichts gefunden hat 36 Liste von Definitionen u.ä. ((4, 5), ’gefunden in Daten’) (3.14, ’nicht in Daten’) 6.7.5 Beispiel mit geschachteltem range Berechne 1 2 3 4 5 P10 Pi i=0 j=0 i ·j summe = 0 for i in range(11): for j in range(i+1): summe += i*j print(summe) 1705 6.7.6 Beispiel mit geschachteltem range und list comprehension List comprehensions erlauben direkt geschachtelte Schleifen: 1 2 3 4 5 6 7 6.7.7 quadrate = [i*j # aeußere Schleife: for i in range(11) # innere Schleife: for j in range(i+1) ] print(quadrate) Eingebaute Funktion: sum Hilfsfunktion sum: Addiere alle Elemente einer Aufzählung auf 1 2 3 4 5 6 7 summe = sum([i*j # aeußere Schleife: for i in range(11) # innere Schleife: for j in range(i+1) ]) print(summe) 6.7. Geschachtelte Schleifen 6.7.8 Beispiel: Sortieren • Gegeben: Eine Liste mit Zahlen • Gesucht: Eine Liste mit den gleichen Zahlen, aber aufsteigend sortiert Idee: Suche die kleinste Zahl aus Restliste • Schritt 1: Suche die kleinste Zahl aus der ganzen Liste – Vertausche mit Element am Anfang • Schritt 2: Genauso, mit der Liste ab Position 2 • Usw. Selection Sort Sog. Selection Sort • Achtung: Für Praxis ungeeignet; viel zu langsam; aber schön einfach • Besser: siehe VL Datenstrukturen und Algorithmen 6.7.9 Selection Sort: Vorüberlegung • Wie viele, welche Schleifen? – Eine Schleife, um das erste, zweite, . . . Element zu bestimmen: for – Um ein Element zu bestimmen, den Rest absuchen: noch eine for, geschachtelt 6.7.10 1 2 3 4 5 6 7 Selection Sort: Code liste = [17, 1, 42, 99, 33] for pos in range(len(liste)): tausch = pos for such in range(pos+1, len(liste)): if liste[such] < liste[tausch]: tausch = such liste[pos], liste[tausch] = liste[tausch], liste[pos] 8 9 print(liste) [1, 17, 33, 42, 99] (PT link) 37 38 Liste von Definitionen u.ä. Visualisierung Abbildung 6.6 visualisiert einen Zwischenstand beim Ablauf von Selection Sort. Abbildung 6.6: Selection Sort 6.7.11 Sortieren in Python: Eingebaut • Niemand schreibt so eine Sortier-Funktion von Hand • Das ist als eingebaute Funktion verfügbar: l.sort() für eine Liste l 6.7.12 Beispiel: Mischen zweier Listen • Gegeben: Zwei sortierte Listen (beliebiger Länge) • Gesucht: Eine sortierte Liste, die die Elemente der beiden Listen enthält 6.7.13 Mischen: Vorüberlegung • Triviale Idee: Listen hintereinander kopieren, dann sortieren – Verschwenderisch – wir können die Sortierung der Listen ja ausnutzen • Bessere Idee: Gehe Listen elementweise durch und nimm jeweils das kleinere Element 6.7.14 1 2 3 Mischen konventionell a = [1, 3, 6, 9, 12, 23, 27, 35] b = [2, 4, 5, 10, 11, 17, 18] r = [] 4 5 aindex = 0 6.7. Geschachtelte Schleifen 6 bindex = 0 7 8 9 10 11 12 13 14 15 while (aindex < len(a)) and (bindex < len(b)): print("Index a und b:", aindex, bindex) if a[aindex] < b[bindex]: r.append(a[aindex]) aindex += 1 else: r.append(b[bindex]) bindex += 1 16 17 print("Index a und b nach Schleife:", aindex, bindex) 18 19 20 21 22 if aindex == len(a): r.extend(b[bindex:]) else: r.extend(a[aindex:]) 23 24 print("Resultat: ", r) (’Index a und b:’, 0, 0) (’Index a und b:’, 1, 0) (’Index a und b:’, 1, 1) (’Index a und b:’, 2, 1) (’Index a und b:’, 2, 2) (’Index a und b:’, 2, 3) (’Index a und b:’, 3, 3) (’Index a und b:’, 4, 3) (’Index a und b:’, 4, 4) (’Index a und b:’, 4, 5) (’Index a und b:’, 5, 5) (’Index a und b:’, 5, 6) (’Index a und b nach Schleife:’, 5, 7) (’Resultat: ’, [1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 17, 18, 23, 27, 35]) 6.7.15 1 2 3 4 5 6 Mischen mit Slicing a = [1, 3, 6, 9, 12, 23] b = [2, 4, 5, 10, 11, 17, 18] r = [] while (len(a) > 0) and (len(b) > 0): print(a, b) if a[0] < b[0]: 39 40 Liste von Definitionen u.ä. 7 8 9 10 11 r.append(a[0]) a = a[1:] else: r.append(b[0]) b = b[1:] 12 13 14 15 16 if len(a) == 0: r.extend(b) else: r.extend(a) 17 18 print("Resultat: ", r) ([1, 3, 6, 9, 12, 23], [2, 4, 5, 10, 11, 17, 18]) ([3, 6, 9, 12, 23], [2, 4, 5, 10, 11, 17, 18]) ([3, 6, 9, 12, 23], [4, 5, 10, 11, 17, 18]) ([6, 9, 12, 23], [4, 5, 10, 11, 17, 18]) ([6, 9, 12, 23], [5, 10, 11, 17, 18]) ([6, 9, 12, 23], [10, 11, 17, 18]) ([9, 12, 23], [10, 11, 17, 18]) ([12, 23], [10, 11, 17, 18]) ([12, 23], [11, 17, 18]) ([12, 23], [17, 18]) ([23], [17, 18]) ([23], [18]) (’Resultat: ’, [1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 17, 18, 23]) 6.7.16 Beispiel: Binäre Suche • Gegeben: – Sortierte Liste mit Werten – Gesuchter Wert • Gesucht: – Index des Wertes in Liste, falls vorhanden – Information dass nicht vorhanden, andernfalls 6.7.17 Binäre Suche, Vorüberlegung • Liste linear absuchen? – Funktioniert, aber wir können Sortierung ausnutzen! • Idee: – Prüfe Wert in der Mitte der Liste * Falls gefunden: fertig * Wenn größer: Suche oben; wenn kleiner: suche unten 6.8. Zusammenfassung – Wenn kein Wert mehr übrig, fertig • Welche Schleifen? 6.7.18 1 2 3 4 5 Binäre Suche, Code l = [2, 4, 5, 10, 11, 17, 18] wert = 11 gefunden = False unten = 0 oben = len(l) 6 7 8 9 10 11 12 13 14 15 16 while ((not gefunden) and (unten <= oben) and (unten < len(l))): mitte = int((unten+oben)/2) if l[mitte] == wert: gefunden = True elif l[mitte] < wert: unten = mitte + 1 else: oben = mitte -1 17 18 print(gefunden, mitte) (True, 4) Frage Wozu braucht man die Schleifenbedingung unten < len(l) ? 6.8 Zusammenfassung 6.8.1 Zusammenfassung • Wiederholte Ausführung von Anweisungen ist extrem nützlich • Unterschiedliche Kontrollmechanismen existieren – while-Schleife: Kontrolle durch einen Ausdruck, der Wahrheitswert liefert 41 42 Liste von Definitionen u.ä. – for-Schleife: In jeder Wiederholung nimmt eine (oder mehrere) Schleifenvariable einen Wert aus einer vorgegebenen Aufzählung an • Schleifen erlauben Ausnahmen (break, continue) und Code für reguläres Ende (else) • Zusammen mit ein paar Hilfsfunktionen (vor allem range) sind insbes. for-Schleifen in Python extrem praktisch 6.8.2 Python-Keywords: Liste bis jetzt • Bis jetzt: – True, False, and, or, def, return, None – in • Neu: – if, else, pass, elif, while, for, break, continue, assert 6.8.3 Python: eingebaute Funktionen • Allgemein – print, range, zip • Typ-bezogen – len • Datentypen erzeugen – tuple – list – set – dict