Algorithmen und Programmieren 1 Funktionale Programmierung - Musterlösung zu Übung 3 Dozent: Prof. Dr. G. Rote Tutoren: J. Fleischer, T. Haimberger, N. Lehmann, C. Pockrandt, A. Steen 01.11.2011 Ziele dieser Übung Mit Funktionen höherer Ordnung sicher in Haskell umgehen und die Idee der Gültigkeitsbereiche verstehen. In dieser Übung sollte man folgende Konzepte geübt und verstanden haben: • Gültigkeitsbereiche (Scope) • Listengeneratoren • Typdenitionen (type) • Funktionen höherer Ordnung iterate map (.) Aufgabe 13: Kleiner als der Durchschnitt Schreiben Sie eine Funktion, die berechnet, wie viele Zahlen in einer Liste von kleiner Integer-Werten als der Durchschnittswert sind. -- Berechnet, wie viele Zahlen in der Liste xs kleiner als der Durchschnittswert sind. lessThanAVG :: [Integer] -> Int lessThanAVG xs = length (filter (`ltAvg` xs) xs) -- Prueft, ob eine Zahl x kleiner als der Durchschnittswert der Liste xs ist. ltAvg :: Integer -> [Integer] -> Bool ltAvg x xs = fromIntegral (length xs) * x < sum xs 1 Aufgabe 12: Gültigkeitsbereiche Geben Sie für jeden eingeführten Namen den Gültigkeitsbereich an. Aufgabe 12a) x = 25:: Integer --> biggerThanAVG3:: Integer->Integer->Integer->Integer biggerThanAVG3 x y z = --> sum (map (\x -> if fromIntegral x > avg3 x y z then 1 where avg3:: Integer->Integer->Integer->Double avg3 a b c = fromIntegral (a+b+c) / 3 --> test1:: Integer test1 = biggerThanAVG3 3 4 5 --> fläche:: Float -> Float fläche x = 2*x^2*pi --> public scope public scope else 0) [x ,y, z]) private scope(biggerThanAVG3) public scope public scope • x ist global sichtbar. Allerdings NICHT in den Funktionen biggerThanAVG3 und fläche. • biggerThanAVG3 ist global sichtbar, jeder kann biggerThanAVG3 aufrufen. • x im Ausdruck biggerThanAVG3 x y z ist nicht im Ausdruck (\x -> if fromIntegral x > avg3 x y z then 1 else 0), y und z hingegen schon. • avg3 biggerThanAVG3 ist nur für und alle Unterfunktionen von biggerThanAVG3 sicht- bar. • test1 ist global sichtbar, jeder kann • fläche test1 ist global sichtbar, jeder kann aufrufen. fläche aufrufen. Aufgabe 12b) f x y = let n = 3 in take n (g y) ++ take n (g x) where g x = take n xys where xys = [x] ++ yxs yxs = [y] ++ xys n = 10 • f --> public scope --> private scope(f) --> private scope(g) --> private scope(g) --> private scope(f) ist global sichtbar, jeder kann f aufrufen. • n im Ausdruck n=3 ist durch die let <expr> in <expr> take n (g y) ++ take n (g x) sichtbar • g ist nur für f und alle Unterfunktionen von g nur im Ausdruck sichtbar. • xys ist nur für g und alle Unterfunktionen von g sichtbar. • xys ist nur für g und alle Unterfunktionen von g sichtbar. • n im Ausdruck n=10 ist nur für f und alle Unterfunktionen von f n aus dem let <expr> in <epxr> dieses n. sichtbar, allerdings überschattet das • x • im Ausdruck Das y f x y im Ausdruck wird durch das f x y ist in ganz x im Ausdruck f sichtbar. 2 g x überschattet. Aufgabe 14: Preisberechnung Listen der beiden folgenden Datentypen type Einkaufsliste = [(String, Float)] type Preisliste = [(String, Float)] geben an, Beispiel was gekauft werden soll und wie viel (in einer passenden Einheit, z. B. kg), zum [("Mehl",0.5), ("Butter",0.25)], und andererseits den Preis in Euro pro Einheit für jeden Artikel. Aufgabe 14a) Denieren Sie eine Funktion preis:: Preisliste -> Einkaufsliste -> (Float,[String]) zur Berechnung des Gesamtpreises aller Artikel einer Einkaufsliste. Die zweite Komponente des Ergebnisses soll die Liste der Artikelnamen enthalten, die in der Preisliste nicht gefunden wurden. type Einkaufsliste = [(String, Float)] type Preisliste = [(String, Float)] preis :: Preisliste -> Einkaufsliste -> (Float, [String]) preis preise einkauf = (gesamt, nicht_in_liste) where nicht_in_liste = [x | x <- map fst einkauf, not $ elem x bekannte_preise] bekannte_preise = map fst preise gesamt = sum [p * p' | (n, p) <- preise, (n', p') <- einkauf, n == n'] -- Testfunktion testEinkauf = preis [("Butter", 1.09),("Brot", 2.49),("Wurst", 0.99),("Käse", 1.99)] [("Brot", 2.49),("Butter", 1.09),("Malzbier", 1.0)] Aufgabe 14b) Erweitern Sie die Funktion so, dass der Preis für jeden gekauften Artikel der Einkaufsliste auf Cent gerundet wird. type Einkaufsliste = [(String, Float)] type Preisliste = [(String, Float)] fixedPreis :: Preisliste -> fixedPreis preise einkauf = where nicht_in_liste = [x | bekannte_preise = map gesamt = sum [centFix Einkaufsliste -> (Float, [String]) (gesamt, nicht_in_liste) x <- map fst einkauf, not $ elem x bekannte_preise] fst preise (p * p') | (n, p) <- preise, (n', p') <- einkauf, n == n'] centFix :: Float -> Float centFix x = fromIntegral (round (x*100))/100 -- Testfunktion testEinkauf = fixedPreis [("Butter", 1.09),("Brot", 2.49),("Wurst", 0.99),("Käse", 1.99)] [("Brot", 2.49),("Butter", 1.09),("Malzbier", 1.0)] 3 Aufgabe 15: Funktionsiteration iter wendet eine Funktion f n-mal hintereinander auf ein Argument iter 3 f x das Ergebnis f (f (f (x))), das man in der Mathematik f 3 (x) oder noch genauer f (3) (x) schreibt, damit man es nicht mit der Potenz Die folgende Funktion x an. Zum Beispiel liefert manchmal als (f (x))3 verwechselt. iter n f x | n==0 = x | n>0 = f (iter (n-1) f x) Aufgabe 15a) Welchen Typ hat iter? -- Theoretisch könnte der Typ von iter wie folgt aussehen: iter :: (Num t, Ord t) => t -> (a -> a) -> a -> a -- Da die Abbruchbedingung aber n == 0 ist (statt z.B. n < 0) -- sollte man Bruchzahlen ausschlieÿen. iter :: Integral t => t -> (a -> a) -> a -> a Aufgabe 15b) Geben Sie eine alternative Denition als Funktion iter n f ohne den Parameter x. -- Eine alternative Definition ohne den Parameter x. iter2 :: Int -> (a -> a) -> a -> a iter2 n f | n == 0 = id -- Identität | n > 0 = f.(iter (n-1) f) -- Komposition Aufgabe 15c) Lösen Sie Aufgabe 4b (Zinseszinsen) mit Hilfe der Funktion iter und der Lösung von Aufgabe 4a. (Achten Sie auf die Reihenfolge der Argumente.) -- Die gegebene Funktion zur Berechnung der Zinsen. zinsen :: Double -> Double -> Double zinsen kapital zinsfuss = kapital * zinsfuss * 0.01 -- Diese Funktion berechnet den Zinseszins nach einer Zeitperiode. endwert :: Double -> Double -> Double endwert kapital zinsfuss = kapital + (zinsen kapital zinsfuss) -- Diese Funktion berechnet den Zinseszins nach zwei Zeitperioden. endwert2 :: Double -> Double -> Double endwert2 kapital zinsfuss = iter 2 (`endwert` zinsfuss) kapital -- Diese Funktion berechnet den Zinseszins nach k Zeitperioden. endwertN :: Double -> Double -> Int -> Double endwertN kapital zinsfuss k = iter k (`endwert` zinsfuss) kapital 4 Aufgabe 16: Iterierter Logarithmus Die Funktion Zahl y, logBase a x = loga x berechnet den Logarithmus von x zur Basis a; das ist die ay = x ist. für die Aufgabe 16a) Bestimmen Sie die kleinste Zahl x, für die logBase 2 x >= 5 ist. -- Findet die kleinste natuerliche Zahl, fuer die logBase b x >= y ist. -- Idee: logBase b x >= y gilt genau dann, wenn x >= b**y ist. minLog :: Double -> Double -> Int minLog b y = ceiling (b**y) -- Es folgt eine alternative Definitionen mit Listengeneratoren. -- Die Auswertung dauert wesentlich länger. minLog' :: Double -> Double -> Int minLog' b y = (truncate.head) kandidaten where kandidaten = [x | x <- [1..], logBase b x >= y] Aufgabe 16b) Bestimmen Sie die kleinste Zahl x, für die iter 3 (logBase 2) x >= 2 ist. -- Findet die kleinste natuerliche Zahl, fuer die -- iter n (logBase b) x >= y ist. -- Idee: logBase b (logBase b (logBase b x)) >= y gilt genau dann, -- wenn x >= b**b**b**y ist. minIter :: Int -> Double -> Double -> Int minIter n b y = ceiling (iter n (b**) y) -- Es folgt eine alternative Definitionen mit Listengeneratoren. -- Die Auswertung dauert wesentlich länger. minIter' :: Int -> Double -> Double -> Int minIter' n b y = (truncate.head) kandidaten where kandidaten = [x | x <- [1..], iter n (logBase b) x >= y] 5 Aufgabe 17: Potenzieren, Summe und Produkt Das Potenzieren mit einer natürlich Zahl als Exponent kann man als iteriertes Multiplizieren denieren: xn := x · x · x · · · x mit n Faktoren: potenz x n = iter n (x*) 1 Aufgabe 17a) ·x ·· turm x k = x ↑ k := rechten Seite x insgesamt k -mal Die Turmfunktion Turm auf der x xx ist eine geschachtelte Potenz, bei der in dem vorkommt. (Potenzieren ist rechtsassoziativ!) Denieren Sie diese Funktion unter Verwendung von potenz und iter. -- Berechnet die Potenz x^n. potenz :: Num a => a -> Integer -> a potenz x n = iter n (x*) 1 -- Die in der Aufgabenstellung beschriebene Turmfunktion. turm :: Integer -> Integer -> Integer turm x k = iter (k-1) (potenz x) x Aufgabe17b) Die Multiplikation chende Funktion kann als iterierte Addition aufgefasst werden. Schreiben Sie eine entspre- mal a b, die analog zur Funktion potenz das Produkt als iterierte Summe deniert. -- Definiert das Produkt als iterierte Summe. mal :: Num a => Integer -> a -> a mal x y = iter x (y+) 0 Aufgabe 17c) Welche Funktion muss man iterieren, damit man die Summenfunktion erhält? Schreiben Sie eine entsprechende Funktionsdenition für plus. plus a b = a + b -- Um die Summenfunktion zu erhalten, muss man die Nachfolgerfunktion (+1) iterieren. plus :: Num a => Integer -> a -> a plus a b = iter a (+1) b 6 Aufgabe 18: Funktionsiteration Aufgabe 18a) g = iter 23 deniert. Wie können Sie das Argument 23 aus der g herausnden? Schreiben Sie eine Funktion entdecke, die für alle n ≥ 0 die folgende Jemand hat die Funktion Funktion Beziehung erfüllt: entdecke (iter n) == n, -- Die Beispielfunktion aus der Aufgabenstellung. g :: (a -> a) -> a -> a g = iter 23 -- Es soll gelten: entdecke (iter n) == n. entdecke :: (Num a, Num b) => ((a -> a) -> b -> c) -> c entdecke f = f (1+) 0 Aufgabe 18b) Addition: Schreiben Sie eine Funktion sumiter mit der Eigenschaft sumiter (iter a) (iter b) == iter (a+b), Verwenden Sie nicht einfach entdecke und +, für alle a, b ≥ 0. sondern suchen Sie eine direktere Denition. -- Es soll gelten: sumiter (iter n) (iter m) == iter (n+m) sumiter :: (a -> b -> c) -> (a -> c -> d) -> a -> b -> d sumiter f g h = (g h).(f h) -- Alternativ: sumiter f g h x = g h (f h x) Aufgabe 18c) Multiplikation: Schreiben Sie eine analoge Funktion proditer für das Produkt von -- Es soll gelten: proditer (iter n) (iter m) == iter (n*m) -- Schrittweise Vereinfachung der Definition: ---> proditer f g h x = f (g h) x ---> proditer f g h = f (g h) ---> proditer f g h = (f.g) h ---> proditer f g = f.g proditer :: (b -> c) -> (a -> b) -> a -> c proditer = (.) 7 a und b.