TU Kaiserslautern - AG Softwaretechnik

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.