Programmierung WS14/15 C. Aschermann, F. Frohn, J. Hensel, T. Ströder Übungsblatt 9 (Abgabe 07.01.2015) Prof.aaDr. J. Giesl Allgemeine Hinweise: Die sollen in Gruppen von je aus der bearbeitet werden. der Studierenden sind auf jedes Blatt der Abgabe zu schreiben. Die muss auf das der Abgabe geschrieben werden. Notieren Sie die Gruppennummer gut sichtbar, damit wir besser sortieren können. Die Lösungen müssen in den entsprechenden Übungskasten eingeworfen werden. Sie nden die Kästen am Eingang Halifaxstr. des Informatikzentrums (Ahornstr. 55). Alternativ können Sie die Lösungen auch vor der Abgabefrist direkt bei Ihrer Tutorin/Ihrem Tutor abgeben. In einigen Aufgaben müssen Sie in Java oder Haskell programmieren und .java- bzw. .hs-Dateien anlegen. Sie diese aus schicken Sie sie per vor Mittwoch, dem 07.01.2015 um 15:00 Uhr an Ihre Tutorin/Ihren Tutor. Stellen Sie sicher, dass Ihr Programm von javac GHC wird, ansonsten werden keine Punkte vergeben. Aufgaben, die mit einem markiert sind, sind Sonderaufgaben mit erhöhtem Schwierigkeitsgrad. Sie tragen nicht zur Summe der erreichbaren Punkte bei, die für die Klausurzulassung relevant ist, jedoch werden Ihnen die in solchen Aufgaben erreichten Punkte ganz normal gutgeschrieben. Die Aufgaben, die Java betreen, zählen bzgl. der Zulassungsbedingungen zur ersten Hälfte, während die Aufgaben, die Haskell betreen, bereits zur zweiten Hälfte zählen. Hausaufgaben 2 Studierenden (Tutorium) gleichen Kleingruppenübung Namen und Matrikelnummern Heften bzw. tackern Sie die Blätter! Nummer der Übungsgruppe links oben erste Blatt bis Mittwoch, den 07.01.2015 um 15:00 Uhr Drucken und E-Mail bzw. akzeptiert ∗ Tutoraufgabe 1 (Collections): In dieser Aufgabe geht es um die Implementierung einer Datenstruktur für Mengen, welche in das bestehende Collections Framework eingebettet werden soll. Sie benötigen dafür die Klassen FunctionalSet, SimpleFunctionalSet, EmptySet, AddSet und RemoveSet, welche Sie als .java Dateien von unserer Webseite herunterladen können. Die in dieser Aufgabe zu betrachtende Mengenstruktur basiert auf einer Liste von Einfüge- (Add) und Löschoperationen (Remove) mit jeweils einem Element, die vom Ausgangspunkt einer leeren Menge (Empty) angewendet werden. Zum Beispiel lässt sich die Menge {1, 2, 3} als die Liste Add 3, Add 2, Add 1, Empty darstellen. Will man nun das Element 2 aus der Menge löschen, so entfernt man nicht das zweite Element aus der Liste, sondern fügt ein weiteres Remove Element hinzu und erhält Remove 2, Add 3, Add 2, Add 1, Empty. Auf diese Weise erhält man eine Datenstruktur, bei der niemals Objekte entfernt werden (mit Ausnahme der clear Methode, welche die Liste wieder auf Empty setzen soll). Eine solche Mengendarstellung nennt man auch funktional, da die Einfüge- und Löschoperationen als Funktionen aufgefasst werden können, welche auf die leere Menge angewendet werden. Die vorgegebene Klasse FunctionalSet implementiert bereits das Set Interface bis auf die iterator Methode. Implementieren Sie diese Methode. Implementieren Sie dazu eine weitere generische Klasse FunctionalSetIterator<E>, welche das Interface Iterator<E> aus dem Package java.util implementiert. Schlagen Sie für die zu implementierenden Methoden hasNext, next und remove die Funktionalitäten in der Java API für das Interface Iterator nach (die remove Operation soll durch Ihren Iterator unterstützt werden). Dies betrit insbesondere auch die durch diese Methoden zu werfenden Exceptions. Sie können die main Methode der Klasse FunctionalSet nutzen, um Ihre Implementierung zu testen. 1 Programmierung WS14/15 Übungsblatt 9 (Abgabe 07.01.2015) (9 + 3 + 2 + 3∗ = 14 + 3∗ Punkte) Aufgabe 2 (Collections): In dieser Aufgabe geht es ebenfalls um die Implementierung einer Datenstruktur für Mengen, welche in das bestehende Collections Framework eingebettet werden soll. Sie benötigen dafür die Klassen TrieSet und TrieNode, welche Sie als .java Dateien von unserer Webseite herunterladen können. Die in dieser Aufgabe zu betrachtende Mengenstruktur basiert auf einer Suchbaumstruktur zur Indizierung der Elemente. Die hier verwendeten Suchbäume werden genannt (von retrieval). Jedem Element der Menge wird ein Schlüssel als String zugeordnet. Die Knoten des Tries enthalten nun Teil-Strings, anhand derer die Suche durch den Baum nach einem Element gesteuert wird. Die Konkatenation der Teil-Strings entlang der besuchten Knoten im Baum ergibt den Schlüssel des gesuchten Elements. Die Wurzel enthält dabei immer den leeren String, da dieser ein Präx für jeden String ist. Folgender Trie speichert beispielsweise vier Elemente mit den Schlüsseln Bahn, Bahnfahrt, Bahnhof und Zug: foo Tries Bahn Zug Bahn Zug hof fahrt Bahnhof Bahnfahrt In diesem Beispiel ist jedes Element identisch zu seinem Schlüssel. Dies muss im Allgemeinen nicht so sein. Insbesondere kann ein Knoten in einem Trie auch mehrere verschiedene Elemente speichern, wenn deren Schlüssel identisch sind. Allerdings werden gleiche Elemente nicht mehrfach gespeichert, da es sich ja um eine Mengenstruktur handelt. Jeder Knoten enthält daher ein Attribut elements vom Typ Set. Die vorgegebene Klasse TrieSet implementiert bereits das Set Interface bis auf die Methoden iterator, retainAll und add (letztere ist eigentlich in TrieSet vollständig implementiert, aber die von ihr aufgerufene add Methode in der Klasse TrieNode ist es nicht). Sie können die main Methode der Klasse TrieSet nutzen, um Ihre Implementierungen der folgenden Aufgabenteile zu testen. Dabei können Sie auch Teile des Tests auskommentieren, falls Sie nur Teile der Aufgaben bearbeitet haben. Implementieren Sie die Methode iterator in der Klasse TrieSet. Implementieren Sie dazu eine generische Klasse TrieIterator<E>, welche das Interface Iterator<E> aus dem Package java.util implementiert. Schlagen Sie für die zu implementierenden Methoden hasNext, next und remove die Funktionalitäten in der Java API für das Interface Iterator nach (die remove Operation soll durch Ihren Iterator unterstützt werden). Dies betrit insbesondere auch die durch diese Methoden zu werfenden Exceptions. Die Klasse TrieIterator muss im gleichen Package wie TrieNode implementiert werden, damit Sie auf die Getter-Methoden der Klasse TrieNode zugreifen können. Hinweise: Denken Sie daran, dass die Set Attribute an den jeweiligen Knoten bereits implementierte Iteratoren anbieten. Sie nden Dokumentationen zu weiteren Klassen aus dem Collections Framework (wie z.B. TreeMap) ebenfalls in der Java API. Implementieren Sie die Methode retainAll in der Klasse TrieSet. Schlagen Sie deren Funktionalität ebenfalls in der Java API nach. Benutzen Sie zur Implementierung dieser Methode nicht die add oder remove Methoden der Klassen TrieSet und TrieNode. Sie dürfen jedoch die remove Methode des Iterators aus der vorigen Teilaufgabe nutzen. Überschreiben Sie die Methode public String toString() in der Klasse TrieSet, sodass diese einen String zurückliefert, der mit einer önenden geschweiften Klammer beginnt, dann die Elemente der Menge durch Kommata getrennt aufzählt und schlieÿlich mit einer schlieÿenden geschweiften Klammer a) b) c) 2 Programmierung WS14/15 Übungsblatt 9 (Abgabe 07.01.2015) ∗ d) endet. Wenn ein TrieSet also beispielsweise die Elemente 1, 2 und 3 enthält, soll der von toString zurückgelieferte String "{1, 2, 3}" sein. Implementieren Sie die Methode add in der Klasse TrieNode, sodass die vorgegebene Implementierung der add Methode in der Klasse TrieSet korrekt bzgl. der Spezikation im Set Interface ist. Achten Sie darauf, dass es zu keiner Zeit einen Knoten im Trie geben darf, der verschiedene Kindknoten hat, deren Teil-Strings aber ein gemeinsames nicht-leeres Präx haben. Sie nden nützliche Methoden zur Verarbeitung von Strings in der zugehörigen Java API. Aufgabe 3 (Klassenhierarchie∗ ): (3∗ + 3∗ + 3∗ + 3∗ = 12∗ Punkte) In dieser Aufgabe wird eine Software zum Verwalten von beliebig vielen Serverfarmen betrachtet. (1) Eine Serverfarm enthält beliebig viele Rechner. (2) Jeder Rechner hat eine eindeutige MAC-Adresse (repräsentiert durch einen String), welche für die IPVergabe verwendet wird. (3) Die meisten Rechner in einer Serverfarm sind Server. (4) Einige Rechner sind jedoch Terminals zur Kontrolle der Server. (5) Alle fernwartbaren Rechner können neu gestartet werden. (6) Alle fernwartbaren Rechner stellen eine Funktion bereit, die angibt, ob der Rechner abgestürzt ist. (7) Eine Serverfarm stellt die Funktionalität bereit, alle ihre abgestürzten, fernwartbaren Rechner neu zu starten. (8) Server und Terminals sind immer fernwartbar. Wie die Fernwartung implementiert ist, hängt von der Rechnerart ab. (9) Bei Servern ist vor allem die Anzahl der CPUs und die zur Verfügung stehende Menge an RAM interessant. (10) Servicetechniker haben kleine Diagnoserechner, welche nicht fernwartbar sind. (11) Datenbankserver sind Server, bei denen vor allem das Volumen der Festplatte relevant ist. (12) Es gibt keine sonstigen Rechner. Entwerfen Sie unter Berücksichtigung der Prinzipien der Datenkapselung eine geeignete Klassenhierarchie für die Serverfarm. Notieren Sie keine Konstruktoren, Getter und Setter. Sie müssen nicht markieren, ob Attribute nal sein sollen. Achten Sie darauf, dass gemeinsame Merkmale in Oberklassen bzw. Interfaces zusammengefasst werden. Verwenden Sie hierbei die Notation aus Aufgabe 3, Blatt 7. Welche Objekte realisieren Sie als Klasse und welche als Interface? Begründen Sie ihre Antwort. Implementieren Sie die in Punkt (6) beschriebene Funktion so, dass sie mit einer Wahrscheinlichkeit von 50% true zurückgibt. Hierzu können Sie die aus Aufgabe 2, Übungsblatt 8 bekannte Klasse Zufall benutzen. Implementieren Sie auÿerdem die Funktion void abgestuerzteNeustarten() in der Klasse ServerFarm, welche alle abgestürzten, fernwartbaren Rechner einer Serverfarm neu startet. Stellen Sie sicher, dass alle Klassen über sinnvolle Konstruktoren verfügen. Bedenken Sie dabei, dass jeweils auch der Konstruktor der Oberklasse aufgerufen werden soll. Die MAC-Adresse eines Rechners soll mit einem zufälligen String der Länge 12 initialisiert werden, der aus den Zeichen abcdef0123456789 besteht. a) b) c) 3 Programmierung WS14/15 Übungsblatt 9 (Abgabe 07.01.2015) d) Erstellen Sie eine Klasse IpVerwaltung, welche Rechnern IP-Adressen zuweist. Gehen Sie dafür davon aus, dass IP-Adressen Integer zwischen 167968768 und 168034303 sind. Implementieren Sie in dieser Klasse eine Funktion int ipZuweisen(Rechner r), welche dem Rechner r eine IP-Adresse zuweist und diese zurückgibt. Dabei soll sich die Klasse IpVerwaltung merken, welche IP-Adresse zu welcher MACAdresse gehört. Falls die Methode ipZuweisen zweimal mit dem gleichen Rechner als Argument aufgerufen wird, soll beide Male IP-Adresse zurückgeliefert werden. Verwenden Sie die Klasse HashMap (siehe http://www.java-tutorial.org/maps.html bzw. https://docs.oracle.com/javase/8/docs/ api/java/util/HashMap.html), um die Zuordnung zwischen MAC- und IP-Adressen zu verwalten. die gleiche Tutoraufgabe 4 (Auswertungsstrategie): Gegeben sei das folgende Haskell-Programm: listProd :: [ Int ] -> Int listProd [] = 1 listProd ( x : xs ) = x * listProd xs everySecond everySecond everySecond everySecond :: [ Int ] -> [ Int ] [] = [] [x] = [x] ( x : _ : xs ) = x : everySecond xs minus10 :: [ Int ] -> [ Int ] minus10 [] = [] minus10 ( x : xs ) = x - 10 : minus10 xs Die Funktion listProd multipliziert die Elemente einer Liste. Beispielsweise ergibt listProd [3,5,2,1] die Zahl 30. Die Funktion everySecond bekommt eine Liste als Eingabe und gibt die gleiche Liste zurück, wobei jedes zweite Element gelöscht wurde. So ergibt everySecond [1,2,3] die Liste [1,3]. Die Funktion minus10 gibt seine Eingabeliste zurück, wobei von jedem Element 10 subtrahiert wurde. Geben Sie alle Zwischenschritte bei der Auswertung des Ausdrucks listProd (everySecond (minus10 [3,2,1])) an. Schreiben Sie hierbei (um Platz zu sparen) p, s und m statt listProd, everySecond und minus10. Hinweise: Beachten Sie, dass Haskell eine Leftmost-Outermost Auswertungsstrategie besitzt. Allerdings sind Operatoren wie * und +, die auf eingebauten Zahlen arbeiten, strikt, d.h. hier müssen vor Anwendung des Operators seine Argumente vollständig ausgewertet worden sein (wobei zunächst das linke Argument ausgewertet wird). 4 Programmierung WS14/15 Übungsblatt 9 (Abgabe 07.01.2015) Aufgabe 5 (Auswertungsstrategie): (5 Punkte) Gegeben sei das folgende Haskell-Programm: isEmpty :: [ a ] -> Int isEmpty [] = 1 isEmpty _ = 0 removePairs :: [ Int ] -> [ Int ] removePairs ( x : y : xs ) | x == y = removePairs xs | otherwise = x : ( removePairs ( y : xs )) removePairs x = x listSum :: [ Int ] -> Int listSum [] = 0 listSum ( x : xs ) = x + ( listSum xs ) Die Funktion isEmpty gibt zu einer Eingabeliste den Wert 1 zurück, falls die Liste leer ist. Sonst gibt sie 0 zurück. Die Funktion removePairs bekommt eine Liste als Eingabe und gibt die gleiche Liste zurück, wobei von links nach rechts jeweils Paare von zwei aufeinander folgenden gleichen Elementen gelöscht wurden. So ergibt removePairs [1,2,2,2,3,3,2] die Liste [1,2,2]. Die Funktion listSum addiert die Elemente einer Liste. Beispielsweise ergibt listSum [3,5,2,1] die Zahl 11. Geben Sie alle Zwischenschritte bei der Auswertung des Ausdrucks listSum [isEmpty (removePairs [1,1]), isEmpty (removePairs [1,2,2,1])] an. Schreiben Sie hierbei (um Platz zu sparen) e, r und s statt isEmpty, removePairs und listSum. Hinweise: Beachten Sie, dass Haskell eine Leftmost-Outermost Auswertungsstrategie besitzt. Allerdings sind Operatoren wie * und +, die auf eingebauten Zahlen arbeiten, strikt, d.h. hier müssen vor Anwendung des Operators seine Argumente vollständig ausgewertet worden sein (wobei zunächst das linke Argument ausgewertet wird). Tutoraufgabe 6 (Listen in Haskell): Seien x, y, z ganze Zahlen vom Typ Int und xs und ys Listen der Längen n und m vom Typ [Int]. Welche der folgenden Gleichungen zwischen Listen sind richtig und welche nicht? Begründen Sie Ihre Antwort. Falls es sich um syntaktisch korrekte Ausdrücke handelt, geben Sie für jede linke und rechte Seite auch an, wie viele Elemente in der jeweiligen Liste enthalten sind und welchen Typ sie hat. : Die Liste [[1,2,3],[4,5]] hat den Typ [[Int]] und enthält 2 Elemente. Hinweise: Hierbei steht ++ für den Verkettungsoperator für Listen. Das Resultat von xs ++ ys ist die Liste, die entsteht, wenn die Elemente aus ys in der Reihenfolge wie sie in ys stehen an das Ende von xs angefügt werden. : [1,2] ++ [1,2,3] = [1,2,1,2,3] Falls linke und rechte Seite gleich sind, genügt Angabe des Typs und der Elementzahl. Beispiel Beispiel eine a) [] ++ [xs] = [] : [xs] b) [[]] ++ [x] = [] : [x] c) [x] ++ [y] = x : [y] d) x:y:z:(xs ++ ys) = [x,y,z] ++ xs ++ ys e) [(x:xs):[ys],[[]]] = (([]:[]):[]) ++ ([([x] ++ xs),ys]:[]) 5 Programmierung WS14/15 Übungsblatt 9 (Abgabe 07.01.2015) Aufgabe 7 (Listen in Haskell): (1,5 + 1,5 + 1,5 + 1,5 + 2 = 8 Punkte) Seien x, y ganze Zahlen vom Typ Int und xs und ys Listen der Längen n und m vom Typ [Int]. Welche der folgenden Gleichungen zwischen Listen sind richtig und welche nicht? Begründen Sie Ihre Antwort. Falls es sich um syntaktisch korrekte Ausdrücke handelt, geben Sie für jede linke und rechte Seite auch an, wie viele Elemente in der jeweiligen Liste enthalten sind und welchen Typ sie hat. : Die Liste [[1,2,3],[4,5]] hat den Typ [[Int]] und enthält 2 Elemente. Hinweise: Falls linke und rechte Seite gleich sind, genügt wiederum Angabe des Typs und der Elementzahl. Beispiel eine a) x : ([y] ++ xs) = [x] ++ (y : xs) b) x:[y] = x:y c) x:ys:xs = (x:ys) ++ xs d) [x,x,y] ++ (x:xs) = x:x:((y:[x]) ++ xs) e) []:[[[1]],[]] = [[],[1]]:[[]] Tutoraufgabe 8 (Haskell-Programmierung): Implementieren Sie alle der im Folgenden beschriebenen Funktionen in Haskell. Geben Sie jeweils auch die Typdeklarationen an. Verwenden Sie auÿer Listenkonstruktoren [] und : (und deren Kurzschreibweise) und Vergleichsoperatoren wie <=, ==,... vordenierten Funktionen (dies schlieÿt auch arithmetische Operatoren ein), auÿer denen, die in den jeweiligen Teilaufgaben explizit erlaubt werden. mult x y Berechnet x · y. Sie dürfen dazu + und - verwenden. Die Funktion darf sich auf negativen Eingaben beliebig verhalten. keine a) bLog x b) Berechnet den aufgerundeten Logarithmus zur Basis 2 von x. Somit liefert bLog 1 den Wert 0, bLog 5 den Wert 3 und bLog 32 den Wert 5. Sie dürfen hier + und * verwenden. Die Funktion darf sich auf negativen Eingaben oder der Eingabe 0 beliebig verhalten. c) getLastTwo xs d) singletons x e) packing xs ys Das erste Argument xs ist eine Liste von Listen von Zahlen (vom Typ [[Int]]), das zweite Argument ist eine einfache Liste von Zahlen (vom Typ [Int]). Die Funktion ersetzt leere Listen in der ersten Eingabeliste durch einelementige Listen. Der Inhalt dieser einelementigen Listen ist die jeweils nächste Zahl aus der zweiten Eingabeliste. Beispielsweise berechnet packing [[5,6],[],[8],[]] [1,2] die Liste [[5,6],[1],[8],[2]]. Wenn im zweiten Argument nicht genug Zahlen zur Verfügung stehen, werden zusätzliche leere Listen im ersten Argument leer gelassen. Wenn im zweiten Argument zu viele Zahlen zur Verfügung stehen, werden diese ignoriert. f) listAdd x xs Berechnet die Teilliste der letzten zwei Elemente der Int-Liste xs. Beispielsweise berechnet getLastTwo [12, 7, 23] den Wert [7, 23]. Die Funktion darf sich auf Listen der Länge 0 und 1 beliebig verhalten. Berechnet eine Liste mit x Elementen, wobei jedes Listenelement eine einelementige Liste ist. Die Elemente der einelementigen Listen sind die Zahlen x, x − 1, ..., 1. Beispielsweise berechnet singletons 3 die Liste [[3],[2],[1]]. Die Funktion darf sich auf negativen Eingaben beliebig verhalten. Sie dürfen hier - verwenden. Addiert jeweils das n-te Listenelement auf das (n+1)-te Listenelement, wobei auf das erste Listenelement addiert wird. Beispielsweise berechnet listAdd 5 [1,9,3] die Liste [6,10,12]. Sie dürfen hier + verwenden. x 6 Programmierung WS14/15 Übungsblatt 9 (Abgabe 07.01.2015) Aufgabe 9 (Haskell-Programmierung): Punkte) (1,5 + 1,5 + 2 + 2 + 3,5 + 2,5 = 13 Implementieren Sie alle der im Folgenden beschriebenen Funktionen in Haskell. Geben Sie jeweils auch die Typdeklarationen an. Verwenden Sie auÿer Listenkonstruktoren [] und : (und deren Kurzschreibweise), der Listenkonkatenation ++, Vergleichsoperatoren wie <=, ==, ... und arithmetischen Operatoren wie +, *, ... vordenierten Funktionen auÿer denen, die in den jeweiligen Teilaufgaben explizit erlaubt werden oder in früheren Teilaufgaben implementiert wurden. Die arithmetischen Funktionen mod und div berechnen die Modulo-Operation bzw. die IntegerDivision. Beide Funktionen dürfen Sie ebenfalls verwenden. keine Hinweis: a) factorial x x! b) digitSum x c) onlyEven xs d) reverses xs e) permutations xs f) anagram xs ys True False Berechnet . Die Funktion darf sich auf nicht-positiven Eingaben beliebig verhalten. Berechnet die Quersumme einer Zahl, d.h. die Summe der Ziern dieser Zahl. Beispielsweise ergibt digitSum 345 den Wert 12. Die Funktion darf sich auf negativen Eingaben beliebig verhalten. Berechnet die Teilliste der übergebenen Liste, welche genau die geraden Elemente der Ursprungsliste in der gleichen Reihenfolge enthält. Beispielsweise berechnet onlyEven [12, 7, 23, 4, 5] den Wert [12, 4]. Berechnet zu einer Liste von Zahlen eine Liste von Listen von Zahlen, wobei die erste Liste in der Ergebnisliste genau die erste Zahl der Ursprungsliste enthält. Die zweite Liste in der Ergebnisliste enthält die zweite Zahl der Ursprungsliste gefolgt von der ersten Zahl. Danach kommt die Liste bestehend aus der dritten Zahl vor der zweiten und der ersten usw. Beispielsweise berechnet reverses [1,9,3] die Liste [[1], [9,1], [3,9,1]]. Berechnet eine Liste aller Permutationen der übergebenen Liste. Beispielsweise berechnet permutations [1,2,3] die Liste [[1,2,3],[2,1,3],[2,3,1],[1,3,2],[3,1,2],[3,2,1]]. Auf Listen, in denen es ein Element gibt, das mehr als einmal in dieser Liste vorkommt, darf sich Ihre Implementierung beliebig verhalten. Liefert zurück, falls die Liste ys eine Permutation der Liste xs ist. Falls das nicht der Fall ist, liefert die Funktion als Ergebnis. Auf Listen, in denen es ein Element gibt, das mehr als einmal in dieser Liste vorkommt, darf sich Ihre Implementierung beliebig verhalten. 7