====== - Programmieren mit Python: Verzweigungen & while-Schleifen ====== In diesem Dossier wiederholen wir die **Grundwerkzeuge** des Programmierens. Wir beschränken uns bewusst auf sehr wenige Sprachelemente: Variablen, Ein-/Ausgabe, **Verzweigungen** (`if`/`elif`/`else`) und **while-Schleifen**. Alles andere (`for`-Schleifen, Funktionen, Listen, Dictionaries) ist //nicht// erlaubt – ausser in den Zusatzaufgaben ganz am Schluss. Das mag zuerst nach einer Einschränkung aussehen. Tatsächlich ist es aber genau umgekehrt: Mit diesen wenigen Bausteinen lässt sich **jedes** Programm schreiben. Wer sie sicher beherrscht, hat das Fundament gelegt. Die Aufgaben werden von Kapitel zu Kapitel anspruchsvoller. Löse sie der Reihe nach und schaue dir die Lösung erst an, wenn du selbst einen ernsthaften Versuch unternommen hast. ++++Wichtige Python-Befehle| # Ausgabe print("Hallo Welt") print("Die Summe ist", summe) # mehrere Dinge mit Komma trennen print("Die Summe ist " + str(summe)) # oder als String zusammenhängen print(f"Die Summe ist {summe}") # f-String (elegant) print("kein Zeilenumbruch", end=" ") # Zeilenumbruch unterdrücken # Eingabe (input() liefert IMMER einen String!) name = input("Wie heisst du? ") zahl = int(input("Gib eine ganze Zahl ein: ")) kommazahl = float(input("Gib eine Kommazahl ein: ")) # Rechenoperatoren 7 + 3 # 10 Addition 7 - 3 # 4 Subtraktion 7 * 3 # 21 Multiplikation 7 / 3 # 2.333... Division (ergibt immer einen float!) 7 // 3 # 2 Ganzzahldivision (schneidet den Rest ab) 7 % 3 # 1 Modulo: der Rest bei der Division 7 ** 3 # 343 Potenz # Vergleichsoperatoren (ergeben True oder False) a == b # gleich (ACHTUNG: zwei Gleichheitszeichen!) a != b # ungleich a < b # kleiner a <= b # kleiner oder gleich a > b # grösser a >= b # grösser oder gleich # Logische Verknüpfungen a > 0 and b > 0 # beide Bedingungen müssen erfüllt sein a > 0 or b > 0 # mindestens eine Bedingung muss erfüllt sein not (a > 0) # Umkehrung # Verzweigung (beachte: elif und else sind optional. Man kann auch alleine verwenden: if, if-else, if-elif if temperatur > 25: print("heiss") elif temperatur > 15: print("angenehm") else: print("kalt") # while-Schleife i = 1 while i <= 10: print(i) i = i + 1 # Zufallszahlen import random random.randint(1, 100) # ganze Zufallszahl zwischen 1 und 100 (beide inklusive) # Nützliches abs(-3.5) # 3.5 Betrag round(3.14159, 2) # 3.14 runden "*" * 5 # "*****" String vervielfachen ++++ ++++Der Werkzeugkasten: 6 Muster, die du auswendig können musst| Fast jede Aufgabe in diesem Dossier ist eine Kombination der folgenden Muster. Präge sie dir gut ein! **1. Der Zähler** – zählt, wie oft etwas passiert ist: anzahl = 0 # Startwert: noch nichts gezählt ... anzahl = anzahl + 1 # ein Treffer mehr **2. Der Summierer (Akkumulator)** – addiert Werte auf: summe = 0 # Startwert: 0, denn 0 ändert bei der Addition nichts ... summe = summe + zahl **3. Der Multiplizierer** – multipliziert Werte auf: produkt = 1 # Startwert: 1, denn 1 ändert bei der Multiplikation nichts. Fehler: 0 anstelle 1 ... produkt = produkt * zahl **4. Die Laufvariable** – zählt die Schleifendurchgänge: i = 1 while i <= n: ... i = i + 1 # Vergisst man diese Zeile -> Endlosschleife! **5. Das Maximum (bzw. Minimum)** – merkt sich den bisher besten Wert: maximum = erste_zahl # der erste Wert ist der bisher beste ... if zahl > maximum: maximum = zahl **6. Das Flag (die Merk-Variable)** – merkt sich, ob etwas eingetreten ist: gefunden = False # bis jetzt haben wir nichts gefunden ... if bedingung: gefunden = True ++++ ===== - Aufwärmen: Variablen, Ein- und Ausgabe ===== Wir starten ganz gemütlich. Hier geht es nur darum, wieder ein Gefühl für Variablen, `input()` und `print()` zu bekommen. **Aufgabe:** Schreibe ein Programm, das nach dem Namen und dem Geburtsjahr fragt und dann ausgibt: ``Hallo Anna, du wirst dieses Jahr 17 Jahre alt.`` ++++Lösung| name = input("Wie heisst du? ") geburtsjahr = int(input("In welchem Jahr bist du geboren? ")) alter = 2026 - geburtsjahr print("Hallo " + name + ", du wirst dieses Jahr " + str(alter) + " Jahre alt.") Beachte: `input()` liefert immer einen **String**. Willst du damit rechnen, musst du ihn mit `int()` in eine Zahl umwandeln. Umgekehrt musst du beim Zusammenhängen mit `+` die Zahl mit `str()` wieder zu einem String machen. Mit einem f-String geht es kürzer: print(f"Hallo {name}, du wirst dieses Jahr {alter} Jahre alt.") ++++ **Aufgabe:** Frage die Länge und die Breite eines Rechtecks ab und gib Fläche und Umfang aus. ++++Lösung| laenge = float(input("Länge: ")) breite = float(input("Breite: ")) flaeche = laenge * breite umfang = 2 * (laenge + breite) print("Fläche:", flaeche) print("Umfang:", umfang) Hier verwenden wir `float()` statt `int()`, damit auch Kommazahlen wie `3.5` eingegeben werden können. ++++ **Aufgabe:** Eine Anzahl Sekunden wird eingegeben (z.B. $3'750$). Gib sie im Format ``1 h 2 min 30 s`` aus. //Tipp:// Du brauchst die Ganzzahldivision `//` und den Modulo-Operator `%`. ++++Lösung| total = int(input("Sekunden: ")) stunden = total // 3600 # wie viele ganze Stunden stecken drin? rest = total % 3600 # was bleibt danach übrig? minuten = rest // 60 sekunden = rest % 60 print(stunden, "h", minuten, "min", sekunden, "s") Das Paar `//` und `%` ist eines der wichtigsten Werkzeuge überhaupt: `//` liefert, //wie oft// etwas hineinpasst, `%` liefert, //was übrig bleibt//. Wir werden es noch oft brauchen. ++++ ===== - Verzweigungen ===== Ein Programm soll nicht immer dasselbe tun. Mit `if`, `elif` und `else` treffen wir Entscheidungen. **Aufgabe:** Lies eine ganze Zahl ein und gib aus, ob sie **gerade** oder **ungerade** ist. ++++Lösung| zahl = int(input("Zahl: ")) if zahl % 2 == 0: print("gerade") else: print("ungerade") Eine Zahl ist genau dann gerade, wenn sie bei der Division durch $2$ den Rest $0$ lässt. Der Test `zahl % 2 == 0` ist //der// Standardtrick für "gerade" und wird dir immer wieder begegnen. ++++ **Aufgabe:** Lies eine ganze Zahl ein und gib aus, ob sie **positiv**, **negativ** oder **null** ist. ++++Lösung| zahl = int(input("Zahl: ")) if zahl > 0: print("positiv") elif zahl < 0: print("negativ") else: print("null") Wichtig: Sobald ein Zweig zutrifft, werden **alle folgenden** `elif`/`else` übersprungen. Wenn also `zahl > 0` wahr ist, wird `zahl < 0` gar nicht mehr geprüft. ++++ **Aufgabe:** Lies drei Zahlen ein und gib die **grösste** aus. Verwende dazu nur `if`/`elif`/`else` (kein `max()`). ++++Lösung| a = float(input("1. Zahl: ")) b = float(input("2. Zahl: ")) c = float(input("3. Zahl: ")) if a >= b and a >= c: print("Die grösste Zahl ist", a) elif b >= a and b >= c: print("Die grösste Zahl ist", b) else: print("Die grösste Zahl ist", c) Eine elegantere Variante nach dem **Maximum-Muster** aus dem Werkzeugkasten – die wird später bei vielen Zahlen unentbehrlich: maximum = a # a ist vorläufig die grösste if b > maximum: maximum = b if c > maximum: maximum = c print("Die grösste Zahl ist", maximum) ++++ **Aufgabe:** Lies eine Punktzahl zwischen $0$ und $100$ ein und gib die Note nach folgender Tabelle aus: ^ Punkte ^ Note ^ | 90–100 | 6 | | 75–89 | 5 | | 60–74 | 4 | | 40–59 | 3 | | 0–39 | 2 | Fange auch ungültige Eingaben (kleiner als $0$ oder grösser als $100$) ab. ++++Lösung| punkte = int(input("Punkte: ")) if punkte < 0 or punkte > 100: print("Ungültige Eingabe!") elif punkte >= 90: print("Note 6") elif punkte >= 75: print("Note 5") elif punkte >= 60: print("Note 4") elif punkte >= 40: print("Note 3") else: print("Note 2") Der Trick bei einer solchen `elif`-Kette: Wir prüfen **von oben nach unten** und müssen die untere Grenze jeweils gar nicht mehr testen. Kommt Python zur Zeile `elif punkte >= 75:`, dann //weiss// es bereits, dass `punkte < 90` gilt. ++++ **Aufgabe:** Lies ein Jahr ein und gib aus, ob es ein **Schaltjahr** ist. Regel: * Durch $4$ teilbar → Schaltjahr, * ausser durch $100$ teilbar → kein Schaltjahr, * ausser durch $400$ teilbar → doch Schaltjahr. Teste dein Programm mit $2024$, $1900$, $2000$ und $2023$. ++++Lösung| Die Reihenfolge ist entscheidend: Wir müssen die **speziellste** Regel zuerst prüfen. jahr = int(input("Jahr: ")) if jahr % 400 == 0: print("Schaltjahr") elif jahr % 100 == 0: print("kein Schaltjahr") elif jahr % 4 == 0: print("Schaltjahr") else: print("kein Schaltjahr") Ergebnisse: $2024$ Schaltjahr, $1900$ kein Schaltjahr, $2000$ Schaltjahr, $2023$ kein Schaltjahr. Alternativ mit einer einzigen logischen Bedingung: if (jahr % 4 == 0 and jahr % 100 != 0) or jahr % 400 == 0: print("Schaltjahr") else: print("kein Schaltjahr") ++++ ===== - Erste while-Schleifen ===== Eine `while`-Schleife wiederholt einen Block **solange** eine Bedingung wahr ist. Drei Dinge gehören immer dazu: - **Initialisierung:** die Laufvariable vor der Schleife setzen - **Bedingung:** wann soll weitergemacht werden? - **Veränderung:** die Laufvariable im Schleifenkörper verändern Vergisst du Punkt 3, läuft dein Programm ewig. Mit ``Ctrl + C`` brichst du eine Endlosschleife ab. **Aufgabe:** Gib die Zahlen von $1$ bis $10$ aus, jede auf einer eigenen Zeile. ++++Lösung| i = 1 # Initialisierung while i <= 10: # Bedingung print(i) i = i + 1 # Veränderung Verfolge im Kopf, was passiert: `i` ist zuerst $1$, wird ausgegeben, dann auf $2$ erhöht. Beim Durchgang mit $i = 10$ wird noch ausgegeben, dann wird `i` auf $11$ gesetzt – und die Bedingung `11 <= 10` ist falsch. Die Schleife endet. ++++ **Aufgabe:** Lies eine Zahl $n$ ein und mache einen **Countdown** von $n$ bis $0$. Am Schluss soll ``Start!`` ausgegeben werden. ++++Lösung| n = int(input("Von welcher Zahl soll der Countdown starten? ")) while n >= 0: print(n) n = n - 1 print("Start!") Hier zählen wir **rückwärts**. Entsprechend dreht sich auch die Bedingung um: `n >= 0` statt `n <= 10`. ++++ **Aufgabe:** Gib alle **Vielfachen von $7$** aus, die kleiner als $100$ sind. Löse die Aufgabe auf **zwei** Arten: - mit einer Laufvariablen, die in Siebnerschritten wächst - mit einer Laufvariablen, die in Einerschritten wächst, plus einer Verzweigung ++++Lösung| **Variante 1 – direkt in Siebnerschritten:** v = 7 while v < 100: print(v) v = v + 7 **Variante 2 – jede Zahl prüfen:** i = 1 while i < 100: if i % 7 == 0: print(i) i = i + 1 Variante 1 ist deutlich effizienter (sie macht 14 statt 99 Durchgänge). Variante 2 ist dafür flexibler: Sie lässt sich sofort auf kompliziertere Bedingungen erweitern, etwa "durch 7 //oder// durch 5 teilbar". ++++ **Aufgabe:** Gib alle **Zweierpotenzen** aus, die höchstens $1000$ sind: $1, 2, 4, 8, \ldots$ ++++Lösung| z = 1 while z <= 1000: print(z) z = z * 2 Ausgabe: $1, 2, 4, \ldots, 512$. Die Laufvariable muss sich **nicht** immer um einen festen Betrag ändern – hier wird sie verdoppelt. Dadurch wächst sie sehr schnell, und die Schleife ist nach 10 Durchgängen fertig. ++++ **Aufgabe:** Gib eine Tabelle der Quadratzahlen und Kubikzahlen von $1$ bis $12$ aus: n n^2 n^3 1 1 1 2 4 8 3 9 27 ... //Tipp:// Mit `\t` erzeugst du einen Tabulator. ++++Lösung| print("n\tn^2\tn^3") n = 1 while n <= 12: print(n, "\t", n**2, "\t", n**3) n = n + 1 Die Ausgabezeile lässt sich auch mit einem f-String schreiben, was besonders bei fixen Spaltenbreiten schöner aussieht: print(f"{n:3} {n**2:6} {n**3:6}") Dabei bedeutet `{n:3}`, dass die Zahl auf eine Breite von 3 Zeichen rechtsbündig ausgerichtet wird. ++++ ===== - Aufsummieren und Aufmultiplizieren ===== Jetzt kommt das wohl wichtigste Muster überhaupt: der **Akkumulator**. Wir legen eine Variable an, die das Zwischenergebnis speichert, und verändern sie in jedem Schleifendurchgang. **Aufgabe:** Lies eine Zahl $n$ ein und berechne die Summe $1 + 2 + 3 + \ldots + n$. ++++Lösung| n = int(input("n: ")) summe = 0 # noch nichts addiert i = 1 while i <= n: summe = summe + i # das ist der Kern des Ganzen i = i + 1 print("Die Summe ist", summe) Die Zeile `summe = summe + i` liest sich anfangs seltsam. In der Mathematik wäre das eine unlösbare Gleichung! In der Programmierung bedeutet `=` aber **zuweisen**, nicht "ist gleich": Berechne die rechte Seite mit dem //alten// Wert von `summe` und speichere das Ergebnis als //neuen// Wert von `summe`. Kürzer schreibt man dasselbe als `summe += i`. //Kontrolle:// Für $n = 100$ muss $5050$ herauskommen (Gauss lässt grüssen). ++++ **Aufgabe:** Berechne die **Fakultät** $n! = 1 \cdot 2 \cdot 3 \cdot \ldots \cdot n$. ++++Lösung| n = int(input("n: ")) produkt = 1 # Startwert 1, NICHT 0! i = 1 while i <= n: produkt = produkt * i i = i + 1 print(n, "! =", produkt) Der häufigste Fehler hier: `produkt = 0` als Startwert. Dann bleibt das Ergebnis für immer $0$. Beim Summieren ist $0$ das neutrale Element, beim Multiplizieren ist es $1$. //Kontrolle:// $5! = 120$, $10! = 3'628'800$. ++++ **Aufgabe:** Berechne die Summe aller **geraden** Zahlen von $1$ bis $n$, und gleichzeitig die Summe aller **ungeraden**. Gib beide aus. ++++Lösung| n = int(input("n: ")) summe_gerade = 0 summe_ungerade = 0 i = 1 while i <= n: if i % 2 == 0: summe_gerade = summe_gerade + i else: summe_ungerade = summe_ungerade + i i = i + 1 print("gerade: ", summe_gerade) print("ungerade:", summe_ungerade) Hier kombinieren wir zum ersten Mal Schleife und Verzweigung: Die Schleife besucht **jede** Zahl, die Verzweigung entscheidet, **welcher** Akkumulator erhöht wird. Zwei Akkumulatoren nebeneinander zu führen ist völlig üblich. ++++ **Aufgabe:** Du legst $1000$ Franken zu $2\%$ Zins pro Jahr an. Nach wie vielen Jahren hast du zum ersten Mal mehr als $2000$ Franken? Gib auch den Kontostand nach jedem Jahr aus. ++++Lösung| kapital = 1000 jahre = 0 while kapital < 2000: kapital = kapital * 1.02 jahre = jahre + 1 print("Jahr", jahre, ":", round(kapital, 2)) print("Nach", jahre, "Jahren ist das Kapital verdoppelt.") Antwort: nach **36** Jahren. Das ist ein neuer Typ von Schleife! Bisher wussten wir vorher, wie oft sie läuft. Hier wissen wir es **nicht** – die Schleife läuft, bis eine Bedingung erfüllt ist. Genau dafür ist `while` gemacht, und genau hier wäre eine `for`-Schleife das falsche Werkzeug. ++++ ===== - Eingabeschleifen: Abbruch durch den Benutzer ===== Bis jetzt haben wir immer eine feste Anzahl Zahlen verarbeitet. Nun soll der Benutzer selbst entscheiden, wann Schluss ist. Dazu vereinbart man einen **Abbruchwert** (engl. //sentinel//), z.B. die $0$. **Aufgabe:** Lies so lange Zahlen ein, bis der Benutzer $0$ eingibt. Gib danach die **Summe** aller eingegebenen Zahlen aus. ++++Lösung| summe = 0 zahl = int(input("Zahl (0 = Ende): ")) # erste Eingabe VOR der Schleife while zahl != 0: summe = summe + zahl zahl = int(input("Zahl (0 = Ende): ")) # nächste Eingabe am ENDE der Schleife print("Summe:", summe) Dieses Muster heisst **"Lies vor der Schleife, lies am Ende der Schleife"**. Es sieht unschön aus, weil die `input()`-Zeile zweimal vorkommt, ist aber der saubere Standardweg: Die Bedingung `zahl != 0` kann nur geprüft werden, wenn bereits eine Zahl vorliegt. Wichtig: Die $0$ selbst wird **nicht** mitgezählt – sobald sie eingegeben wird, bricht die Schleife ab, bevor sie addiert wird. ++++ **Aufgabe:** Erweitere das vorige Programm: Gib zusätzlich die **Anzahl** der eingegebenen Zahlen und den **Mittelwert** aus. Achte darauf, dass dein Programm nicht abstürzt, wenn der Benutzer sofort $0$ eingibt. ++++Lösung| summe = 0 anzahl = 0 zahl = int(input("Zahl (0 = Ende): ")) while zahl != 0: summe = summe + zahl anzahl = anzahl + 1 # der Zähler aus dem Werkzeugkasten zahl = int(input("Zahl (0 = Ende): ")) print("Anzahl:", anzahl) print("Summe: ", summe) if anzahl > 0: print("Mittelwert:", summe / anzahl) else: print("Keine Zahlen eingegeben.") Der Zähler und der Summierer laufen hier **parallel** mit. Die abschliessende Verzweigung ist kein Schönheitsfehler, sondern notwendig: Ohne sie würde `summe / anzahl` bei $0$ Eingaben eine Division durch null verursachen und das Programm abstürzen lassen. Solche **Randfälle** (engl. //edge cases//) mitzudenken gehört zum Handwerk. ++++ **Aufgabe:** Lies wieder Zahlen ein, bis $0$ eingegeben wird. Gib die **grösste** und die **kleinste** eingegebene Zahl aus. Es dürfen auch negative Zahlen eingegeben werden! ++++Lösung| zahl = int(input("Zahl (0 = Ende): ")) if zahl == 0: print("Keine Zahlen eingegeben.") else: maximum = zahl # die erste Zahl ist vorläufig grösste UND kleinste minimum = zahl while zahl != 0: if zahl > maximum: maximum = zahl if zahl < minimum: minimum = zahl zahl = int(input("Zahl (0 = Ende): ")) print("Grösste:", maximum) print("Kleinste:", minimum) Der entscheidende Punkt ist die **Initialisierung**: Wir setzen `maximum = 0` **nicht**! Gibt der Benutzer nur negative Zahlen ein, wäre sonst $0$ das falsche Ergebnis. Der einzig sichere Startwert ist der **erste tatsächlich eingegebene Wert**. Beachte auch, dass zwei aufeinanderfolgende `if` verwendet werden und **kein** `elif`. Beim allerersten Durchgang müssen ja beide Vergleiche stattfinden können. ++++ **Aufgabe:** Programmiere eine Passwortabfrage. Das Passwort lautet ``python``. Der Benutzer hat **höchstens drei Versuche**. Bei richtigem Passwort: ``Willkommen!``, nach drei Fehlversuchen: ``Zugang gesperrt.`` ++++Lösung| passwort = "python" versuche = 1 eingabe = input("Passwort: ") while eingabe != passwort and versuche < 3: versuche = versuche + 1 eingabe = input("Falsch. Passwort: ") if eingabe == passwort: print("Willkommen!") else: print("Zugang gesperrt.") Neu ist hier die **zusammengesetzte Bedingung**: Die Schleife läuft nur weiter, wenn das Passwort falsch ist **und** noch Versuche übrig sind. Sobald eine der beiden Bedingungen kippt, ist Schluss. Nach der Schleife wissen wir aber nicht, //welche// der beiden Bedingungen zum Abbruch geführt hat. Deshalb müssen wir das mit einem abschliessenden `if` nachträglich herausfinden. Das ist ein sehr typisches Vorgehen. ++++ ===== - Schleife und Verzweigung kombiniert ===== Jetzt wird es interessant: Innerhalb der Schleife treffen wir Entscheidungen. **Aufgabe:** Wie viele Zahlen von $1$ bis $100$ sind durch $3$ teilbar, aber **nicht** durch $5$? Lass das Programm die Antwort berechnen. ++++Lösung| i = 1 anzahl = 0 while i <= 100: if i % 3 == 0 and i % 5 != 0: anzahl = anzahl + 1 i = i + 1 print("Anzahl:", anzahl) Antwort: **27**. Nachrechnen: Von $1$ bis $100$ gibt es $33$ Vielfache von $3$. Davon sind $6$ auch durch $5$ teilbar (nämlich die Vielfachen von $15$: $15, 30, 45, 60, 75, 90$). Bleiben $33 - 6 = 27$. ++++ **Aufgabe:** Der Computer denkt sich eine Zufallszahl zwischen $1$ und $100$. Der Benutzer rät. Nach jedem Tipp gibt das Programm ``zu klein`` oder ``zu gross`` aus. Bei richtigem Tipp wird die Anzahl der Versuche ausgegeben. ++++Lösung| import random zahl = random.randint(1, 100) tipp = int(input("Dein Tipp: ")) versuche = 1 while tipp != zahl: if tipp < zahl: print("Zu klein!") else: print("Zu gross!") tipp = int(input("Dein Tipp: ")) versuche = versuche + 1 print("Richtig! Du hast", versuche, "Versuche gebraucht.") Wieder dasselbe Muster wie bei der Eingabeschleife: erste Eingabe vor der Schleife, weitere Eingaben am Ende des Schleifenkörpers. //Übrigens:// Wer geschickt rät (immer in der Mitte des verbleibenden Bereichs), braucht **nie mehr als 7 Versuche**. Denn $2^7 = 128 > 100$. Diese Strategie heisst **binäre Suche** – sie kommt in der letzten Zusatzaufgabe wieder vor. ++++ **Aufgabe (Collatz):** Lies eine natürliche Zahl $n > 0$ ein und wende folgende Regel wiederholt an: * Ist $n$ gerade, ersetze $n$ durch $n/2$. * Ist $n$ ungerade, ersetze $n$ durch $3n+1$. Gib jeden Zwischenschritt aus und höre auf, sobald $n = 1$ erreicht ist. Zähle die Schritte. Probiere danach $n = 27$ aus. ++++Lösung| n = int(input("Startzahl: ")) schritte = 0 while n != 1: if n % 2 == 0: n = n // 2 # Ganzzahldivision, damit kein float entsteht else: n = 3 * n + 1 schritte = schritte + 1 print(n) print("Anzahl Schritte:", schritte) Für $n = 27$ braucht man **111 Schritte**, und zwischendurch klettert die Zahl bis auf $9232$ hinauf – obwohl sie so klein startet. Das Bemerkenswerte an dieser Aufgabe: Es ist bis heute **unbewiesen**, ob die Schleife für jede Startzahl überhaupt terminiert. Das ist die berühmte //Collatz-Vermutung//. Man hat sie für sehr grosse Zahlen per Computer überprüft, aber niemand konnte sie bisher allgemein beweisen. Du schreibst hier also ein Programm, von dem niemand weiss, ob es immer anhält. ++++ **Aufgabe:** Berechne die **Quersumme** einer eingegebenen Zahl. Beispiel: $4271 \rightarrow 4+2+7+1 = 14$. //Tipp:// Mit `n % 10` bekommst du die **letzte** Ziffer, mit `n // 10` schneidest du sie ab. ++++Lösung| n = int(input("Zahl: ")) quersumme = 0 while n > 0: quersumme = quersumme + n % 10 # letzte Ziffer dazuzählen n = n // 10 # letzte Ziffer abschneiden print("Quersumme:", quersumme) Verfolge es an $4271$ Schritt für Schritt: ^ Durchgang ^ n ^ n % 10 ^ quersumme ^ | Start | 4271 | – | 0 | | 1 | 427 | 1 | 1 | | 2 | 42 | 7 | 8 | | 3 | 4 | 2 | 10 | | 4 | 0 | 4 | 14 | Sobald `n` den Wert $0$ erreicht, ist die Zahl "aufgebraucht" und die Schleife endet. Diese Technik, eine Zahl **ziffernweise zu zerlegen**, ist extrem nützlich – merke sie dir gut. ++++ **Aufgabe:** Drehe eine eingegebene Zahl um: $4271 \rightarrow 1724$. Das Ergebnis soll eine **Zahl** sein, kein String. ++++Lösung| n = int(input("Zahl: ")) rueckwaerts = 0 while n > 0: ziffer = n % 10 rueckwaerts = rueckwaerts * 10 + ziffer # bisheriges Ergebnis "nach links schieben" n = n // 10 print("Umgekehrt:", rueckwaerts) Der Trick steckt in `rueckwaerts * 10 + ziffer`: Die Multiplikation mit $10$ schiebt alle bisherigen Ziffern eine Stelle nach links und macht rechts Platz für die neue. Ablauf für $4271$: $0 \rightarrow 1 \rightarrow 17 \rightarrow 172 \rightarrow 1724$. //Zusatz:// Baue das Programm so um, dass es prüft, ob eine Zahl ein **Palindrom** ist (vorwärts wie rückwärts gleich, z.B. $12321$). Du musst dir dazu die ursprüngliche Zahl in einer zweiten Variablen merken, bevor du sie in der Schleife zerstörst! ++++ ===== - Anspruchsvollere Aufgaben ===== **Aufgabe:** Lies eine Zahl $n \geq 2$ ein und prüfe, ob sie eine **Primzahl** ist. ++++Lösung| n = int(input("Zahl: ")) ist_prim = True # Flag: wir gehen erst mal davon aus, dass es eine Primzahl ist teiler = 2 while teiler < n: if n % teiler == 0: ist_prim = False # ein Teiler gefunden -> doch keine Primzahl teiler = teiler + 1 if ist_prim: print(n, "ist eine Primzahl.") else: print(n, "ist keine Primzahl.") Das ist die erste Anwendung des **Flag-Musters**: Eine Wahrheitswert-Variable merkt sich, ob während der Schleife etwas passiert ist. Nach der Schleife wird sie ausgewertet. **Verbesserung:** Man muss nur bis zur Wurzel von $n$ testen. Denn wenn $n = a \cdot b$ mit $a \leq b$, dann ist zwingend $a \leq \sqrt{n}$. Statt `math.sqrt` zu verwenden, quadriert man einfach den Teiler: while teiler * teiler <= n: if n % teiler == 0: ist_prim = False teiler = teiler + 1 Für $n = 1'000'003$ sind das statt einer Million nur noch rund tausend Durchgänge. Ein spürbarer Unterschied! ++++ **Aufgabe:** Gib **alle Primzahlen bis $100$** aus. Du brauchst dazu zwei ineinander verschachtelte while-Schleifen. ++++Lösung| n = 2 # äussere Schleife: die Kandidaten while n <= 100: ist_prim = True teiler = 2 # innere Schleife: die möglichen Teiler while teiler * teiler <= n: if n % teiler == 0: ist_prim = False teiler = teiler + 1 if ist_prim: print(n, end=" ") n = n + 1 Ausgabe: ``2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97`` **Der wichtigste Punkt bei verschachtelten Schleifen:** Die Variablen der inneren Schleife (`teiler` und `ist_prim`) müssen **innerhalb** der äusseren Schleife zurückgesetzt werden. Setzt man sie versehentlich ganz nach oben, funktioniert nur der erste Durchgang – ein Klassiker unter den Fehlern. ++++ **Aufgabe:** Berechne den **grössten gemeinsamen Teiler** zweier Zahlen mit dem euklidischen Algorithmus: Ersetze wiederholt das Paar $(a, b)$ durch $(b,\ a \bmod b)$, bis $b = 0$ ist. Dann ist $a$ der ggT. ++++Lösung| a = int(input("a: ")) b = int(input("b: ")) while b != 0: rest = a % b a = b b = rest print("Der ggT ist", a) Die Hilfsvariable `rest` ist unverzichtbar: Würde man direkt `a = b` schreiben, wäre der alte Wert von `a` verloren, bevor man ihn für `a % b` braucht. Dieses **Zwischenspeichern beim Vertauschen** ist eine Standardtechnik. Ablauf für $a = 48$, $b = 18$: $(48,18) \rightarrow (18,12) \rightarrow (12,6) \rightarrow (6,0)$. Ergebnis: $6$. Der Algorithmus ist über 2000 Jahre alt und immer noch der schnellste bekannte Weg – er stammt aus Euklids //Elementen//. ++++ **Aufgabe:** Berechne die **Quadratwurzel** einer positiven Zahl $x$ mit dem Heron-Verfahren, ohne `math.sqrt` zu verwenden. Starte mit einer Näherung $g = x/2$ und verbessere sie wiederholt mit $$g_{\text{neu}} = \frac{1}{2}\left(g + \frac{x}{g}\right)$$ bis sich der Wert kaum mehr ändert. ++++Lösung| x = float(input("Zahl: ")) g = x / 2 schritte = 0 while abs(g * g - x) > 0.000001: # solange das Quadrat noch zu weit weg ist g = (g + x / g) / 2 schritte = schritte + 1 print(schritte, ":", g) print("Wurzel aus", x, "ist ungefähr", g) Für $x = 2$ ist man nach vier Schritten schon bei $1.41421356\ldots$ – das Verfahren **verdoppelt die Anzahl korrekter Stellen** mit jedem Schritt. Zwei Dinge sind hier wichtig: * Der Abbruch erfolgt über eine **Toleranz**, nicht über Gleichheit. `while g * g != x` würde bei Kommazahlen fast nie terminieren, weil floats gerundet gespeichert werden. * Für $x = 0$ würde das Programm mit einer Division durch null abstürzen. Ein sauberes Programm fängt das vorher ab. ++++ **Aufgabe:** Wandle eine eingegebene Dezimalzahl in ihre **Binärdarstellung** um, ohne `bin()` zu verwenden. //Tipp:// Teile wiederholt durch $2$ und sammle die Reste ein – aber achte auf die Reihenfolge! ++++Lösung| n = int(input("Dezimalzahl: ")) binaer = "" while n > 0: binaer = str(n % 2) + binaer # neue Ziffer VORNE anfügen n = n // 2 if binaer == "": # Sonderfall: die Eingabe war 0 binaer = "0" print("Binär:", binaer) Der springende Punkt ist `str(n % 2) + binaer` statt `binaer + str(n % 2)`: Die Reste fallen bei der Division von **hinten nach vorne** an, also müssen sie vorne angehängt werden. Ablauf für $13$: Reste $1, 0, 1, 1$ – der String wächst als ``"1"``, ``"01"``, ``"101"``, ``"1101"``. Und tatsächlich ist $13 = 8 + 4 + 1 = 1101_2$. Der Sonderfall $n = 0$ muss extra behandelt werden, weil die Schleife dann gar nie durchlaufen wird und der String leer bleibt. ++++ **Aufgabe:** Gib mit verschachtelten while-Schleifen folgende Figuren aus. Die Höhe $n$ wird eingelesen. * Figur 1: ***** Figur 2: * ** **** *** *** *** ***** **** ** ******* ***** * ++++Lösung| **Figur 1** – wachsendes Dreieck, ganz ohne innere Schleife: n = int(input("Höhe: ")) zeile = 1 while zeile <= n: print("*" * zeile) zeile = zeile + 1 **Figur 2** – schrumpfendes Dreieck: zeile = n while zeile >= 1: print("*" * zeile) zeile = zeile - 1 **Figur 3** – die Pyramide braucht zusätzlich führende Leerzeichen. In Zeile $z$ stehen $n - z$ Leerzeichen und $2z - 1$ Sterne: zeile = 1 while zeile <= n: print(" " * (n - zeile) + "*" * (2 * zeile - 1)) zeile = zeile + 1 Wer den Trick `"*" * zeile` nicht verwenden darf, baut die Zeile mit einer **inneren Schleife** zusammen: zeile = 1 while zeile <= n: ausgabe = "" spalte = 1 while spalte <= zeile: ausgabe = ausgabe + "*" spalte = spalte + 1 print(ausgabe) zeile = zeile + 1 Das ist wieder der **Akkumulator** – diesmal aber mit einem String statt einer Zahl. ++++ **Aufgabe:** Gib die kleine Einmaleins-Tabelle ($1$ bis $10$) als sauberes Gitter aus. ++++Lösung| zeile = 1 while zeile <= 10: ausgabe = "" spalte = 1 while spalte <= 10: ausgabe = ausgabe + str(zeile * spalte) + "\t" spalte = spalte + 1 print(ausgabe) zeile = zeile + 1 Zwei Beobachtungen: * Die **innere** Schleife läuft vollständig durch, bevor die äussere einen Schritt weitergeht. Insgesamt gibt es $10 \cdot 10 = 100$ Durchgänge der inneren Schleife. * Die Variable `spalte` und der String `ausgabe` werden bei jedem äusseren Durchgang **neu initialisiert**. Genau wie bei der Primzahl-Aufgabe. Alternativ kann man auch direkt ausgeben und den Zeilenumbruch unterdrücken: while spalte <= 10: print(zeile * spalte, end="\t") spalte = spalte + 1 print() # leeres print() erzeugt den Zeilenumbruch ++++ **Aufgabe (Knacknuss):** Ein Bauer hat Hühner und Kaninchen. Zusammen haben die Tiere $35$ Köpfe und $94$ Beine. Wie viele Tiere jeder Sorte hat er? Löse die Aufgabe mit einer **while-Schleife durch systematisches Ausprobieren** – nicht mit einem Gleichungssystem. ++++Lösung| koepfe = 35 beine = 94 huehner = 0 while huehner <= koepfe: kaninchen = koepfe - huehner # der Rest muss Kaninchen sein if 2 * huehner + 4 * kaninchen == beine: print("Hühner:", huehner, " Kaninchen:", kaninchen) huehner = huehner + 1 Ergebnis: **23 Hühner und 12 Kaninchen**. Diese Technik heisst **Brute Force** (rohe Gewalt): Man probiert einfach alle Möglichkeiten durch und prüft jede. Für einen Menschen wäre das mühsam, für einen Computer sind $36$ Durchgänge ein Klacks. Beachte den Trick, dass nur **eine** Grösse durchprobiert wird – die zweite ergibt sich aus der Kopfzahl. Mit zwei verschachtelten Schleifen ginge es auch, wäre aber unnötig aufwendig. ++++ ===== - Zusatzaufgaben (Listen erlaubt) ===== Wer alles Bisherige gelöst hat, darf sich hier austoben. Ab jetzt sind **Listen** erlaubt. Die wichtigsten Befehle: liste = [] # leere Liste liste = [3, 1, 4, 1, 5] # Liste mit Startwerten liste.append(9) # Element hinten anfügen liste[0] # erstes Element (Zählung beginnt bei 0!) liste[-1] # letztes Element len(liste) # Anzahl Elemente liste[2] = 7 # Element überschreiben liste.pop(1) # Element an Position 1 entfernen **Aufgabe:** Lies Zahlen ein, bis $0$ eingegeben wird, und speichere sie in einer **Liste**. Gib danach aus: Anzahl, Summe, Mittelwert, Maximum und Minimum – und zwar alles selbst berechnet mit while-Schleifen, ohne `sum()`, `max()` oder `min()`. ++++Lösung| zahlen = [] zahl = int(input("Zahl (0 = Ende): ")) while zahl != 0: zahlen.append(zahl) zahl = int(input("Zahl (0 = Ende): ")) if len(zahlen) == 0: print("Keine Zahlen eingegeben.") else: summe = 0 maximum = zahlen[0] minimum = zahlen[0] i = 0 while i < len(zahlen): summe = summe + zahlen[i] if zahlen[i] > maximum: maximum = zahlen[i] if zahlen[i] < minimum: minimum = zahlen[i] i = i + 1 print("Anzahl: ", len(zahlen)) print("Summe: ", summe) print("Mittelwert:", summe / len(zahlen)) print("Maximum: ", maximum) print("Minimum: ", minimum) Achte auf die Bedingung `i < len(zahlen)` mit **echt kleiner**: Bei einer Liste mit $5$ Elementen sind die gültigen Indizes $0$ bis $4$. Ein `<=` würde einen Zugriffsfehler auslösen – der berühmte //off-by-one error//. Der Vorteil der Liste: Wir können die Zahlen **mehrfach** durchgehen. Ohne Liste ist jede Zahl nach der Verarbeitung für immer weg. ++++ **Aufgabe:** Sortiere eine Liste von Zahlen mit **Bubble Sort**: Gehe die Liste wiederholt durch und vertausche benachbarte Elemente, wenn sie in der falschen Reihenfolge stehen. Wiederhole, bis kein Tausch mehr nötig war. ++++Lösung| zahlen = [5, 2, 9, 1, 7, 3] getauscht = True # Flag while getauscht: getauscht = False # Annahme: diesmal ist alles sortiert i = 0 while i < len(zahlen) - 1: if zahlen[i] > zahlen[i + 1]: merker = zahlen[i] # Vertauschen mit Hilfsvariable zahlen[i] = zahlen[i + 1] zahlen[i + 1] = merker getauscht = True # doch nicht sortiert! i = i + 1 print(zahlen) # Zwischenstand anschauen print("Sortiert:", zahlen) Das Flag `getauscht` steuert hier die **äussere** Schleife. Solange in einem Durchgang noch getauscht wurde, kann die Liste nicht sortiert sein. Ging ein ganzer Durchgang ohne Tausch vorbei, sind wir fertig. In Python kann man übrigens eleganter tauschen: `zahlen[i], zahlen[i+1] = zahlen[i+1], zahlen[i]`. Die Variante mit dem Merker funktioniert dafür in **jeder** Programmiersprache – und du solltest sie kennen. ++++ **Aufgabe (Sieb des Eratosthenes):** Finde alle Primzahlen bis $100$, ohne eine einzige Division durchzuführen. Idee: Schreibe alle Zahlen von $2$ bis $100$ auf. Nimm die erste unmarkierte Zahl – sie ist prim. Streiche danach alle ihre Vielfachen. Wiederhole. ++++Lösung| n = 100 ist_prim = [True] * (n + 1) # ist_prim[k] sagt, ob k prim ist ist_prim[0] = False ist_prim[1] = False p = 2 while p * p <= n: if ist_prim[p]: vielfaches = p * p # alles Kleinere wurde schon gestrichen while vielfaches <= n: ist_prim[vielfaches] = False vielfaches = vielfaches + p # ADDIEREN, nicht dividieren! p = p + 1 k = 2 while k <= n: if ist_prim[k]: print(k, end=" ") k = k + 1 Der Clou ist die Zeile `vielfaches = vielfaches + p`: Statt für jede Zahl zu prüfen, //ob// sie durch $p$ teilbar ist, springen wir direkt von Vielfachem zu Vielfachem. Das Sieb ist deshalb dramatisch schneller als das Durchprobieren aller Teiler. Warum startet die innere Schleife bei $p \cdot p$ und nicht bei $2p$? Weil alle kleineren Vielfachen von $p$ bereits einen kleineren Primfaktor haben und darum schon gestrichen wurden. Zum Beispiel wurde $3 \cdot 5 = 15$ schon beim Sieben mit $3$ erwischt. ++++ **Aufgabe (Umgekehrtes Zahlenraten):** Jetzt denkt //du// dir eine Zahl zwischen $1$ und $1000$, und der **Computer** rät. Nach jedem Tipp antwortest du mit ``k`` (zu klein), ``g`` (zu gross) oder ``r`` (richtig). Programmiere den Computer so, dass er möglichst wenige Versuche braucht. ++++Lösung| unten = 1 oben = 1000 versuche = 0 antwort = "" while antwort != "r": tipp = (unten + oben) // 2 # immer die Mitte des Bereichs! versuche = versuche + 1 print("Ist es", tipp, "?") antwort = input("k = zu klein, g = zu gross, r = richtig: ") if antwort == "k": unten = tipp + 1 # untere Hälfte ausschliessen elif antwort == "g": oben = tipp - 1 # obere Hälfte ausschliessen print("Gefunden nach", versuche, "Versuchen!") Das ist die **binäre Suche**. Bei jedem Tipp halbiert sich der verbleibende Bereich: $1000 \rightarrow 500 \rightarrow 250 \rightarrow 125 \rightarrow \ldots$ Nach höchstens **10 Versuchen** ist die Zahl gefunden, denn $2^{10} = 1024 > 1000$. Zum Vergleich: Würde der Computer stur $1, 2, 3, \ldots$ durchprobieren, bräuchte er im Mittel $500$ Versuche. Ein gut gewählter Algorithmus schlägt rohe Rechenkraft um Längen. //Zum Weiterdenken:// Was passiert, wenn du beim Antworten schummelst? Der Bereich schrumpft dann irgendwann auf nichts zusammen, und `unten` wird grösser als `oben`. Ein robustes Programm würde das erkennen und dich des Betrugs bezichtigen. ++++