Schleifen

Allgemeiner Aufbau einer Schleife

Moebius ring as an infinite loop

In unserem Python-Kurs tauchen wir nun in die mächtige Welt der while-Schleifen ein. Sie stellen in grundlegendes Konzept der Programmierung. Man findet schwerlich eine Programmiersprache, in der es nicht möglich ist, eine Schleife über den Code zu legen. While-Schleifen bieten einen Mechanismus, um einen Codeblock (oder eine Folge von Anweisungen) wiederholt auszuführen, solange eine bestimmte Bedingung erfüllt ist. Der Code innerhalb der Schleife, d. h. der wiederholt ausgeführte Code, wird als Schleifenkörper bezeichnet.

Schleifen werden also benötigt, um einen Codeblock, den man auch als Schleifenkörper bezeichnet, wiederholt auszuführen. In Python gibt es zwei Schleifentypen:

  • die while-Schleife und
  • die for-Schleife

Ablaufdiagramm einer Schleife

Die meisten Schleifen enthalten einen Zähler oder ganz allgemein Variablen, die im Verlauf der Berechnungen innerhalb des Schleifenkörpers ihre Werte ändern. Außerhalb, d.h. noch vor dem Beginn der Schleife, werden diese Variablen initialisiert. Vor jedem Schleifendurchlauf wird geprüft, ob ein Ausdruck, in dem dieser Zähler und gegebenenfalls auch andere Variablen vorkommen, wahr ist. Dieser Ausdruck bestimmt das Endekriterium der Schleife. Solange die Berechnung dieses Ausdrucks "True" liefert, wird der Rumpf der Schleife ausgeführt. Nachdem alle Anweisungen des Schleifenkörpers durchgeführt worden sind, springt die Programmsteuerung automatisch zum Anfang der Schleife, also zur Prüfung des Endekriteriums zurück und prüft wieder, ob dieses nochmals erfüllt ist. Wenn ja, geht es wie oben beschrieben weiter, ansonsten wird der Schleifenkörper nicht mehr ausgeführt und es wird mit dem Rest des Skriptes fortgefahren. Das nebenstehende Diagramm zeigt dies schematisch.

Ganz allgemein unterscheidet man drei verschiedene Schleifentypen in Programmiersprachen:

  • Zähler-kontrollierte Schleifen Ein Programmkonstrukt, mit dem der Schleifenkörper unter der Kontrolle einer Zählervariablen eine bestimmte Anzahl von Malen durchlaufen wird. Dies entspricht der for-Schleife, wie wir sie in C bzw. C++ vorfinden. Python kennt diesen Schleifentyp nicht: for (i=0; i <= n; i++)
  • Bedingungs-kontrollierte Schleifen Eine Schleife wird solange wiederholt, bis sich eine Bedingung ändert, also solange eine Bedingung z.B. wahr ist. Es gibt while-Schleifen und Do-While-Schleifen, die dieses Verhalten haben.
  • Sammlung-kontrollierte Schleifen Mit Sammlung meinen wir Listen, Arrays oder sonstige Anordnungen von Objekten. Über die Elemente einer solchen Sammlung wird mittels einer Schleife iteriert. Diese Schleifen werden meistens eingeleitet mit dem Schlüsselwort "foreach", aber auch mit "for" wie in Python. Wohl einen der bekanntesten Vertreter dieser "Gattung" liefert die Bash-Shell:
     
      for i in *; do echo $i; done 
      

Einfache Beispiele von Schleifen

Das folgende Skript, das wir in der interaktiven Shell direkt eintippen können, gibt die Zahlen von 1 bis 10 aus:

In [4]:
i = 1
while i <= 10:
      print(i)
      i += 1
1
2
3
4
5
6
7
8
9
10

Mit dem nächsten kleinen Python-Skript berechnen wir die Summe der Zahlen von 1 bis 100. In ähnlicher Form würde man es auch in C, C++ oder Java machen. Allerdings geht es in Python deutlich einfacher, wie wir im folgenden Kapitel sehen werden.

In [5]:
n = 100

sum_of_numbers = 0
i = 1
while i <= n:
    sum_of_numbers += i
    i += 1

result = "Summe von 1 bis " + str(n) + ": " + str(sum_of_numbers)
print(result)
Summe von 1 bis 100: 5050

Standard-Eingabe lesen

Bevor wir mit der while-Schleife weitermachen, müssen wir noch ein paar grundsätzliche Dinge über die Standardeingabe und die Standardausgabe klären. Als Standardeingabe gilt normalerweise die Tastatur. Die meisten Shell-Programme schreiben ihre Ausgaben in die Standardausgabe, d.h. das Terminalfenster oder die Textkonsole. Fehlermeldungen werden in die Standard-Fehlerausgabe ausgegeben, was üblicherweise auch dem aktuellen Terminalfenster oder der Textkonsole entspricht. Auch der Python-Interpreter stellt drei Standard-Dateiobjekte zur Verfügung:

Standardeingabe
Standardausgabe
Standardfehlerausgabe 

Sie stehen im Modul sys als

sys.stdin
sys.stdout
sys.stderror 

zur Verfügung.

Das folgende Beispiel-Skript zeigt nun, wie man Zeichen für Zeichen mittels einer while-Schleife von der Standardeingabe (Tastatur) einliest. Mit dem import-Befehl wird das benötigte Modul sys eingelesen.

import sys 

text = ""
while 1:
   c = sys.stdin.read(1)
   text = text + c
   if c == '\n':
       break

print("Eingabe: %s" % text)

Eleganter kann man eine beliebige Eingabezeile von der Standardeingabe natürlich mit der Funktion input(prompt) einlesen.

In [2]:
name = input("Wie heißen Sie?\n")
print(name)
Wie heißen Sie?
Tux
Tux

Der else-Teil

Ablaufdiagramm einer Schleife mit else-Teil

Wie auch die bedingte if-Anweisung hat die while-Schleife in Python im Gegensatz zu anderen Programmiersprachen einen optionalen else-Zweig, was für viele Programmierer gewöhnungsbedürftig ist.

Die Anweisungen im else-Teil werden ausgeführt, sobald die Bedingung nicht mehr erfüllt ist. Sicherlich fragen sich einige nun, worin dann der Unterschied zu einer normalen while-Schleife liegt. Hätte man die Anweisungen nicht in den else-Teil gesteckt sondern einfach hinter die while-Schleife gestellt, wären sie ja auch genauso ausgeführt worden. Es wird erst mit einem break-Kommando, was wir später kennenlernen sinnvoll. Allgemein sieht eine while-Schleife mit else-Teil in Python wie folgt aus:

while Bedingung:
    Anweisung1

    Anweisung n
else:
    Anweisung1

    Anweisung n

Vorzeitiger Abbruch einer while-Schleife

Ablaufdiagramm einer Schleife mit else und break

Normalerweise wird eine while-Schleife nur beendet, wenn die Bedingung im Schleifenkopf nicht mehr erfüllt ist. Mit break kann man aber eine Schleife vorzeitig komplett verlassen. Mit 'continue' beendet man lediglich einen Durchlauf, d.h. man kehrt zum Schleifenkopf, also zur Überprüfung der Bedingung, zurück. Im folgenden Beispiel, einem einfachen Zahlenratespiel, kann man erkennen, dass in Kombination mit einem break der else-Zweig durchaus sinnvoll sein kann. Nur wenn die while-Schleife regulär beendet wird, d.h. der Spieler die Zahl erraten hat, gibt es einen Glückwunsch. Gibt der Spieler auf, d.h. break, dann wird der else-Zweig der while-Schleife nicht ausgeführt.

In [1]:
import random
n = 20
to_be_guessed = random.randint(1,n)

guess = 0
while guess != to_be_guessed:
    guess = int(input("Neuer Versuch: "))
    if guess > 0:
        if guess > to_be_guessed:
            print("Zu gross")
        elif guess < to_be_guessed:
            print("Zu klein")
    else:
        print("Schade, dass du aufgibst!")
        break
else:
    print("Gratuliere, das war's")
Neuer Versuch: 1
Zu klein
Neuer Versuch: 5
Zu klein
Neuer Versuch: 8
Zu klein
Neuer Versuch: 9
Zu klein
Neuer Versuch: 12
Zu klein
Neuer Versuch: 18
Zu klein
Neuer Versuch: 19
Zu klein
Neuer Versuch: 20
Gratuliere, das war's

Das vorige Programm prüft nicht, ob die Zahl einen Sinn ergibt, d.h. ob die Zahl zwischen einer Untergrenze und einer Obergrenze liegt. Wir können unser Programm verbessern. Die Grenzen müssen entsprechend der Benutzereingabe angepasst werden:

In [2]:
import random
upper_bound = 20
lower_bound = 1
to_be_guessed = random.randint(lower_bound, upper_bound)
guess = 0
while guess != to_be_guessed:
    guess = int(input("Dein Versuch: "))
    if guess == 0:   # Aufgegeben
        print("Sorry that you're giving up!")
        break   # breche Schleife ab und gehe nicht zu "else"
    if guess < lower_bound or guess > upper_bound:
        print("Rateversuch außerhalb der Schranken!")
    elif guess > to_be_guessed:
        upper_bound = guess - 1
        print("Zahl ist zu groß!")
    elif guess < to_be_guessed:
        lower_bound = guess + 1
        print("Zahl ist zu klein!")   
else:
    print("Gratulation. Das war's!")
Zahl ist zu groß!
Rateversuch außerhalb der Schranken!
Zahl ist zu groß!
Zahl ist zu groß!
Rateversuch außerhalb der Schranken!
Gratulation. Das war's!

Übungen

Übung 1: Hundealter nach Menschenalter

In unserem Kapitel [Bedingte Anweisungen] (./python3_bedingte_anweisungen.php) hatten wir ein Programm zur Umrechnung des Alters eines Hundes in ein menschliches Alter.

Das war das Programm:

In [ ]:
dog_age = int(input("Alter des Hundes: "))
print()
if dog_age <= 0:
    human_age = -1
elif dog_age == 1:
    human_age = 14
else:
    # this means: dog_age >= 2:
    human_age = 22 + (dog_age - 2) * 5

if human_age > 0:
    print("Entspricht " + str(human_age) + " Menschenjahren!")
else:
    print("Negative Werte oder Null ergeben keinen Sinn!")

Schreiben Sie eine Version mit einer while-Schleife, damit die Leute wiederholt Hundealter umwandeln können. 0 oder ein negativer Wert bedeutet, dass sie aufhören wollen.

Übung 2: Fakultäts-Rechner

Schreiben Sie ein Python-Programm, das die Fakultät einer vom Benutzer eingegebenen Zahl mit Hilfe einer while-Schleife errechnet. Die Fakultät einer nichtnegativen ganzen Zahl n, in der Mathematik als n! bezeichnet, ist das Produkt aller positiven ganzen Zahlen von 1 bis n. Zum Beispiel ist 5! (gelesen als "5 Fakultät") ist gleich 5 * 4 * 3 * 2 * 1, was 120 entspricht.

Übung 3: Passwort-Prüfer

Schreiben Sie ein Python-Programm, das einen einfachen Passwort-Prüfer simuliert. Das Programm soll den Benutzer auffordern, ein Passwort einzugeben, und ihn so lange auffordern, bis er das richtige Passwort eingibt. Sobald das richtige Passwort eingegeben wurde, soll das Programm eine Erfolgsmeldung ausgeben.

Übung 4: Pawword Checker Fortsetzung

Erweitern wir das vorangegangene Beispiel für die Kennwortüberprüfung, indem wir eine maximale Anzahl von Versuchen hinzufügen. Wenn der Benutzer die maximale Anzahl an Versuchen überschreitet, soll das Programm ihn aussperren.

Übung 5: Primzahl-Prüfer

Schreiben Sie ein Python-Programm, das prüft, ob eine gegebene positive ganze Zahl eine Primzahl ist. Das Programm soll den Benutzer auffordern, eine Zahl einzugeben und dann in einer while-Schleife prüfen, ob die Zahl eine Primzahl ist.

Übung 6: Fibonacci-Folge

Schreiben Sie ein Python-Programm, das die Fibonacci-Folge bis zu einer bestimmten Anzahl von Termen erzeugt. Die Fibonacci-Folge ist eine Reihe von Zahlen, bei der jede Zahl die Summe der beiden vorhergehenden ist. Die ersten beiden Zahlen in der Folge sind normalerweise 0 und 1.

Übung 7: Collatz Sequenz

Bei dem Problem geht es um Zahlenfolgen, die nach einem einfachen Bildungsgesetz konstruiert werden:

  • Beginne mit irgendeiner natürlichen Zahl n > 0 n>0.
  • Ist n n gerade, so nimm als nächstes n / 2 n/2.
  • Ist n n ungerade, so nimm als nächstes 3 n + 1 3n+1.
  • Wiederhole die Vorgehensweise mit der erhaltenen Zahl.

Zum Beispiel ergibt sich mit der Startzahl n = 15 die Folge

46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1

Die Folge tritt somit in einen Zyklus ein, in dem die Zahlen 4, 2, 1 ständig wiederholt werden.

Die Collatz-Vermutung lautet nun:

Die Zahlenfolge mündet immer in den Zyklus 4, 2, 1, egal, mit welcher positiven natürlichen Zahl man beginnt.

Diese Vermutung konnte man bislang weder beweisen noch widerlegen.

Mathematische Formulierung:

Die Collatz-Vermutung besagt, dass für eine beliebige positive ganze Zahl $n$ die Folge ${a_k}$ schließlich die Zahl $1$ für eine gewisse Anzahl $k$ erreicht, wenn $a_0$ auf $n$ gesetzt wird.}

$$a_{k+1} = \begin{cases} \frac{a_k}{2} & \text{if $a_k$ is even} \\ 3a_k + 1 & \text{if $a_k$ is odd} \end{cases}$$

  • Schreibe ein Programm, das die Folge einer Zahl $n$ ausgibt.
  • Wie lang ist die Folge für die Zahl 271114753?
  • Schreibe ein Programm, das die Längen der Collatz-Folgen für die Zahlen von 1 bis 100 ausgibt.

Lösungen

Löung zu Übung 1

In [2]:
dog_age = 1
while dog_age > 0:
    dog_age = int(input("Alter des Hundes: (0 oder negative für Abbruch"))
    print()
    if dog_age <= 0:
        human_age = -1
    elif dog_age == 1:
        human_age = 14
    elif dog_age >= 2:
        human_age = 22 + (dog_age - 2) * 5

    print("Entspricht  " + str(human_age) + " Menschenjahren!")
    
print("Programm ist zu Ende!")
Entspricht  37 Menschenjahren!
Entspricht  22 Menschenjahren!
Entspricht  -1 Menschenjahren!
Programm ist zu Ende!

Löung zu Übung 2

In [3]:
n = int(input("Ganze Zahl deren Fakultät berechnet werden soll: "))

factorial = 1
counter = 1

while counter <= n:
    factorial *= counter
    counter += 1

print(f"{n}! = {factorial}")
20! = 2432902008176640000

Löung zu Übung 3

In [5]:
correct_password = "password123"

user_input = input("Eingabe des Passwortes: ")
while user_input != correct_password:
    print("Falsches Passwort. Probiere es nochmals.")
    user_input = input("Eingabe des Passwortes: ")


print("Willkommen. Das war das korrekte Passwort!")
Incorrect password. Please try again.
Willkommen. Das war das korrekte Passwort!

Alternativ können wir die Programmieraufgabe auch wie im Folgenden lösen. Dabei haben wir die Input-Zeile nur einmal. Deswegen müssen wir aber die Variable ùser_input vor der while-Schleife vorbesetzen und die input-Zeile muss als erstes im Scleifen-Körper stehen:

In [4]:
correct_password = "password123"

user_input = " "
while user_input != correct_password:
    user_input = input("Eingabe des Passwortes: ")
    print("Falsches Passwort. Probiere es nochmals.")


print("Willkommen. Das war das korrekte Passwort!")
Falsches Passwort. Probiere es nochmals.
Falsches Passwort. Probiere es nochmals.
Willkommen. Das war das korrekte Passwort!

Löung zu Übung 4

In [6]:
correct_password = "password123"
max_attempts = 3
attempts = 0

while attempts < max_attempts:
    user_input = input("Passwort eingeben: ")
    if user_input == correct_password:
        print("Willkommen. Das war das korrekte Passwort!")
        break
    else:
        attempts += 1
        remaining_attempts = max_attempts - attempts
        if remaining_attempts > 0:
            print(f"Falsches Passwort. Noch {remaining_attempts} Versuche übrig")
        else:
            print("Das war's maximale Eingebeversuche erreicht.")
Falsches Passwort. Noch 2 Versuche übrig
Falsches Passwort. Noch 1 Versuche übrig
Willkommen. Das war das korrekte Passwort!

Löung zu Übung 5

In [5]:
number = int(input("Positive ganze Zahl eingeben: "))

divisor = 1
is_prime = True
while divisor <= number // 2:
    divisor += 1
    if number % divisor == 0 and is_prime:
        is_prime = False
        break  # Schleife wird verlassen, weil Divisor gefunden wurde
    
# Display the result
if is_prime:
    print(number, "ist eine Primzahl.")
else:
    print(number, "ist keine Primzahl.")
11 ist eine Primzahl.

Löung zu Übung 6

In [8]:
n_terms = int(input("Anzahl der gewünschten Fibonacci-Folge-Glieder: "))

old, new = 0, 1
count = 0
while count < n_terms:
    print(old, end=" ")  
    old, new = new, old + new  
    count += 1

print()
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 

Löung zu Übung 7

Der Algorithmus für die Collatz-Sequenz funktioniert wie folgt:

  • Man beginnt mit einer beliebigen positiven ganzen Zahl n.
  • Wenn n gerade ist, teile es durch 2.
  • Wenn n ungerade ist, multipliziere es mit 3 und addiere 1.
  • Wiederhole den Vorgang mit dem berechneten Wert als neuem Wert von n und fahre fort, bis n 1 wird.
In [9]:
n = int(input("Enter a positive integer: "))

if n <= 0:
    print("Please enter a positive integer.")
else:
    print(f"Collatz sequence for {n}:")
    while n != 1:
        if n % 2 == 0:
            n = n // 2
        else:
            n = 3 * n + 1
        print(n, end=', ')
Collatz sequence for 29:
88, 44, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1, 
In [10]:
n = int(input("Enter a positive integer: "))
length_of_sequence = 0
if n <= 0:
    print("Please enter a positive integer.")
else:
    while n != 1:
        if n % 2 == 0:
            n = n // 2
        else:
            n = 3 * n + 1
        length_of_sequence += 1
print(f"Length of sequence: {length_of_sequence}")
Length of sequence: 279
In [11]:
counter = 1
stop_value = 100
while counter <= stop_value:
    #print(f"Collatz sequence for {counter}:")
    n = counter
    length_of_sequence = 0
    while n != 1:
        if n % 2 == 0:
            n = n // 2
        else:
            n = 3 * n + 1
        #print(n, end=", ")
        length_of_sequence += 1
    print(f"{counter}: {length_of_sequence}", end=", ")
    counter += 1
print()
1: 0, 2: 1, 3: 7, 4: 2, 5: 5, 6: 8, 7: 16, 8: 3, 9: 19, 10: 6, 11: 14, 12: 9, 13: 9, 14: 17, 15: 17, 16: 4, 17: 12, 18: 20, 19: 20, 20: 7, 21: 7, 22: 15, 23: 15, 24: 10, 25: 23, 26: 10, 27: 111, 28: 18, 29: 18, 30: 18, 31: 106, 32: 5, 33: 26, 34: 13, 35: 13, 36: 21, 37: 21, 38: 21, 39: 34, 40: 8, 41: 109, 42: 8, 43: 29, 44: 16, 45: 16, 46: 16, 47: 104, 48: 11, 49: 24, 50: 24, 51: 24, 52: 11, 53: 11, 54: 112, 55: 112, 56: 19, 57: 32, 58: 19, 59: 32, 60: 19, 61: 19, 62: 107, 63: 107, 64: 6, 65: 27, 66: 27, 67: 27, 68: 14, 69: 14, 70: 14, 71: 102, 72: 22, 73: 115, 74: 22, 75: 14, 76: 22, 77: 22, 78: 35, 79: 35, 80: 9, 81: 22, 82: 110, 83: 110, 84: 9, 85: 9, 86: 30, 87: 30, 88: 17, 89: 30, 90: 17, 91: 92, 92: 17, 93: 17, 94: 105, 95: 105, 96: 12, 97: 118, 98: 25, 99: 25, 100: 25,