diff --git a/notebooks/seminar07.ipynb b/notebooks/seminar07.ipynb index 23f1bb211ff73eacfaa4ae3305788f3bc7ab07e1..a874cb02e2f23106d0c840ca4dd3250e6b1c6672 100644 --- a/notebooks/seminar07.ipynb +++ b/notebooks/seminar07.ipynb @@ -111,11 +111,11 @@ "source": [ "Wenn wir `x` beim ersten Mal ausgeben, ist sein Wert 5; beim zweiten Mal ist sein Wert 7.\n", "\n", - "Die folgende Abbildung zeigt, wie diese **Neuzuweisung** (*reassignment*) in einem Zustandsdiagramm aussieht. \n", + "Die folgende Abbildung zeigt, wie diese **Neuzuweisung** (*reassignment*) in einem Zustandsdiagramm aussieht:\n", "\n", "\n", "\n", - "An dieser Stelle möchte ich auf eine häufige Ursache für Verwechslungen hinweisen. Da Python das Gleichheitszeichen (`=`) für die Zuweisung verwendet, ist es verlockend, eine Anweisung wie `a = b` wie eine mathematische Aussage der Gleichheit zu interpretieren, das heisst, die Behauptung, dass `a` und `b` gleich seien. Aber diese Interpretation ist falsch! \n", + "An dieser Stelle wollen wir auf eine häufige Ursache für Verwechslungen hinweisen: Da Python das Gleichheitszeichen (`=`) für die Zuweisung verwendet, ist es verlockend, eine Anweisung wie `a = b` wie eine mathematische Aussage der Gleichheit zu interpretieren, das heisst, die Behauptung, dass `a` und `b` gleich seien. Aber diese Interpretation ist falsch! \n", "\n", "Erstens ist Gleichheit eine symmetrische Beziehung und die Zuweisung ist es nicht. Beispielsweise gilt in der Mathematik: wenn $a=7$, dann ist auch $7=a$. Aber in Python ist die Anweisung `a = 7` erlaubt und `7 = a` ist es nicht. \n", "\n", @@ -157,7 +157,7 @@ "\n", "Das bedeutet \"nimm' den aktuellen Wert von `x`, füge eins hinzu und aktualisiere dann `x` mit dem neuen Wert\".\n", "\n", - "Wenn wir versuchen eine Variable zu aktualisieren, die nicht existiert, erhalten wir einen Fehler, denn Python evaluiert die rechte Seite der Zuweisung bevor es den Wert ver Variablen auf der linken Seite zuweist:\n" + "Wenn wir versuchen eine Variable zu aktualisieren, die nicht existiert, erhalten wir einen Fehler, denn Python evaluiert die rechte Seite der Zuweisung bevor es den Wert der Variablen auf der linken Seite zuweist:\n" ] }, { @@ -190,7 +190,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Das Aktualisieren einer Variable mittels Addition der Zahl 1 wird **inkrementieren** genannt, das Subtrahieren einer 1 **dekrementieren**." + "Das Aktualisieren einer Variable mittels Addition der Zahl 1 wird **inkrementieren** genannt, das Subtrahieren einer 1 **dekrementieren**.\n", + "\n", + "\n", + "\n", + "[Footnote Labyrinths](https://xkcd.com/1208/), Randall Munroe" ] }, { @@ -199,9 +203,9 @@ "source": [ "### 7.3 Die `while`-Anweisung\n", "\n", - "Computer werden häufig dazu genutzt, um sich wiederholende Aufgaben zu automatisieren. Identische oder ähnliche Aufgaben zu wiederholen ohne dabei Fehler zu machen, ist etwas was Computer sehr gut können und Menschen eher schlecht. In einem Computerprogramm wird die Wiederholung auch als **Iteration** bezeichnet. \n", + "Computer werden häufig zur Automatisierung sich wiederholender Aufgaben genutzt. Identische oder ähnliche Aufgaben zu wiederholen ohne dabei Fehler zu machen, ist etwas was Computer sehr gut können und Menschen eher schlecht. In einem Computerprogramm wird die Wiederholung auch als **Iteration** bezeichnet. \n", "\n", - "Wir haben bereits zwei Funktionen gesehen, `countdown` und `print_t`, die mit Hilfe einer Rekursion eine Wiederholung durchführen. Da Wiederholung sehr häufig benötigt wird, bietet in Python Sprachkonstrukte die das vereinfachen. Eines ist die `for`-Anweisung, die wir in [Abschnitt 4.2](seminar04.ipynb#4.2-Einfache-Wiederholung) kennengelernt haben. Darauf kommen wir später noch einmal zurück.\n", + "Wir haben bereits zwei Funktionen gesehen, `countdown` und `print_t`, die mit Hilfe einer Rekursion eine Wiederholung durchführen. Da Wiederholung sehr häufig benötigt wird, gibt es in Python Sprachkonstrukte die das vereinfachen. Eines ist die `for`-Anweisung, die wir in [Abschnitt 4.2](seminar04.ipynb#4.2-Einfache-Wiederholung) kennengelernt haben. Darauf kommen wir später noch einmal zurück.\n", "\n", "Eine andere Möglichkeit ist die `while`-Anweisung. Dies ist eine Version von `countdown` die eine `while`-Schleife verwendet:" ] @@ -216,19 +220,22 @@ " while n > 0:\n", " print(n)\n", " n = n - 1\n", - " print(\"Abheben!\")" + " print(\"Abheben!\")\n", + " \n", + "# probieren Sie die Funktion aus\n", + "countdown(3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Wir können die `while`-Anweisung fast so lesen, als wäre es natürliche Sprache. Es bedeutet \"Solange `n` größer als 0 ist, zeige den Wert von `n` und dann dekrementiere `n`. Sobald 0 erreicht ist, gib das Wort `Abheben!` aus.\"\n", + "Wir können die `while`-Anweisung fast so lesen, als wäre es natürliche Sprache: \"Solange `n` größer als 0 ist, zeige den Wert von `n` und dann dekrementiere `n`. Sobald 0 erreicht ist, gib das Wort `Abheben!` aus.\"\n", "\n", "Der Kontrollfluss der `while`-Schleife etwas formaler ausgedrückt sieht so aus:\n", "\n", "1. Bestimme ob die Bedingung wahr oder falsch ist.\n", - "2. Wenn die Bedingung unwahr ist, beende die `while`-Schleife und fahre mit der Ausführung der nächsten Anweisung nach dem eingerückten Block von Anweisungen fort.\n", + "2. Wenn die Bedingung unwahr ist, beende die `while`-Schleife und fahre mit der Ausführung der nächsten Anweisung nach der eingerückten Folge von Anweisungen fort.\n", "3. Wenn die Bedingung wahr ist, führe die eingerückte Folge von Anweisungen im Schleifenrumpf aus und gehe dann zu Schritt 1.\n", "\n", "Diese Art von Kontrollfluss wird Schleife genannt, weil der dritte Schritt wieder zum ersten Schritt springt und damit den Kreis (Schleife) schließt. (Im Englischen Original passt es besser: *This type of flow is called a loop because the third step loops back around to the top*.)\n", @@ -254,7 +261,7 @@ " while n != 1:\n", " print(n)\n", " if n % 2 == 0: # n ist gerade\n", - " n = n / 2\n", + " n = n // 2\n", " else: # n ist ungerade\n", " n = n*3 + 1" ] @@ -273,16 +280,22 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "sequence(23)" + ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Da `n` manchmal wächst und manchmal schrumpft gibt es keinen offensichtlichen Beweis, dass `n` jemals 1 erreichen wird oder das Programm beendet wird. Für einige bestimmte Werte von `n` können wir zeigen, dass das Programm beendet wird. Wenn beispielsweise der Startwert eine Potenz von 2 ist, dann ist `n` bei jedem Schleifendurchlauf eine gerade Zahl (und wird daher halbiert) bis die Schleife den Wert 1 erreicht. Das eben genannte Beispiel endet mit einer solchen Folge, die durch die Zahl 16 beginnt.\n", + "Da `n` manchmal wächst und manchmal schrumpft gibt es keinen offensichtlichen Beweis, dass `n` jemals 1 erreichen wird oder das Programm beendet wird. Für einige bestimmte Werte von `n` können wir zeigen, dass das Programm beendet wird. Wenn beispielsweise der Startwert eine Potenz von 2 ist (2, 4, 8, 16, 32, ...), dann ist `n` bei jedem Schleifendurchlauf eine gerade Zahl (und wird daher halbiert) bis die Schleife den Wert 1 erreicht. Das eben genannte Beispiel endet mit einer solchen Folge, die mir der Zahl 16 beginnt.\n", "\n", "Die schwierige Frage ist, ob wir beweisen können, dass dieses Programm für *jeden* positiven Wert von `n` beendet wird. Bis jetzt hat es noch niemand geschafft, dies zu beweisen *oder* das Gegenteil zu beweisen (siehe https://de.wikipedia.org/wiki/Collatz-Problem).\n", "\n", + "\n", + "\n", + "[Collatz Conjecture](https://xkcd.com/710/), Randall Munroe\n", + "\n", "Schreiben Sie als Übung die Funktion `print_n` aus [Abschnitt 5.8](seminar05.ipynb#5.8-Rekursion) so um, dass eine Schleife statt der Rekursion verwendet wird:" ] }, @@ -306,7 +319,7 @@ "source": [ "### 7.4 `break`\n", "\n", - "Manchmal wissen wir nicht, dass es Zeit wird eine Schleife zu beenden bevor wir den Schleifenrumpf nicht schon zur Hälfte ausgeführt haben. In einem solchen Fall können wir die `break`-Anweisung nutzen, um eine Schleife zu verlassen.\n", + "Manchmal wissen wir nicht, dass es Zeit wird eine Schleife zu beenden, bevor wir den Schleifenrumpf nicht schon zur Hälfte ausgeführt haben. In einem solchen Fall können wir die `break`-Anweisung nutzen, um eine Schleife zu verlassen.\n", "\n", "Nehmen wir beispielsweise an, wir wollen eine Eingabe von der Nutzerin einlesen bis Sie `fertig` eingibt. Dann könnten wir folgendes schreiben:" ] @@ -343,6 +356,10 @@ "source": [ "### 7.5 Quadratwurzeln\n", "\n", + "\n", + "\n", + "[Estimation](https://xkcd.com/612/), Randall Munroe\n", + "\n", "Schleifen werden häufig in Programmen genutzt, die numerische Werte berechnen, indem sie mit einem Näherungswert beginnen und diesen iterativ verbessern. \n", "\n", "Beispielsweise kann die Quadratwurzel einer Zahl mit dem [Newton-Verfahren](https://de.wikipedia.org/wiki/Newton-Verfahren) berechnet werden. Angenommen, wir wollen die Quadratwurzel von $a$ berechnen. Wenn wir mit einem (fast beliebigen) Näherungswert $x$ beginnen, können wir einen besseren Näherungswert $y$ mit der folgenden Formel berechnen:\n", @@ -370,7 +387,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Das Ergebnis ist näher an der richtigen Antwort ($\\sqrt{4} = 2). Wenn wir den Vorgang mit dem neuen Näherungswert wiederholen, kommen wir noch näher heran:" + "Das Ergebnis ist näher an der richtigen Antwort ($\\sqrt{4} = 2$). Wenn wir den Vorgang mit dem neuen Näherungswert wiederholen, kommen wir noch näher heran:" ] }, { @@ -472,7 +489,7 @@ "source": [ "Für die meisten Werte von `a` funktioniert das sehr gut aber im Allgemeinen ist es gefährlich, die Gleichheit von Gleitkommazahlen zu testen. Gleitkommazahlen sind nur ungefähr exakt: die meisten rationalen Zahlen wie z.B. 1/3 und irrationale Zahlen wie z.B. $\\sqrt{2}$ können nicht exakt als Gleitkommazahl repräsentiert werden. \n", "\n", - "Statt zu prüfen ob `x` und `y` exakt gleich sind ist es sicherer die eingebaute Funktion `abs` zu nutzen, um den Betrag des Unterschieds zwischen den beiden Zahlen zu berechnen:\n", + "Statt zu prüfen ob `x` und `y` exakt gleich sind ist es sicherer, die eingebaute Funktion `abs` zu nutzen, um den Absolutbetrag des Unterschieds zwischen den beiden Zahlen zu berechnen:\n", "\n", "```python\n", "if abs(y-x) < epsilon:\n", @@ -488,15 +505,19 @@ "source": [ "### 7.6 Algorithmen\n", "\n", + "\n", + "\n", + "[Algorithms](https://xkcd.com/1667/), Randall Munroe\n", + "\n", "Das Newton-Verfahren ist ein klassisches Beispiel für einen **Algorithmus**: ein Prozess zur Lösung einer bestimmten Problemklasse (in diesem Fall die Berechnung von Quadratwurzeln). \n", "\n", "Um zu verstehen, was ein Algorithmus ist, hilft es vielleicht, sich etwas anzuschauen, was kein Algorithmus ist. Als Sie (wohl in der Grundschule) gelernt haben, Zahlen mit nur einer Ziffer zu multiplizieren, haben Sie wahrscheinlich die Multiplikationstabelle (das [Kleine Einmaleins](https://de.wikipedia.org/wiki/Einmaleins)) auswendig gelernt. Effektiv haben Sie sich damit also 100 verschiedene Lösungen gemerkt. Diese Art von Wissen ist nicht algorithmisch.\n", "\n", - "Aber wenn Sie \"faul\" waren, haben Sie vielleicht ein paar Tricks gelernt. Beispielsweise kann man das Produkt einer Zahl $n$ mit 9 berechnen, indem man $n-1$ als erste Ziffer des Ergebnisses aufschreibt und dann $10-n$ als zweite Ziffer anhängt. Dieser Trick ist eine allgemeine Lösung, um jede Zahl mit nur einer Zahl mit 9 zu multiplizieren. Das ist ein Algorithmus!\n", + "Aber wenn Sie \"faul\" waren, haben Sie vielleicht ein paar Tricks gelernt. Beispielsweise kann man das Produkt einer Zahl $n$ mit 9 berechnen, indem man $n-1$ als erste Ziffer des Ergebnisses aufschreibt und dann $10-n$ als zweite Ziffer anhängt. Dieser Trick ist eine allgemeine Lösung, um jede Zahl mit nur einer Ziffer mit 9 zu multiplizieren. Das ist ein Algorithmus!\n", "\n", "Genauso sind die Verfahren zur schriftlichen Addition (mit Übertrag), Subtraktion und Division Algorithmen. Eine Eigenschaft von Algorithmen ist, dass Sie keine Intelligenz benötigen, um ausgeführt zu werden. Sie sind mechanische Prozesse bei denen jeder Schritt vom vorherigen mittels einfacher und eindeutiger Regeln folgt.\n", "\n", - "Algorithmen auszuführen ist langweilig aber sie zu entwerfen ist interessant, intellektuell herausforderng und ein wesentlicher Teil der Informatik.\n", + "Algorithmen auszuführen ist langweilig aber sie zu entwerfen ist interessant, intellektuell herausfordernd und ein wesentlicher Teil der Informatik.\n", "\n", "Einige Dinge die Menschen natürlicherweise tun - ohne Schwierigkeiten oder bewusst einen Gedanken daran zu verschwenden - gehören zu den am schwersten repräsentierbaren Algorithmen. Sprachverstehen ist ein gutes Beispiel. Wir alle machen das ständig aber noch niemand konnte richtig erklären *wie* wir das machen - zumindest nicht in Form eines Algorithmus.\n" ] @@ -507,11 +528,11 @@ "source": [ "### 7.7 Debugging\n", "\n", - "Sobald Sie größere Programme schreiben werden Sie bemerken, dass Sie mehr Zeit mit Debuggen verbringen. Mehr Programmcode bedeutet halt auch dass es mehr Möglichkeiten gibt, einen Fehler zu machen und mehr Stellen, an denen sich \"Bugs\" verstecken können.\n", + "Sobald Sie größere Programme schreiben werden Sie bemerken, dass Sie mehr Zeit mit Debuggen verbringen. Mehr Programmcode bedeutet halt auch, dass es mehr Möglichkeiten gibt, einen Fehler zu machen und mehr Stellen, an denen sich \"Bugs\" verstecken können.\n", "\n", "Eine Möglichkeit die Zeit für das Debuggen zu reduzieren ist \"Debugging durch Halbieren\" (binäre Suche). Wenn Ihr Programm beispielsweise 100 Zeilen hat und Sie jede Zeile einzeln prüfen würden, dann bräuchten Sie 100 Schritte zum Debuggen.\n", "\n", - "Stattdessen können Sie versuchen, das Problem zu halbieren. Gehen Sie (ungefähr) zur Hälfte des Programms und suchen Sie dort nach einem Zwischenwert (eine Variable), den Sie überprüfen können. Fügen Sie eine `print`-Anweisung (oder etwas anderes, was einen prüfbare Auswirkung hat) hinzu und starten Sie das Programm.\n", + "Stattdessen können Sie versuchen, das Problem zu halbieren. Gehen Sie (ungefähr) zur Hälfte des Programms und suchen Sie dort nach einem Zwischenwert (eine Variable), den Sie überprüfen können. Fügen Sie eine `print`-Anweisung, die den Zwischenwert ausgibt (oder etwas anderes, was einen prüfbare Auswirkung hat), hinzu und starten Sie das Programm.\n", "\n", "Wenn diese Überprüfung in der Mitte das falsche Ergebnis ausgibt, muss das Problem in der ersten Hälfte des Programms liegen, ansonsten in der zweiten Hälfte.\n", "\n", @@ -585,7 +606,7 @@ "9.0 3.0 3.0 0.0\n", "```\n", "\n", - "Dabei ist die erste Spalte eine Zahl; `a`, die zweite Spalte ist die Quadratwurzel von `a` die mit `mysqrt` berechnet wurde; die dritte Spalte ist die Quadratwurzel, die mittels `math.sqrt` berechnet wurde; und die vierte Spalte ist der Absolutbetrag des Unterschieds zwischen den beiden Werten." + "Dabei ist die erste Spalte eine Zahl, `a`; die zweite Spalte ist die Quadratwurzel von `a` die mit `mysqrt` berechnet wurde; die dritte Spalte ist die Quadratwurzel, die mittels `math.sqrt` berechnet wurde; und die vierte Spalte ist der Absolutbetrag des Unterschieds zwischen den beiden Werten." ] }, {