Dr. Annette Bieniusa Mathias Weber, M. Sc. Peter Zeller, M. Sc. TU Kaiserslautern Fachbereich Informatik AG Softwaretechnik Übungsblatt 12: Software-Entwicklung 1 (WS 2015/16) Ausgabe: in der Woche vom 25.01. bis zum 29.01.16 Abgabe: in der Woche vom 31.01. bis zum 06.02.16 Aufgabe 1 Mengen, Equals und HashCode (Präsenzaufgabe) In dieser Aufgabe sollen Sie die Klasse SortedBinTree<T> (siehe Blatt 7 und Blatt 11) weiterentwickeln. a) Passen Sie die Implementierung so an, dass die add-Methode ein Element nicht noch einmal einfügt, wenn schon ein gleiches Element im Baum existiert. b) Überschreiben Sie die equals-Methode für die Klasse SortedBinTree. Zwei Objekte vom Typ SortedBinTree sollen als gleich gelten, wenn Sie genau die gleichen Elemente enthalten. Die Reihenfolge, in der die Elemente eingefügt worden sind, soll keinen Einfluss auf die Gleichheit haben. Hinweis: Schreiben Sie zuerst eine Methode private boolean equalsTree(SortedBinTree<T> t). c) Überschreiben Sie die hashCode-Methode für die Klasse SortedBinTree. Dabei soll der hashCode die Summe der hashCodes der Elemente im Baum sein. d) Passen Sie die Klasse so an, dass sie das folgende Interface für den Abstrakten Datentyp “Menge” implementiert: public interface Set <T> { void add(T t); boolean contains (T t); } e) (Freiwillige Zusatzaufgabe) Implementieren Sie noch eine Methode void remove(T t) für die Klasse SortedBinTree, welche ein Element aus dem Baum entfernt. Aufgabe 2 Multiset (Einreichaufgabe, 22 Punkte) Verwenden Sie für diese Aufgabe keine Implementierungen aus der Java-Standardbibliothek. Eine Multimenge (Multiset) ist ein abstrakter Datentyp ähnlich einer Menge. Im Gegensatz zu einer Menge kann ein Element aber mehrmals in der Multimenge vorkommen. Wir beschreiben ein Multiset durch das folgende Interface: public interface Multiset <T> { /** fuegt ein Element in die Multimenge ein */ void add(T elem); /** entfernt ein Vorkommen des Elements aus der Multimenge */ void remove (T elem); /** gibt an , wie oft ein Element in der Multimenge vorkommt */ int count (T elem); } a) Schreiben Sie eine Klasse HashMultiset<T>, welche das Interface Multiset<T> implementiert. Verwenden Sie für die Implementierung eine Hashtabelle mit statischer Größe und einer Kollisionsauflösung durch Verkettung, wie in der Vorlesung vorgestellt. Es soll nicht möglich sein, null in die Multimenge einzufügen. b) Überschreiben Sie die equals-Methode für HashMultiset. Zwei Multimengen gelten als gleich, wenn sie die gleichen Elemente mit jeweils gleicher Häufigkeit enthalten. c) Überschreiben Sie die hashCode-Methode für HashMultiset. Der Wert soll durch folgende Summe über P alle Elemente in der Multimenge berechnet werden: count(e) · h(e). e d) Verwenden Sie Ihre Implementierung von HashMultiset um folgende Aufgabe zu lösen: Schreiben Sie eine Prozedur boolean isPermutation(Object[] a, Object[] b), welche zwei Object-Arrays nimmt und prüft, ob beide Arrays die gleichen Objekte mit der gleichen Häufigkeit enthalten. Aufgabe 3 Index erstellen (Einreichaufgabe, 14 Punkte) In Büchern findet man oft einen “Index” am Ende des Buchs. Der Index ist eine alphabetisch sortierte Liste von Schlüsselwörtern, in der zu jedem Schlüsselwort die Seiten-Nummern stehen, auf denen das Schlüsselwort vorkommt. a) Schreiben Sie ein Programm, welches einen solchen Index erstellt, dabei jedoch die Zeilen-Nummer statt der Seiten-Nummer angibt. Zum Lesen von Dateien und aufspalten in Wörter können Sie die Prozedur getWords verwenden, welche Sie in den Vorlagen zu dieser Übung finden. Diese Prozedur nimmt einen Dateinamen und liefert eine Liste von Wörtern, welche durch folgende Klasse dargestellt werden: public class Word { private int lineNumber ; private String word; // Getter und Konstruktor } Ihr Programm soll einen Dateinamen als Programm-Parameter nehmen und dann den Index berechnen und alphabetisch sortiert ausgeben. 1 2 3 Beispiel-Eingabe Ausgabe (Index) Häufig finden sich in Stichwortverzeichnissen auch Verweisungen auf andere Begriffe . Verweisungen auf synonyme Begriffe erleichtern dem Nutzer das Finden . andere [2] auch [1] auf [2, 3] Begriffe [2, 3] das [3] dem [3] erleichtern [3] finden [1, 3] Häufig [1] in [1] Nutzer [3] sich [1] Stichwortverzeichnissen [1] synonyme [3] Verweisungen [2] b) Schreiben Sie mindestens 3 JUnit Testfälle für Ihr Programm. Achten Sie darauf, dass Ihr Programm gut testbar ist. Das heisst zum Beispiel, dass Testfälle keine externen Dateien lesen müssen. Aufgabe 4 Anweisungen und Variablen (Einreichaufgabe, 8 Punkte) Diese Aufgabe ist eine Fortsetzung der Präsenzaufgabe 3 (“Mathematische Ausdrücke”) von Blatt 8 und der Fallstudie aus Kapitel 17. Dort haben wir bereits betrachtet, wie man mathematische Ausdrücke durch Klassen darstellen und rekursiv auswerten kann. In dieser Aufgabe betrachten wir zwei Erweiterungen. Dazu können Sie sich als Vorlage einen erweiterten Parser herunterladen, der die Eingaben in entsprechende Objekte umwandelt, mit denen Sie arbeiten sollen. Insbesondere bietet die Klasse Parser die folgenden drei Methoden zum Parsen: public static Anweisung parseAnweisung ( String input) public static Ausdruck parseAusdruck ( String input) public static Programm parseProgramm ( String input) a) Als erste Erweiterung betrachten wir Variablen in den Ausdrücken (Klasse Variable). Um einen Ausdruck auszuwerten benötigen wir nun noch die Werte für die Variablen, die in einem Ausdruck vorkommen. Wir verwenden eine Map<String, Integer>, um die Werte zu speichern. Diese Variablen-Zuweisungen werden oft auch Variablen-Umgebung (oder im Englischen “Environment”) genannt. Wie der Typ schon zeigt, betrachten wir nur Variablen in denen ganze Zahlen (Integer) gespeichert werden. Das folgende Beispiel zeigt, wie der Ausdruck “x + y” mit x = 5 und y = 3 ausgewertet werden kann: Map <String , Integer > env = new HashMap <>(); env.put("x", 5); env.put("y", 3); assertEquals (8, Parser . parseAusdruck ("x+y"). evaluate (env)); Implementieren Sie eine Methode int evaluate(Map<String, Integer> env) für alle Subklassen von Ausdruck. b) Als zweite Erweiterung betrachten wir nun neben Ausdrücken auch einfache Anweisungen. Eine Anweisung kann hier eine Zuweisung oder eine Print-Anweisung sein. Die Bedeutung der Anweisungen ist hier analog zu Java definiert. Eine Zuweisung (zum Beispiel x = y + 1;) verändert wie in Java den Wert einer Variablen. Eine Print-Anweisung (zum Beispiel StdOut.println(x+y);) gibt den Wert eines Ausdrucks aus. Ein Programm besteht dann aus einer Liste von Anweisungen. Wir nehmen an, dass alle verwendeten Variablen mit dem Typ int deklariert sind und im ganzen Programm gültig und sichtbar sind. Zum Beispiel kann ein Programm wie folgt aussehen: x = 5; y = 3; StdOut . println (x+y); Schreiben Sie nun auch eine Methode void evaluate(Map<String, Integer> env) für die die Subklassen von Anweisung. Beachten Sie, dass beim Auswerten einer Zuweisung Einträge in der Map env geändert werden müssen, wie das folgende Beispiel für eine Zuweisung zeigt: Map <String , Integer > env = new HashMap <>(); Parser . parseAnweisung ("x = 3+4;"). evaluate (env); assertEquals (7, (int) env.get("x")); Schreiben Sie dann noch eine Methode void evaluate() für die Klasse Programm, welche, startend mit einer leeren Map als Variablen-Umgebung, die Anweisungen nacheinander auswertet. Sie können die IterpreterMain-Klasse aus der Vorlage verwenden, um Ihre Methoden auszuprobieren. Die main-Methode liest entweder ein Programm aus einer Datei, wenn ein Dateiname als ProgrammParameter gegeben wurde oder ansonsten ein Programm von der Standard-Eingabe.