diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9e05a5d --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +graphify-out/ +graphity-out +.json +main.pdf +CLAUDE.md +.idea/ +__pycache__/ +*.pyc diff --git a/Code/09_schleifen/countdown.py b/Code/09_schleifen/countdown.py new file mode 100644 index 0000000..1d15845 --- /dev/null +++ b/Code/09_schleifen/countdown.py @@ -0,0 +1,13 @@ +# Eine while-Schleife wiederholt einen Block, solange die Bedingung wahr ist. + +sekunden = 5 + +while sekunden > 0: + print(sekunden) + sekunden = sekunden - 1 # Ohne diese Zeile laeuft die Schleife ewig! + +print("Start!") + +# Ablauf: Python prueft "sekunden > 0". Ist das wahr, laeuft der eingerueckte +# Block einmal komplett durch - danach wird die Bedingung ERNEUT geprueft. +# Erst wenn sie falsch ist, geht es unterhalb der Schleife weiter. diff --git a/Code/09_schleifen/eingabe_pruefen.py b/Code/09_schleifen/eingabe_pruefen.py new file mode 100644 index 0000000..bd566fe --- /dev/null +++ b/Code/09_schleifen/eingabe_pruefen.py @@ -0,0 +1,26 @@ +# Ein haeufiger Einsatz von Schleifen: so lange fragen, bis die Eingabe passt. + +alter = -1 # Ein Startwert, der die Bedingung sicher erfuellt. + +while alter < 0 or alter > 120: + alter = int(input("Wie alt bist du? (0 bis 120) ")) + + if alter < 0 or alter > 120: + print("Das kann nicht stimmen. Versuch es noch einmal.") + +print(f"Danke! Du bist {alter} Jahre alt.") + + +# Dieselbe Idee mit "while True" und "break". +# "while True" ist eine Schleife ohne Ende - sie wird von innen heraus +# mit "break" verlassen. Das spart den kuenstlichen Startwert oben. + +while True: + lieblingsfarbe = input("Nenne eine Farbe (nicht leer): ") + + if lieblingsfarbe != "": + break # verlaesst die Schleife sofort + + print("Da stand nichts. Bitte nochmal.") + +print(f"Schoene Wahl: {lieblingsfarbe}") diff --git a/Code/09_schleifen/einmaleins.py b/Code/09_schleifen/einmaleins.py new file mode 100644 index 0000000..7829bc8 --- /dev/null +++ b/Code/09_schleifen/einmaleins.py @@ -0,0 +1,14 @@ +# Schleifen duerfen ineinander stecken. Die innere Schleife laeuft dabei +# bei jedem einzelnen Durchlauf der aeusseren Schleife komplett durch. + +for reihe in range(1, 11): + + for faktor in range(1, 11): + ergebnis = reihe * faktor + # end=" " verhindert den Zeilenumbruch nach jeder Ausgabe. + # :4 sorgt fuer eine Breite von vier Zeichen - so stehen die Spalten untereinander. + print(f"{ergebnis:4}", end=" ") + + print() # leeres print() beendet die Zeile + +# 10 Durchlaeufe aussen mal 10 Durchlaeufe innen ergeben 100 Ausgaben. diff --git a/Code/09_schleifen/for_range.py b/Code/09_schleifen/for_range.py new file mode 100644 index 0000000..a360415 --- /dev/null +++ b/Code/09_schleifen/for_range.py @@ -0,0 +1,39 @@ +# Wenn von vornherein feststeht, wie oft etwas passieren soll, +# ist die for-Schleife die passende Wahl. + +for i in range(5): + print(i) + +# Ausgabe: 0 1 2 3 4 +# range(5) liefert fuenf Zahlen - beginnend bei 0, OHNE die 5 selbst. + +print("---") + +# Mit zwei Angaben: von wo bis wo (Ende bleibt aussen vor). +for zahl in range(1, 6): + print(zahl) + +# Ausgabe: 1 2 3 4 5 + +print("---") + +# Mit drei Angaben: von, bis, Schrittweite. +for zahl in range(0, 21, 5): + print(zahl) + +# Ausgabe: 0 5 10 15 20 + +print("---") + +# Rueckwaerts zaehlen mit negativer Schrittweite. +for zahl in range(3, 0, -1): + print(zahl) +print("Start!") + +print("---") + +# Dieselbe Summe wie mit while - nur deutlich kuerzer. +summe = 0 +for zahl in range(1, 101): + summe += zahl +print(f"Summe von 1 bis 100: {summe}") diff --git a/Code/09_schleifen/for_zeichenkette.py b/Code/09_schleifen/for_zeichenkette.py new file mode 100644 index 0000000..dc940f1 --- /dev/null +++ b/Code/09_schleifen/for_zeichenkette.py @@ -0,0 +1,24 @@ +# Eine for-Schleife laeuft nicht nur ueber Zahlen, sondern ueber alles, +# was aus mehreren Teilen besteht. Ein Text besteht aus einzelnen Zeichen. + +wort = "Python" + +for zeichen in wort: + print(zeichen) + +print("---") + +# Damit lassen sich Texte untersuchen. Hier zaehlen wir die Vokale. + +satz = input("Schreib einen Satz: ") +vokale = 0 + +for zeichen in satz: + if zeichen in "aeiouAEIOU": + vokale += 1 + +print(f"Dein Satz enthaelt {vokale} Vokale.") + +# Das Wort "in" hat hier zwei verschiedene Aufgaben: +# In der for-Zeile bedeutet es "laufe nacheinander durch". +# In der if-Zeile bedeutet es "kommt darin vor?" und liefert True oder False. diff --git a/Code/09_schleifen/summe_sammeln.py b/Code/09_schleifen/summe_sammeln.py new file mode 100644 index 0000000..c0934d3 --- /dev/null +++ b/Code/09_schleifen/summe_sammeln.py @@ -0,0 +1,15 @@ +# Eine Schleife eignet sich gut, um Werte aufzusammeln. +# Wir addieren alle Zahlen von 1 bis 100. + +zahl = 1 +summe = 0 # Der "Sammelbehaelter". Er startet leer, also bei 0. + +while zahl <= 100: + summe = summe + zahl # Kurzform: summe += zahl + zahl = zahl + 1 # Kurzform: zahl += 1 + +print(f"Die Summe der Zahlen von 1 bis 100 ist {summe}.") + +# Zwei Variablen mit klar getrennten Aufgaben: +# "zahl" zaehlt hoch und steuert, wann die Schleife endet. +# "summe" sammelt das Ergebnis ein und wird nie zurueckgesetzt. diff --git a/Code/09_schleifen/zahlenraten_schleife.py b/Code/09_schleifen/zahlenraten_schleife.py new file mode 100644 index 0000000..6e5141e --- /dev/null +++ b/Code/09_schleifen/zahlenraten_schleife.py @@ -0,0 +1,25 @@ +# Das Ratespiel aus der letzten Lektion - jetzt mit mehreren Versuchen. + +GESUCHTE_ZAHL = 7 +MAX_VERSUCHE = 3 + +versuch = 0 +geraten = False + +while versuch < MAX_VERSUCHE and not geraten: + versuch += 1 + tipp = int(input(f"Versuch {versuch} von {MAX_VERSUCHE}. Zahl von 1 bis 10? ")) + + if tipp == GESUCHTE_ZAHL: + print("Volltreffer!") + geraten = True + elif tipp < GESUCHTE_ZAHL: + print("Zu klein.") + else: + print("Zu gross.") + +if not geraten: + print(f"Schade, die Zahl war {GESUCHTE_ZAHL}.") + +# Die Schleife endet aus ZWEI Gruenden: entweder sind die Versuche +# aufgebraucht, oder die Zahl wurde erraten. Beides steht in der Bedingung. diff --git a/Code/HelloWorld.java b/Code/HelloWorld.java new file mode 100644 index 0000000..9bc1603 --- /dev/null +++ b/Code/HelloWorld.java @@ -0,0 +1,5 @@ +public class HelloWorld { + public static void main(String[] args) { + System.out.println("Hello World!"); + } +} diff --git a/Code/division_pruefen.py b/Code/division_pruefen.py new file mode 100644 index 0000000..278709b --- /dev/null +++ b/Code/division_pruefen.py @@ -0,0 +1,18 @@ +# Aus der letzten Lektion: Dieses Programm stuerzt ab, wenn die zweite +# Zahl 0 ist (ZeroDivisionError). Jetzt fangen wir diesen Fall hoeflich ab. + +erste_zahl = int(input("Gib eine ganze Zahl ein: ")) +zweite_zahl = int(input("Gib noch eine ganze Zahl ein: ")) + +# Die bedingte Anweisung: Python prueft, ob die Bedingung nach "if" wahr ist. +if zweite_zahl == 0: + # Dieser Block wird NUR ausgefuehrt, wenn die Bedingung wahr ist. + print("Fehler: Durch 0 darf nicht geteilt werden.") +else: + # Dieser Block wird ausgefuehrt, wenn die Bedingung falsch ist. + ergebnis = erste_zahl / zweite_zahl + print(f"{erste_zahl} geteilt durch {zweite_zahl} ist {ergebnis}") + +# Diese Zeile steht NICHT eingerueckt - sie gehoert zu keinem Block +# und wird deshalb immer ausgefuehrt, egal wie die Eingabe war. +print("Das Programm ist fertig.") diff --git a/Code/eingabe_zahlen.py b/Code/eingabe_zahlen.py new file mode 100644 index 0000000..269dfce --- /dev/null +++ b/Code/eingabe_zahlen.py @@ -0,0 +1,17 @@ +# input() hält das Programm an und wartet auf eine Eingabe der Nutzerin. +# WICHTIG: input() liefert das Eingegebene IMMER als Text (str) zurück - +# auch dann, wenn die Nutzerin eine Zahl eingetippt hat. + +erste_eingabe = input("Gib eine Zahl ein: ") +zweite_eingabe = input("Gib noch eine Zahl ein: ") + +# "+" verbindet zwei Texte einfach hintereinander (Konkatenation). +# Bei den Eingaben "12" und "3" käme hier "123" heraus - nicht 15! +print("Als Text aneinandergehängt:", erste_eingabe + zweite_eingabe) + +# Damit wir rechnen können, müssen wir den Text erst in eine ganze Zahl +# umwandeln. Das erledigt die Funktion int(). +erste_zahl = int(erste_eingabe) +zweite_zahl = int(zweite_eingabe) + +print("Als Zahlen addiert:", erste_zahl + zweite_zahl) diff --git a/Code/einrueckung_wichtig.py b/Code/einrueckung_wichtig.py new file mode 100644 index 0000000..ee8532c --- /dev/null +++ b/Code/einrueckung_wichtig.py @@ -0,0 +1,23 @@ +# In Python entscheidet die EINRUECKUNG, welche Zeilen zu einem Block +# gehoeren. Es gibt keine geschweiften Klammern wie in anderen Sprachen. + +alter = int(input("Wie alt bist du? ")) + +# --- Version A: beide print-Zeilen sind eingerueckt --------------------- +# Beide Zeilen gehoeren zum if-Block. Bei alter >= 18 werden BEIDE +# ausgegeben, sonst KEINE. +if alter >= 18: + print("Du bist volljaehrig.") + print("Du darfst waehlen.") + +# --- Zum Ausprobieren: ruecke die zweite Zeile aus -------------------- +# Wenn "Du darfst waehlen." NICHT eingerueckt ist, gehoert sie nicht mehr +# zum if-Block. Dann wird sie IMMER ausgegeben - auch fuer eine 12. +# +# if alter >= 18: +# print("Du bist volljaehrig.") +# print("Du darfst waehlen.") # <- steht jetzt ausserhalb des if + +# Merke: Die Einrueckung ist nicht Deko, sie ist Teil der Bedeutung. +# Ueblich sind 4 Leerzeichen pro Stufe. Alle Zeilen eines Blocks muessen +# gleich weit eingerueckt sein. diff --git a/Code/helloworld.cpp b/Code/helloworld.cpp new file mode 100644 index 0000000..7dd560a --- /dev/null +++ b/Code/helloworld.cpp @@ -0,0 +1,6 @@ +#include + +int main() { + std::cout<<"Hello world!"<= 90: + note = "sehr gut" +elif punkte >= 75: + note = "gut" +elif punkte >= 60: + note = "befriedigend" +elif punkte >= 50: + note = "ausreichend" +else: + # Wird nur erreicht, wenn KEINE der Bedingungen oben wahr war. + note = "nicht bestanden" + +print(f"Ergebnis: {note}") + +# Warum ist die Reihenfolge wichtig? Bei 95 Punkten ist auch +# "punkte >= 75" wahr - aber Python hat den ersten passenden Zweig +# (punkte >= 90) schon genommen und ueberspringt den Rest. diff --git a/Code/rechnen.py b/Code/rechnen.py new file mode 100644 index 0000000..c6e011d --- /dev/null +++ b/Code/rechnen.py @@ -0,0 +1,18 @@ +# Rechnen mit ganzen Zahlen. Python nutzt die Rechenausdrücke, die wir aus +# der Schule kennen, plus die bereits bekannte Zuweisung mit "=". + +a = 18 +b = 5 + +print("Summe: ", a + b) # 23 +print("Differenz: ", a - b) # 13 +print("Produkt: ", a * b) # 90 +print("Quotient: ", a / b) # 3.6 <- / liefert IMMER eine Kommazahl (float) +print("Ganzzahl-Div: ", a // b) # 3 <- // schneidet den Rest ab +print("Rest (modulo):", a % b) # 3 <- was bei 18 : 5 übrig bleibt +print("Potenz: ", a ** b) # 1889568 <- a hoch b + +# Rechenreihenfolge ("Punkt vor Strich") gilt wie in der Mathematik. +# Mit Klammern kann man sie steuern: +print(2 + 3 * 4) # 14 +print((2 + 3) * 4) # 20 diff --git a/Code/rechner_eingabe.py b/Code/rechner_eingabe.py new file mode 100644 index 0000000..5bfdf6d --- /dev/null +++ b/Code/rechner_eingabe.py @@ -0,0 +1,13 @@ +# Ein kleines Rechenprogramm: Es nimmt zwei ganze Zahlen entgegen und gibt +# Summe, Differenz, Produkt und Quotient aus. + +erste_zahl = int(input("Gib eine ganze Zahl ein: ")) +zweite_zahl = int(input("Gib noch eine ganze Zahl ein: ")) + +print("Summe: ", erste_zahl + zweite_zahl) +print("Differenz: ", erste_zahl - zweite_zahl) +print("Produkt: ", erste_zahl * zweite_zahl) +print("Quotient: ", erste_zahl / zweite_zahl) + +# f-strings sind bequem, um Text und Werte zu mischen: +print(f"{erste_zahl} geteilt durch {zweite_zahl} ist {erste_zahl / zweite_zahl}") diff --git a/Code/teilbarkeit.py b/Code/teilbarkeit.py new file mode 100644 index 0000000..e7e8ae5 --- /dev/null +++ b/Code/teilbarkeit.py @@ -0,0 +1,14 @@ +# Teilt die zweite Zahl die erste? Wir nutzen den Rest-Operator "%" +# aus der letzten Lektion: Ist der Rest 0, dann ist die Division glatt +# aufgegangen - die zweite Zahl ist ein Teiler der ersten. + +zahl = int(input("Zahl: ")) +teiler = int(input("Moeglicher Teiler: ")) + +if teiler == 0: + print("Durch 0 teilt es sich nicht.") +elif zahl % teiler == 0: + print(f"{teiler} teilt {zahl}.") +else: + rest = zahl % teiler + print(f"{teiler} teilt {zahl} nicht (Rest {rest}).") diff --git a/Code/test_installation.py b/Code/test_installation.py new file mode 100644 index 0000000..fb7149f --- /dev/null +++ b/Code/test_installation.py @@ -0,0 +1,20 @@ +import sys +import platform + +print("=" * 50) +print("Installation Überprüfung") +print("=" * 50) + +# Python-Version +print(f"\n✓ Python-Version: {sys.version}") + +# Betriebssystem +print(f"✓ Betriebssystem: {platform.system()}") + +# Architektur +print(f"✓ Architektur: {platform.machine()}") + +print("\n" + "=" * 50) +print("Alle Überprüfungen bestanden! 🎉") +print("=" * 50) +print("\nNächster Schritt: Lektion 2 - Hello World") diff --git a/Code/variablen.py b/Code/variablen.py new file mode 100644 index 0000000..2fd9e51 --- /dev/null +++ b/Code/variablen.py @@ -0,0 +1,5 @@ +beschreibung = "wundervoll" + +print(f"Hallo, mein Tag war {beschreibung}!") +print(f"ich habe {beschreibung} aus einem skript gelernt.") +print(f"Ich hoffe dir geht es genauso {beschreibung}") diff --git a/Code/variablen_aendern.py b/Code/variablen_aendern.py new file mode 100644 index 0000000..3ae0c77 --- /dev/null +++ b/Code/variablen_aendern.py @@ -0,0 +1,23 @@ +# Ein Programm ist eine Folge von Anweisungen, die von oben nach unten +# abgearbeitet werden - keine Sammlung mathematischer Aussagen. + +zaehler = 0 # Lasse zaehler zuerst den Wert 0 haben. +zaehler = zaehler + 1 # Nimm den aktuellen Wert (0), addiere 1, speichere zurueck. +zaehler = zaehler + 1 # Und noch einmal: aus 1 wird 2. + +print("zaehler ist jetzt:", zaehler) + +# Mathematisch ergibt "zaehler = zaehler + 1" keinen Sinn. +# Als Anweisung gelesen heisst es: "Der Name links soll ab jetzt den +# Wert der rechten Seite tragen." + +# Eine Variable kann im Laufe des Programms ihren Wert - und sogar ihre +# Art von Wert - wechseln: +inhalt = "ein Text" +print(inhalt, "->", type(inhalt)) + +inhalt = 42 +print(inhalt, "->", type(inhalt)) + +inhalt = 3.5 +print(inhalt, "->", type(inhalt)) diff --git a/Code/vergleiche_und_wahrheit.py b/Code/vergleiche_und_wahrheit.py new file mode 100644 index 0000000..556ded5 --- /dev/null +++ b/Code/vergleiche_und_wahrheit.py @@ -0,0 +1,21 @@ +# Jede Bedingung ist im Kern eine Frage, die Python mit True (wahr) oder +# False (falsch) beantwortet. True und False sind ein eigener Datentyp: bool. + +alter = 20 + +print(alter == 20) # Gleich? -> True +print(alter != 20) # Ungleich? -> False +print(alter < 18) # Kleiner? -> False +print(alter >= 18) # Groesser-gleich? -> True + +# Mit and, or und not lassen sich Bedingungen verknuepfen: +temperatur = 24 +sonnig = True + +print(temperatur > 20 and sonnig) # beide muessen wahr sein -> True +print(temperatur > 30 or sonnig) # eine reicht -> True +print(not sonnig) # kehrt um -> False + +# Genau dieselben Ausdruecke stehen sonst hinter einem "if": +if temperatur > 20 and sonnig: + print("Zeit fuer eine Pause draussen.") diff --git a/Code/zahlenraten.py b/Code/zahlenraten.py new file mode 100644 index 0000000..411c38a --- /dev/null +++ b/Code/zahlenraten.py @@ -0,0 +1,22 @@ +# Ein kleines Ratespiel - noch mit nur einem Versuch. +# (In der naechsten Lektion, "Schleifen", geben wir mehrere Versuche.) + +GESUCHTE_ZAHL = 7 + +tipp = int(input("Ich denke an eine Zahl von 1 bis 10. Dein Tipp? ")) + +if tipp == GESUCHTE_ZAHL: + print("Volltreffer!") +elif tipp < GESUCHTE_ZAHL: + print("Zu klein.") +else: + print("Zu gross.") + +# Bonus: verschachtelte Bedingung. Der innere if steht weiter eingerueckt +# und wird nur geprueft, wenn der aeussere if schon wahr war. +if tipp != GESUCHTE_ZAHL: + abstand = GESUCHTE_ZAHL - tipp + if abstand < 0: + abstand = -abstand + if abstand == 1: + print("Aber ganz knapp daneben!") diff --git a/Lektionen/helloworld.typ b/Lektionen/helloworld.typ new file mode 100644 index 0000000..4862b14 --- /dev/null +++ b/Lektionen/helloworld.typ @@ -0,0 +1,116 @@ +#import "../lib/helpers.typ": * + +== Lektion 1: Hello World + +#Theorie[ + === Was ist eine Programmiersprache? + + Ein Computer versteht eigentlich nur Maschinencode – eine Abfolge von 0en und 1en. Eine Programmiersprache ist eine formale Sprache, die es Menschen ermöglicht, Anweisungen so zu schreiben, dass ein Computer sie verstehen _kann_. Sie ist ein Übersetzungssystem: Du schreibst in einer für Menschen lesbaren Syntax auf, was der Computer tun soll, und ein Programm (der Interpreter oder Compiler) übersetzt deine Anweisungen in Maschinencode. + + === Warum Python? + + Es gibt Hunderte von Programmiersprachen. Python hat sich für Anfänger als ideal erwiesen, weil: + + 1. *Lesbarkeit:* Python-Code sieht dem Englischen ähnlich. `print(“Hallo”)` ist selbsterklärend, anders als etwa #Code("helloworld.cpp") oder #Code("helloworld.java") . + + 2. *Minimalistische Syntax:* Python erzwingt saubere Formatierung durch Einrückungen. Das führt zu besseren Gewohnheiten. + + 3. *Unmittelbare Feedback-Schleife:* Du kannst Code schreiben und ihn sofort ausführen. Keine langen Kompilierungsschritte. + + 4. *Breite Anwendung:* Datenanalyse, KI, Web-Backend, Automatisierung – Python ist überall. + + === Ausführung: Quellcode → Interpretation + + Wenn du eine Python-Datei ausführst, passiert folgendes: + + 1. *Quellcode:* Du schreibst lesbare Befehle in `helloworld.py`. Das ist _Text_ – noch keine Anweisung für die CPU. + + 2. *Interpreter:* Python liest deine Datei zeilenweise. Für jede Zeile: + - Prüft Python die Syntax (ist die Syntax korrekt?) + - Übersetzt Python den Befehl in interne Anweisungen + - Führt Python den Befehl aus + + 3. *Ausführung:* Wenn Python `print(“Hallo Welt!”)` liest, sagt es der Konsole: “Gib diesen Text aus.” Die Konsole druckt es. + + 4. *Output:* Du siehst das Ergebnis. + + _Wichtig:_ Python ist eine *interpretierte* Sprache. Das bedeutet: Jedes Mal, wenn du das Programm startest, wird es neu interpretiert. (Das unterscheidet sich von *kompilierten* Sprachen wie C++, die einmal übersetzt werden und dann direkt laufen.) + + === Das “Hello World”-Ritual + + “Hello World” ist die Tradition in der Informatik. Es ist nicht wegen der Aufgabe wichtig, sondern weil es das Fundamental-Konzept demonstriert: *Du kannst dem Computer befehlen, etwas zu tun, und er tut es.* + + Das ist der Kern des Programmierens. +] + +#Praxis[ + Erstelle eine neue Datei namens `helloworld.py` und schreibe folgende Zeile: + + ```python + print(“Hello World!”) + ``` + + Speichere und führe das Programm aus. Die Ausgabe sollte sein: `Hello World!` +] + +#Code("helloworld.py") + +=== Anatomie des Befehls + +#Theorie[ + Der `print()`-Befehl zerlegt sich in drei Teile: + + 1. *Funktionsname:* `print` – der Name der Funktion, die wir aufrufen. Funktionen sind wiederverwendbare Bausteine, die eine spezifische Aufgabe erfüllen. Hier: Text in die Konsole schreiben. + + 2. *Klammern:* `()` – sie umschließen die _Argumente_ (die Eingaben für die Funktion). `print()` ohne Argumente würde eine leere Zeile drucken. + + 3. *Argument:* `”Hello World!”` – der String (Zeichenkette), den wir ausgeben möchten. Die Anführungszeichen sind syntaktisch erforderlich; sie teilen Python mit, dass es sich um Text handelt, nicht um Code. + + _Grundregel:_ Python unterscheidet Groß- und Kleinschreibung. `print` ist korrekt, `Print` führt zu einem Fehler. +] + + +#Spiel[ + Ihr könnt nun versuchen, den Quellcode selbst zu verändern und damit ein wenig herumzuspielen. Öffnet dazu einen Editor und öffnet die Datei. Denkt daran, nach jeder Änderung die Datei zu speichern und im Terminal neu auszuführen. + + Dinge, die ihr ausprobieren könntet sind zum Beispiel: + + - Was passiert, wenn ihr „Hello world!" in etwas anderes ändert? + - Was passiert, wenn ihr die Anführungszeichen löscht? + - Was passiert, wenn ihr zwei `print()`-Befehle hintereinander schreibt? + - Wie könnte man mehrere Sätze in einer Zeile ausgeben? + + Experimentiert! Das Schöne an Python ist, dass ihr sofort Feedback bekommt, wenn etwas schiefläuf +] +#Quiz(answer: "Das andere wird ausgegeben" )[Was passiert, wenn ihr Hello world durch etwas anderes ersetzt? ++ Das andere wird ausgegeben ++ Es gibt einen Fehler ++ Das Programm tut garnichts mehr ++ Das Programm gibt trotzdem Hello world aus +] + +=== Häufige Fehler und ihre Ursachen + +#Theorie[ + Wenn dein Programm nicht läuft, liegt es an einer dieser Syntaxfehler: + + *Fehler 1: Fehlende oder falsche Anführungszeichen* + ```python + print(Hallo Welt!) # SyntaxError – kein String definiert + print(“Hallo Welt!”) # Korrekt + ``` + + *Fehler 2: Falsche Großschreibung* + ```python + Print(“Hallo Welt!”) # NameError – Python kennt Print nicht + print(“Hallo Welt!”) # Korrekt + ``` + + *Fehler 3: Falsche Klammer-Typen* + ```python + print[“Hallo Welt!”] # SyntaxError – eckige Klammern sind für Listen + print(“Hallo Welt!”) # Korrekt – runde Klammern für Funktionen + ``` + + Diese Fehler sind typisch – sie passieren jedem. Der Schlüssel ist, die Fehlermeldung zu lesen. Python teilt dir präzise mit, was schiefgelaufen ist. +] \ No newline at end of file diff --git a/Lektionen/kontrollfluss.typ b/Lektionen/kontrollfluss.typ new file mode 100644 index 0000000..9ff13c2 --- /dev/null +++ b/Lektionen/kontrollfluss.typ @@ -0,0 +1,285 @@ +#import "../lib/helpers.typ": * + +== Lektion: Kontrollfluss + +Bisher lief jedes unserer Programme stur von oben nach unten durch , jede Zeile genau einmal, immer in derselben Reihenfolge. Am Ende der letzten Lektion ist uns dabei etwas Unangenehmes passiert: Gibt die Nutzerin als Divisor eine `0` ein, stürzt das Programm mit einem `ZeroDivisionError` ab. In dieser Lektion geben wir dem Programm die Fähigkeit, *abhängig von seinen Daten unterschiedliche Wege einzuschlagen* , und solche Abstürze höflich abzufangen. Diese Abhängigkeit des Programmablaufs von den Werten, mit denen es arbeitet, nennen wir *Kontrollfluss*. + +#Theorie[ + === Eine Weggabelung im Programm + + Stell dir vor, du kochst nach einem Rezept. Die meisten Schritte machst du einfach der Reihe nach. An einer Stelle steht aber: „Falls du keine frische Milch hast, nimm Hafermilch , sonst nimm die frische Milch.“ Ab hier hängt dein nächster Handgriff von einer Bedingung ab. Du prüfst etwas, und je nach Antwort tust du das eine *oder* das andere, aber nie beides. + + Genau dieses Muster gibt es in Python. Es heißt *bedingte Anweisung* oder kurz *if-Anweisung* (von englisch _if_, „falls“): + + ```python + if zweite_zahl == 0: + print("Fehler: Durch 0 darf nicht geteilt werden.") + else: + print(erste_zahl / zweite_zahl) + ``` + + Wörtlich gelesen: „*Falls* `zweite_zahl` gleich `0` ist, gib die Fehlermeldung aus. *Andernfalls* (`else`) gib das Ergebnis der Division aus.“ Python prüft die Bedingung hinter `if`. Trifft sie zu, wird der erste Block ausgeführt und der `else`-Block übersprungen. Trifft sie nicht zu, ist es umgekehrt. + + Drei Bestandteile sind syntaktisch verpflichtend: + + 1. Das Schlüsselwort `if`, gefolgt von einer *Bedingung* , einem Ausdruck, der entweder wahr oder falsch ist. + 2. Ein *Doppelpunkt* `:` am Zeilenende. Er kündigt an: „Jetzt kommt ein Block, der zu diesem `if` gehört.“ + 3. Der *eingerückte Block* darunter: die Anweisungen, die nur in diesem Fall ausgeführt werden. + + Der `else`-Zweig ist optional. Oft will man nur etwas tun, *wenn* eine Bedingung zutrifft, und sonst nichts , dann lässt man `else` einfach weg. +] + +#Code("division_pruefen.py") + +#Theorie[ + === Der Doppelgänger: `=` und `==` + + In der letzten Lektion war das einfache Gleichheitszeichen eine *Zuweisung*: `x = 5` bedeutet „lege den Wert `5` unter dem Namen `x` ab“. Zum *Vergleichen* braucht Python ein anderes Zeichen, nämlich das *doppelte* Gleichheitszeichen `==`. Der Ausdruck `x == 5` stellt eine Frage: „Ist der Wert von `x` gleich `5`?“ , und die Antwort ist wahr oder falsch. + + Diese Verwechslung ist ein klassischer Stolperstein. Merkhilfe: `=` *macht* gleich, `==` *fragt*, ob gleich. + + Neben `==` gibt es weitere *Vergleichsoperatoren*, die du aus dem Matheunterricht kennst: + + - `==` — gleich + - `!=` — ungleich + - `<` — kleiner als + - `>` — größer als + - `<=` — kleiner oder gleich + - `>=` — größer oder gleich + + Alle vergleichen zwei Werte und liefern als Ergebnis wahr oder falsch. Das funktioniert übrigens nicht nur mit Zahlen: `name == "Xenia"` prüft, ob in `name` genau diese Zeichenkette steht. + + === Warum die Leerzeichen zählen + + In vielen Programmiersprachen markiert man zusammengehörige Anweisungen mit geschweiften Klammern `{ ... }`. Die Einrückung ist dort nur Kosmetik für das menschliche Auge. Python geht einen anderen Weg: Hier *ist die Einrückung die Syntax*. Es gibt keine Klammern um Blöcke , stattdessen entscheidet allein die Position am Zeilenanfang, was zu einem Block gehört und was nicht. + + Das ist zunächst ungewohnt, aber die Idee dahinter ist angenehm: Code, der ordentlich eingerückt ist, *sieht* so aus, wie er *funktioniert*. Es gibt keine zwei Wahrheiten mehr , kein „sieht eingerückt aus, ist aber nicht drin“. + + *Analogie:* Denk an eine Gliederung mit Unterpunkten. Was unter einer Überschrift eingerückt steht, gehört zu ihr. Rückt ein Punkt wieder nach links aus, beginnt ein neuer, übergeordneter Abschnitt. + + Betrachte den Unterschied: + + ```python + if alter >= 18: + print("Du bist volljaehrig.") + print("Du darfst waehlen.") + print("Programmende.") + ``` + + Die ersten beiden `print`-Zeilen sind eingerückt , sie gehören zum `if` und werden nur bei `alter >= 18` ausgeführt. Die dritte Zeile steht wieder am linken Rand, *außerhalb* des `if`, und läuft deshalb immer. + + Verschiebt man nun die zweite Zeile an den Rand: + + ```python + if alter >= 18: + print("Du bist volljaehrig.") + print("Du darfst waehlen.") + ``` + + … dann gehört `"Du darfst waehlen."` nicht mehr zum `if`. Jetzt bekommt auch eine 12-Jährige die Wahlberechtigung bescheinigt. Das Programm ist weiterhin gültig , es tut nur etwas anderes. + + *Die Regeln in Kurzform:* + + - Pro Einrückungsstufe verwendet man üblicherweise *vier Leerzeichen*. + - Alle Zeilen eines Blocks müssen *gleich weit* eingerückt sein. + - *Mische niemals Leerzeichen und Tabulatoren.* Für das Auge sehen beide gleich aus, für Python sind es verschiedene Zeichen. Die meisten Editoren (auch VSCode) wandeln einen Tastendruck auf `Tab` automatisch in Leerzeichen um , lass diese Einstellung an. +] + +#Code("einrueckung_wichtig.py") + +#Praxis[ + + Öffne `division_pruefen.py` und führe es zweimal aus: einmal mit einer `0` als zweiter Zahl, einmal mit einer anderen Zahl. Verfolge, welcher Block jeweils läuft. + + + Ersetze in `division_pruefen.py` das `==` durch ein einfaches `=` (also `if zweite_zahl = 0:`). Führe das Programm aus und lies die Fehlermeldung. Mach die Änderung anschließend rückgängig. + + + Entferne in `einrueckung_wichtig.py` die vier Leerzeichen vor der ersten `print`-Zeile im `if`-Block. Was meldet Python? In welcher Zeile? + + + Schreibe ein Programm, das nach dem Alter fragt und `Zutritt erlaubt` ausgibt, wenn die Person mindestens 16 ist, sonst `Zutritt verweigert`. +] + +=== Mehr als zwei Fälle + +#Theorie[ + Nicht jede Entscheidung hat nur zwei Ausgänge. Eine Punktzahl in eine Note zu übersetzen heißt, zwischen mehreren Stufen zu unterscheiden. Dafür gibt es `elif` , eine Zusammenziehung von _else if_, „sonst falls“. + + ```python + if punkte >= 90: + note = "sehr gut" + elif punkte >= 75: + note = "gut" + elif punkte >= 60: + note = "befriedigend" + else: + note = "nicht bestanden" + ``` + + Python arbeitet die Bedingungen *von oben nach unten* ab und nimmt den *ersten* Zweig, dessen Bedingung wahr ist. Alle weiteren werden dann nicht mehr geprüft. Der `else`-Zweig am Ende fängt alles ab, was durch keine der Bedingungen erfasst wurde. + + Deshalb ist die *Reihenfolge* entscheidend. Bei `95` Punkten ist auch `punkte >= 75` wahr , aber Python hat den passenden Zweig `punkte >= 90` bereits genommen und überspringt den Rest. Würde man die Prüfungen in umgekehrter Reihenfolge notieren (die kleinste zuerst), bekäme *jede* bestandene Arbeit die Note „ausreichend“. + + Ein `if` darf beliebig viele `elif`-Zweige haben, aber *höchstens ein* `else`, und das steht immer am Schluss. +] + +#Code("noten_kategorie.py") + +#Praxis[ + + Führe `noten_kategorie.py` mit den Eingaben `95`, `60` und `30` aus und prüfe, ob die Note stimmt. + + + Erweitere das Programm um eine Stufe `ausreichend` für `punkte >= 50`. + + + Schreibe ein Programm, das eine Uhrzeit als ganze Stunde (`0`–`23`) einliest und `Guten Morgen`, `Guten Tag`, `Guten Abend` oder `Gute Nacht` ausgibt , je nach Tageszeit. + + + Was passiert in `noten_kategorie.py`, wenn du `120` eingibst? Und bei `-5`? Überlege, ob das Programm das abfangen sollte. +] + +=== Wahrheitswerte und verknüpfte Bedingungen + +#Theorie[ + Jede Bedingung, die wir hinter ein `if` schreiben, ist im Kern eine Frage, die Python mit einem von genau zwei Werten beantwortet: `True` (wahr) oder `False` (falsch). Diese beiden bilden einen eigenen Datentyp, den *Wahrheitswert* , in Python `bool`, benannt nach dem Mathematiker George Boole. + + Du kannst dir das direkt ansehen, indem du einen Vergleich einfach ausgibst: + + ```python + print(5 > 3) # True + print(2 == 10) # False + ``` + + Manchmal soll eine Anweisung nur laufen, wenn *mehrere* Dinge gleichzeitig zutreffen , oder wenn *mindestens eines* von mehreren zutrifft. Dafür gibt es drei Wörter: + + - `and` — wahr, wenn *beide* Seiten wahr sind + - `or` — wahr, wenn *mindestens eine* Seite wahr ist + - `not` — kehrt wahr und falsch um + + ```python + if temperatur > 20 and sonnig: + print("Zeit fuer eine Pause draussen.") + ``` + + Gelesen: „Falls es wärmer als 20 Grad ist *und* die Sonne scheint …“. Ist auch nur eine der beiden Bedingungen falsch, wird der Block übersprungen. +] + +#Code("vergleiche_und_wahrheit.py") + +#Theorie[ + Mit diesem Werkzeug können wir die Aufgabe aus der letzten Lektion sauber lösen: prüfen, ob eine Zahl eine andere teilt. Der Rest-Operator `%` verrät es uns , ist der Rest `0`, geht die Division glatt auf. +] + +#Code("teilbarkeit.py") + +#Praxis[ + + Schreibe ein Programm, das eine ganze Zahl einliest und ausgibt, ob sie gerade oder ungerade ist. (Tipp: `zahl % 2`.) + + + Schreibe ein Programm, das prüft, ob eine eingegebene Jahreszahl ein Schaltjahr ist. Regel: durch 4 teilbar, aber nicht durch 100, es sei denn, auch durch 400. (Diese Aufgabe braucht `and`, `or` und `not`.) + + + Frage nach einer Zahl und gib `im Bereich` aus, wenn sie zwischen `1` und `100` liegt (beide Grenzen eingeschlossen), sonst `ausserhalb`. +] + +#Spiel[ + Das Programm `zahlenraten.py` denkt sich eine feste Zahl aus und sagt dir nach deinem Tipp, ob er zu groß, zu klein oder richtig war. Führe es ein paar Mal aus. Verändere anschließend `GESUCHTE_ZAHL` und den erlaubten Bereich im `input`-Text. + + Weitere Dinge zum Ausprobieren: + + + Was passiert, wenn du in `division_pruefen.py` den Doppelpunkt hinter `if zweite_zahl == 0` weglässt? Lies die Fehlermeldung genau. + + + Baue in `zahlenraten.py` mit `and` eine zusätzliche Reaktion ein: Wenn der Tipp *kleiner als 1 oder größer als 10* ist, soll das Programm „Das war gar keine gültige Zahl!“ sagen. + + + Schreibe ein Programm, das nach deinem Namen fragt. Tippt die Nutzerin den Namen deiner Tutorin ein, soll das Programm sich begeistert über die Namensgleichheit zeigen, sonst einfach freundlich grüßen. + + + In Python gilt eine `0` in einer Bedingung als falsch und jede andere Zahl als wahr. Probier `if 3:` und `if 0:` aus. Merkwürdig? Für den Anfang: bleib bei echten Vergleichen, dann bist du auf der sicheren Seite. +] + +#Code("zahlenraten.py") + +#Quiz(answer: "b)")[ + Was ist der Unterschied zwischen `=` und `==`? + + Es gibt keinen, beide vergleichen zwei Werte. + + `=` weist einen Wert zu, `==` prüft auf Gleichheit. + + `=` prüft auf Gleichheit, `==` weist einen Wert zu. + + `==` funktioniert nur mit Zahlen, `=` auch mit Text. +] + +#Quiz(answer: "c)")[ + ```python + temperatur = 15 + if temperatur >= 20: + print("warm") + print("fertig") + ``` + Was gibt dieses Programm aus? + + `warm` und `fertig` + + nur `warm` + + nur `fertig` + + einen `IndentationError` +] + +#Quiz(answer: "b)")[ + ```python + punkte = 82 + if punkte >= 50: + note = "ausreichend" + elif punkte >= 75: + note = "gut" + elif punkte >= 90: + note = "sehr gut" + print(note) + ``` + Welche Note wird ausgegeben? + + `gut` + + `ausreichend` + + `sehr gut` + + einen Fehler, weil mehrere Bedingungen zutreffen +] + +#Quiz(answer: "d)")[ + Wodurch erkennt Python, welche Zeilen zu einem `if`-Block gehören? + + An geschweiften Klammern `{ }` um den Block. + + An einem `end`-Schlüsselwort am Blockende. + + An einem Strichpunkt `;` hinter jeder Zeile des Blocks. + + An der Einrückung der Zeilen. +] + +#Quiz(answer: "b), c)")[ + Welche Aussagen über `and`, `or` und `not` sind korrekt? + + `a and b` ist wahr, wenn mindestens eine Seite wahr ist. + + `a or b` ist wahr, wenn mindestens eine Seite wahr ist. + + `not True` ergibt `False`. + + `and` verbindet nur Zahlen, nicht Wahrheitswerte. +] + + +=== Häufige Fehler und ihre Ursachen + +#Theorie[ + *`IndentationError: expected an indented block after 'if' statement`* + + Nach einem `if:` (oder `elif:`, `else:`) fehlt der eingerückte Block. Jede bedingte Anweisung braucht mindestens eine Zeile darunter, und die muss eingerückt sein. + + *`IndentationError: unexpected indent`* + + Eine Zeile ist eingerückt, obwohl davor kein `:` einen Block eröffnet hat. Häufig ein versehentliches Leerzeichen am Zeilenanfang. + + *`TabError: inconsistent use of tabs and spaces in indentation`* + + Innerhalb desselben Blocks wurden Tabulatoren und Leerzeichen gemischt. Optisch nicht zu sehen. Lösung: im Editor „Tabs in Leerzeichen umwandeln“ aktivieren und die betroffenen Zeilen neu einrücken. + + *`SyntaxError: expected ':'`* + + Der Doppelpunkt am Ende der `if`-, `elif`- oder `else`-Zeile fehlt. + + *`SyntaxError: invalid syntax` bei `if x = 5:`* + + Hier steht eine Zuweisung, wo eine Bedingung hingehört. Gemeint war der Vergleich `if x == 5:`. + + *Das Programm läuft, entscheidet aber falsch* + + Kein Absturz, keine Fehlermeldung , trotzdem stimmt das Ergebnis nicht. Fast immer liegt es an der *Reihenfolge* der `elif`-Zweige oder an einer verrutschten Einrückung. Hilfreich: an den entscheidenden Stellen ein `print()` einbauen und den Wert der geprüften Variable ausgeben lassen. +] + + + +#Theorie[ + === Ausblick + + Mit bedingten Anweisungen kann dein Programm jetzt an einer Stelle *einen* von mehreren Wegen wählen. Was es noch nicht kann: eine Aufgabe *wiederholen*, bis etwas erledigt ist, etwa so lange nach einer Zahl fragen, bis die Eingabe gültig ist, oder alle Teiler einer Zahl durchprobieren. Genau das leisten *Schleifen*, das Thema der nächsten Lektion. Du wirst dabei feststellen, dass die Einrückung dort dieselbe Rolle spielt wie hier. +] + diff --git a/Lektionen/schleifen.typ b/Lektionen/schleifen.typ new file mode 100644 index 0000000..cdb9748 --- /dev/null +++ b/Lektionen/schleifen.typ @@ -0,0 +1,452 @@ +#import "../lib/helpers.typ": * +#import "@preview/fletcher:0.5.7" as fletcher: diagram, node, edge + +// --- Darstellung der Abbildungen in dieser Lektion --- + +#let Abbildung(body, caption: []) = block(width: 100%, breakable: false, above: 1.3em, below: 1.5em)[ + #align(center)[ + #body + #v(0.5em) + #block(width: 85%)[ + #set align(center) + #text(size: 9pt, style: "italic", fill: luma(70))[#caption] + ] + ] +] + +// Einheitliches Aussehen der Flussdiagramme +#let flow = ( + spacing: (13mm, 9mm), + node-stroke: 0.7pt, + node-inset: 7pt, + edge-stroke: 0.7pt, +) + +// Ein Kaestchen fuer den Zahlenstrahl: gefuellt = gehoert dazu +#let rangebox(inhalt, dabei: true) = box( + width: 9mm, + height: 9mm, + radius: 2pt, + stroke: if dabei { 0.8pt + black } else { (thickness: 0.8pt, paint: luma(160), dash: "dashed") }, + fill: if dabei { luma(232) } else { none }, + align(center + horizon, text(size: 9pt, fill: if dabei { black } else { luma(140) }, inhalt)), +) + +== Schleifen + +In der letzten Lektion hat unser Programm gelernt, an einer Weggabelung den passenden Weg zu wählen. Eine Sache blieb dabei unbefriedigend: Das Ratespiel gewährt genau *einen* Versuch. Und eine unsinnige Eingabe können wir zwar erkennen, aber nicht noch einmal erfragen – wir können nur höflich abbrechen. Was fehlt, ist die Fähigkeit, etwas zu *wiederholen*. Genau darum geht es in dieser Lektion. Das Werkzeug dafür heißt *Schleife*. + +#Theorie[ + === Solange noch etwas zu tun ist + + Stell dir vor, du schälst Kartoffeln für ein Abendessen. Niemand sagt dir vorher: „Schäle genau vierzehn Kartoffeln.“ Die Anweisung lautet vielmehr: *Solange noch ungeschälte Kartoffeln im Korb liegen, nimm eine heraus und schäle sie.* Vor jedem Griff in den Korb schaust du kurz nach, ob es noch weitergeht. Irgendwann ist der Korb leer, und du hörst auf. + + Dieses Muster gibt es in Python wörtlich. Es heißt `while` (englisch für „solange“): + + ```python + while sekunden > 0: + print(sekunden) + sekunden = sekunden - 1 + ``` + + Gelesen: „*Solange* `sekunden` größer als `0` ist, gib den Wert aus und ziehe eins ab.“ + + Der Aufbau ist derselbe wie beim `if` aus der letzten Lektion, und das ist kein Zufall: + + 1. Das Schlüsselwort `while`, gefolgt von einer *Bedingung*. + 2. Ein *Doppelpunkt* `:` am Zeilenende. + 3. Der *eingerückte Block* darunter – er heißt *Schleifenrumpf*. + + Der Unterschied zum `if` liegt allein im Verhalten danach: Ein `if` führt seinen Block *höchstens einmal* aus und geht dann weiter. Ein `while` springt nach dem letzten Befehl des Rumpfes *zurück nach oben* und prüft die Bedingung erneut. Erst wenn sie falsch ist, läuft das Programm unterhalb der Schleife weiter. + + Einen einzelnen solchen Durchlauf nennt man eine *Iteration* (von lateinisch _iterare_, „wiederholen“). +] + +#Abbildung(caption: [Beide Diagramme sind bis auf einen einzigen Pfeil gleich. Dieser Rücksprung – die _Rückwärtskante_ – ist alles, was eine Schleife von einer Verzweigung unterscheidet.])[ + #text(size: 8.5pt)[ + #grid( + columns: (1fr, 1fr), + align: center + top, + column-gutter: 6mm, + + [ + #text(size: 10pt, weight: "bold")[`if` – höchstens einmal] + #v(6pt) + #diagram( + ..flow, + node((0, 0), [Bedingung?], shape: fletcher.shapes.diamond), + edge("-|>", [wahr], label-side: left), + node((0, 1), [Block]), + edge("-|>"), + node((0, 2), [weiter im\ Programm]), + edge((0, 0), (1.4, 0), (1.4, 2), (0, 2), "-|>", [falsch], label-pos: 0.2), + ) + ], + + [ + #text(size: 10pt, weight: "bold")[`while` – immer wieder] + #v(6pt) + #diagram( + ..flow, + node((0, 0), [Bedingung?], shape: fletcher.shapes.diamond), + edge("-|>", [wahr], label-side: left), + node((0, 1), [Rumpf]), + node((0, 2), [weiter im\ Programm]), + edge((0, 0), (1.4, 0), (1.4, 2), (0, 2), "-|>", [falsch], label-pos: 0.2), + edge((0, 1), (-1.4, 1), (-1.4, 0), (0, 0), "-|>", [zurück], label-pos: 0.5), + ) + ], + ) + ] +] + +#Code("09_schleifen/countdown.py") + +#Abbildung(caption: [Was `countdown.py` Schritt für Schritt tut. Solche Tabellen von Hand aufzuschreiben ist die wirksamste Methode, um eine Schleife zu verstehen, die sich nicht so verhält wie erwartet.])[ + #text(size: 9pt)[ + #table( + columns: 4, + align: (center, center, center, center), + stroke: 0.5pt + luma(180), + inset: 6pt, + table.header( + [*Durchlauf*], [`sekunden` vorher], [`sekunden > 0`], [*Ausgabe*], + ), + [1], [`5`], [wahr], [`5`], + [2], [`4`], [wahr], [`4`], + [3], [`3`], [wahr], [`3`], + [4], [`2`], [wahr], [`2`], + [5], [`1`], [wahr], [`1`], + [–], [`0`], [falsch], [Schleife endet], + ) + ] +] + +#Theorie[ + === Der wichtigste Stolperstein: die Endlosschleife + + Schau dir in `countdown.py` die Zeile `sekunden = sekunden - 1` genau an. Sie sieht nebensächlich aus, ist aber das Herzstück der Schleife. Lässt man sie weg, verändert sich `sekunden` nie. Die Bedingung `sekunden > 0` bleibt damit für immer wahr, und das Programm gibt bis in alle Ewigkeit `5` aus. + + Das nennt man eine *Endlosschleife*. Sie ist kein Fehler, den Python melden könnte – syntaktisch ist alles korrekt, das Programm tut ja genau das, was dort steht. Deshalb erscheint keine Fehlermeldung; das Programm hängt einfach. + + Daraus folgt die zentrale Regel für jede `while`-Schleife: + + #align(center)[*Im Rumpf muss sich etwas ändern, das in der Bedingung vorkommt.*] + + Beim Schreiben einer Schleife lohnt sich deshalb jedes Mal die Frage: _Wodurch wird diese Bedingung irgendwann falsch?_ Findest du keine Antwort, läuft die Schleife ewig. + + *Und wenn es doch passiert?* Das wird es, und zwar jedem. Ein hängendes Programm brichst du im Terminal mit `Strg` + `C` ab (auf macOS ebenfalls `Ctrl` + `C`, nicht `Cmd`). Das ist kein Absturz, sondern der reguläre Weg, ein laufendes Programm zu beenden. + + === Ein Muster: sammeln + + Sehr oft dient eine Schleife dazu, ein Ergebnis Stück für Stück *aufzusammeln*. Dafür legt man vor der Schleife eine Variable als „Sammelbehälter“ an und füllt sie im Rumpf immer weiter auf. Entscheidend ist die Reihenfolge: Der Behälter wird *vor* der Schleife auf einen Startwert gesetzt, nicht darin. Stünde `summe = 0` im Rumpf, würde die Summe bei jedem Durchlauf wieder geleert. +] + +#Code("09_schleifen/summe_sammeln.py") + +#Praxis[ + + Führe `countdown.py` aus. Lösche anschließend die Zeile `sekunden = sekunden - 1` und starte erneut. Beende das Programm mit `Strg` + `C`. Stelle die Zeile danach wieder her. + + + Ändere `countdown.py` so, dass von `10` heruntergezählt wird und immer zwei Schritte auf einmal gemacht werden (`10`, `8`, `6`, …). + + + Schreibe ein Programm, das die Zahlen von `1` bis `20` ausgibt, aber nur die geraden. (Tipp: `zahl % 2 == 0` aus der letzten Lektion.) + + + Verschiebe in `summe_sammeln.py` die Zeile `summe = 0` versuchsweise in den Schleifenrumpf. Welches Ergebnis kommt heraus, und warum? +] + +=== Eingaben absichern + +#Theorie[ + Jetzt können wir das Problem lösen, an dem wir in der letzten Lektion gescheitert sind: eine Nutzerin so lange fragen, bis die Eingabe brauchbar ist. + + Die Bedingung formuliert dabei den *unerwünschten* Zustand: „Solange die Eingabe noch unsinnig ist, frage weiter.“ Das fühlt sich beim ersten Lesen verdreht an, ist aber genau richtig – die Schleife läuft, *solange etwas nicht stimmt*. + + Ein wenig unschön ist daran der künstliche Startwert (`alter = -1`), den man nur setzt, damit die Bedingung beim ersten Mal überhaupt zutrifft. Für diesen Fall gibt es eine elegantere Schreibweise mit zwei neuen Bausteinen: + + - `while True:` startet eine Schleife, deren Bedingung *immer* wahr ist – also absichtlich eine Endlosschleife. + - `break` verlässt die Schleife sofort, mitten im Rumpf, und springt hinter sie. + + Beides zusammen ergibt: „Wiederhole für immer – bis die Eingabe stimmt, dann raus hier.“ Das ist unter Programmiererinnen ein gängiges und gut lesbares Muster. + + Ein Verwandter von `break` ist `continue`. Es bricht nicht die ganze Schleife ab, sondern nur den aktuellen Durchlauf und springt sofort zur nächsten Iteration – nützlich, um einzelne Fälle zu überspringen. +] + +#Code("09_schleifen/eingabe_pruefen.py") + +#Praxis[ + + Führe `eingabe_pruefen.py` aus und gib absichtlich erst `-3`, dann `500` und schließlich `20` ein. + + + Schreibe ein Programm, das nach einem Passwort fragt und so lange weiterfragt, bis `"heureka"` eingegeben wurde. + + + Erweitere es: Nach drei Fehlversuchen soll das Programm mit `Zu viele Versuche.` aufgeben. (Du brauchst einen Zähler *und* `and` in der Bedingung.) +] + +=== Wenn die Anzahl feststeht: `for` + +#Theorie[ + Bei den Kartoffeln wusstest du nicht im Voraus, wie viele es sind – du hast vor jedem Griff nachgesehen. Nun stell dir eine andere Aufgabe vor: Du sollst zehn Briefumschläge beschriften. Hier steht die Anzahl von Anfang an fest. Niemand prüft nach jedem Umschlag, ob noch welche da sind; man arbeitet einfach den Stapel ab. + + Für genau diesen Fall gibt es die zweite Schleifenart, die `for`-Schleife. Sie läuft nicht, *solange etwas gilt*, sondern *einmal für jedes Element* einer vorgegebenen Folge: + + ```python + for i in range(5): + print(i) + ``` + + Gelesen: „Für jede Zahl `i` aus `range(5)`: gib `i` aus.“ Die Variable `i` heißt *Schleifenvariable*. Sie wird bei jedem Durchlauf automatisch auf den nächsten Wert gesetzt – hier musst du also nichts selbst hochzählen, und eine Endlosschleife kann gar nicht erst entstehen. +] + +#Abbildung(caption: [Die Folge steht schon fest, bevor die Schleife beginnt. Aus ihr wird bei jedem Durchlauf der nächste Wert in die Schleifenvariable `zahl` gelegt, und der Rumpf läuft damit einmal durch. Sind alle Werte aufgebraucht, endet die Schleife von selbst.])[ + #text(size: 8.5pt)[ + #diagram( + ..flow, + spacing: (10mm, 9mm), + + node((-1.6, 0), text(size: 9pt)[`for zahl in range(1, 4):`], stroke: none), + node((0, 0), [`1`]), + node((1, 0), [`2`]), + node((2, 0), [`3`]), + + edge((0, 0), (1, 1), "-|>"), + edge((1, 0), (1, 1), "-|>"), + edge((2, 0), (1, 1), "-|>"), + + node((1, 1), [Schleifenvariable `zahl`], fill: luma(235)), + edge("-|>"), + node((1, 2), [Rumpf: `print(zahl)`]), + + edge((1, 2), (2.9, 2), (2.9, 1), (1, 1), "-|>", [nächster Wert], label-pos: 0.5), + ) + ] +] + +#Theorie(show-heading: false)[ + `range()` erzeugt dabei eine Folge ganzer Zahlen. Es gibt sie in drei Ausführungen: + + - `range(5)` – die Zahlen `0`, `1`, `2`, `3`, `4` + - `range(1, 6)` – die Zahlen `1`, `2`, `3`, `4`, `5` + - `range(0, 21, 5)` – die Zahlen `0`, `5`, `10`, `15`, `20` + + *Zwei Eigenheiten, über die anfangs fast alle stolpern:* + + 1. Gibt man nur eine Zahl an, beginnt die Zählung bei *`0`*, nicht bei `1`. Das ist in der Informatik durchgehend so und wird dir noch häufig begegnen. + + 2. Die *obere Grenze gehört nicht dazu*. `range(1, 6)` endet bei `5`. Merkhilfe: Die zweite Zahl ist nicht das letzte Element, sondern die Stelle, an der abgebrochen wird. Praktisch daran ist, dass `range(5)` genau fünf Werte liefert – die Zahl in den Klammern ist immer die Anzahl der Durchläufe. +] + +#Abbildung(caption: [Gefüllt heißt „gehört dazu“. Die zweite Zahl in `range()` ist nicht das letzte Element, sondern die Stelle, an der abgebrochen wird – sie bleibt selbst außen vor. Beide Zeilen liefern deshalb genau fünf Werte.])[ + #grid( + columns: (auto, auto), + column-gutter: 7mm, + row-gutter: 5mm, + align: (right + horizon, left + horizon), + + text(size: 10pt)[`range(5)`], + stack( + dir: ltr, + spacing: 3pt, + rangebox[0], rangebox[1], rangebox[2], rangebox[3], rangebox[4], + rangebox(dabei: false)[5], rangebox(dabei: false)[6], + ), + + text(size: 10pt)[`range(1, 6)`], + stack( + dir: ltr, + spacing: 3pt, + rangebox(dabei: false)[0], + rangebox[1], rangebox[2], rangebox[3], rangebox[4], rangebox[5], + rangebox(dabei: false)[6], + ), + ) +] + +#Code("09_schleifen/for_range.py") + +#Theorie[ + === Nicht nur Zahlen + + Eine `for`-Schleife kann über alles laufen, was aus mehreren Teilen besteht – Fachwort: über alles *Iterierbare*. Eine Zeichenkette ist so ein Fall, denn sie besteht aus einzelnen Zeichen. `for zeichen in "Python":` liefert nacheinander `P`, `y`, `t`, `h`, `o`, `n`. + + Damit lassen sich Texte untersuchen, ohne über Positionen und Zählerstände nachdenken zu müssen. In Lektion 11 wirst du die *Liste* kennenlernen, und diese Schreibweise wird dort unverändert weiterfunktionieren. +] + +#Code("09_schleifen/for_zeichenkette.py") + +#Theorie[ + === `while` oder `for`? + + Beide Schleifen können im Prinzip dasselbe – `summe_sammeln.py` und der letzte Abschnitt von `for_range.py` berechnen exakt dieselbe Summe. Die Wahl ist deshalb keine Frage von richtig und falsch, sondern eine der Lesbarkeit. Eine einfache Faustregel: + + - Steht die Anzahl der Durchläufe *vorher fest* oder läuft man eine Folge ab: `for`. + - Hängt das Ende von etwas ab, das sich *erst während des Laufens* zeigt (eine Eingabe, ein erreichtes Ziel): `while`. + + Wer die Anzahl kennt und trotzdem `while` verwendet, muss den Zähler selbst anlegen, selbst erhöhen und die Grenze selbst prüfen – drei Stellen, an denen sich Fehler einnisten können. Die `for`-Schleife erledigt alle drei auf einmal. +] + +#Abbildung(caption: [Die `for`-Schleife hat dieselbe Gestalt wie die `while`-Schleife – nur übernimmt Python den grau hinterlegten Schritt selbst. Genau deshalb kann eine `for`-Schleife nicht endlos laufen: Das Weiterschalten kann man nicht vergessen.])[ + #text(size: 8.5pt)[ + #diagram( + ..flow, + node((0, 0), [noch ein\ Wert übrig?], shape: fletcher.shapes.diamond), + edge("-|>", [ja], label-side: left), + node((0, 1), [`zahl` = nächster Wert], fill: luma(235)), + edge("-|>"), + node((0, 2), [Rumpf]), + node((0, 3), [weiter im Programm]), + edge((0, 0), (1.8, 0), (1.8, 3), (0, 3), "-|>", [nein], label-pos: 0.15), + edge((0, 2), (-1.8, 2), (-1.8, 0), (0, 0), "-|>", [zurück], label-pos: 0.5), + ) + ] +] + +#Praxis[ + + Gib mit einer `for`-Schleife die Quadratzahlen von `1` bis `10` aus (`1`, `4`, `9`, …). + + + Schreibe das Programm aus der ersten Praxis-Aufgabe (gerade Zahlen von 1 bis 20) mit `for` statt `while`. Welche Fassung liest sich besser? + + + Lass ein Programm die Buchstaben deines Namens einzeln untereinander ausgeben. + + + Was gibt `for i in range(10, 1):` aus? Überlege erst, probiere dann. +] + +=== Schleifen in Schleifen + +#Theorie[ + Der Rumpf einer Schleife ist ein ganz normaler Block – dort darf also alles stehen, was sonst auch erlaubt ist, einschließlich einer weiteren Schleife. Man spricht dann von *verschachtelten Schleifen*. + + Das Bild dazu ist ein Zeigersystem: Die äußere Schleife ist der Stundenzeiger, die innere der Minutenzeiger. Für *jeden* Schritt des Stundenzeigers dreht der Minutenzeiger eine komplette Runde. Bei zehn äußeren und zehn inneren Durchläufen kommt der Rumpf also hundertmal an die Reihe. + + Entscheidend ist wieder die Einrückung: Sie allein legt fest, was zur inneren und was noch zur äußeren Schleife gehört. +] + +#Abbildung(caption: [Die innere Schleife beginnt bei jedem Schritt der äußeren wieder von vorn. Zehn äußere Durchläufe mit je zehn inneren ergeben $10 dot 10 = 100$ Ausführungen des Rumpfes – die vollständige Einmaleins-Tabelle.])[ + #text(size: 9pt)[ + #table( + columns: 3, + stroke: none, + align: (right + horizon, center + horizon, left + horizon), + inset: (x: 7pt, y: 5pt), + fill: (x, y) => if x == 2 and y > 0 { luma(238) }, + + table.header( + [*äußere Schleife*], [], [*innere Schleife*], + ), + [`reihe = 1`], [$arrow.r$], [`faktor = 1 2 3 4 5 6 7 8 9 10`], + [`reihe = 2`], [$arrow.r$], [`faktor = 1 2 3 4 5 6 7 8 9 10`], + [`reihe = 3`], [$arrow.r$], [`faktor = 1 2 3 4 5 6 7 8 9 10`], + [$dots.v$], [], [$dots.v$], + [`reihe = 10`], [$arrow.r$], [`faktor = 1 2 3 4 5 6 7 8 9 10`], + ) + ] +] + +#Code("09_schleifen/einmaleins.py") + +#Praxis[ + + Lass `einmaleins.py` laufen. Ändere die innere Schleife auf `range(1, 6)` – was passiert mit der Tabelle? + + + Gib ein Dreieck aus Sternchen aus, das in jeder Zeile ein Sternchen mehr hat (fünf Zeilen). Tipp: `print("*" * anzahl)` wiederholt einen Text. + + + Rücke in `einmaleins.py` das abschließende leere `print()` um vier Leerzeichen weiter ein, sodass es zur inneren Schleife gehört. Erkläre die Ausgabe. +] + +#Spiel[ + Das Ratespiel aus der letzten Lektion gibt jetzt mehrere Versuche – `zahlenraten_schleife.py` zeigt, wie. Beachte, dass die Schleife aus *zwei* Gründen enden kann: Die Versuche sind aufgebraucht, oder die Zahl wurde erraten. Beides steht in der Bedingung, verbunden mit `and`. + + Zum Ausprobieren: + + + Erhöhe `MAX_VERSUCHE` und beobachte, wie sich das Spiel anfühlt. Wie viele Versuche sind fair für Zahlen von 1 bis 10? + + + Schreibe das Programm mit `while True` und `break` um, statt die Hilfsvariable `geraten` zu verwenden. Welche Fassung findest du klarer? + + + Baue es andersherum: *Du* denkst dir eine Zahl aus, und das Programm rät. Nach jedem Tipp gibst du „zu klein“, „zu groß“ oder „richtig“ ein. + + + Lass ein Programm die Zahlen von 1 bis 100 durchlaufen und dabei nur die ausgeben, die sowohl durch 3 als auch durch 5 teilbar sind. +] + +#Code("09_schleifen/zahlenraten_schleife.py") + +#Quiz(answer: "c)")[ + ```python + zahl = 3 + while zahl > 0: + print("hallo") + ``` + Was tut dieses Programm? + + Es gibt `hallo` dreimal aus. + + Es gibt `hallo` einmal aus. + + Es gibt endlos `hallo` aus, weil sich `zahl` nie ändert. + + Es meldet einen `IndentationError`. +] + +#Quiz(answer: "b)")[ + Welche Zahlen gibt `for i in range(1, 5): print(i)` aus? + + `1 2 3 4 5` + + `1 2 3 4` + + `0 1 2 3 4` + + `0 1 2 3` +] + +#Quiz(answer: "d)")[ + Welche Zahlen liefert `range(0, 10, 3)`? + + `0 1 2 3` + + `3 6 9` + + `0 3 6 9 10` + + `0 3 6 9` +] + +#Quiz(answer: "a)")[ + Du möchtest so lange nach einer Zahl fragen, bis die Eingabe positiv ist. Welche Schleife passt? + + `while`, weil das Ende von der Eingabe abhängt und vorher nicht feststeht. + + `for`, weil jede Eingabe ein Element einer Folge ist. + + `for` mit `range(3)`, weil drei Versuche üblich sind. + + Beide sind hier gleich gut geeignet. +] + +#Quiz(answer: "b)")[ + Was ist der Unterschied zwischen `break` und `continue`? + + `break` überspringt einen Durchlauf, `continue` beendet die Schleife. + + `break` beendet die Schleife, `continue` überspringt den Rest des aktuellen Durchlaufs. + + Beide beenden die Schleife, `continue` gibt zusätzlich eine Meldung aus. + + `break` funktioniert nur in `while`, `continue` nur in `for`. +] + +=== Häufige Fehler und ihre Ursachen + +#Theorie[ + *Das Programm hängt und gibt endlos dasselbe aus* + + Eine Endlosschleife. Im Rumpf ändert sich nichts, was in der Bedingung vorkommt – meist wurde das Hochzählen vergessen. Mit `Strg` + `C` abbrechen und prüfen: _Wodurch soll diese Bedingung jemals falsch werden?_ + + *Die Schleife läuft überhaupt nicht* + + Die Bedingung war schon beim ersten Prüfen falsch. Ein `while` prüft *vor* dem ersten Durchlauf, nicht danach. Bei `for` liegt es oft an einem leeren `range()`: `range(10, 1)` zählt aufwärts, kommt von `10` aber nie bei `1` an und liefert deshalb gar nichts. Für rückwärts braucht es eine negative Schrittweite: `range(10, 1, -1)`. + + *Ein Durchlauf zu viel oder zu wenig* + + Der klassische „Zaunpfahlfehler“ (englisch _off-by-one error_). Fast immer ist die obere Grenze von `range()` gemeint: Für die Zahlen `1` bis `10` schreibt man `range(1, 11)`, nicht `range(1, 10)`. + + *`TypeError: 'int' object is not iterable`* + + In der `for`-Zeile steht eine einzelne Zahl statt einer Folge: `for i in 5:`. Eine einzelne Zahl kann man nicht durchlaufen. Richtig ist `for i in range(5):`. + + *`IndentationError: expected an indented block after 'while' statement`* + + Nach dem Doppelpunkt fehlt der eingerückte Rumpf. Jede Schleife braucht mindestens eine Anweisung darunter. + + *Das Ergebnis wird bei jedem Durchlauf zurückgesetzt* + + Kein Absturz, nur ein falsches Ergebnis: Die Sammelvariable (`summe = 0`) steht versehentlich *im* Rumpf statt davor. Sie muss vor der Schleife angelegt werden, sonst beginnt sie jede Iteration von vorn. + + *`ValueError` mitten in der Schleife* + + `int(input(...))` bricht ab, sobald jemand etwas eingibt, das keine Zahl ist. Die Schleife schützt nur vor Werten im falschen *Bereich*, nicht vor der falschen *Art* von Eingabe. Wie man auch das abfängt, lernst du später. +] + +#Theorie[ + === Ausblick + + Dein Programm kann jetzt entscheiden *und* wiederholen. Damit hast du alle Bausteine beisammen, um im Prinzip jede Berechnung zu formulieren, die ein Computer ausführen kann – das ist eine bemerkenswerte Grenze, die du gerade überschritten hast. + + Praktisch fehlt aber noch etwas: Bislang wächst jedes Programm als ein einziger langer Block von oben nach unten. Willst du dieselbe Prüfung an drei Stellen durchführen, musst du sie dreimal hinschreiben. In der nächsten Lektion lernst du *Funktionen* – die Möglichkeit, einem Stück Code einen Namen zu geben und es von überall aufzurufen. Ab da baust du Programme aus wiederverwendbaren Bausteinen statt aus einer durchgehenden Textwand. +] diff --git a/Lektionen/variablen_aritmetik.typ b/Lektionen/variablen_aritmetik.typ new file mode 100644 index 0000000..1019c4c --- /dev/null +++ b/Lektionen/variablen_aritmetik.typ @@ -0,0 +1,206 @@ +#import "../lib/helpers.typ": * + +== Variablen und Arithmetik + +Bisher haben unsere Programme immer exakt das Gleiche getan. Wir haben Text ausgegeben, und dieser Text stand fest im Quellcode. In dieser Lektion geben wir unseren Programmen ein Gedächtnis: Wir lernen, Werte unter einem Namen zu speichern, sie später wieder abzurufen und mit ihnen zu rechnen. Damit ist der Schritt von einer starren Ausgabe zu einem Programm getan, das auf Eingaben reagiert und etwas ausrechnet. + +#Theorie[ + === Was ist eine Variable? + + Stell dir ein beschriftetes Schließfach vor. Auf dem Schild steht ein Name, im Fach liegt ein Inhalt. Wenn du den Namen nennst, meinst du eigentlich den Inhalt. Genau das ist eine *Variable*: ein Name, unter dem ein Wert abgelegt ist. + + Die Ablage geschieht durch ein Gleichheitszeichen. Der Name steht links, der Wert rechts: + + ```python + beschreibung = "wundervoll" + ``` + + Ab dieser Zeile ist es im Programm so, als stünde überall dort, wo `beschreibung` geschrieben wird, das Wort `"wundervoll"`. Das ist praktisch, wenn derselbe Wert an mehreren Stellen gebraucht wird – ändern wir ihn an einer Stelle, ändert er sich überall. + + Das folgende Programm erzählt von seinem Tag. Führe es aus und schau dir die Ausgabe an. +] + +#Code("variablen.py") + +#Theorie[ + === Das Gleichheitszeichen ist keine Gleichung + + In der Mathematik bedeutet `=` „beide Seiten sind gleich“. In Python bedeutet `=` etwas anderes: *„Der Name auf der linken Seite soll ab jetzt den Wert der rechten Seite tragen.“* Es ist eine *Zuweisung*, kein Vergleich. + + Der Unterschied wird bei einer Zeile wie dieser deutlich: + + ```python + zaehler = zaehler + 1 + ``` + + Als Gleichung gelesen ist das Unsinn. Als Anweisung gelesen ist es klar: Nimm den aktuellen Wert von `zaehler`, addiere 1 dazu, und speichere das Ergebnis wieder unter dem Namen `zaehler`. + + Das liegt daran, dass ein Programm eine *Folge von Anweisungen* ist, die von oben nach unten abgearbeitet wird – keine Sammlung von Aussagen, die alle gleichzeitig gelten. Der Wert einer Variable kann sich im Laufe des Programms also ändern. + + === Datentypen + + Jeder Wert hat einen *Datentyp* – eine Art von Wert. `"wundervoll"` ist ein Text (in Python `str`, von _string_, „Zeichenkette“). `42` ist eine ganze Zahl (`int`, von _integer_). `3.5` ist eine Kommazahl (`float`). Der Typ ist wichtig, weil Python mit verschiedenen Typen unterschiedlich umgeht: Zwei Zahlen kann man multiplizieren, zwei Texte nicht sinnvoll. + + Anders als in vielen anderen Sprachen musst du den Typ in Python *nicht* vorab hinschreiben. Python erkennt ihn automatisch am Wert. Mit der Funktion `type()` kannst du nachfragen, welchen Typ ein Wert gerade hat. +] + +#Code("variablen_aendern.py") + +#Praxis[ + + Weise `beschreibung` in `variablen.py` ein anderes Wort zu und führe das Programm erneut aus. Was ändert sich in der Ausgabe? + + + Definiere in `variablen.py` eine zweite Variable (zum Beispiel `wetter = "sonnig"`) und schreibe mit ihr einen weiteren `print()`-Satz. + + + Schreibe ein Programm, das eine Variable `alter` auf einen Wert setzt, sie dann in mehreren Schritten verändert (`alter = alter + 1`) und nach jedem Schritt ausgibt. +] + +=== Eingaben von der Nutzerin + +#Theorie[ + Ein Programm wird erst dann wirklich nützlich, wenn es mit einer Nutzerin interagiert. Ausgaben kennen wir schon: `print()`. Für Eingaben gibt es die Funktion `input()`. + + ```python + name = input("Wie heißt du? ") + ``` + + `input()` hält das Programm an, zeigt den Text in den Klammern an und wartet, bis die Nutzerin etwas eintippt und Enter drückt. Das Eingetippte wird dann unter dem Namen links vom `=` gespeichert. + + *Der wichtigste Stolperstein dieser Lektion:* `input()` liefert das Eingegebene *immer als Text* (`str`) zurück – auch dann, wenn die Nutzerin eine Zahl eingetippt hat. Der Text `"12"` und die Zahl `12` sind für Python zwei verschiedene Dinge. + + Warum ist das ein Problem? Das `+`-Zeichen bedeutet für zwei Texte „hänge sie aneinander“. Die Eingaben `"12"` und `"3"` ergeben mit `+` also `"123"`, nicht `15`. + + Um mit einer Eingabe zu rechnen, musst du sie erst umwandeln: + + - `int("12")` macht aus dem Text die ganze Zahl `12` + - `float("3.5")` macht aus dem Text die Kommazahl `3.5` +] + +#Code("eingabe_zahlen.py") + +#Praxis[ + + Schreibe ein Programm, das nach dem Namen fragt und anschließend `Hallo, !` ausgibt. + + + Erweitere es: Frage zusätzlich nach dem Geburtsjahr, wandle die Eingabe mit `int()` um und gib aus, wie alt die Person dieses Jahr wird. + + + Was passiert, wenn du bei der Umwandlung mit `int()` keinen Zahlentext, sondern `"hallo"` eingibst? Lies die Fehlermeldung. +] + +=== Rechnen: Arithmetik + +#Theorie[ + Mit Zahlen können wir rechnen. Python versteht die Rechenausdrücke, die du aus der Schule kennst: + + - `+` — Addition + - `-` — Subtraktion + - `*` — Multiplikation + - `/` — Division + - `//` — Ganzzahl-Division (schneidet den Rest ab) + - `%` — Rest der Division („modulo“) + - `**` — Potenz (`a ** b` ist „a hoch b“) + + „Punkt vor Strich“ gilt wie gewohnt, und mit Klammern kannst du die Reihenfolge steuern: `(2 + 3) * 4` ergibt `20`. + + *Zwei Besonderheiten, an denen viele ins Straucheln geraten:* + + 1. `/` liefert in Python *immer* eine Kommazahl. `10 / 2` ergibt nicht `5`, sondern `5.0` (Typ `float`). Wenn du ein ganzzahliges Ergebnis willst, nutze `//`: `10 // 3` ergibt `3`. + + 2. `%` gibt den Rest an. `18 % 5` ist `3`, weil $18 = 3 dot 5 + 3$. Das ist überraschend nützlich, etwa um zu prüfen, ob eine Zahl gerade ist (`zahl % 2 == 0`). + + Eine angenehme Eigenschaft: Ganze Zahlen (`int`) haben in Python *keine Obergrenze*. `2 ** 200` rechnet Python ohne zu murren exakt aus. In vielen anderen Sprachen würde eine so große Zahl „überlaufen“. +] + +#Code("rechnen.py") + +#Theorie[ + Genauso können wir mit Zahlen rechnen, die die Nutzerin eingibt – vorausgesetzt, wir wandeln die Eingabe vorher mit `int()` um. +] + +#Code("rechner_eingabe.py") + +#Praxis[ + + Schreibe ein Programm, das zwei ganze Zahlen entgegennimmt und Summe, Differenz, Produkt *und* Quotient ausgibt. + + + Was fällt auf, wenn du `19` und `7` eingibst? Schau dir den Quotienten genau an. + + + Erweitere das Programm um Ganzzahl-Division (`//`) und Rest (`%`). + + + Was passiert, wenn du als zweite Zahl eine `0` eingibst? Lies die Fehlermeldung – wie heißt der Fehler? +] + +#Spiel[ + + Wie groß ist die größte Zahl, die du in Python speichern kannst? Probier `2 ** 1000` aus. Vergleiche das mit dem, was du vielleicht über andere Programmiersprachen gehört hast. + + + Gib `0.1 + 0.2` aus. Kommt `0.3` heraus? Kommazahlen werden im Computer nur *näherungsweise* gespeichert – das ist normal und begegnet dir in jeder Programmiersprache. + + + Was passiert, wenn du dich im Namen einer Variable vertippst, etwa `print(beschreibnug)` statt `print(beschreibung)`? + + + Texte kann man „addieren“ und sogar „multiplizieren“: Probier `"ha" * 3` und `"Hallo " + "Welt"` aus. Was passiert bei `"Hallo" + 3`? + + + Statt `zaehler = zaehler + 1` gibt es die Kurzschreibweise `zaehler += 1`. Es gibt auch `-=`, `*=` und `/=`. Probier sie aus. +] + +=== Häufige Fehler und ihre Ursachen + +#Theorie[ + *`NameError: name 'x' is not defined`* + + Python kennt den Namen nicht. Meist ein Tippfehler im Variablennamen, oder die Variable wurde erst *nach* ihrer Verwendung zugewiesen. Groß- und Kleinschreibung zählt: `Alter` und `alter` sind zwei verschiedene Namen. + + *`TypeError: can only concatenate str (not "int") to str`* + + Du versuchst, einen Text und eine Zahl mit `+` zu verbinden. Häufig, weil eine `input()`-Eingabe noch Text ist. Lösung: die Zahl mit `int()` umwandeln, oder im `print()` ein Komma statt `+` verwenden, oder einen f-string nutzen: `print(f"Alter: {alter}")`. + + *`ZeroDivisionError: division by zero`* + + Du hast durch `0` geteilt. Durch null zu teilen ist mathematisch nicht definiert, deshalb bricht das Programm ab. In der nächsten Lektion lernst du, solche Fälle vorher abzufangen. + + *`ValueError: invalid literal for int() with base 10: 'hallo'`* + + Du hast `int()` einen Text gegeben, der keine Zahl ist. + + *Verwechslung von `=` und `==`* + + `=` weist zu, `==` vergleicht („sind diese beiden gleich?“). Diese Unterscheidung wird in der nächsten Lektion wichtig. +] + +#Quiz(answer: "a ist 8")[ + Was steht am Ende in `a`? + ```python + a = 5 + a = a + 3 + ``` + + a ist 8 + + a ist 5 + + Es gibt einen Fehler, weil `a = a + 3` keine gültige Gleichung ist + + a ist 53 +] + +#Quiz(answer: "5.0 (eine Kommazahl)")[ + Was gibt `print(10 / 2)` in Python aus? + + 5.0 (eine Kommazahl) + + 5 (eine ganze Zahl) + + 2.5 + + Einen Fehler +] + +#Quiz(answer: "Immer einen Text (str)")[ + Welchen Datentyp liefert `input()` zurück? + + Immer einen Text (str) + + Eine ganze Zahl, wenn eine Zahl eingegeben wurde + + Den Typ, der zum Eingegebenen passt + + Eine Kommazahl (float) +] + +#Quiz(answer: "b), c)")[ + Welche Aussagen über `%` (modulo) sind korrekt? + + `10 % 3` ergibt `3.33` + + `10 % 2 == 0` prüft, ob `10` gerade ist + + `18 % 5` ergibt `3` + + `%` funktioniert nur mit Kommazahlen +] + +#Theorie[ + === Ausblick + + Du kannst jetzt Werte speichern, Eingaben entgegennehmen und rechnen. Aufgefallen ist dir vielleicht, dass ein Programm bei einer Eingabe wie `0` als Divisor einfach abstürzt. In der nächsten Lektion, *Kontrollfluss*, lernst du, wie ein Programm abhängig von seinen Eingaben unterschiedliche Wege einschlägt – und solche Abstürze höflich abfängt. +] diff --git a/lib/helpers.typ b/lib/helpers.typ index 9614250..388fe6b 100644 --- a/lib/helpers.typ +++ b/lib/helpers.typ @@ -52,26 +52,52 @@ let content = { // Remove trailing newline if (raw_c.last() == "\n") { str.slice(raw_c, 0, -1) - - + + } else {raw_c} } +let ext-to-lang(ext) = { + let mapping = ( + "py": "Python", + "cpp": "C++", + "java": "Java", + "js": "JavaScript", + "ts": "TypeScript", + "rs": "Rust", + "go": "Go", + "rb": "Ruby", + "php": "PHP", + "c": "C", + "h": "C", + "hpp": "C++", + ) + mapping.at(ext, default: "") +} -zebraw(raw(lang: "Python", content, block: true)) +let ext = path.split(".").last() +let lang = ext-to-lang(ext) + +stack( + if lang != "" {text(size: 0.85em, weight: "bold", fill: gray)[#lang]} else [], + zebraw(raw(lang: lang, content, block: true)), + spacing: 0.3em +) } #let Quiz_answers = state("Quiz_answers", ()) +#let Quiz_counter = counter("quiz") -// Quiz is abit more complicated than the other environments +// Quiz is abit more complicated than the other environments #let Quiz(answer: "", body) = { - context { - let number = counter(heading).get().at(0) + Quiz_counter.step() + context { + let number = Quiz_counter.get().at(0) Quiz_answers.update(s => s + ((number, answer),)) - + [=== Quiz #number ] set enum(numbering: "a)") body diff --git a/main.typ b/main.typ index 3d7377f..bb5de8d 100644 --- a/main.typ +++ b/main.typ @@ -56,24 +56,13 @@ = Python -== Numpy - +#include "Lektionen/helloworld.typ" +#include "Lektionen/variablen_aritmetik.typ" -#let codesnippet(path, name) = { - [#set align(center) - #text(gray, style: "italic")[ - #name]] - - rect(stroke: gray, width: 100%)[ - #raw(lang: "Python", read(path), block: true)] -} - +#include "Lektionen/kontrollfluss.typ" -//#zebraw(raw(lang: "Python", code, block: true)) - - -#Code("helloworld.py") +== Numpy