TU Kaiserslautern - AG Softwaretechnik
TU Kaiserslautern - AG Softwaretechnik
TU Kaiserslautern - AG Softwaretechnik
Erfolgreiche ePaper selbst erstellen
Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.
Prof. Dr. A. Poetzsch-Heffter<br />
Dipl.-Inf. J.-M. Gaillourdet<br />
Dipl.-Inf. P. Michel<br />
<strong>TU</strong> <strong>Kaiserslautern</strong><br />
Fachbereich Informatik<br />
<strong>AG</strong> <strong>Softwaretechnik</strong><br />
Übungsblatt 8: Software-Entwicklung 1 (WS 2009/10)<br />
Aufgabe 1 Parameterinduktion (Präsenzaufgabe)<br />
Ausgabe: in der Woche vom 14.12. bis zum 18.12.<br />
Abgabe: in der Woche vom 04.01. bis zum 08.01.<br />
Abnahme: max. zwei Tage nach der Übung<br />
Geben Sie die Implementierung der Haskell-Funktion gauss an, welche für gegebenen Parameter n die Summe<br />
der natürlichen Zahlen von 0 bis n durch rekursive Berechnung ermittelt. Zeigen Sie für Ihre Implementierung<br />
unter Verwendung der vollständigen Induktion, dass die folgende Gleichheit gilt:<br />
gauss n = n(n+1)<br />
2<br />
Aufgabe 2 Parameterinduktion (Einreichaufgabe)<br />
a) Geben Sie die Implementierung der Haskell-Funktion multiply an, welche die Multiplikation zweier<br />
natürlicher Zahlen auf Addition / Subtraktion zurückführt. Zeigen Sie für Ihre Implementierung unter<br />
Verwendung der vollständigen Induktion, dass die folgende Gleichheit gilt:<br />
b) Beweisen Sie, dass die Haskell-Funktion<br />
g :: Integer -> Integer<br />
g n = h n 1 0<br />
multiply x y = x · y<br />
h :: Integer -> Integer -> Integer -> Integer<br />
h 0 _ r = r<br />
h n l r = h (n-1) r (l+r)<br />
ebenfalls die Fibonacci-Zahlen<br />
f :: Integer -> Integer<br />
f 0 = 0<br />
f 1 = 1<br />
f n = f (n - 1) + f (n - 2)<br />
berechnet, also g n = f n für alle n ∈ N gilt. Zeigen Sie hierzu zunächst mittels Induktion über k, dass<br />
h (n-k) (f (k-1)) (f k) = f n<br />
für n, k ∈ N mit 1 ≤ k ≤ n und n > 0 gilt. Nutzen Sie dieses Teilergebnis, um die eigentliche Aussage zu<br />
beweisen.<br />
Hinweis: Im Fall obiger Induktion über k ist es ratsam, bei k = n zu beginnen und den Induktionsschluß<br />
k → (k − 1) durchzuführen!
Aufgabe 3 Terminierung (Präsenzaufgabe)<br />
In dieser Aufgabe wollen wir die Terminierung von Funktionen mit dem Verfahren aus der Vorlesung beweisen.<br />
Wir betrachten wieder einmal die Fibonacci Funktion, implementiert durch:<br />
f :: Integer -> Integer<br />
f 0 = 0<br />
f 1 = 1<br />
f n = f (n - 1) + f (n - 2)<br />
Beweisen Sie, dass diese Funktion terminiert, denken Sie aber daran, dass wir uns nur für Parameter aus den<br />
natürlichen Zahlen interessieren.<br />
Aufgabe 4 Terminierung (Einreichaufgabe)<br />
a) Beweisen Sie, dass die Funktion divConstZero, die wir im Rahmen der Expressions-Aufgabe geschrieben<br />
haben, terminiert:<br />
data Expr =<br />
Const Integer<br />
| Binary Op Expr Expr<br />
deriving (Eq, Ord , Show)<br />
data Op = Mult | Div | Plus | Minus<br />
deriving (Eq, Ord , Show)<br />
divConstZero :: Expr -> Bool<br />
divConstZero (Const _) = False<br />
divConstZero (Binary Div _ (Const 0)) = True<br />
divConstZero (Binary _ a b) = divConstZero a || divConstZero b<br />
Hinweis: Betrachten Sie ausschließlich endliche Ausdrücke und machen Sie dies im Beweis klar.<br />
b) Wir betrachten folgende Implementierung der Primfaktorzerlegung (für Zahlen ≥ 2) und wollen beweisen,<br />
dass sie für jede Eingabe terminiert:<br />
primfaktoren :: Integer -> [Integer]<br />
primfaktoren c = if c < 2 then [c] else faktor (c, 2)<br />
faktor :: (Integer , Integer) -> [Integer]<br />
faktor (c, x) =<br />
if c == x then [x] else<br />
if c �mod� x /= 0 then faktor (c, x + 1) else<br />
x : faktor (c �div� x, 2)<br />
Da die Funktion primfaktoren nicht rekursiv ist, terminiert sie, wenn alle von ihr aufgerufenen Funktionen<br />
terminieren. Wir müssen also beweisen, dass die Funktion faktor terminiert, zumindest auf dem<br />
Parameter-Bereich, der von primfaktoren oder faktor aufgerufen wird. Sei also P der zulässige Parameterbereich:<br />
P = � (c, x) | 2 ≤ x ≤ c � ⊂ N × N<br />
Beweisen Sie, dass es durch einen Aufruf von primfaktoren (und die Folgeaufrufe durch faktor selbst)<br />
nur zulässige Aufrufe von faktor mit einem Parameter aus P geben wird: G(n) ∈ P.<br />
c) Um die Terminierung nun zu beweisen, verwenden wir das Verfahren aus der Vorlesung. Als noethersche<br />
Ordnung verwenden wir die Menge der Paare aus natürlichen Zahlen (N × N, ≤) mit der komponentenweisen<br />
Ordnung, d.h. ist die erste Komponente gleich wird nach der zweiten geordnet, wie auf<br />
natürlichen Zahlen, ansonsten wird nach der ersten Komponente geordnet. Das kleinste Element ist dann<br />
einfach (0, 0).
Geben Sie eine Funktion δ : P ↦→ N × N an, die Parameter auf unsere Ordnung abbildet, so dass Sie den<br />
Terminierungsbeweis führen können. Im vorhergehenden Aufgabenteil haben Sie schon bewiesen, dass<br />
G(n) ∈ P, es bleibt also noch zu zeigen, dass die Parameter echt kleiner werden: δ(G(n)) < δ(n).<br />
Aufgabe 5 Java (Einreichaufgabe)<br />
Im weiteren Verlauf der Vorlesung wird die Sprache Java verwendet. In dieser Aufgabe soll ein kleines<br />
“Hallo Welt”-Programm geschrieben, übersetzt und ausgeführt werden.<br />
a) Legen Sie eine Datei mit dem Namen “Hallo.java” an und geben Sie folgendes Javaprogramm ein:<br />
public class Hallo {<br />
}<br />
static String greeting = "Hallo Java";<br />
public static void main (String[] args) {<br />
IO.println(greeting);<br />
}<br />
Beachten Sie, dass eine Java Quelltextdatei immer den gleichen Namen wie die in ihr enthaltene Klasse<br />
plus die Endung “.java” trägt. In unserem Fall heisst die Klasse Hallo und die Datei entsprechend<br />
Hallo.java.<br />
b) Im nächsten Schritt werden wir diese Datei übersetzen, d.h. Bytecode für die Java Virtuelle Maschine<br />
erzeugen. Dazu laden Sie als erstes die Datei “IO.java” von der Webseite der Vorlesung und speichern<br />
diese im gleichen Verzeichnis wie Ihren Quelltext (Quellverzeichnis). Rufen Sie im Quellverzeichnis<br />
den Javacompiler auf der Kommandozeile mit folgendem Befehl auf:<br />
> javac Hallo.java<br />
Dem Compiler wird die Datei übergeben, die eine main-Methode enthält. Er erstellt dann zu jeder benutzen<br />
Klasse eine “.class”-Datei, die sich im selben Verzeichnis befindet. In unserem Fall müssten<br />
Sie jetzt Hallo.class und IO.class in ihrem Verzeichnis haben. Um das gerade geschriebene und<br />
übersetzte Programm auszuführen, rufen Sie aus dem Quellverzeichnis java mit der Klasse auf, die<br />
eine main-Methode enthält. In unserem Fall also:<br />
> java Hallo<br />
Wenn jetzt der Begrüßungstext auf dem Bildschirm erscheint haben Sie alles richtig gemacht.<br />
c) Um den Bytecode von den Quelldateien sauber zu trennen, können wir angeben wohin der Übersetzer<br />
seine Ausgabe speichert. Legen Sie ein Unterverzeichnis “build” an, in dem wir nun alle übersetzten<br />
Dateien ablegen.<br />
Hinweis: Löschen Sie die “class” Dateien die Sie in b) im Quellverzeichnis erzeugt haben, um Probleme<br />
mit Java zu vermeiden!<br />
Rufen Sie im Quellverzeichnis den Javacompiler auf der Kommandozeile mit folgendem Befehl auf:<br />
> javac -d build Hallo.java<br />
Er erstellt nun im build-Verzeichnis zu jeder benutzen Klasse eine “.class”-Datei. Um das Programm<br />
auszuführen rufen Sie aus dem Quellverzeichnis java mit der zusätzlichen Angabe wo sich die Klassen<br />
befinden auf:<br />
> java -cp build Hallo<br />
Wenn jetzt der Begrüßungstext auf dem Bildschirm erscheint haben Sie alles richtig gemacht.
Aufgabe 6 Imperative Programmierung mit Java<br />
Kreuzen Sie an, ob folgende Aussagen wahr oder falsch sind. Bereiten Sie diese Aufgabe bis zu Ihrer<br />
nächsten Übungsstunde vor, so dass Sie bei Unklarheiten nachfragen und die Antworten diskutieren können.<br />
wahr falsch<br />
� � Ausdrücke in Java erzeugen keine Seiteneffekte.<br />
� � Jeder Ausdruck ist eine Anweisung.<br />
� � Es gibt Anweisungen, welche die Abfolge, in der Anweisungen abgearbeitet werden,<br />
beeinflussen.<br />
� � Auf der rechten Seite einer Zuweisung kann ein Prozeduraufruf stehen.<br />
� � Überall dort wo eine einzelne Anweisung stehen kann, kann auch ein Anweisungsblock<br />
stehen.<br />
� � Der Rumpf einer while-Anweisung wird mindestens einmal ausgeführt.<br />
� � Die Schleifenvariable einer for-Schleife kann auch um mehr als eins pro Schleifendurchlauf<br />
erhöht werden.<br />
� � Geschachtelte Fallunterscheidungen lassen sich durch eine Auswahlanweisung ersetzen.<br />
� � Prozeduren enthalten Anweisungen und liefern immer ein Ergebnis.<br />
� � Eine Prozedur kann sich in ihrem Rumpf selbst aufrufen.<br />
� � Jede Prozedurinkarnation hat ihre eigenen lokalen Variablen.<br />
� � Wenn v eine lokale Variable einer Prozedur ist, dann wird mit v immer dieselbe Speichervariable<br />
angesprochen.<br />
� � Auf jede Variable kann während der gesamten Programmausführung zugegriffen<br />
werden.<br />
� � Eine Variable des Typs boolean[] speichert ein Feld.<br />
� � Ein Programm kann Felder erzeugen, auf die es später nicht mehr zugreifen kann.<br />
� � Es kann mehrere Variablen geben, die auf dasselbe Feld zeigen.
Aufgabe 7 Weihnachtsaufgabe (freiwillige Zusatzaufgabe)<br />
Mit Hilfe von Turtlegraphics lassen sich auf einfache Art und Weise sehr komplexe Grafiken generieren.<br />
Man stelle sich dafür vor, dass man eine Schildkröte (engl. Turtle) über eine Fläche steuert, die dabei eine<br />
Spur hinterlässt. Die Schildkröte reagiert auf einfache Befehle, wie “vorwärts” oder “drehen”.<br />
Die Schildkröte startet im Ursprung (0, 0) des karthesischen Koordinatensystems und schaut nach rechts<br />
(Richtung x-Achse). Sie reagiert auf die folgenden Befehle:<br />
forward x Die Schildkröte läuft vorwärts in Blickrichtung und legt eine Strecke von x zurück.<br />
left x / right x Die Schildkröte dreht sich um x Grad nach links/rechts.<br />
up / down Die Schildkröte hört auf zu zeichnen / setzt wieder an zu zeichnen.<br />
Wie auch bei Ein-/Ausgabe in Haskell, so ist auch bei Schildkröten-Programmen die Reihenfolge der Befehle<br />
relevant! Analog zum Typ IO a verwenden wir also Turtle a Befehle, die in do-Blöcken kombiniert<br />
werden können.<br />
Betrachten wir den Effekt des folgenden Turtle-Programms:<br />
nico :: Turtle ()<br />
nico = do<br />
left 90<br />
forward 100<br />
right 45<br />
forward (sqrt 5000)<br />
right 90<br />
forward (sqrt 5000)<br />
right 45 -- Bild 1<br />
forward 100<br />
right 135<br />
forward (sqrt 20000)<br />
right 135<br />
forward 100 -- Bild 2<br />
right 135<br />
forward (sqrt 20000)<br />
left 135<br />
forward 100 -- Bild 3<br />
Laden Sie sich die Grundbibliothek “Turtle.hs” sowie die Datei “PDFTurtle.hs” (oder eine andere Turtle-<br />
Implementierung Ihrer Wahl) von der Vorlesungsseite. Wenn Sie nun das Modul PDFTurtle importieren,<br />
erhalten Sie sowohl alle Turtle-Befehle, als auch die Funktion render :: String -> Turtle a -> IO ().<br />
Diese nimmt einen Namen sowie ein Turtle-Programm und gibt das entstehende Bild im Sinne der Implementierung<br />
aus (hier: als PDF-Dokument). Da alle Implementierungen die gleiche Schnittstelle haben,<br />
können Sie jederzeit durch Austauschen des import auf eine andere Implementierung wechseln.<br />
a) Zeichnen Sie ein Dreieck, eine eckige Spirale und einen Kreis:<br />
Hinweis: Da die Schildkröte ausschließlich geradeaus laufen kann, müssen Sie den Kreis approximieren.<br />
Hinweis: Sie müssen sich nicht darum kümmern wie groß Ihre Grafik insgesamt wird. Einzig die relative<br />
Größe und Position zwischen Objekten einer Grafik ist für die Ausgabe relevant.
) Viele Grafiken lassen sich mit sehr kurzen rekursiven Turtle-Programmen zeichnen, da sie eine große<br />
Selbstähnlichkeit besitzen. Ein Baum, zum Beispiel, besteht letztendlich nur aus Zweigen. Ein Baum der<br />
Höhe 1 besteht aus nur einem einzigen geraden Zweig (Stamm). Ein Baum der Höhe 2 hat drei weitere,<br />
kürzere Zweige, die aus dem Baum der Höhe 1 entspringen. Bei Höhe 3 kommen wieder jeweils drei<br />
kürzere Zweige hinzu. Das gesuchte Programm hat also zwei Parameter, einen für die Höhe, an der<br />
abgebrochen werden soll, und einen für die aktuelle Länge des Zweigs.<br />
Schreiben Sie ein rekursives Turtle-Programm tree :: Int -> Double -> Turtle (), welches einen<br />
Baum dieser Art zeichnet.<br />
Hinweis: Obwohl das Programm tree schwieriger zu schreiben ist, als das Beispielprogramm nico, ist<br />
es dennoch ein paar Zeilen kürzer!<br />
Tipp: Achten Sie darauf, in welcher Position sich die Schildkröte vor einem rekursiven Aufruf befindet<br />
und in welcher Sie sie nach dem Aufruf wiederfinden. Stellen Sie nützliche Garantien bezüglich Position<br />
und Blickrichtung evtl. vor dem Rücksprung des Programms wieder her!<br />
Hinweis: Wie stark Sie die Länge der Zweige von einer Ebene zur nächsten verkürzen und wie Sie die<br />
Winkel wählen in denen Zweige abstehen, bestimmen maßgeblich das Aussehen des Baums!
c) Eine andere selbstähnliche Figur ist das Sierpinski Dreieck. In der Datei “sierpinski.hs” finden Sie<br />
ein Turtle-Programm, welches es für verschiedene Stufen zeichnet:<br />
Es gibt auch hier eine wesentlich kürzere – wenn auch nicht einfachere – Variante das Sierpinski Dreieck<br />
anzunähern. Anstatt immer kleinere, unabhängige Dreiecke zu zeichnen, wird eine einzige Kurve dafür<br />
benutzt:<br />
Das Grundprimitiv auf Level 0 ist ein einfacher Strich. Level 1 baut drei dieser Striche zu einer Linkskurve<br />
mit 60 Grad Neigungen zusammen. Im allgemeinen baut das jeweils nächste Level drei Versionen
des Vorlevels zu einer Links- oder Rechtskurve zusammen! Dabei wird die Drehrichtung der ersten und<br />
der dritten Version umgekehrt zur eigenen gewählt, die zweite Version dreht in dieselbe Richtung. Für<br />
die Abbildungen wurde jeweils die Anfangsdrehrichtung auf jedem Level alterniert, damit das Dreieck<br />
insgesamt richtig zum Betrachter ausgerichtet ist.<br />
Schreiben Sie ein Turtle-Programm sierpinski :: Integer -> Bool -> Turtle (), welches das aktuelle<br />
Level, sowie die aktuelle Drehrichtung nimmt und das Sierpinski-Dreieck als Kurve realisiert.<br />
Hinweis: Die korrekte Lösung ist schon mit wenigen, kurzen Zeilen möglich!<br />
d) Informieren Sie sich über die Drachenkurve und implementieren Sie sie als Turtle-Programm:<br />
e) Zeichnen Sie einen Farn:<br />
Hinweis: Das Rekursionsschema ist im Prinzip dasselbe wie bei Bäumen, jedoch sind die Parameter<br />
völlig anders gewählt und der Abbruch der Rekursion wird durch die Größe, anstatt die Höhe, herbeigeführt.