while-Schleifen#
Um die Idee einer while-Schleife zu motivieren, möchten wir uns zunächst mit der Frage
Wie schnell können Sie ein Spielbrett mit 40 Feldern umrunden?
auseinandersetzen. Angenommen Sie starten auf Feld Eins, würfeln und ziehen vorwärts. Dann würfeln Sie erneut – und das so lange, bis Sie das Spielbrett vollständig umrundet haben. Wie viele Würfe brauchen Sie wohl, um einmal ganz herumzukommen? Genügen zehn? Oder eher zwanzig?
Genau hier kommt die while-Schleife ins Spiel: Sie wiederholt einen Codeblock, hier das Würfeln, so lange, wie eine Bedingung erfüllt ist. In unserem Beispiel ist die Bedingung: Spielbrett noch nicht umrundet. Solange das zutrifft, wird der Codeblock weiter ausgeführt. Es wird also weiter gewürfelt. Erst wenn die Bedingung nicht mehr erfüllt ist, das heißt wenn Sie eine Runde geschafft haben, hören Sie auf und endet die Schleife automatisch.
In manchen Situationen genügt es also nicht, eine Anweisung eine festgelegte Anzahl von Malen auszuführen. Stattdessen ist es notwendig einen Codeblock so lange zu wiederholen, bis eine bestimmte Bedingung erfüllt ist – unabhängig davon, wie oft das geschieht. Im Kontext der Numerik kommen while-Schleifen häufig dann zum Einsatz, wenn Sie einen Algorithmus so lange wiederholen wollen bis das Ergebnis eine gewünschte Genauigkeit erreicht hat.
Bemerkung
Wenn Code solange wiederholt werden soll bis eine bestimmte Bedingung erfüllt ist, verwenden Sie eine while-Schleife.
Die Syntax#
Eine while-Schleife hat in Python folgende Struktur:
Der Code im Schleifenrumpf wird ausgeführt solange die Bedingung \(\texttt{condition}\) wahr, also \(\texttt{True}\), ist. Der Doppelpunkt \(\texttt{:}\) leitet den Schleifenrumpf ein. Es ist an dieser Stelle wichtig, den Code unterhalb des Schlüsselworts \(\texttt{while}\) einzurücken, zum Beispiel durch ein Leerzeichen oder ein Tab, da dies Python signalisiert, dass der Code nur ausgeführt werden soll, wenn die Bedingung wahr ist. Der Code \(\texttt{another statement 1, 2, ...}\) wird unabhängig vom Wahrheitsgehalt der Bedingung \(\texttt{condition}\) außerhalb der Schleife ausgeführt, denn er gehört nicht zum Schleifenrumpf. Er wird erst dann ausgeführt, nachdem die Bedingung \(\texttt{condition}\) zum ersten Mal den Wert \(\texttt{False}\) annimmt und die Schleife somit beendet ist.
Ein Code-Beispiel#
Angenommen Sie wollen für ein festes \(\texttt{x}\) die kleinste natürliche Zahl \(n\) bestimmen, sodass \(\texttt{y} = \frac{\texttt{x}}{2^n}\) kleiner als \(1\) ist. Dazu können Sie eine while-Schleife schreiben und im Schleifenrumpf solange \(\texttt{x}\) durch \(2^n\) teilen bis der Quotient kleiner als \(1\) ist.
Die Variable \(n\) wird mit \(n=0\) initialisiert. Außderdem wird die Variable \(\texttt{y}\) erstellt und mit \( \texttt{y} = \texttt{x}\) initialisiert.
Zu Beginn der while-Schleife wird die Bedingung \( \texttt{y} > 1\), also \( \frac{\texttt{x}}{2^n} > 1\) ausgewertet.
Da die Bedingung erfüllt ist, wird der Schleifenrumpf ausgeführt. Daher wird \(\texttt{y}\) verändert und anschließend \(n\) um \(1\) erhöht. Es gilt \(2^0 = 1\), sodass der Wert für \(\texttt{y}\) gleich bleibt.
Die Bedingung \(\texttt{y} > 1\) wird erneut mit dem gerade neu berechneten \(\texttt{y}\) überprüft.
Die Bedingung \(\texttt{y} > 1\) wir erneut mit dem gerade neu berechneten \(\texttt{y}\) überprüft.
Endlosschleifen#
Ein häufiger Fehler beim Arbeiten mit while-Schleifen ist die Entstehung sogenannter Endlosschleifen. Das bedeutet, dass der Schleifenrumpf nie endet und das Programm sich „aufhängt“ oder dauerhaft weiterläuft. Das passiert vor allem dann, wenn:
Die Bedingung nie auf \(\texttt{False}\) gesetzt wird, weil sich die beteiligten Variablen innerhalb des Schleifenrumpfs nicht verändern.
Selbst wenn die Variable im Schleifenrumpf verändert werden, kann es dennoch passieren, dass die Bedingung nie als \(\texttt{False}\) ausgewertet wird.
Der folgende Code soll ausgeben, wie oft \(10\) halbiert werden muss, bis das Ergebnis kleiner als \(1\) ist. Beide Beispiel sind jedoch Endlosschleifen.
n = 0
r = 10
while r > 1:
n = n + 1
x = 10 / 2**n
Die Variable \(\texttt{r}\) wird innerhalb des Schleifenrumpfs nicht verändert. Die Variable \(\texttt{x}\) wird verändert, da aber stets \(\texttt{r} = 10\) gilt, bricht die Schleife nie ab.
n = 0
r = 10
while r > 1:
r = 10 / 2**n
Die Variable \(n\) wird innerhalb des Schleifenrumpf nicht erhört, wodurch die Variable \(\texttt{r}=10\) konstant bleibt. Die Schleife bricht wieder niemals ab.
Was also tun, wenn Sie versehentlich eine Endlosschleife erzeugt und ausgeführt haben?
In einem Jupyter Notebook können Sie einfach auf den Stopp Button klicken.
In diesem Jupyter Book, kommentieren Sie am besten die while-Schleife aus und klicken auf den Button restart oder restart & run all.
In einem Python-Terminal beendet die Tastenkombination Strg + C den laufenden Code.
Aufgabe 1 - Maschinengenauigkeit#
Computer sind nicht in der Lage Zahlen beliebig genau darzustellen und zu speichern. Daher werden nur eine bestimmte, festgelegte Anzahl an Dezimalstellen gespeichert. Wenn zum Beispiel maximal zwei Dezimalstellen gespeichert werden, dann sind die Zahlen \(0.001\) und \(0.008\) für den Computer beide “gleich” Null.
Es stellt sich also die Frage was der größte Wert für \(\varepsilon\) ist, sodass Python zwischen \(1\) und \(1 + \varepsilon\) nicht mehr unterscheiden kann?
Aufgabe 1
Modifizieren Sie den nachfolgenden Code so, dass \(\varepsilon\) so lange halbiert wird bis für Python \(1 + \varepsilon\) gleich \(1\) ist.
x = 1
epsilon = x
y = x + epsilon
epsilon = epsilon / 2
y = x + epsilon
print(str(x) + " + " + str(epsilon) + " ist das gleiche wie " + str(x))
Hinweis
Nutzen Sie eine while-Schleife. Iterieren Sie so lange bis \(\texttt{y}\) (also \( 1 + \varepsilon\)) gleich \(\texttt{x}\) (also \(1\)) ist. Die Python-Syntax für den logischen Operator \(\neq\) ist \(\texttt{!=}\).
Lösung
x = 1
epsilon = x
y = x + epsilon
while y != x:
epsilon = epsilon / 2
y = x + epsilon
print(str(x) + " + " + str(epsilon) + " ist das gleiche wie " + str(x))
Numerische Werte werden in Python standardmäßig im Datentyp \(\texttt{double}\) dargestellt. Ein Skalar benötigt dabei 64 Bit Speicherplatz, was 16 Dezimalstellen entspricht. Beachten Sie, dass \(\varepsilon\) in der Größenordnung von \(10^{-16}\) liegt.
Aufgabe 2 - Best-of-Five#
Um bei einem Spiel mit der Gewinnregel „best out of five“ müssen Sie nur drei Runden gewinnen. Ein Sieger kann in drei, vier oder fünf Runden ermittelt werden, und Sie wissen vorab nicht wie viele Runden Sie brauchen werden. Der nachfolgende Code modelliert eine Runde, indem er einen Sieger bestimmt (\(1\) für Team A und \(2\) für Team B). Die bisherigen Gesamtsiege für jedes Team werden in \(\texttt{teamA}\) und \(\texttt{teamB}\) gespeichert. Mit \(\texttt{np.random.randint(1,3)}\) wird zufällig die Zahl \(1\) (Team A gewinnt) oder \(2\) (Team B gewinnt) generiert.
Aufgabe 2.1
Modifizieren Sie den Code so, dass dieser so lange ausgeführt wird, bis entweder Team A oder Team B drei Spiele gewonnen hat.
import numpy as np
k = 0
teamA = 0
teamB = 0
ergebnis = np.zeros(5)
ergebnis[k] = np.random.randint(1, 3)
teamA = np.sum(ergebnis == 1)
teamB = np.sum(ergebnis == 2)
k = k + 1
Hinweis
Überprüfen Sie ob sowohl Team A als auch Team B bisher weniger als dreimal gewonnen hat. Eine Möglichkeit ist das Maximum von \(\texttt{teamA}\) und \(\texttt{teamB}\) zu betrachten (\(\texttt{np.max([teamA, teamB]) < 3}\)).
Lösung
import numpy as np
k = 0
teamA = 0
teamB = 0
ergebnis = np.zeros(5)
while np.max([teamA, teamB]) < 3:
ergebnis[k] = np.random.randint(1, 3)
teamA = np.sum(ergebnis == 1)
teamB = np.sum(ergebnis == 2)
k = k + 1