#!/usr/bin/env python # coding: utf-8 # # Konzept 4: Kontrollstrukturen # Eine sehr vereinfachte Vorstellung eines Computerprogrammes ist ein Kochrezept. # Zuerst werden die Zutaten (Daten) zurechtgelegt, # dann anhand einer Liste von Befehlen bearbeitet, # und zum Schluss gibt es ein hoffentlich bekömmliches Ergebnis. # # Ein Computerprogramm ist aber mehr als eine lineare Abfolge von Befehlen. # Üblicherweise werden anhand diverser Kriterien *Entscheidungen* getroffen, # oder *alle Elemente in einer Menge* werden durch einen festen Handlungsablauf abgearbeitet, usw. # # Das Konzept **Kontrollstrukturen** behandelt all diesen Steuerungselementen für so einen nichtlinearen Programmablauf. # ## Bedingte Verzweigung # # Eine **Verzweigung** ist eine Stelle im Programmablauf, # wo ein, zwei oder mehr alternative Wege wurzeln. # Es wird maximal eine dieser Alternativen abgearbeitet! # Am Schluss dieses Programmteils vereinigen sich diese Stränge (üblicherweise) wieder zu einem einzigen Handlungsablauf. # # Schlüsselwörter: # # * `if`: wenn der nachgestellte Wahrheitsausdruck (engl. "boolean expression") wahr ist, wird der nachfolgende Block abgearbeitet. # * `else`: ist der Wahrheitsausdruck falsch, so springt die Ausführung in diesen optionalen `else` Block. # In[1]: x = 4 if x > 0: print("x ist positiv, ich kann die Wurzel ziehen!") from math import sqrt x = sqrt(x) "x = %f" % x # **Quiz:** Editiere vorhergehende Zelle so, dass x negativ ist und beobachte das Resultat. # In[2]: y = 55 if y % 5 == 0: k = y / 5 y += k "y = %f" % y # Zusätzlich kann es vorkommen, dass Verzweigungen verschachtel werden; wie hier in Zeile 3 ein weiteres `if` in Zeile 4 vorkommt: # In[3]: a = 5 b = -3 if a > 0: if b > 0: x = a + b else: x = a - b else: x = b "x = %f" % x # Gibt es mehr als zwei Alternativen, # wird diese `if`-`else` Struktur mit dem Schlüsselwort `elif` erweitert. # # Beispiel: Implementiere # # \begin{equation} # \operatorname{capped}(x) := \begin{cases} # 1 & -1 \le x < 0 \\ # \frac{1}{2} & x = 0 \\ # 1 - x^2 & \text{otherwise} # \end{cases} # \end{equation} # In[4]: def capped(x): if x == 0: return 0 elif -1 <= x < 0: return 1 else: return 1 - x**2 # In[5]: [capped(x) for x in [-5, -1, -.5, 0, .5, 1, 2, 5]] # **Bemerkung**: Besonders praktisch ist der `-1 <= x < 0` Ausdruck. # Er ließe sich auch als `-1 <= x and x < 0` anschreiben. # **Quiz:** Angenommen, zwei Testausdrücke sind gleichzeitig wahr. Was passiert dann? # In[6]: x = 10 if x % 2 == 0: print("teilbar durch 2") elif x > 0: print("x ist positiv") else: print("x ist weder positiv, noch teilbar durch 2") # ... und was ist der Unterschied zu dem folgenden Beispiel? # In[7]: x = 10 if x % 2 == 0: print("teilbar durch 2") if x > 0: print("x ist positiv") # ## Iterationen # # Neben Verzweigungen gibt es verschiedene Formen, # einen Codeblock nicht nur einmal, sondern öfters auszuführen. # Dies geschieht durch zwei verschiedene Schlüsselwörter: `while` und `for`: # # * `while expr`: der Block wird so lange ausgeführt, so lange der Wahrheitsausdruck `` wahr ist. # * `for i in iterable`: das `iterable` liefert nach und nach einzelne Elemente, welche der Reihe nach von `i` angenommen werden. # * `for i in collection`: für jedes `i` in der abzuarbeitenden Menge wird der Block ausgeführt. # Dabei nimmt `i` der Reihe nach jeden Wert der Sammlung an. # In[8]: x = 10 while x > 5: print("x = %f" % x) x = x * .95 # **Achtung:** Ist der zu testende Ausdruck immer Wahr, wiederholt sich die Schleife "unendlich" oft. # Das verträgt sich mit der endlichen Physik des Computers nicht, # bzw. funktioniert das IPython Notebook im Webbrowser nicht mehr. # Zum Abbrechen einer Berechnung muss man den Interrupt drücken. # Ein ebenfalls wichtiger Umstand ist, # dass Zuweisungen an die Schleifenvariable (hier `i`) im sich wiederholenden Codeblock # bei jeder erneuten Wiederholung überschrieben werden. # In[9]: for i in range(1, 10): i = 42 * i**2 print("i = %6d" % i) # Will man neben dem zu iterierenden Element auch den Index in der Liste wissen, # so verwendet man den `enumerate(...)` Iterator. # Er liefert pro Eintrag ein Paar ("tupel") zurück, # welches aus dem Index und dem Element besteht. # Durch "`i, o`" wird das Tupel aufgetrennt und in `i` und `o` abgelegt. # In[10]: obst = ["Apfel", "Tomate", "Birne", "Kiwi"] for i, o in enumerate(obst): print("{:2d}: {}".format(i, o)) # Die Zählung startet wie üblich bei `0`. # ### break # # Es können solche Iterationen vorzeitig mit dem Schlüsselwort `break` abgebrochen werden. # Im folgenden Beispiel fehlt die "Kiwi"! # In[11]: for o in obst: print(o) if o == "Birne": break # ### continue # # Das Gegenstück zu dem `break` Schlüsselwort ist `continue`: # Wird dieses erreicht, springt die Ausführung vorzeitig an den Beginn der Schleife! # Beachte im folgenden Beispiel, # dass die durch 3 teilbaren Zahlen _nicht_ aufscheinen. # In[12]: for k in range(10): if k % 3 == 0: continue print("k = %d" % k) # ### for-else / while-else # Umgekehrt kann man auch überprüfen, # ob man in einer Schleife vorzeitig abgebrochen hat oder nicht. # Im folgenden Beispiel wird nach dem Obst "Banane" gesucht, aber nicht gefunden. # Beachte, dass das `else` Schlüsselwort auf selber Höhe wie das `for` Schlüsselwort ist. # In[14]: for o in obst: print(o) if o == "Banane": print("Banane wurde gefunden") break else: print("Die Banane wurde leider nicht gefunden") # Ebenso, für `while-else` Schleifen. Der `else:`-Fall tritt dann ein, wenn die logische Bedingung im Kopf der `while`-Schleife falsch wird (und daher kein `break` passiert ist). Reduziere die `energie` auf 30, um den Unterschied zu sehen. # In[15]: ziel = 42 # muss zu 42 kommen position = 1 # startet bei 1 energie = 50 # energie für 30 schritte! while position != ziel: position += 1 # ein Schritt weiter energie -= 1 # verbraucht eine Energieeinheit if energie <= 0: # Abbruch, da keine Energie mehr break else: print("Ziel gefunden! Restenergie: %s" % energie) # ## Verschachtelte Kontrollstrukturen # # Schleifen und Verzweigungen lassen sich beliebig verschachteln. # Das bedeutet, innerhalb einer `while`-Schleife können mehrere `for`-Schleifen sein, # in denen wiederum `if`-Verzweigungen vorkommen. # In[16]: k = 0 while k < 1000: for i in range(10): if i % 5 == 4: print("Addiere %d zu k=%d" % (i, k)) k += i if k > 50: for i in [1, 2]: print("Setzen von k auf 2*(%d + %d)" % (k, i)) k = 2*(k + i) print("Ergebnis: k = %d" % k) # **Wichtig:** Einrückungen sind hierbei entscheidend, welche Teile der Verschachtelungen wann ausgeführt werden. # Beispiel für einen feinen, aber entscheidenden Unterschied in Zeile 4: # In[17]: for k in [1, 2]: for j in ["u", "v"]: print(j) print(k) # In[18]: for k in [1, 2]: for j in ["u", "v"]: print(j) print(k) # ## "Unendliche" Schleifen # # Nicht ganz ungewöhnlich sind "unendliche" Schleifen ohne vorgegebenem Ende. # Prominentester Vertreter ist eine `while`-Schleife mit der Bedingung `True`. # In solchen Fällen muss es Vorkehrungen geben, dass die Schleife nicht tatsächlich unendlich lange läuft ;-) # In[19]: counter = 1 while True: print("x" * counter) counter += 1 if counter > 10: break # Auch bei `for`-Schleifen in Kombination mit Iteratoren kann dies eintreten. # Hier ein Beispiel, wo ein zyklischer Iterator `cycle` eine Liste immer wieder wiederholt. # In[20]: from itertools import cycle for counter, element in enumerate(cycle(obst)): print("%3d: %s" % (counter, element)) if counter > 10: break