Hasso-Plattner-Institut Potsdam Operating Systems and Middleware Group at HPI University of Potsdam, Germany
Operating Systems and Middleware Group at HPI

Programmiertechnik II

Sommersemester 2007

Dr. Martin v. Löwis

Die Prüfungen finden in den Wochen vom 30.7.-3.8. (Raum A-1.2) und vom 24.9.-28.9. (Raum A-E.7) statt. Ab sofort können Sie sich zur Prüfung anmelden. Die Online-Anmeldung ist nur noch bis zum 16.7. möglich.

Am 16.7. bietet Paul Bouche um 17:00 einen zusätzlichen Konsultationstermin zu den Übungen im Hörsaal 3 an.

Die Lehrveranstaltung vermittelt Theorie und Praxis der Programmierung von Software am Beispiel der Sprachen C, Java und Prolog.

Diskutiert werden Algorithmen und Datenstrukturen zum Sortieren und Suchen, Graphenalgorithmen, Algorithmen und Datenstrukturen zur Implementierung objekt-orientierter Sprachen sowie die deklarative Programmierung. Diese Inhalte werden im allgemeineren Kontext der Softwareproduktion eingebettet.

Die Leistungserfassung besteht aus einer mündlichen Prüfung und der semesterbegleitenden Bearbeitung der Übungsaufgaben; Abgabetermin für die erste Serie von Übungsaufgaben ist der 7. Mai. Zur Zulassung zur mündlichen Prüfung muss die Hälfte der Punkte aus den Übungsaufgaben erreicht werden; die Note wird in der mündlichen Prüfung festgelegt.

Vorlesungen

  1. Übersicht (05.05.2007 17:41:34)
  2. C (05.05.2007 17:42:56)
  3. Repräsentation von Objecten (05.05.2007 17:43:46)
  4. Freispeicherverwaltung (05.05.2007 17:44:54)
  5. Automatische Speicherverwaltung (18.05.2007 18:02:36)
  6. Polymorphie und spätes Binden (05.05.2007 17:38:08)
  7. Weitere Konzepte (09.05.2007 08:55:01)
  8. Analyse von Algorithmen (22.05.2007 20:01:15)
  9. Datentypen in Java (18.05.2007 17:53:36)
  10. Modultests (18.06.2007 13:50:25)
  11. Sortieren: Einfache Algorithmen (18.06.2007 13:51:10)
  12. Sortieren: Quicksort und Mergesort (13.07.2007 13:15:24)
  13. Sortieren: Priority Queues and Heapsort (18.06.2007 13:51:32)
  14. Sortieren: Radixsort (19.06.2007 18:35:11)
  15. Code-Durchsicht (19.06.2007 20:26:36)
  16. Prolog (08.07.2007 14:34:10)
  17. Bäume (17.07.2007 18:07:29)
  18. Hash-Tabellen (17.07.2007 18:08:30)
  19. X.509: Eine Einführung (17.07.2007 18:08:04)
  20. Graph-Algorithmen (16.07.2007 13:20:05)

Praktikumsaufgaben

1. Aufgabe

Abgabetermin: 7. Mai (17:00 GMT)

Punktzahl: 20P

  1. Machen Sie sich mit dem Editor /usr/bin/vim vertraut
  2. Richten Sie in Ihrem Browser ein exportierbares Zertifikat ein
  3. Melden Sie sich beim Abgabesystem für die Übungsaufgaben an
  4. Implementieren Sie eine Freispeicherverwaltung mit der Schnittstelle
        /* allocate.h */
        void* allocate();
        void deallocate(void *data);
      
    die den Speicher aus einem festen Speicherblock entsprechend der Deklarationen
        #define BLOCKSIZE 40
        #define NUM_BLOCKS 1024
        extern unsigned char arena[BLOCKSIZE*NUM_BLOCKS];
        extern unsigned short allocated_map[NUM_BLOCKS/16];
      
    erhält; verwenden Sie zu Ihrer Lösung die Bibliothek libarena. Die Funktion allocate() alloziert Blöcke fester Größe (BLOCKSIZE); die Funktion deallocate() gibt diese Blöcke wieder frei. Falls alle Blöcke alloziert sind, gibt allocate() als Ergebnis 0. Das Feld allocated_map speichert mit einem Bit pro Block, welche Blöcke alloziert sind.
  5. Senden Sie Ihre Lösung in Form eines einzelen gzip-komprimierten Tarfiles ein. Dieses sollte im Wurzelverzeichnis ein Makefile haben, mit zwei Zielen "liballocate.a" und "testapp" (basierend auf testapp.c). Gehen Sie in Ihrer Lösung davon aus, dass die Makefile-Variable LIBARENA das Verzeichnis angibt, in dem sich libarena.a und arena.h befinden.

2. Aufgabe

Abgabetermin: 23. Mai (17:00 GMT)

Punktzahl: 20P

  1. Machen Sie sich mit dem Debugger /usr/bin/gdb vertraut
  2. Gegeben sei ein C++-Programm zur Manipulation, Darstellung und Berechnung von Ausdrücken. Übertragen Sie dieses Programm in ein struktur-ähnliches C-Programm; gehen Sie dazu von beiliegendem Gerüst aus.
  3. Generieren Sie mittels /usr/bin/script eine Abschrift einer Debugger-Sitzung, aus der Ausschnitte aus dem Ablauf Ihres Programms ersichtlich werden (wie etwa die Berechnung des Ausdrucks).
  4. Legen Sie die Quelldateien Ihrer Lösung in Ihrem Subversion-Repository ab und reichen Sie im Abgabesystem den URL zu Ihrer Lösung ein.

3. Aufgabe

Abgabgetermin: 6. Juni (17:00 GMT)

Punktzahl: 20P

Gegeben sind zwei Programme zur Matrizenmultiplikation, multiply.c und multiply.java. Bestimmen Sie die Komplexität der Multiplikation empirisch und theoretisch.

Zur empirischen Analyse:

  1. Ändern Sie die Programme so, dass Matrizen der Größen 10, 20, 30, 40, 50, 100, 500, 1000 Eingabe sind.
  2. Verwenden Sie einerseits das Programm /usr/bin/time zur Ermittlung der Gesamtlaufzeit, und andererseits die C-Funktion clock(3) und die Java-Methode java.lang.System.currentTimeMillis() zur Ermittlung der Laufzeit von mult.
  3. Protokollieren Sie Ihre Experimente; geben Sie im Protokoll alle für die Messung relevanten Einflussparameter an.
  4. Stellen Sie die Gesamtergebnisse tabellarisch oder graphisch dar.

Zur theoretischen Analyse bestimmen Sie die Komplexitätsklasse der verwendeten Algorithmen, und erläutern Sie Ihre Analyse. Vergleichen Sie die theoretischen und die experimentellen Ergebnisse.

Geben Sie Ihre Lösung in Form eines Tarfiles ab, das einerseits Ihre Protokolle und Analysen als Text- oder PDF-Datei enthält und andererseits Anhänge zu den Protokollen, wie etwa modifizierten Quelltextdateien.

i

4. Aufgabe

Abgabgetermin: 20. Juni (17:00 GMT)

Punktzahl: 20P

Implementieren Sie die folgende Schnittstelle, die eine abstrakte Warteschlange repräsentiert, zwei Mal:

package de.uni_potsdam.hpi;
public interface Queue{
    int capacity();
    int size();
    void clear();
    Object get() throws QueueEmpty;
    void put(Object o) throws QueueFull;
}

Eine Klasse, die die Schnittstelle Queue implementiert, soll dabei folgende Eigenschaften besitzen:

  • Der Konstruktor der Klasse erwartet die maximale Kapazität des Queue-Exemplars. Die Methode capacity() gibt diese Größe zurück.
  • Mit der Methode put(Object) wird ein Element in die Warteschlange am Ende eingefügt. Falls die Kapazität bereits erreicht ist, löst .put die Ausnahme de.uni_potsdam.hpi.QueueFull aus.
  • Die Methode get() liefert das erste Elemente der Warteschlange und entfernt es aus dieser. Sollte die Warteschlange leer sein, so löst .get die Ausnahme de.uni_potsdam.hpi.QueueEmpty aus.
  • Die Methode size() liefert die aktuelle Zahl von Elementen in der Warteschlange.
  • Die Methode clear() löscht alle Elemente aus der Warteschlange.

Achten Sie bei Ihrer Implementierung darauf, dass alle Methoden die Komplexität O(1) besitzen (vorausgesetzt, dass alle Elementaroperationen, einschließlich der Speicherallozierung, in konstanter Zeit erfolgen).

  1. Implementieren Sie Queue zum einen als einfach verkettete Liste. Zur Erreichung konstanter Zeit für alle Operationen empfiehlt es sich, eine Referenz nicht nur auf den Anfang der Liste, sondern auch auf das Ende der Liste zu führen.
  2. Implementieren Sie Queue zum anderen als Ringpuffer auf Basis eines Arrays. Für einen solchen Ringpuffer sind neben dem Array selbst zwei Integer-Variablen (etwa first und last) erforderlich, die den ersten und den letzten belegten Index anzeigen.
  3. Vergleichen Sie die Laufzeit beider Implementierungen für folgendes Anwendungsszenario: In eine Warteschlange der Kapazität 1000 werden 500 Elemente eingefügt, danach wird oft abwechselnd ein Element entfernt und der Leerstring ("") eingefügt; vergleichen Sie insbesondere die Zeit für dieses Einfügen und Entfernen.
  4. Schreiben Sie auf Basis von JUnit eine Sammlung von Testfällen für Ihre Klassen, die wenigstens die folgenden Eigenschaften testet:
    • Für eine leere Queue q gilt nach q.put(o): o == q.get()
    • Fügt man in eine Queue 6 mal den Wert null ein und danach ein von null verschiedenes Objekt o, und führt danach wiederholt get aus, so erhält man 6 mal den Wert null, und danach o.
    • Nach Aufruf von .clear() liefert .size() den Wert 0.
    • .get liefert für eine leere Queue eine Ausnahme.
    • .put liefert für eine volle Queue eine Ausnahme.

Legen Sie in Ihrem Subversion-Repository sämtliche Klassen im Quelltext sowie ein Ant-File ab, welches Ihre Klassen übersetzt und die Testsuite ausführt. Sie können für das ant-File davon ausgehen, dass sowohl junit.jar als auch ant-junit.jar im Klassenpfad aufgeführt sind.

Reichen Sie im Abgabesystem den URL zu Ihrer Lösung ein. Ihre Lösung wird in einer Folgeaufgabe zur Code-Durchsicht einigen Kommilitonen zur Verfügung gestellt. Achten Sie darauf, dass aus Ihrem Code Ihre Autorenschaft möglichst nicht hervorgeht.

5. Aufgabe

Abgabgetermin: 4. Juli (17:00 GMT)

Punktzahl: 20P

  1. Implementieren Sie eine stabile Version von Quicksort für verkettete Listen. Verwenden Sie zum Vergleichen die Annahme, dass die Elemente in der Liste java.lang.Comparable konsistent implementieren. Verzichten Sie dabei auf indexbasierte Zugriffe.
  2. Testen Sie Ihren Algorithmus, in dem Sie eine zufällige Zahl von Zufallsexemplaren des Typs java.lang.Integer generieren und diese sortieren.
  3. Entwickeln Sie eine Anwendung Ihres Sortierverfahrens, die die Standardeingabe zeilenweise sortiert und ausgibt.
  4. Führen Sie eine Code-Durchsicht der Ihnen zugeteilten Teillösung von Aufgabe 4 durch.
  5. Legen Sie Ihre Lösung in Ihrem Subversion-Repository ab, und tragen Sie im Abgabesystem einen Verweis auf die Lösung ein.

6. Aufgabe

Abgabgetermin: 17. Juli (17:00 GMT)

Punktzahl: 20P

Formulieren Sie ein Prolog-Programm, welches ein gegebenes Puzzle löst, und geben Sie Ihre Lösung im Abgabesystem ab.
Hinweis: Eine Lösung kann in Prolog beispielsweise als Liste von Nummern der Puzzlekarten und deren Orientierung dargestellt werden.