Aufgabe 1 (Terminal) ¶ Das echte Leben spielt sich leider nicht immer nur in Jupyter-Notebooks ab. Eine wichtige Kernkompetenz stellt das Bedienen einer Shell (auch Kommandozeile oder Konsole genannt) dar; eine Shell läuft in der Regel in einem sogenannten Terminal-Programm. In Jupyter können Sie ein Terminal samt Shell öffnen, indem Sie auf die Schaltfläche New klicken und dort dann Terminal auswählen. Anmerkung: Selbsverständlich kann man all diese Dinge auch auf einem lokalen Terminal außerhalb von Jupyter tun. Oder Sie melden sich über das Programm ssh auf gp1.cs.upb.de oder einem der Poolrechner an. Eine Liste der Poolräume samt Belegung finden Sie hier: https://cs.unipaderborn.de/rechnerbetrieb-irb/poolraeume/ (https://cs.uni-paderborn.de/rechnerbetriebirb/poolraeume/) ; eine Liste der Rechner ist hier: https://irb-service.cs.unipaderborn.de/poolrechner.html (https://irb-service.cs.uni-paderborn.de/poolrechner.html). (Vorzugsweise melden Sie sich auf einem Poolrechner an und nicht auf gp1.cs.upb.de. Dort funktioniert allerdings nur Ihr regulärer Account, nicht die gp1_16_... Accounts). Vorsicht: Manche Befehle variieren aber je nach Betriebssystem. a) Erstmal ein paar Aufwärmübungen mit dem Terminal. Wenden Sie jeden dieser Befehle einmal an. Wenn Sie factor eingeben, können Sie danach eine Zahl eingeben, die dann vom Terminal in Primfaktoren zerlegt wird. Der date Befehl gibt Ihnen das aktuelle Datum aus. Mithilfe von id kommen Sie an Informationen über sich selbst. Wenn Sie time vor einen Befehl schreiben, wird dessen Laufzeit gemessen. (Besonders interessant, wenn Sie selbst geschriebene Programme ausführen.) Vielleicht der wichtigste Befehl von allen: man - kurz für manual (Handbuch). man nimmt als ersten Parameter den Befehl entgegen, zu dem Sie Hilfe brauchen. Probieren Sie z.B.: man man - Das Handbuch zum Handbuch man bash- Das Handbuch zur üblichen Shell. Viele Kommandos verstehen auch optionale Parameter wie -h, -? oder --help. Probieren Sie das bei ein paar Beispielen aus. b) Nun beschäftigen wir uns mit der Navigation im Dateisystem. Sie können sich jederzeit mithilfe des Befehls ls die Liste der im aktuellen Verzeichnis liegenden Verzeichnisse und Ordner anzeigen lassen. Das aktuelle Verzeichnis selbst wird mit pwd angezeigt (print working directory). cd (change directory) ist der Befehl, mit dem man das Verzeichnis wechseln kann. (Syntax: cd Verzeichnis oder cd .. um ins übergeordnete Verzeichnis zu gelangen) Erstellen Sie (touch Dateiname) eine neue Textdatei (.txt) in einem beliebigen Ordner und bearbeiten Sie diese (nano Dateiname). Nano verlassen Sie wieder mit der Tastenkombination Ctrl+X. Nachdem Sie das getan haben, gehen Sie über die übliche Oberfläche von Jupyter in das entsprechende Verzeichnis und überprüfen, ob in der Datei tatsächlich das steht, was Sie gerade hineingeschrieben haben. Das funktioniert so im Prinzip auch mit Notebooks (.ipynb), aber tun Sie sich das nicht an (Notebooks sind sog. JSON-Dateien, die von Hand nicht komfortabel editiert werden können). Finden Sie nun mithilfe des Befehls info (eine Alternative zu man) heraus, wie Sie Dateien kopieren, umbenennen, löschen und verschieben können. Das info-Menü können sie mit der Taste Q wieder verlassen. Hinweis: Zum Umbenennen und Verschieben von Dateien können Sie denselben Befehl nutzen. Hinweis: Alternative Editoren sind etwa vi oder emacs. c) In einem Terminal kann man auch Python verwenden. Dazu ruft man zunächst den Interpreter mit dem Befehl python3 auf. Probieren Sie das einmal aus, indem Sie einige Zeilen Code eintippen. Zum Beispiel ein paar Zuweisungen, print-Befehle, oder was Ihnen sonst so in den Sinn kommt. Auf diese Art können Sie beliebig große und komplexe Programme schreiben, es gibt hier keine Einschränkungen gegenüber Editoren oder Notebooks. Auch Funktions- und Klassendefinitionen sind möglich. Sie können Ihren Code allerdings nicht speichern, sondern nur direkt ausführen. Beachten Sie aber, dass eine eingegebene Zeile nicht mehr korrigiert werden kann. Das heißt bei einem Fehler muss man schlimmstenfalls mit der gesamten Funktions- oder Klassendefinition neu beginnen. Implementieren Sie im Interpreter eine Klasse, deren Objekte einen String speichern, der im Konstruktor übergeben wird. Der Aufruf der Methode __str__ soll diesen String zurückgeben. Aufgabe 2 (Programme ausführen) a) In dieser Aufgabe soll es darum gehen, dass Sie eine Python-Datei, welche im Dateisystem vorliegt, ausführen. Dazu erstellen Sie mithilfe eines Editors (der gewöhnliche Texteditor reicht dafür bereits aus) eine .py Datei, in welche Sie lauffähigen Code hineinschreiben. Dieser Code sollte einige print()Anweisungen enthalten, da diese sich für die Veranschaulichung in diesem Fall gut eignen. Sie könnten zum Beispiel ein kurzes Programm implementieren, das einfach das kleine Einmaleins auf der Konsole ausgibt. Eventuell ist es notwendig, die Datei manuell zu *.py umzubenennen, wenn Sie ein Textdokument bearbeiten (je nach Editor). Anmerkung: Das Zeichen * wird in diesem Kontext auch Wildcard genannt, und kann für eine beliebige Zeichenfolge stehen. Die Notation *.py steht also für einen beliebigen Dateinamen mit der Endung .py Nun muss die Datei noch irgendwie ausgeführt werden. Dazu öffnen Sie nun ein Terminal (Jupyter oder lokales System, je nachdem wo Sie Ihre Datei erstellt haben), navigieren mittels cd in das entsprechende Verzeichnis und verwenden dort den Befehl python3 Dateiname.py. Wenn Sie alles richtig gemacht haben, sollte Ihr Code nun ausgeführt werden und Sie sollten die entsprechenden Ausgaben im Terminal sehen können. b) Man kann über die Kommandozeile auch Parameter an ein Programm übergeben. Diese folgen einfach durch Leerzeichen getrennt auf den Dateinamen im Programmaufruf. Im Code kann man dann mit diesen Parametern arbeiten. Alle Parameter sind in der Liste sys.argv gespeichert. Diese ist eine gewöhnliche Liste, wie Sie sie bereits kennen. Um diese verwenden zu können, müssen Sie allerdings das Modul sys importieren. Veranschaulichen Sie sich dieses Prinzip, indem Sie ein Programm schreiben, das seine Kommandozeilenargumente der Reihe nach auf der Konsole ausgibt. Dann führen Sie es mit verschiedenen Parametern aus. Verwenden Sie auch verschiedene Typen. c) Schreiben Sie ein Programm, das zwei übergebene Zahlen addiert, wenn der letzte übergebene Parameter False ist, oder diese multipliziert, falls er True ist. Das Ergebnis soll auf der Konsole ausgegeben werden. d) Man kann nicht nur Python-Programme auf diese Weise ausführen. Auch Java lässt sich per Terminal ausführen. Da Java allerdings im Gegensatz zu Python kompiliert werden muss, sind hier zwei Schritte vonnöten. Ihnen wurde mit dieser Präsenzübung eine Datei namens HelloWorld.java mitgeliefert. Diese beinhaltet ein lauffähiges Java-Programm. Zunächst müssen Sie im Terminal mittels cd in das entsprechende Verzeichnis navigieren, sofern Sie sich noch nicht dort befinden. Daraufhin können Sie die Datei kompilieren, indem Sie den Befehl javac Dateiname.java aufrufen. Wie Sie sehen, befindet sich jetzt im Ordner eine weitere Datei namens HelloWorld.class. Class-Dateien sind das ausführbare Äquivalent zu Java-Dateien und können mittels java Dateiname ausgeführt werden. Sie sollten bei Ausführung des Java-Programms die Ausgabe "Hello World" auf dem Terminal sehen. e) Sie stellen sich vermutlich die berechtigte Frage, weshalb man nicht einfach Java-Dateien ausführen kann, sondern diese erst zu einer Class-Datei kompilieren muss. Dies hat technische Gründe. Java läuft auf einer virtuellen Maschine, die nur Java-Bytecode ausführen kann. Sie können diesen betrachten, indem sie den Befehl javap -c Dateiname verwenden. Dafür muss die Datei vorher selbstverständlich kompiliert worden sein. Aufgabe 3 (Tests/Fehlersuche) Schreiben Sie umfangreiche Tests für den folgenden Code, in denen Sie auch möglichst viele Sonderfälle prüfen. Finden Sie Fehler? Können Sie einige davon korrigieren? Verwenden Sie die Funktionen print und assert um Fehler zu finden. Fällt Ihnen merkwürdiges Verhalten auf? Wenn ja, versuchen Sie herauszufinden, welche Methode nicht so arbeitet wie Sie es erwartet hätten. In [ ]: class A: """Eine Art Warteschlange für Strings""" def __init__(self, wait = [], now = ""): self.wait = wait self.now = now def new_string(self, x): """Fügt einen String zur Warteschlange hinzu""" self.wait.append(str(x)) def next_string(self): """Holt den nächsten String aus der Warteschlange, speicher t ihn als aktuellen String""" now = self.wait[0] self.wait.pop(0) def top(self): """Gibt den aktuellen String zurück""" return self.now Aufgabe 4 (Umgang mit "schwierigen" Problemen) Zur Bearbeitung dieser Aufgabe kann es hilfreich sein, online nach Lösungsansätzen zu suchen. a) Implementieren Sie eine Methode, die True zurückgibt, wenn sie am Klausurtermin von Gp1 aufgerufen wird, und ansonsten immer False. b) Implementieren Sie eine Methode, die einen String übergeben bekommt und die Summe aller UnicodeInteger-Repräsentationen der einzelnen Zeichen in diesem String berechnet. c) Erklären Sie, weshalb der Code in Aufgabe 3 ein solch merkwürdiges Verhalten an den Tag legt.