Musterlösung

Transcription

Musterlösung
Algorithmen und Programmieren 1
Funktionale Programmierung
- Musterlösung zu Übung 1 Dozent: Prof. Dr. G. Rote
Tutoren: J. Fleischer, T. Haimberger,
N. Lehmann, C. Pockrandt, A. Steen
18.10.2011
Ziele dieser Übung
Grundlagen von Haskell lernen und verstehen.
In dieser Übung sollte man folgende Konzepte geübt und verstanden haben:
•
Haskellprogramme schreiben, compilieren und ausführen
•
Kommentare schreiben
•
Signaturen schreiben
Wahl guter Funktionsnamen
Datentypen
∗
∗
∗
Int, Integer (ganzzahlige Werte)
Float, Double (Gleitkommazahlen)
Bool (Wahrheitswerte)
•
einfache Funktionen schreiben
•
Die Funktion: div
•
Die Funktion: mod
•
Die Funktion: max
•
Präx- und Inx-Schreibweise von Funktionen (div,mod)
•
Hilfsfunktionen
Funktionen rufen Funktionen auf
Weitergabe von Funktionsparametern
1
Aufgabe 1: Dreiecke
Schreiben Sie eine Funktion zum Testen, ob
x, y, z
die Seitenlängen eines Dreiecks (mit positiver Flä-
che) sind. (Entartete Dreiecke, wo zum Beispiel zwei Ecken zusammenfallen, sind ausgeschlossen.)
Lösung zu Aufgabe 1:
-- Prüft, ob 3 Kanten ein Dreieck bilden können.
istDreieck1::Int->Int->Int->Bool
istDreieck1 x y z = x + y > z && x+z>y && z+y>x && x>0 && y>0 && z>0
-- Prüft, ob 3 Kanten ein Dreieck bilden können.
istDreieck2::Int->Int->Int->Bool
istDreieck2 x y z = x+y+z > 2*(max (max x y) z)
x > 0, y > 0, z > 0
istDreieck1 ist.
Zusatzfrage: Beweisen Sie, dass man die drei Bedingungen
Beweisen Sie, dass
istDreieck2
äquivalent zu
auch weglassen kann.
Aufgabe 2: Negative Argumente
Die zweistelligen Funktionen mod x y und div x y bestimmen den Rest von x bei Division durch y,
beziehungsweise den ganzzahligen Quotienten der Division unter Vernachlässigung des Restes.
a) Finden Sie durch Probieren heraus, was passiert, wenn für x und y auch negative Werte oder
0 eingesetzt werden.
Lösung zu Aufgabe 2a:
div
div
div
div
div
mod
mod
mod
mod
mod
(+7)
(-7)
(+7)
(-7)
(+2)
(+8)
(-8)
(+8)
(-8)
(+2)
(+2)
(+2)
(-2)
(-2)
( 0)
(+3)
(+3)
(-3)
(-3)
( 0)
=
=
=
=
=
=
=
=
=
=
(+3)
(-4)
(-4)
(+3)
Program error: divide by zero
(+2)
(+1)
(-1)
(-2)
Program error: divide by zero
b) Welche Beziehung gilt immer (mit wenigen Ausnahmen; mit welchen?) zwischen
div
x − mod x y
x y?
Lösung zu Aufgabe 2b:
∀x, y ∈ Z, y 6= 0 : x − mod x y = (div x y) · y
c) Fassen Sie Ihre Ergebnisse zu Aufgabe a) in möglichst einfache Regeln.
Lösung zu Aufgabe 2c:
• y=0
•
ist nie erlaubt.
Das Ergebnis von mod
xy
hat immer das Vorzeichen von
• |mod x y| < |y|
•
x y wird das Ergebnis
y > 0 abgerundet (gegen −∞),
y < 0 aufgerundet (gegen +∞).
Bei div
für
für
2
y
(oder es ist Null).
und
Aufgabe 3: Überlauf
Im Gegensatz zum Typ Integer haben Gröÿen vom Typ Int einen Wert von höchstens
auch
231 − 1
oder
2147483647.
a) Was passiert, wenn bei einer Rechnung mit Gröÿen vom Type Int diese Grenze überschritten
wird?
(Bemerkung: Je nach Haskell-Umgebung oder Betriebssystem können Int-Werte auch bis zu
263 − 1
groÿ sein.)
Lösung zu Aufgabe 3a:
Sobald man das Maximum des Denitionsbereichs von Int überschritten hat, geht es bei dem Minimum des Denitionsbereichs von Int weiter.
(2^31)-1 +0::Int
2147483647
(2^31)-1 +1::Int
-2147483648
(2^31)-1 +2::Int
-2147483647
b) Welches ist der kleinste Wert, der im Typ Int dargestellt werden kann?
Lösung zu Aufgabe 3b:
Mit dem folgenden Haskell-Code kann man die untere Grenze von Int bestimmen:
minBound::Int
-2147483648
Das Ergebnis ist
−2147483648
oder auch
−231 .
Aufgabe 4: Zinseszinsberechnung
Die folgende Funktion bestimmte die Zinsen einer Anlage von zinsfuÿ %.
zinsen kapital zinsfuÿ = kapital * zinsfuÿ * 0.01
a) Denieren Sie unter Zuhilfenahme von
zinsen
eine Funktion
endwert
(mit geeigneten Parame-
tern), die den Wert der Anlage (Kapital+Zinsen) am Ende einer Zinsperiode bestimmt.
Lösung zu Aufgabe 4a:
zinsen::Double->Double->Double
zinsen kapi zfus = kapi * zfus * 0.01
-- Diese Funktion berechnet den Zinseszins nach einer Zeitperiode.
endwert::Double->Double->Double
endwert kapital zinssatz = kapital+(zinsen kapital zinssatz)
b) Denieren Sie eine Funktion
endwert2, die den Wert nach zwei Zinsperioden berechnet, wenn die
Zinsen am Ende der ersten Periode wieder angelegt werden.
Lösung zu Aufgabe 4b:
-- Diese Funktion berechnet den Zinseszins nach zwei Zeitperioden.
endwert2::Double->Double->Double
endwert2 kapital2 zinsfuss2 = endwert (endwert kapital2 zinsfuss2) zinsfuss2
3
Zusatzlösung zu Aufgabe 4b:
-- Diese Funktion berechnet endrekursiv den Zinseszins mit beliebiger Zeitperiode t
endwertN::Double->Double->Integer->Double
endwertN k z t
| t == 0
= k
| otherwise = endwertN (k+(zinsen k z)) z (t-1)
c) Funktioniert es auch mit der folgenden Denition?
zinsen kapital zinsfuÿ = kapital * zinsfuÿ / 100
Lösung zu Aufgabe 4c:
Die Typklassen der Funktionen (*) und (/) unterscheiden sich zwar,
:type (*)
(*) :: Num a => a -> a -> a
:type (/)
(/) :: Fractional a => a -> a -> a
da aber Float alle Eigenschaften beider Typklassen hat funktioniert es trotzdem.
Aufgabe 5: Gleitkommarechnungen
Vom mathematischen Standpunkt aus müsste die folgende Funktion immer 0 liefern:
zero:: Float -> Float
zero x = (1/x)*x - 1
Wegen Rundungsfehlern ist dies nicht immer der Fall.
a) Finden Sie Werte x, bei denen das Ergebnis von 0 verschieden ist.
-- Die Funktionen geben k Ergebnisse zurück,
Lösung zu Aufgabe 5a:
-- a) die die Funktion mit Float erfolgreich auf Abweichungen von 0 testen.
zeroF::Float->Float
zeroF x = (1/x )*x-1
zeroTesterF::[(Float,Float)]
zeroTesterF k = take k [(n,(zeroF n)) | n<-[1.0,2.0..], (zeroF n) /= 0]
Ergebnisliste für k=3:
[(41.0,-5.960464e-008),(47.0,-5.960464e-008),(55.0,-5.960464e-008)]
b) Ändert sich das Ergebnis beim Übergang zu Double?
Lösung zu Aufgabe 5b:
-- b) die die Funktion mit Float erfolgreich auf Abweichungen von 0 testen.
zeroD::Double->Double
zeroD x = (1/x )*x-1
zeroTesterD::[(Double,Double)]
zeroTesterD k = take k [(n,(zeroD n)) | n<-[1.0,2.0..], (zeroD n) /= 0]
Ergebnisliste für k=3:
[(49.0,-1.11022302462516e-016),(98.0,-1.11022302462516e-016),
(103.0,-1.11022302462516e-016)]
Warum ist das Ergebnis nie positiv??
4
Aufgabe 6: Gröÿer als der Durchschnitt
Schreiben Sie eine Funktion von drei Integer-Parametern, die ausgibt, wie viele der Eingabeparameter
gröÿer als der Durchschnittswert sind.
Lösung zu Aufgabe 6:
-- Gibt 1
b2i::Bool
b2i True
b2i False
aus falls True, gibt 0 aus falls False.
-> Integer
= 1
= 0
-- Berechnet den Durchschnitt der 3 Eingabeparameter.
-- Wichtig ist die Typumwandlung vor der Division
avg::Integer->Integer->Integer->Double
avg a b c = fromIntegral (a+b+c) / 3
-- Zählt wie viel der Eingabeparameter gröÿer als der Durchschnitt sind.
biggerThanAVG::Integer->Integer->Integer->Integer
biggerThanAVG x y z = b2i (fromIntegral x > avg x y z)
+ b2i (fromIntegral y > avg x y z)+ b2i (fromIntegral z > avg x y z)
Diese Lösung kann versagen, wenn die Werte so groÿ sind, dass beim Umwandeln in eine Gleitkomma-
x = 123123123123123123 und die Werte x, x, x + 1.
x > (x + y + z)/3 um, indem man sie mit 3 multipliziert:
zahl Rundungsfehler auftreten, zum Beispiel für
Alternative: Man formt die Ungleichung
3x > x + y + z ⇐⇒ 2x > x + z
biggerThanAVG':: Integer->Integer->Integer->Integer
biggerThanAVG' x y z = b2i (2*x > y+z) + b2i (2*y > x+z)+ b2i (2*z > x+y)
Alternative 2: Wenn man
(a+b+c) 3),
avg
mit ganzzahliger Division deniert (zum Beispiel
dann funktioniert es auch. Der Grund ist, dass für ganzzahliges
x
avg a b c = div
α gilt
und beliebiges
x > α ⇐⇒ x > bαc
Dabei steht
α
für den exakten Durchschnittswert, und
bαc
ist der abgerundete Wert von
kleiner oder ≥ statt gröÿer würde diese Lösung nicht funktionieren.
5
α.
Mit

Documents pareils