Algorithmen und Programmieren 1 Funktionale Programmierung - Musterlösung zur Übungsklausur Punkte: A1: 30, A2: 20, A3: Punkte: 20, A4: 20, A5: 10, A6: 20 /120 12.02.2012 Hinweis: Geben Sie bei allen verwendeten Funktionen die Signaturen an. 1 Viel Erfolg! (10+10+10=30 Punkte) Haskell-la-vista! 1.1 Funktionen höherer Ordnung (10 Teilpunkte) Schreiben Sie unter Verwendung der map und foldr Funktionen eine Funktion sumQuad, die bei Eingabe eines ganzzahligen positiven Werts n, die Summe aller Quadratzahlen von 1 bis n berechnet. Lösung sumQuad :: Integer -> Integer sumQuad n | (n >= 0) = foldr (+) 0 (map (^2) [1..n]) | otherwise = error "wrong input!" 1 1.2 Klassen + Instanzen (10 Teilpunkte) Seien zwei verschiedene algebraische Datentypen, einer für die Darstellung der natürlichen Zahlen (N) und einer für die Darstellung der ganzen Zahlen (Z) gegeben: data N = Zero | S (N) deriving (Show) data Z = Z (N,N) deriving (Show) Erklärung: Wenn a die Zahl x darstellt und b die Zahl y darstellt, dann stellt Z (a,b) die Zahl x − y dar. Beispiele: Mit dem Datentyp N kann man Zahlen wie folgt darstellen: 3 = S(S(S(Zero))) 7 = S(S(S(S(S(S(S(Zero))))))) Mit dem Datentyp Z kann man Zahlen wie folgt darstellen: 3 = ( S(S(S(Zero))) , Zero ) 3 = ( S(S(S(S(Zero)))) , S(Zero) ) -2 = ( Zero , S(S(Zero)) ) -2 = ( S(S(Zero)) , S(S(S(S(Zero)))) ) Schreiben Sie die Typ-Klasse Rechnen, die Ihnen die Operation Addition bereitstellt und implementieren Sie jeweils eine Instanz für jeden algebraischen Datentypen. Lösung class Rechnen a where add::a -> a -> a instance Rechnen N where add::N -> N -> N add Zero y = y add S(x) y = add x S(y) instance Rechnen Z where add::Z -> Z -> Z add (Z (a,b)) (Z (x,y)) = Z ((add a x),(add b y)) 2 1.3 Typ-Inferenz (10 Teilpunkte) Es seien folgende Funktionssignaturen bekannt. map sum length show :: :: :: :: (a -> b) -> [a] -> [b] (Num a) => [a] -> a [a] -> Int (Show a) => a -> String Bestimmen Sie den Typ der Funktionen: f = sum . map sum g = map (show . length) Lösung f :: (Num a) => [[a]] -> a Erklärung zu f: map hat den sum hat den sum hat den sum hat den 01) 02) 03) 04) 05) 06) 07) 08) 09) 10) 11) Typ (a -> b) -> [a] -> [b] Typ Num c => [c] -> c Typ Num d => [d] -> d Typ a -> b, weil es das erste Argument von map ist a = Num d => [d], wegen 03) und 04) b = Num d => d, wegen 03) und 04) x hat den Typ [a] =05) Num d => [[d]], weil es das zweite Argument von map ist (map length x) hat den Typ [b] =06) Num d => [d], wegen 01) (map length x) hat den Typ Num c => [c], weil es das erste Argument von sum ist c = d, wegen 08) und 09) sum (map length x) hat den Typ Num d => d, wegen 02) und 10) Aus 07) und 11) folgt, dass (sum . map length) den Typ Num d => [[d]] -> d hat. g :: [[a]] -> [String] Um den Typ von map (show . length) zu bestimmen, schauen wir zuerst, was der Typ von show . length ist. Nach Denition von (.) gilt: (show . length) x = show (length x) Erklärung zu g: 01) show hat den Typ Show a => a -> String 02) length hat den Typ [b] -> Int 03) x hat den Typ [b], weil es das erste Argument von length ist 04) (length x) hat den Typ Int, wegen 02) 05) (length x) hat den Typ Show a => a, weil es das erste Argument von show ist 06) show (length x) hat den Typ String, wegen 01) 07) (show . length) hat den Typ [b] -> String, wegen 03) und 06) Nun können wir den Typ von map (show . length) bestimmen: 08) map hat den Typ (c -> d) -> [c] -> [d] 09) (show . length) hat den Typ c -> d, weil es das erste Argument von map ist 10) c = [b], wegen 07) und 09) 11) d = String, wegen 07) und 09) Aus 08), 10) und 11) folgt, dass map (show . length) den Typ [[b]] -> [String] hat. 3 2 (20 Punkte) Datenbank Schreiben Sie ein Modul SimpleDB, das einen algebraischen Datentyp Datenbank verwendet um eine einfache Datenbank zu simulieren. Eine Datenbank ist eine Liste von 2-Tupeln. Jedes dieser 2Tupel hat die Form: (key, [value1 , value2 , ... , valuen ]), wobei n ≥ 1 und jeder Schlüssel einzigartig sein muss (Primärschlüssel). Überlegen Sie sich geeignete Typklassen für die Typen der Schlüssel und der Werte. Es sollen folgende Funktionen implementiert werden: • create Erzeugt eine leere Datenbank. Der Rückgabewert ist eine Datenbank. • select db key Diese Funktion gibt alle Wert zurück, die einem Schlüssel in einer Datenbank zugeordnet sind. Falls der Schlüssel nicht existiert wird eine leere Liste ausgegeben. Falls der Schlüssel existiert wird eine Liste mit allen Werten, die diesem Schlüssel zugeordnet sind, zurückgegeben. Der Rückgabewert ist eine Liste von Werten. • update db key value Diese Funktion fügt einer Datenbank einen Schlüsseleintrag mit zugeordnetem Wert hinzu. Falls der Schlüssel existiert und der Wert noch nicht dem Schlüssel zugeordnet ist, wird der Wert dem Schlüssel zugeordnet. Falls der Schlüssel existiert und der Wert bereits dem Schlüssel zugeordnet ist, wird nichts verändert. Falls der Schlüssel nicht existiert wird dieser erzeugt und der Wert diesem zugeordnet. Der Rückgabewert ist eine Datenbank. • drop db key value Diese Funktion löscht aus einer Datenbank einen Wert, der einem Schlüssel zugeordnet war. Falls der Schlüssel oder der Wert nicht existiert wird ein Fehler ausgegeben. Falls der Schlüssel existiert wird der Wert aus der Zuordnung zum Schlüssel entfernt. Falls der Schlüssel existiert und nur noch der angegebene Wert als einziger Wert dem Schlüssel zugeordnet ist, wird der Schlüssel ebenfalls aus der Datenbank entfernt. Der Rückgabewert ist eine Datenbank. Tipp: Hilfreiche Funktionen sind elem, takeWhile, dropWhile und filter. 4 Lösung module SimpleDB (Datenbank, create, select, update, drop) where -- Dieses Modul stellt eine ganz einfache Datenbank dar. data (Eq k, Eq v) => Datenbank k v = DB [(k,[v])] deriving (Show) create select update drop :: :: :: :: (Eq (Eq (Eq (Eq k, k, k, k, Eq Eq Eq Eq v) v) v) v) => => => => Datenbank Datenbank Datenbank Datenbank k k k k v v -> k -> [v] v -> k -> [v] -> Datenbank k v v -> k -> [v] -> Datenbank k v create = DB [] select select | | (DB []) _ = [] (DB ((key', values'):xs)) key (key' == key) = values' otherwise = select (DB xs) key update (DB xs) key values | (isElement (DB xs) key) == False = error "Key existiert nicht!" | otherwise = (DB (loKey ++ [(key, values)] ++ roKey)) where loKey = (takeWhile (\x -> (fst x) /= key) xs) roKey = (tail ((dropWhile (\x -> (fst x) /= key)) xs)) drop (DB xs) key values | (isElement (DB xs) key) == False = error "Key existiert nicht!" | otherwise = (DB (loKey ++ roKey)) where loKey = (takeWhile (\x -> (fst x) /= key) xs) roKey = (tail ((dropWhile (\x -> (fst x) /= key)) xs)) -- Diese Funktion kann nach dem Import des Moduls nicht direkt aufgerufen werden. -- Scope = private, da isElement nicht in der Exportliste aufgeführt ist. isElement :: (Eq k, Eq v) => Datenbank k v -> k -> Bool isElement (DB []) _ = False isElement (DB ((key',_):xs)) key = (key' == key) || isElement (DB xs) key Eine andere Lösung nden Sie auf der Veranstaltungsseite. 5 3 (10+10=20 Punkte) Sortieren und Laufzeit 3.1 Sortieren durch Einfügen (10 Teilpunkte) Implementieren Sie den Insertion-Sort-Algorithmus. Lösung isort :: [Integer] -> [Integer] isort [] = [] isort (x:xs) = ins x (isort xs) where ins :: Integer -> [Integer] -> [Integer] ins x [] = [x] ins x (y:ys) | (x <= y) = (x:y:ys) | otherwise = y:(ins x ys) 3.2 Laufzeit (10 Teilpunkte) Bestimmen Sie die Laufzeit Ihrer Implementierung. Es reicht wenn Sie die Laufzeit in O-Notation angeben und diese textuell begründen. Lösung Die Funktion isort hat eine Laufzeit von O(n2 ). iSort besteht aus zwei Teilen. Der erste Teil durchläuft alle Elemente der Liste, also n Elemente. Bei jedem Durchlauf wird der zweite Teil aufgerufen. Im zweiten Teil wird jedes Element mit dem Kopf der Restliste verglichen und sortiert eingefügt. Der zweite Teil vergleicht (n − 1) Elemente miteinander. Zusammen ergibt das eine Laufzeit von n · (n − 1) = n2 − n ∈ O(n2 ). 6 4 (10+10=20 Punkte) Strukturelle Induktion 4.1 Bäume (10 Teilpunkte) Sei folgender algebraischer Datentyp und folgende Gleichungen gegeben: data Baum = Blatt Int | Knoten Baum Baum deriving (Show) -- wendet die Funktion f auf jedes Element des Baumes an mapTree :: (Int -> Int) -> Baum -> Baum mapTree f (Blatt x) = Blatt (f x) -- mapT.1 mapTree f (Knoten lB rB) = Knoten (mapTree f lB) (mapTree f rB) -- mapT.2 Beweisen Sie mit struktureller Induktion folgende Behauptung: mapTree id baum = id baum Lösung Es folgt ein Beweis mit struktureller Induktion über (die Tiefe des Baumes) t. zu zeigen: mapTree id baum = id baum Induktionsbasis: t = 0, Baum = Blatt x mapTree id Baum[t=0] d.h. mapTree id (Blatt x) = id (Blatt x) <==> (Blatt (id x)) = id (Blatt x) <==> (Blatt x) = (Blatt x) -- mapT.1 Induktionsvoraussetzung: Sei die Behauptung wahr für alle DT vom Typ Baum der Tiefe t <= n. Induktionsbehauptung: Die Behauptung ist wahr für alle DT vom Typ Baum der Tiefe t <= n+1. Induktionsschritt: n -> n+1 d.h. <==> <==> <==> mapTree mapTree (Knoten (Knoten (Knoten id Baum[t<=(n+1)] id (Knoten lB rB) = id (Knoten (mapTree id lB) (mapTree id rB)) = id (Knoten (id lB) (id rB)) = id (Knoten (lB) (rB)) = (Knoten lB lB rB) lB rB) lB rB) rB) -- mapT.2 -- nach IV Es wurde mit struktureller Induktion gezeigt, dass die Behauptung gilt. 7 4.2 Listen (10 Teilpunkte) Seien folgende Gleichungen gegeben: ++.1 ++.2 : [] ++ ys : (x:xs) ++ ys = ys = x:(xs ++ ys) rev.1 : reverse [] = [] rev.2 : reverse (x:xs) = reverse xs ++ [x] rev.3 : reverse (xs ++ ys) = (reverse ys) ++ (reverse xs) Beweisen Sie mit struktureller Induktion folgende Behauptung: reverse (reverse xs) = xs Lösung Es folgt ein Beweis mit struktureller Induktion. Induktionsbasis: xs = [] reverse (reverse []) = [] <==> reverse [] = [] <==> [] = [] rev.1 rev.1 Induktionsvoraussetzung: Für eine Liste xs mit beliebiger, aber fester Länge gilt: reverse (reverse xs) = xs Induktionsbehauptung: Dann gilt auch: reverse (reverse (x:xs)) = (x:xs) Induktionsschritt: xs -> (x:xs) reverse (reverse (x:xs)) = (x:xs) <==> reverse (reverse xs ++ [x]) = (x:xs) <==> reverse [x] ++ reverse (reverse xs) = (x:xs) <==> reverse (x:[]) ++ reverse (reverse xs) = (x:xs) <==> (reverse [] ++ [x]) ++ reverse (reverse xs) = (x:xs) <==> ([] ++ [x]) ++ reverse (reverse xs) = (x:xs) <==> [x] ++ reverse (reverse xs) = (x:xs) <==> [x] ++ xs = (x:xs) <==> x:[] ++ xs = (x:xs) <==> x:([] ++ xs) = (x:xs) <==> (x:xs) = (x:xs) rev.2 rev.3 rev.2 rev.1 ++.1 nach IV ++.2 ++.1 Es wurde mit struktureller Induktion gezeigt, dass die Behauptung gilt. 8 5 (10 Punkte) Primitive Rekursion Rekursionsschema: R(0, x1 , ..., xm ) = g(x1 , ..., xm ) R(S(n), x1 , ..., xm ) = h(R(n, x1 , ..., xm ), n, x1 , ..., xm ) Zeigen Sie, dass die Funktion isOdd (Ein Prädikat, das prüft ob eine Zahl ungerade ist. Das Ergebnis ist 1 wenn die Zahl ungerade ist und 0 sonst.) primitiv-Rekursiv ist. Sie dürfen nur die Grundfunktionen S und Z voraussetzen, das heiÿt, alle Funktion die Sie ansonsten für Ihren Beweis verwenden müssen Sie ebenfalls beweisen. Lösung not 0 = one not n = Z (not (n-1)) where one = S Z isOdd 0 = Z isOdd n = not (isOdd (n-1)) 6 λ - Kalkül 6.1 Freie und gebundene Ausdrücke (5+5+10=20 Punkte) (5 Teilpunkte) Bestimmen Sie die freien und gebundenen Variablen im folgenden Ausdruck und geben Sie diese explizit an. Benennen Sie die gebundenen Variablen so um, dass in jedem Ausdruck alle λAbstraktionen verschiedene Variablen binden. (λab.(λab.(λc.(λab.abc))ab(cc))(λab.a)(λac.c(c(a)))) Lösung (λab.(λde.(λc.(λf g.f gc))de(xx))(λhi.h)(λjk.k(k(j)))) Gebundene Variablen: a, b, c, d, e, f, g, h, i, j, k Freie Variablen: x 9 6.2 λ - Ausdruck reduzieren (5 Teilpunkte) (λa.a(λab.b)(λa.a(λab.b)(λab.a))(λab.b))(λab.ab) Lösung Es gibt 2 Möglichkeiten das Problem zu lösen: Elegante Lösung: Z 1 F z }| { z }| { z }| { (λa.a (λab.b) (λa.a(λab.b)(λab.a)) (λab.b)) (λab.ab) = (λab.b) | {z } | {z } | {z } ¬ F F Normale Lösung: (λa.a(λab.b)(λa.a(λab.b)(λab.a))(λab.b))(λab.ab) β =⇒ (λab.ab)(λab.b)(λa.a(λab.b)(λab.a))(λab.b) =⇒β (λb.(λab.b)b)(λa.a(λab.b)(λab.a))(λab.b) =⇒β (λab.b)(λa.a(λab.b)(λab.a))(λab.b) =⇒β (λb.b)(λab.b) =⇒β (λab.b) ≡ F 6.3 λ - Ausdruck erstellen (10 Teilpunkte) Schreiben Sie einen λ - Ausdruck, der rekursiv die Quadrate aller natürlichen Zahlen von 0 bis n addiert. Sie können dabei die folgenden Funktionen als gegeben voraussetzen: • A(Addition) • M(Multiplikation) • P(Vorgänger) • Y(Fixpunktoperator) • Z(Vergleich auf 0) Lösung sumQuad :: Integer -> Integer sumQuad = if n == 0 then 0 else (n*n) + sumQuad (n-1) sumQuad ≡ Y (λrn. (Zn) (0) (A(M (n)(n))(r(P n)))) {z } | {z } |{z} | if then else 10