Programmieren – Sommersemester 2015 Software-Design und Qualität (SDQ) https://sdqweb.ipd.kit.edu/wiki/Programmieren Prof. Dr. Ralf H. Reussner · Philipp Merkle · Kiana Rostami Übungsblatt 6 Ausgabe: 29.06.2015 – 13:00 Abgabe: 13.07.2015 – 13:00 Bearbeitungshinweise • Achten Sie darauf nicht zu lange Zeilen, Methoden und Dateien zu erstellen1 • Programmcode muss in englischer Sprache verfasst sein • Kommentieren Sie Ihren Code angemessen: So viel wie nötig, so wenig wie möglich • Wählen Sie geeignete Sichtbarkeiten für Ihre Klassen, Methoden und Attribute • Verwenden Sie keine Klassen der Java-Bibliotheken ausgenommen Klassen der Pakete java.lang und java.io, es sei denn die Aufgabenstellung erlaubt ausdrücklich weitere Pakete1 • Achten Sie auf fehlerfrei kompilierenden Programmcode1 • Halten Sie alle Whitespace-Regeln ein1 • Halten Sie die Regeln zu Variablen-, Methoden und Paketbenennung ein und wählen Sie aussagekräftige Namen1 • Halten Sie die Regeln zu Javadoc-Dokumentation ein1 • Nutzen Sie nicht das default-Package1 • Halten Sie auch alle anderen Checkstyle-Regeln ein Abgabemodalitäten Die Praktomat-Abgabe wird am Montag, den 6. Juli, freigeschaltet. Geben Sie die Java-Klassen als *.javaDateien ab. Laden Sie die Terminal-Klasse nicht mit hoch. Planen Sie für die Abgabe ausreichend Zeit ein, sollte der Praktomat Ihre Abgabe wegen einer Regelverletzung ablehnen. Fehlerbehandlung Ihr Programm soll auf ungültige Benutzereingaben mit einer aussagekräftigen Fehlermeldung reagieren. Aus technischen Gründen muss eine Fehlermeldung unbedingt mit Error, beginnen. Eine Fehlermeldung führt nicht dazu, dass das Programm beendet wird; es sei denn, die nachfolgende Aufgabenstellung verlangt dies ausdrücklich. Achten Sie inbesondere auch darauf, dass unbehandelte RuntimeExceptions, bzw. Subklassen davon—sogenannte Unchecked Exceptions—nicht zum Abbruch des Programms führen. 1 Der Praktomat wird die Abgabe zurückweisen, falls diese Regel verletzt ist. Programmieren – Sommersemester 2015 A 29.06.2015 – 13:00 Expertensystem (20 Punkte + 4 Bonuspunkte) Terminal-Klasse Laden Sie für diese Aufgabe die Terminal-Klassea von unserer Homepage herunter und platzieren Sie diese unbedingt im Paket edu.kit.informatik. Die Methode Terminal.readLine() liest eine Benutzereingabe von der Konsole und ersetzt System.in. Die Methode Terminal.printLine() schreibt eine Ausgabe auf die Konsole und ersetzt System.out. Verwenden Sie für jegliche Konsoleneingabe oder Konsolenausgabe die Terminal-Klasse. Verwenden Sie in keinem Fall System.in oder System.out. Fehlermeldungen werden ausschließlich über Terminal.printLine() ausgegeben und müssen aus technischen Gründen unbedingt mit Error, beginnen. Laden Sie die Terminal-Klasse niemals zusammen mit Ihrer Abgabe hoch. a https://sdqweb.ipd.kit.edu/lehre/SS15-Programmieren/Terminal.java Tagtäglich treffen wir eine Vielzahl an Entscheidungen, abhängig von unserer Umgebung, persönlichen Vorlieben und weiteren Faktoren. Das beginnt schon mit der Entscheidung per Fuß, Fahrrad, Straßenbahn, Bahn, oder mit dem PKW zum Campus zu gelangen. Abbildung 1 illustriert einen solchen Entscheidungsprozess. In diesem Beispiel fließen Informationen über den momentanen Standort und die Wetteraussichten in die Entscheidung ein. Ist der aktuelle Standort benachbart zum Campus, ist es am einfachsten zu Fuß zu gehen. Ist der aktuelle Standort hingegen weiter entfernt, aber immer noch im Gebiet des Karlsruher Verkehrsverbunds (KVV), kommen abhängig vom Regenrisiko Fahrrad oder Straßenbahn in Frage. Außerhalb des KVV-Gebiets bleibt die Wahl zwischen Zug und Auto, abhängig von der Entfernung zum nächsten Bahnhof. Die in Abbildung 1 dargestellten Entscheidungsmöglichkeiten bezeichnet man als Entscheidungsbaum. Um zu einer Entscheidung zu gelangen, startet man bei der zuoberst dargestellten Wurzel, beantwortet die darin stehende Frage und verfolgt denjenigen Zweig, der mit der gegebenen Antwort beschriftet ist. Dies setzt man so lange fort, bis man bei einem Blatt ankommt—einem Knoten, von dem aus keine weiteren Zweige ausgehen. In einem solchen Blatt steht schließlich die Entscheidung, die zu den soeben gegebenen Antworten passt. Solche Entscheidungsbäume können eingesetzt werden, um ein Expertensystem aufzubauen. Ein Expertensystem kann als Ersatz für einen menschlichen Experten dienen, indem es dessen Fachwissen abbildet. Das Expertensystem stellt seinem Benutzer eine Reihe von aufeinander aufbauenden Fragen, um schließlich eine fundierte Antwort zu liefern. Beispielsweise könnte ein medizinisches Expertensystem eine Reihe von Symptomen abfragen und als Antwort die wahrscheinlichsten Krankheitsursachen ausgeben. Standort? Campusnähe Fuß Innerhalb KVV-Gebiet Außerhalb KVV-Gebiet Regenrisiko? Gering Fahrrad Entfernung Bahnhof? Hoch Straßenbahn Nah Bahn Fern PKW Abbildung 1: Entscheidungsbaum zur Bestimmung eines Verkehrsmittels Seite 2 von 8 Programmieren – Sommersemester 2015 29.06.2015 – 13:00 Ihre Aufgabe ist es, ein einfaches Expertensystem zu programmieren, das auf einem Entscheidungsbaum basiert. Der Entscheidungsbaum wird in einer Eingabedatei beschrieben und beim Programmstart übergeben. Das Expertensystem stellt dem Benutzer eine Folge von Fragen und wartet jeweils auf eine Antwort des Benutzers, bevor—abhängig von der Antwort—die nächste Frage gestellt wird. Schließlich wird dem Benutzer eine Entscheidung zurückgeliefert, die von den dessen Antworten abhängt. Diesen Programmablauf veranschaulicht Abschnitt A.4.5. A.1 Bäume und Entscheidungsbäume Die im Folgenden eingeführten Begriffe sind zum besseren Verständnis zusätzlich in Abbildung 2 veranschaulicht. Ein Baum ist ein gerichteter Graph mit genau einem Wurzelknoten. Ein Wurzelknoten besitzt als einziger Knoten keine eingehende Kante. Ausgehend von der Wurzel ist jeder Knoten eines Baums auf genau einem Pfad erreichbar (mit Ausnahme der Wurzel). Jede Kante zeigt von einem Elternknoten auf einen Kindknoten. Knoten ohne Kinder werden als Blätter bezeichnet. Alle anderen Knoten bezeichnen wir als innere Knoten. Ein Entscheidungsbaum ist ein spezieller Baum, dessen Knoten und Kanten beschriftet sind: • Die Beschriftung eines inneren Knoten bezeichnen wir als Frage. • Die Beschriftung einer Kante bezeichnen wir als Antwort. • Die Beschriftung eines Blattes bezeichnen wir als Entscheidung. In dieser Aufgabe trägt jeder Knoten zusätzlich eine eindeutige Identifikationsnummer (id), d.h. zwei verschiedene Knoten dürfen nicht die selbe Identifikationsnummer tragen. A.2 Serialisierung eines Entscheidungsbaums Unter einer Serialisierung verstehen wir im Rahmen dieser Aufgabe die textuelle Repräsentation eines Entscheidungsbaums: ein Format, um einen Entscheidungsbaum in eine Datei zu speichern bzw. aus einer Datei auszulesen. Eine gültige Serialisierung besteht aus drei oder mehreren Zeilen: einer Wurzel und mindestens zwei Kindknoten. Jede Zeile beschreibt einen Knoten und—falls vorhanden—dessen eingehende Kante. Die erste Zeile beschreibt die Wurzel: <Identifikationsnummer>;<Knotenbeschriftung> Standort? (id=1) (Wurzel-)Knoten Campusnähe Kante Fuß (id=2) (Blatt-)Knoten Entscheidung Innerhalb KVV-Gebiet Frage Regenrisiko? (id=3) Gering Fahrrad (id=4) Antwort Außerhalb KVV-Gebiet Entfernung Bahnhof? (id=6) Hoch Straßenbahn (id=5) Nah Bahn (id=7) Fern PKW (id=8) Abbildung 2: Bestandteile eines Entscheidungsbaums am Beispiel von Abb. 1 Seite 3 von 8 Programmieren – Sommersemester 2015 29.06.2015 – 13:00 <Identifikationsnummer> ist ein Integer größer oder gleich 0. <Knotenbeschriftung> ist ein String, der durch den regulären Ausdruck [a-zA-Z_?]+ beschrieben ist. Fragezeichen und Unterstriche gehören somit zu den erlaubten Zeichen. Alle weiteren Zeilen beschreiben jeweils einen inneren Knoten oder ein Blatt. Im Folgenden kürzen wir diesen Knoten mit k ab. <Identifikationsnummer>;<Knotenbeschriftung>;<Elternknoten-Id>;<Kantenbeschriftung> <Identifikationsnummer> bezeichnet die Identifikationsnummer von k und ist ein Integer größer oder gleich 0. <Knotenbeschriftung> bezeichnet die in k gespeicherte Frage oder Entscheidung und ist ein String, der durch den regulären Ausdruck [a-zA-Z_?]+ beschrieben ist. <Elternknoten-Id> bezeichnet die Identifikationsnummer des Elternknoten von k und ist ein Integer größer oder gleich 0. <Kantenbeschriftung> bezeichnet die in der eingehenden Kante von k gespeicherte Antwort und ist ein String, der durch den regulären Ausdruck [a-zA-Z_]+ beschrieben ist. Zur Vereinfachung der Eingabeverarbeitung gilt darüber hinaus, dass alle Zeilen (mit Ausnahme der ersten Zeile) so sortiert sind, dass ein Elternknoten stets vor seinen Kindknoten steht. Die Ausgabereihenfolge dieser Kindknoten ist jedoch beliebig. A.3 Kommandozeilenargumente Ihr Programm nimmt als erstes und einziges Kommandozeilenargument einen Pfad auf eine Textdatei entgegen. Diese Datei beschreibt einen Entscheidungsbaum und ist nach dem in Abschnitt A.2 eingeführten Format aufgebaut. Zum Einlesen einer Datei beachten Sie den Hinweis im Anhang. Tritt beim Verarbeiten des Kommandozeilenarguments ein Fehler auf, so wird eine Fehlermeldung ausgegeben und das Programm beendet sich mittels System.exit(1) . A.4 Interaktive Benutzerschnittstelle Nach dem Start nimmt Ihr Programm über die Konsole mittels Terminal.readLine() vier Befehle entgegen. Nach Abarbeitung eines Befehls wartet das Programm auf weitere Befehle, bis das Programm irgendwann durch quit beendet wird. A.4.1 decide-Befehl Der decide-Befehl startet einen Entscheidungsprozess. Ein Entscheidungsprozess ist ein Dialog zwischen dem Programm und seinem Benutzer. Der Dialog beginnt, indem das Programm die im Wurzelknoten gespeicherte Frage auf die Konsole ausgibt. In jeweils einer weiteren Zeile gibt das Programm alle auf diese Frage möglichen Antworten auf die Konsole aus. Das Programm wartet nun auf eine Benutzereingabe zur Auswahl einer Antwort. Sobald der Benutzer eine gültige Eingabe getätigt hat, verfolgt das Programm die zur Eingabe passende Kante und arbeitet mit dessen Zielknoten bzw. Kindknoten weiter: die im Kindknoten gespeicherte Frage wird auf die Konsole ausgegeben, zusammen mit den möglichen Antworten. Wieder wartet das Programm auf eine Benutzereingabe zur Auswahl einer Antwort. Dieser Dialog wird so lange fortgesetzt, bis der Kindknoten ein Blatt ist. Dann gibt das Programm die im Blatt stehende Entscheidung auf die Konsole aus. Ein solcher Dialog ist in Abschnitt A.4.5 veranschaulicht. Tätigt der Benutzer während des Dialogs eine unzulässige Eingabe, wird eine Fehlermeldung ausgegeben und das Programm verlangt die Eingabe erneut. Die Frage bzw. mögliche Antworten wird jedoch nicht erneut ausgegeben. Während eines laufenden decide-Dialogs wird dieser Befehl nicht akzeptiert. Seite 4 von 8 Programmieren – Sommersemester 2015 29.06.2015 – 13:00 Eingabeformat Der Befehl besitzt keine Parameter, so dass kein Eingabeformat spezifiziert wird. Ausgabeformat einer Frage mit Antworten <Fragetext> [<Antwort-Id>] <Antworttext> (eine Zeile je möglicher Antwort bzw. je Kindknoten) <Fragetext> ist die Beschriftung des Frageknotens. <Antworttext> ist die Beschriftung einer vom Frageknoten ausgehenden Kante k. <Antwort-Id> ist die Identifizierungsnummer des Knotens, der von der ausgehenden Kante k getroffen wird. Eingabeformat einer Antwort <Antwort-Id> <Antwort-Id> ist—wie soeben definiert—die Identifizierungsnummer des Knotens, der von der ausgehenden Kante k getroffen wird. Ausgabeformat einer Entscheidung Decision: <Entscheidungstext> <Entscheidungstext> ist die Beschriftung des Entscheidungsknoten. A.4.2 serialise-Befehl Der serialise-Befehl serialisiert den beim Programmstart eingelesenen Entscheidungsbaum und gibt diesen auf die Konsole aus2 . Dazu wird das in Abschnitt A.2 beschriebene Format genutzt. Während eines laufenden decide-Dialogs wird dieser Befehl nicht akzeptiert. Eingabeformat Der Befehl besitzt keine Parameter, so dass kein Eingabeformat spezifiziert wird. Ausgabeformat Siehe Abschnitt A.2. A.4.3 node-Befehl Der node-Befehl gibt Informationen über einen bestimmten Knoten auf die Konsole aus. Während eines laufenden decide-Dialogs wird dieser Befehl nicht akzeptiert. Eingabeformat node <Identifikationsnummer> <Identifikationsnummer> ist die Identifikationsnummer des Knotens, zu dem Informationen zurückgeliefert werden sollen. Ausgabeformat <Knotenart>;<Knotentext>;<Kinderanzahl> <Knotenart> ist entweder Question , falls es sich bei dem angegebenen Knoten um einen inneren Knoten handelt, oder Decision , falls es sich um einen Blattknoten handelt. <Knotentext> ist die Beschriftung des angegebenen Knoten. <Kinderanzahl> ist die Anzahl der Kindknoten des angegebenen Knotens. A.4.4 quit-Befehl Dieser Befehl beendet das Programm. Dabei findet keine Konsolenausgabe statt. Beachten Sie, dass dieser Befehl auch während eines laufenden decide-Dialogs akzeptiert wird. 2 Es wird keine Punkte dafür geben, bei diesem Befehl einfach die eingelesene Datei auszugeben. Seite 5 von 8 Programmieren – Sommersemester 2015 A.4.5 29.06.2015 – 13:00 Beispielinteraktionen Beachten Sie im Folgenden, dass Eingabezeilen mit dem > -Zeichen eingeleitet werden, gefolgt von einem Leerzeichen. Diese beiden Zeichen sind ausdrücklich kein Bestandteil des eingegebenen Befehls, sondern dienen nur der Unterscheidung zwischen Ein- und Ausgabe. Das Beispiel aus Abbildung 1 wird im Folgenden leicht angepasst, weil das Eingabeformat weder Leerzeichen noch Umlaute zulässt. Leerzeichen wurden durch Unterstriche ersetzt; Umlaute und ß durch deren Umschreibung ae, oe, ue bzw. ss. Bindestriche entfallen. Dialog am Beispiel von Abbildung 1 > decide Standort? [2] Campusnaehe [3] Innerhalb_KVV_Gebiet [6] Ausserhalb_KVV_Gebiet > 6 Entfernung_Bahnhof? [7] Nah [8] Fern > 7 Decision: Bahn > quit Eine mögliche Serialisierung des (angepassten) Beispiels aus Abbildung 1 > serialise 1;Standort? 6;Entfernung_Bahnhof?;1;Ausserhalb_KVV_Gebiet 7;Bahn;6;Nah 8;PKW;6;Fern 3;Regenrisiko?;1;Innerhalb_KVV_Gebiet 4;Fahrrad;3;Gering 5;Strassenbahn;3;Hoch 2;Fuss;1;Campusnaehe > quit node-Befehl am (angepassten) Beispiel aus Abbildung 1 > node 3 Question;Regenrisiko?;2 > quit Beachten Sie bei obigem Beispiel, dass es eine Vielzahl an gültigen Serialisierungen gibt. Seite 6 von 8 Programmieren – Sommersemester 2015 A.5 A.5.1 29.06.2015 – 13:00 Bonusaufgaben frequency-Befehl (2 Bonuspunkte) Sie können bis zu 2 Bonuspunkte erhalten, wenn Sie diesen Befehl implementieren. Geben Sie diese Erweiterung zusammen mit Ihrem Programm ab; eine separate Abgabe ist nicht vorgesehen. Der frequency-Befehl gibt für einen bestimmten Knoten auf die Konsole aus, wie häufig dieser Knoten seit Programmstart als Antwort in einem decide-Dialog ausgewählt wurde. Entspricht dieser Knoten dem Wurzelknoten, gibt dieser Befehl stattdessen zurück, wie viele decide-Dialoge durchgeführt wurden. Während eines laufenden decide-Dialogs wird dieser Befehl nicht akzeptiert. Eingabeformat frequency <Identifikationsnummer> <Identifikationsnummer> ist die Identifikationsnummer des Knotens, dessen Auswahlhäufigkeit zurückgeliefert werden sollen. Ausgabeformat <Auswahlhäufigkeit> <Auswahlhäufigkeit> ist ein Integer größer oder gleich 0 und gibt an, wie häufig dieser Knoten seit Programmstart als Antwort in einem decide-Dialog ausgewählt wurde. A.5.2 probability-Befehl (2 Bonuspunkte) Sie können bis zu 2 Bonuspunkte erhalten, wenn Sie diesen Befehl implementieren. Geben Sie diese Erweiterung zusammen mit Ihrem Programm ab; eine separate Abgabe ist nicht vorgesehen. Der probability-Befehl berechnet das Verhältnis der Auswahlhäufigkeit (vergleiche Abschnitt A.5.1) eines bestimmten Knotens zur Gesamtanzahl durchgeführter decide-Dialoge. Wurde der decide-Dialog häufig aufgerufen, ergibt sich daraus die Wahrscheinlichkeit, dass ein Benutzer im Verlauf eines decide-Dialogs einen bestimmten Knoten passieren wird (anders ausgedrückt, dass der Benutzer Antworten wählt, die zu diesem Knoten führt). Dieser Befehl ist erst zulässig, nachdem der decide-Dialog mindestens ein mal ausgeführt wurde. Während eines laufenden decide-Dialogs wird dieser Befehl nicht akzeptiert. Eingabeformat probability <Identifikationsnummer> freq = 4 prob = 100% Standort? (id=1) Campusnähe freq = 1 prob = 25% Fuß (id=2) Innerhalb KVV-Gebiet Außerhalb KVV-Gebiet Regenrisiko? (id=3) freq = 2 prob = 50% Gering freq = 2 prob = 50% Fahrrad (id=4) Entfernung Bahnhof? (id=6) Hoch Straßenbahn (id=5) Nah Bahn (id=7) freq = 1 prob = 25% Fern PKW (id=8) freq = 1 prob = 25% Abbildung 3: Beispiel mit Auswahlhäufigkeiten (freq) und den sich daraus ergebenden Auswahlwahrscheinlichkeiten (prob) je Knoten. Für Knoten ohne Annotation ist die Anzeigehäufigkeit = 0. Seite 7 von 8 Programmieren – Sommersemester 2015 29.06.2015 – 13:00 <Identifikationsnummer> ist die Identifikationsnummer des Knotens, dessen Auswahlwahrscheinlichkeit zurückgeliefert werden sollen. Ausgabeformat <Auswahlwahrscheinlichkeit>% <Auswahlwahrscheinlichkeit> ist eine ganze Zahl zwischen 0 und 100; es wird auf die nächstkleinere ganze Zahl abgerundet. Beachten Sie auch das abschließende Prozent-Zeichen. Anhang: Zeilenweises Lesen einer Datei Sie dürfen folgendes Code-Beispiel benutzen, um in einer Schleife die Zeilen aus einer gegebenen Datei auszulesen. Für die Fehlermeldungen wurden Konstanten benutzt, die Sie definieren und anpassen müssen. Das TODO markiert die Stelle, an der Sie Ihren Code zum Verarbeiten der Zeile einhängen können. Schauen Sie zur Verarbeitung der Zeile die API-Dokumentation der String-Klasse3 an. Beachten Sie insbesondere die split-Methode. Die für die Eingabe verwendeten Klassen finden Sie im Paket java.io4 . public static void main(String[] args) { if (args.length != 1) { System.out.println(USAGE); System.exit(1); } FileReader in = null; try { in = new FileReader(args[0]); } catch (FileNotFoundException e) { System.out.println(ERROR_MESSAGE); System.exit(1); } BufferedReader reader = new BufferedReader(in); try { String line = reader.readLine(); while (line != null) { // TODO: process line here line = reader.readLine(); } } catch (IOException e) { System.out.println(ERROR_MESSAGE); System.exit(1); } } 3 http://docs.oracle.com/javase/7/docs/api/java/lang/String.html 4 http://docs.oracle.com/javase/7/docs/api/java/io/package-summary.html Seite 8 von 8