Programmierkurs Aufgaben und Anleitung Lucas Mann 19.04.2017 1 Wiederholung Wir wiederholen kurz, wie man in Python Algorithmen schreiben und ausführen kann. 1.1 Programmstruktur Ein Algorithmus (oft auch als Funktion bezeichnet) wird folgendermaßen definiert: def mein_algorithmus(a, b): Schritt1 Schritt2 ... Dabei ist mein_algorithmus der Name des Algorithmus und a und b sind die Eingabewerte (auch Parameter genannt). Natürlich kann dein Algorithmus auch mehr (oder weniger) Eingabewerte haben. Die folgende Tabelle zeigt, wie die grundlegenden Schritte eines Algorithmus aus der Umgangssprache in Python übersetzt werden: Umgangssprache Python Eingabe: a, b. Kein extra Schritt, da in der Algorithmusdefinition enthalten (s.o.) Ausgabe: r . Wenn a > 2: Schritt1 ... Sonst, wenn a < 0: ... Sonst: ... Wiederhole solange, wie n > 0: Schritt1 ... Für k im Bereich(1, n): ... Setze a = 0 Erhöhe x um n Wende Algorithmus “alg” auf die Werte n und 3 an. Speichere das Ergebnis des Algorithmus “alg”, angewendet auf a, in r . Ausgabe: Ergebnis des Algorithmus “abc”. 1 return r if a > 2: Schritt1 ... elif a < 0: ... else: ... while n > 0: Schritt1 ... for k in range(1, n + 1): ... a = 0 x = x + n oder x += n alg(n, 3) r = alg(a) return abc() 1.2 Konstanten In Python gibt es verschiedene Arten von Werten, zum Beispiel Zahlen, Wahrheitswerte und Texte. Die nächste Tabelle zeigt, wie du verschiedene Arten von Werten im Code eintippst: Werttyp Ganze Zahl Reelle Zahl Wahrheitswert Text Zeichen Liste Beispielwerte 0, 5, −14 3,1415 “Wahr”, “Falsch” “Hello World” “d” [ 1, 2, “Wahr”, 5 ] In Python 0, 5, -14 3.1415 True, False "Hello World" ’d’ [1, 2, True, 5] 1.3 Operatoren Operatoren berechnen einen neuen Wert aus einem oder zwei vorhandenen Werten. Bei diesen Werten kann es sich um Zahlen, Wahrheitswerte und Texte handeln, wobei abhängig vom Werttyp unterschiedliche Operatoren möglich sind. Wir fassen im Folgenden einiger dieser Operatoren zusammen: Operator +, −, ·, / Ganzzahldivision In Python a+b, a-b, . . . Modulo Potenz Minimum, Maximum a % b a ** b min(a, b), max(a, b) Betrag Gleich, ungleich abs(a) a == b, a != b Zahlvergleiche a < b, a >= b, a // b Beschreibung Grundrechenarten Wie Division, aber Nachkommastellen des Ergebnisses werden abgeschnitten Berechnet a mod b Berechnet a b Berechnet den kleineren bzw. größeren Wert zweier Zahlen; kann auch auf eine Liste angewendet werden, z.B. min(l) Berechnet |a| Überprüft, ob a und b den gleichen Wert haben Vergleicht die Größe zweier Zahlwerte ... Und, oder, nicht Listenzugehörigkeit a and b, a or b, not a x in l Verknüpft zwei Wahrheitswerte Ist “Wahr”, wenn x in der Liste l ist Zum Beispiel berechnet if (a > 0 and b < 0) or (a < 0 and b > 0): ob a und b verschiedene Vorzeichen haben; in diesem Fall bekommt obiger Ausdruck den Wert “Wahr”, sonst “Falsch”. 1.4 Listen Eine Liste ist eine endliche Folge von Werten, wobei die Werte innerhalb einer Liste auch verschiedene Typen haben können (in den meisten Fällen hat man aber eine Liste von lauter gleichen Typen, zum Beispiel alles Zahlen). Listen werden mit eckigen Klammern geschrieben, also zum Beispiel l1 = [1, 1, 2, 3, 5, 8] l2 = ["Hallo", True, 1, -3.2] l3 = [] # leere Liste 2 Die folgende Tabelle zeigt ein paar wichtige Operationen mit Listen: Umgangssprache Setze den 5-ten Eintrag der Liste l auf „Wahr“ Setze den a-ten Eintrag der Liste l auf den Wert in k Lies den 3-ten Eintrag der Liste l und speichere ihn in r Nimm die Unterliste von l1 der Indizes im Bereich von a bis b − 1 und speichere sie in l2 Nimm die Unterliste von l1 der Indizes von a bis zum Ende und speichere sie in l2 Hänge den Wert 3,1414 hinten an die Liste l an Hänge die Listen l1 und l2 aneinander und speichere das Ergebnis in l3 Sortiere die Liste l aufsteigend Invertiere die Reihenfolge der Elemente in l Speichere die Länge der Liste l in x Python l[5] = True l[a] = k r = l[3] l2 = l1[a:b] l2 = l1[a:] l.append(3.1415) l3 = l1 + l2 l.sort() l.reverse() x = len(l) Es ist dabei wichtig zu wissen, dass die Nummern der Einträge immer bei 0 anfangen zu zählen. In obiger Liste l2 ist also l2[1] = True und l2[0] = "Hallo", ferner ist l1[2:4] = [2, 3] und l2[1:] = [True, 1, -3.2]. Beachte, dass Texte sehr ähnlich zu Listen sind: Ein Text ist im Wesentlichen eine Liste von Zeichen. Daher funktionieren einige der Operationen von Listen auch mit Texten. Ist zum Beispiel a = "Hello World!", so ist a[4] = ’o’ und a[2:8] = "llo Wo". 1.5 Beispiel Es folgt ein Beispiel für einen Algorithmus in Umgangsprache und seine Übersetzung in Python: 1. Eingabe: Eine ganze Zahl n. 2. Setze n = |n|. 3. Setze r = [] (eine leere Liste). def number_to_digits(n): n = abs(n) r = [] while n > 0: r.append(n % 10) n = n // 10 return r 4. Wiederhole die folgenden Schritte solange, wie n > 0: 4.1. Hänge an r den Wert n mod 10 an. 4.2. Teile n durch 10 und schneide alles hinter dem Komma ab. 5. Ausgabe: r . Implementiere obigen Algorithmus wie folgt in Python: 1. Tippe obigen Code in eine Textdatei und gib dieser Datei den Namen digits.py. 2. Öffne ein Terminal, gehe dort mittels cd Pfad zu dem Ordner, in dem sich die Codedatei befindet. 3. Tippe python3 -i digits.py ein, um Python zu starten und deine Codedatei zu laden. 4. Nun kannst du alle Algorithmen in der Codedatei benutzen. Tippe zum Beispiel number_to_digits(143) ein; es erscheint das Ergebnis [3, 4, 1]. 3 2 Spielereien mit Ziffern und Listen Nachdem du das Beispiel aus dem letzten Kapitel implementiert hast, hast du einen Algorithmus number_to_digits in der Datei digits.py, welcher zu einer ganzen Zahl die Liste ihrer Ziffern bestimmt. Ausgehend von diesem Beispiel wollen wir den Umgang mit Listen noch ein bisschen üben. Löse dafür die folgenden Aufgaben, indem du weitere Algorithmen zu der Datei digits.py hinzufügst, die natürlich den Algorithmus number_to_digits verwenden dürfen. Aufgabe 1. Implementiere die folgenden Algorithmen: (a) num_digits(n): Gibt die Anzahl der Ziffern der ganzen Zahl n zurück. (b) cross_sum(n): Berechnet die Quersumme der ganzen Zahl n. Benutze dafür zunächst eine Schleife. Als zweite Variante kannst du die Operation sum(l) benutzen, welche die Summe der Elemente einer Liste berechnet. (c) even_digits(n) Gibt “Wahr” zurück, wenn die ganze Zahl n nur gerade Ziffern besitzt, sonst ”Falsch“. (d) is_palindrom(n) Bestimmt, ob die ganze Zahl n ein Palindrom ist, also ob sie sich nicht ändert, wenn man die Reihenfolge ihrer Ziffern umkehrt (zum Beispiel ist 43834 ein Palindrom). Aufgabe 2. Schreibe den Algorithmus digits_to_number. Dieser nimmt eine Liste von Ziffern und berechnet die zugehörige ganze Zahl. Mit dessen Hilfe kannst du die folgenden Algorithmen implementieren: (a) invert_digits(n): Berechnet diejenige Zahl, die beim Umkehren der Reihenfolge der Ziffern von n entsteht. Für n = 524 ergibt sich also das Ergebnis 425. (b) sort_digits(n): Sortiert die Ziffern der Zahl n absteigend und gibt die resultierende Zahl zurück. Für n = 85248 ergibt sich also 88542. Aufgabe 3. Sei k eine positive ganze Zahl. Ein k-stelliger „Kaprikar-Schritt“ transformiert eine k-stellige Zahl n wie folgt: Berechne die beiden Zahlen, die durch Sortieren der Ziffern von n in aufsteigender bzw. absteigender Reihenfolge entstehen und subtrahiere sie voneinander. Dadurch entsteht eine neue Zahl n 0 . Für vierstellige Zahlen gilt: Wiederholt man diesen Prozess oft genug, so landet man immer bei der Zahl 6174, der sogenannten Kaprikar-Konstante. (a) Implementiere einen Algorithmus kaprikar_step(n, num_digits), welcher eine Zahl n und eine Anzahl an Ziffern nimmt und dann den Kaprikar-Schritt ausführt. Der Parameter num_digits ist notwendig, da n auch weniger als die gewünschte Anzahl an Ziffern haben darf – in diesem Fall sind die verbleibenden Ziffern mit 0 aufzufüllen. Zum Beispiel ist die Zahl 728 im vierstelligen Modus als 0728 zu lesen. Hinweis: Du musst als erstes den Algorithmus number_to_digits anpassen, sodass diese Funktion einen zweiten Parameter num_digits nimmt, der die Anzahl der zu berechnenden Ziffern angibt. Benenne den neuen Algorithmus am besten um, damit der alte verfügbar bleibt. (b) Implementiere den Algorithmus kaprikar(n, num_digits), welcher den KaprikarSchritt auf die Startzahl n so lange anwendet, bis n sich nicht mehr ändert und den dann erhaltenen Wert zurückgibt. Teste für verschiedene Zahlen n, ob man tatsächlich immer irgendwann 6174 erhält. 4 3 Berechnung von π Das Ziel dieses Abschnittes ist es, die bekannte Kreiszahl π zu berechnen. Dazu werden wir uns verschiedene Methoden ansehen. Implementiere die folgenden Algorithmen in der Datei pi.py. Aufgabe 4. Die sogenannte Monte-Carlo-Methode benutzt Wahrscheinlichkeitstheorie, um π auszurechnen. Betrachte einen Viertelkreis mit Radius 1 innerhalb eines Quadrates mit Seitenlänge 1. Ist P ein zufälliger Punkt in dem Quadrat, dann ist die Wahrscheinlichkeit dafür, dass P im Viertelkreis liegt, genau π4 . Das Ziel ist es nun, diese Wahrscheinlichkeit empirisch zu berechnen, indem man sehr viele Punkte zufällig generiert und dann die Anzahl der Punkte im Kreis durch die Anzahl aller Punkte berechnet. Schreibe einen Algorithmus pi_montecarlo(n), welcher die oben beschriebene Wahrscheinlichkeitsberechnung mit n zufälligen Punkten durchführt und die daraus gewonnene Näherung für π zurückgibt. Zum Generieren von Zufallszahlen kannst du das Modul random benutzen: Schreibe dazu an den Anfang der Datei die Zeile from random import random. Nun kannst du mit random() eine Zufallszahl zwischen 0 und 1 erzeugen. Teste den Algorithmus für große n – Wie gut ist die Näherung? Aufgabe 5. Mithilfe elementarer Analysis kann man zeigen: µ ¶ 1 1 1 1 1 π = 4· 1− + − + − ±... 3 5 7 9 11 Schreibe einen Algorithmus pi_analysis(n), welcher die ersten n Additionen/Subtraktionen dieser Formel ausrechnet und somit eine Näherung für π bestimmt. Teste die Formel für große n – wie gut ist die Näherung? Aufgabe 6. Ein wesentlich besserer, aber leider auch komplizierter Weg zur Berechnung von π ist folgendermaßen gegeben: Als erstes berechnet man zwei Zahlenfolgen (a n ) und (b n ) durch folgende rekursive Vorschrift: a 0 = 1, ak = a k−1 + b k−1 , 2 p 0.5, p b k = a k−1 · b k−1 , b0 = für k ≥ 1. Zu gegebenem n berechnen wir nun s n = 20 (a 0 − b 0 )2 + 21 (a 1 − b 1 )2 + 22 (a 2 − b 2 )2 + 23 (a 3 − b 3 )2 + · · · + 2n (a n − b n )2 . Dann ist eine gute Näherung für π gegeben durch π≈ (a n + b n )2 , 1 − sn wobei die Näherung besser wird, je größer man n wählt. Implementiere den Algorithmus pi(n), welcher eine Näherung für π nach diesem Verfahren berechnet. Berechne pi(4) und vergleiche die Genauigkeit (und Geschwindigkeit) dieser Rechnung mit pi_analysis(10000). Was fällt auf? Tipp: Um die Wurzel aus einer Zahl x zu berechnen, kannst du x**.5 benutzen. Bemerkung: Dieser Algorithmus ist so schnell, dass man sehr leicht an die Rechengenauigkeit von Python herankommt. In der Tat lässt sich aufgrund dieser Rechengenauigkeit kein genauerer Wert als pi(4) bestimmen. Natürlich gibt es Methoden, die Rechengenauigkeit zu erhöhen. Bei Interesse bitte nachfragen. 5 4 Verschlüsselung In diesem Abschnitt beschäftigen wir uns mit einfachen Verschlüsselungsalgorithmen: der CäsarVerschlüsselung und der Vigenère-Verschlüsselung. Implementiere die folgenden Algorithmen in einer Datei encryption.py. Aufgabe 7. Die Verschlüsselungen ordnen den Zeichen des Alphabets üblicherweise Zahlwerte zu: „A“ ist 0, „B“ ist 1, „C“ ist 2 und so weiter. Wir brauchen daher als erstes zwei Funktionen zum Umwandeln eines Zeichens in den zugehörigen Zahlwert und umgekehrt. Dafür kann man die folgenden Funktionen in Python verwenden:1 Python-Code ord(c) chr(n) c = c.upper() c.isalpha() Bedeutung Konvertiert das Zeichen c in den zugehörigen ASCII-Wert Konvertiert den ASCII-Wert n in ein Zeichen Konvertiert das Zeichen c in den zugehörigen Großbuchstaben Überprüft, ob es sich bei c um einen Buchstaben handelt Schreibe die folgenden Algorithmen: (a) Der Algorithmus c2n(c) konvertiert den Buchstaben c in den zugehörigen Zahlwert. Beispielsweise sollen die folgenden Konvertierungen stattfinden: „A“ 0, „a“ 0, „B“ 1, „b“ 1, „Z“ 25, „z“ 25. Falls c kein Buchstabe ist, spielt der Rückgabewert keine Rolle. (b) Der Algorithmus n2c(n) konvertiert den Zahlwert n in den zugehörigen Großbuchstaben. Zum Beispiel: 0 „A“, 1 „B“, 25 „Z“. Aufgabe 8. Die Cäsar-Verschlüsselung funktioniert folgendermaßen: Der Schlüssel k ist ein Zahlwert zwischen 0 und 25. Bei der Verschlüsselung wird auf jeden Buchstaben des Klartextes der Schlüssel k addiert (modulo 26) und der sich ergebende Buchstabe in den Kryptotext übernommen. Bei der Entschlüsselung wird der Schlüssel k zeichenweise subtrahiert. (a) Schreibe den Algorithmus caesar_encrypt(text, key), welcher den Text text mit dem Schlüssel key verschlüsselt und das Ergebnis zurückgibt. Enthält der Text Zeichen, die keine Buchstaben sind, so werden diese Zeichen einfach ignoriert. Ferner gibt es keine Unterscheidung von Groß- und Kleinbuchstaben. Hinweis: Python sieht die Umlaute ebenfalls als Buchstaben an (sinnvollerweise), doch unsere Konvertierung c2n funktioniert nicht mit Umlauten. Probiere daher am besten nur Texte ohne Umlaute. (b) Schreibe den Algorithmus caesar_decrypt(text, key), welcher den Text text mit dem Schlüssel key entschlüsselt. (c) Wie sicher ist die Cäsar-Verschlüsselung? Lasse von einem Mitschüler einen Text verschlüsseln und versuche den verschlüsselten Text ohne Kenntnis des Schlüssels zu knacken. Aufgabe 9. Die Vigenère-Verschlüsselung funktioniert folgendermaßen: Der Schlüssel k ist ein Text. Dieser Text wird wiederholend unter den Klartext geschrieben. Ein Zeichen des Kryptotextes ergibt sich als Summe (modulo 26) des Klartextzeichens und des darunter befindlichen Schlüsselzeichens. 1 Der ASCII-Wert eines Zeichens ist eine eindeutige Zahl, die jedem Zeichen zugeordnet ist. Der ASCII-Wert von „A“ ist zum Beispiel 65, der von „B“ ist 66 und so weiter. Bei den Kleinbuchstaben ist es ähnlich: Der ASCII-Wert von „a“ ist 97, der von „b“ ist 98 und so weiter. 6 (a) Schreibe den Algorithmus vigenere_encrypt(text, key), welcher den Text text mit dem Schlüssel key verschlüsselt. Der Schlüssel ist hierbei ein Text, welcher nur Buchstaben enthält. (b) Schreibe den Algorithmus vigenere_decrypt(text, key), welcher den Text text mit dem Schlüssel key entschlüsselt. (c) Wie sicher ist die Vigenère-Verschlüsselung? Zusatz: Schreibe einen Algorithmus vigenere_hack(text, key_len), welcher den Text text entschlüsselt, indem er nur die Länge des Schlüssels verwendet, nicht den Schlüssel selbst. Diese Aufgabe ist deutlich schwieriger als die anderen Aufgaben und kann am besten von dir zu Hause probiert werden. 5 Die Türme von Hanoi „Die Türme von Hanoi“ ist der Name des folgenden Rätsels: Gegeben sind drei Lagerplätze für Bausteine. Auf dem ersten Lagerplatz befinden sich zu Beginn n Bausteine, der Größe nach geordnet (der größte unten), die anderen beiden Lagerplätze sind leer. Die Aufgabe ist nun, alle Bausteine vom ersten auf den dritten Lagerplatz zu bewegen, indem man nacheinander einzelne Bausteine umstapelt. Dabei müssen jedoch zu jedem Zeitpunkt alle Bausteine auf allen Lagerplätzen der Größe nach geordnet sein. Schreibe die folgenden Algorithmen in der Datei hanoi.py. Aufgabe 10. Schreibe einen Algorithmus hanoi(n), der das Hanoi-Problem zu gegebener Bausteinzahl n löst. Gehe dazu wie folgt vor. Implementiere rekursiv den Algorithmus hanoi_move(x, y, z, k), welcher eine Lösung zu folgendem Problem findet: Bewege die oberen k Steine von Platz x nach Platz z und benutze dabei eventuell als Zwischenlager den Platz y. Das eigentliche Hanoi-Problem wird dann durch den Aufruf hanoi_move(1, 2, 3, n) gelöst. Tipp: Im Fall k = 1 ist hanoi_move(x, y, z, k) einfach. Verwende die Python-Funktion print, um einen Text auszugeben; hierbei kannst du den Text-Operator % benutzen, um Werte in den Text einzusetzen, zum Beispiel: print("Bewege von {} nach {}"% (2, 3)). Im Fall k > 2 kannst du das Problem mittels Rekursion lösen; in diesem Fall sind drei Aufrufe von hanoi_move nötig. 6 Analytische Geometrie In diesem Abschntit wollen wir Python ein wenig analytische Geometrie beibringen. Dazu betrachten wir die folgenden Arten von Objekten: Punkte. Dargestellt durch eine Liste bestehend aus zwei Zahlen. Geraden. Dargestellt durch eine Liste von einem Punkt und einem zweiten Punkt (die Richtung der Gerade). Schreibe die folgenden Algorithmen in der Datei geometry.py. Aufgabe 11. Schreibe zunächst die Algorithmen point(x, y) und line(p, v), mit denen man Punkte und Geraden erstellen kann. 7 Aufgabe 12. Schreibe Algorithmen add(p1, p2) und subtract(p1, p2), welche die Addition und Subtraktion von je zwei Punkten durchführen und das Ergebnis zurückgeben. Schreibe analog mult(p, a), welches den Punkt p mit der Zahl a multipliziert und das Ergebnis zurückgibt. Schreibe schließlich lstinline{dot(p1, p2), welches das Skalarprodukt der beiden gegebenen Punkte berechnet und zurückgibt. Aufgabe 13. Implementiere die folgenden Algorithmen: (a) midpoint(p1, p2): Berechnet den Mittelpunkt der gegebenen Punkte. (b) focus(p1, p2, p3): Berechnet den Schwerpunkt des gegebenen Dreiecks. (c) orthogonal(l1, l2): Überprüft, ob die beiden gegebenen Geraden senkrecht aufeinanderstehen. Aufgabe 14. Implementiere den Algorithmus intersect(l1, l2), welcher den Schnittpunkt der beiden gegebenen Geraden berechnet. Falls die Geraden keinen Schnittpunkt haben, wird None zurückgegeben. 8