Algorithmen und Programmieren 1 Funktionale Programmierung - Musterlösung zu Übung 4 Dozent: Prof. Dr. G. Rote Tutoren: J. Fleischer, T. Haimberger, N. Lehmann, C. Pockrandt, A. Steen 11.11.2011 Ziele dieser Übung Mit Funktionen höherer Ordnung sicher in Haskell umgehen und die Idee der strukturellen Induktion verstehen. In dieser Übung sollte man folgende Konzepte geübt und verstanden haben: Funktionen höherer Ordnung map takeWhile foldr Strukturelle Induktion ohne Fallunterscheidung mit Fallunterscheidung Lauflängencodierung als Beispiel Aufgabe 19: Funktionen höherer Ordnung Drücken Sie die Funktion length mit Hilfe von sum und map und geeigneten selbst denierten Funktionen aus. (Eine Zeile sollte für die Denition reichen.) Lösung zu Aufgabe 19: myLength :: [a] -> Int myLength xs = sum ( map (\x -> 1) xs ) myLength' :: [a] -> Int myLength' = sum . map (\x -> 1) myLength ist äquivalent zu myLength' und zu length. 1 Aufgabe 20: Listenfunktionen Denieren Sie die Funktionen: takeWhile :: (a -> Bool) -> [a] -> [a] splitAt :: Int -> [a] -> ([a],[a]) Der Ausdruck takeWhile p x erzeugt das Anfangsstück einer Liste x, solange die Elemente das Prädikat p erfüllen. splitAt n x spaltet die Liste x nach den ersten n Elementen in zwei Teile auf. (Diese Funktionen sind in Haskell schon deniert. Zum Testen müssen Sie daher andere Funktionsnamen wählen.) Lösung zu Aufgabe 20: mySplitAt mySplitAt mySplitAt mySplitAt :: Int -> [a] -> ([a],[a]) 0 l = ([],l) n [] = ([],[]) n (x:xs) | n > 0 = (x:xs1,xs2) where (xs1,xs2) = mySplitAt (n-1) xs -- splitAt n liste = (take n liste, drop n liste) myTakeWhile :: (a -> Bool) -> [a] -> [a] myTakeWhile _ [] = [] myTakeWhile p (x:xs) | p x = x : myTakeWhile p xs | otherwise = [] Aufgabe 21: Gemapte Zinsen Welchen Typ hat der Ausdruck map ( zinsen 2.25) im Zusammenhang von Aufgabe 4? Was bewirkt diese Funktion? Lösung zu Aufgabe 21: Die Funktion hat den Typ [Double] -> [Double], generiert also eine Funktion die eine Liste von Double-Werten als einziges Argument erwartet und eine Liste von Double-Werten zurückgibt. Diese Funktion berechnet für jedes Element der Eingabeliste die Zinsen bei einer Verzinsung von 2,25 %. Erklärung: Durch die Verwendung von map wird die partiell angewandte Funktion zinsen auf jedes Element in der Liste angewandt und eine neue Liste mit den Ergebnissen erstellt. Durch die Verwendung des Backticks (`) wird die Funktion als Inx-Operator verwendet, das rechte (zweite) Argument wird mit 2.25 belegt (den selben Eekt kann man übrigens auch mit der Funktion flip erzielen). Da das linke (erste) Argument ausgelassen wird, entsteht somit eine Funktion die das linke (erste) Argument der Funktion zinsen als einziges Argument erwartet (also das zu verzinsende Kapital). 2 Aufgabe 22: Nicht-assoziative Faltung von Listen Beschreiben Sie das Ergebnis der Funktion differenzen:: Integer -> Integer -> Integer -> Integer differenzen a b c = foldr (-) a [b..c] durch eine explizite Formel in den Gröÿen a, b, c (oder mehrere Formeln). Beweisen Sie Ihre Formel (zum Beispiel durch vollständige Induktion nach c − b, oder auch direkt). Lösung zu Aufgabe 22: c-b c-b c-b c-b c-b c-b c-b ... = = = = = = = 0: 1: 2: 3: 4: 5: 6: b-a b-((b+1)-a) b-((b+1)-((b+2)-a)) ... ... ... ... = = = = = = = 0+b-a -1+a 1+b-a -2+a 2+b-a -3+a 3+b-a differenzen' a b c | c<b = a | even (c-b) = (div (b+c) 2) - a | odd (c-b) = (div (b-c-1) 2) + a a, falls c < b b+c − a, falls c − b ≥ 0 und gerade f (a, b, c) = 2 b − c − 1 + a, falls c − b ≥ 0 und ungerade 2 Falls c < b ist, ist die Liste [b .. c] leer; damit folgt unmittelbar, dass das Ergebnis a ist. Es folgt ein Beweis für den Fall c − b ≥ 0 mit vollständiger Induktion über c − b. b+c − a, falls (c − b) gerade 2 zu zeigen: ∀a, b, c : foldr (-) a [b..c] = f (a, b, c) := b − c − 1 + a, falls (c − b) ungerade 2 Induktionsbasis: Für c − b = 0 (d. h. c = b) gilt: foldr (-) a [b] = = = = b - (foldr (-) a []) b - a (b+b)/2 - a f(a,b,b) -- foldr.2 -- foldr.1 Induktionsvoraussetzung: Für c − b = k (d. h. c = b + k) und beliebiges a gilt: b + (b + k) − a, falls k gerade 2 foldr (-) a [b..(b+k)] = f (a, b, b + k) = b − (b + k) − 1 + a, falls k ungerade 2 3 Induktionsschritt: Für c − b = k + 1 (d. h. c = b + k + 1) gilt: foldr (-) a [b..(b+k+1)] = foldr (-) a ([b..(b+k)] ++ [b+k+1]) = foldr (-) (foldr (-) a [b+k+1]) [b..(b+k)] -- Aufg. 23b = foldr (-) (b+k+1-a) [b..(b+k)] -- foldr.1/2 Fall 1: k gerade foldr (-) a [b..(b+k+1)] = = = = foldr (-) (b+k+1-a) [b..(b+k)] (b+(b+k))/2 - (b+k+1-a) (b-(b+k+1)-1)/2 + a f(a,b,b+k+1) -- IV Fall 2: k ungerade foldr (-) a [b..(b+k+1)] = = = = foldr (-) (b+k+1-a) [b..(b+k)] (b-(b+k)-1)/2 + (b+k+1-a) (b+(b+k+1))/2 - a f(a,b,b+k+1) Es wurde mit vollständiger Induktion gezeigt, dass die angegebene Formel stimmt. Aufgabe 23: Strukturelle Induktion Aufgabe 23a: Beweisen Sie: map f (a ++ b) = map f a ++ map f b Lösung zu Aufgabe 23a: Folgende Funktionsdenitionen werden benötigt: map, ++ map::(a -> b) -> [a] -> [b] map _ [] = [] map f (x:xs) = (f x) : map f xs -- map.1 -- map.2 (++) :: [a] -> [a] -> [a] (++) [] ys = ys (++) (x:xs) ys = x : ((++) xs ys) -- ++.1 -- ++.2 Es folgt ein Beweis mit struktureller Induktion über xs. zu zeigen: map f (xs ++ ys) = map f xs ++ map f ys Induktionsbasis: xs == [] map f ([] ++ ys) = map f [] ++ map f ys ⇐⇒[++.1] map f ys = map f [] ++ map f ys ⇐⇒[map.1] map f ys = [] ++ map f ys ⇐⇒[++.1] map f ys = map f ys 4 -- IV Induktionsschritt: xs −→ (x:xs) Induktionsvoraussetzung: (IV) map f (xs ++ ys) = map f xs ++ map f ys Induktionsbehauptung: map f ((x:xs) ++ ys) = map f (x:xs) ++ map f ys ⇐⇒[++.2] map f (x:(xs ++ ys)) = map f (x:xs) ++ map f ys ⇐⇒[map.2] map f (x:(xs ++ ys)) = f x : (map f xs) ++ map f ys ⇐⇒[++.2] map f (x:(xs ++ ys)) = f x : (map f xs ++ map f ys) ⇐⇒[map.2] f x : map f (xs ++ ys) = f x : (map f xs ++ map f ys) ⇐⇒[IV ] f x : (map f xs ++ map f ys) = f x : (map f xs ++ map f ys) Aufgabe 23b: Wie kann man foldr g z (a ++ b) durch foldr auf den Listen a und b ausdrücken? Beweisen Sie Ihre Formel. Lösung zu Aufgabe 23b: Folgende Funktionsdenitionen werden benötigt: foldr, ++ foldr :: (a -> b -> b) -> b -> [a] -> b foldr f z [] = z -- foldr.1 foldr f z (x:xs) = f x (foldr f z xs) -- foldr.2 (++) :: [a] -> [a] -> [a] (++) [] ys = ys (++) (x:xs) ys = x : ((++) xs ys) -- ++.1 -- ++.2 Behauptung: foldr g z (a ++ b) = foldr g (foldr g z b) a Es folgt ein Beweis mit struktureller Induktion über xs. zu zeigen: foldr op n (xs ++ ys) = foldr op (foldr op n ys) xs Induktionsbasis: xs == [] foldr op n ([] ++ ys) = foldr op (foldr op n ys) [] ⇐⇒[++.1] foldr op n ys = foldr op (foldr op n ys) [] ⇐⇒[f oldr.1] foldr op n ys = foldr op n ys Induktionsschritt: xs −→ (x:xs) Induktionsvoraussetzung: foldr op n (xs ++ ys) = foldr op (foldr op n ys) xs Induktionsbehauptung: foldr op n ((x:xs) ++ ys) = foldr op (foldr op n ys) (x:xs) ⇐⇒[++.2] foldr op n (x:(xs ++ ys)) = foldr op (foldr op n ys) (x:xs) ⇐⇒[f oldr.2] op x foldr op n (xs ++ ys) = foldr op (foldr op n ys) (x:xs) ⇐⇒[f oldr.2] op x foldr op n (xs ++ ys) = op x foldr op (foldr op n ys) xs ⇐⇒[IV ] op x foldr op n (xs ++ ys) = op x foldr op n (xs ++ ys) xs 5 Aufgabe 23c: Drücken Sie elem x l durch map und foldr aus, und beweisen Sie damit elem x (a ++ b) = elem x a || elem x b, indem Sie Aufgabe (a) und (b) verwenden. Lösung zu Aufgabe 23c: Folgende Funktionsdenitionen werden benötigt: map, ++, foldr map::(a -> b) -> [a] -> [b] map _ [] = [] map f (x:xs) = (f x) : map f xs -- map.1 -- map.2 (++) :: [a] -> [a] -> [a] (++) [] ys = ys (++) (x:xs) ys = x : ((++) xs ys) -- ++.1 -- ++.2 foldr :: (a -> b -> b) -> b -> [a] -> b foldr f z [] = z foldr f z (x:xs) = f x (foldr f z xs) -- foldr.1 -- foldr.2 Es gilt: elem n xs = foldr (||) False (map (==n) xs) (1) zu zeigen: elem n (xs ++ ys) = elem n xs || elem n ys Beweis: linke Seite = elem n (xs ++ ys) =(1) foldr (||) False (map (==n) (xs++ys) =[23a] foldr (||) False (map (==n) xs ++ map (==n) (xs++ys)) =[23b] foldr (||) (foldr (||) False (map (==n) ys)) (map (==n) xs) =(1) foldr (||) (elem n ys)) (map (==n) xs) rechte Seite = elem n xs || elem n ys =(1) foldr (||) False (map (==n) xs) || elem n ys =(2), siehe unten foldr (||) (elem n ys)) (map (==n) xs) Um die Behauptung zu zeigen, benötigen wir als Abschluss folgende Hilfsaussage: foldr (||) z xs = z || foldr (||) False xs || z Beweis durch Induktion nach xs. Induktionsbasis: xs = []. zu zeigen: foldr (||) z [] = foldr (||) False [] || z ⇐⇒[foldr.1] z = False || z Das ist gültig (einfach z=True und z=False einsetzen). 6 (2) Induktionsschritt: von xs auf x:xs. foldr (||) z xs = foldr (||) False xs || z Induktionsbehauptung: foldr (||) z (x:xs) = foldr (||) False (x:xs) || z linke Seite = foldr (||) z (x:xs) =[foldr.2] x || foldr (||) z xs =[I.V.] x || (foldr (||) False xs || z) =[Der Operator || ist assoziativ] (x || foldr (||) False xs) || z =[foldr.2] foldr (||) False (x:xs) || z = rechte Seite Induktionsvoraussetzung: Aufgabe 23d: Beweisen Sie (take k) . (take l) = take (min k l) Lösung zu Aufgabe 23d: Folgende Funktionsdenitionen werden benötigt: take, min, (.) take take take take :: Int -> [a] -> [a] _ [] = [] n _ | n<=0 = [] n (x:xs) = x:(take (n-1) xs) -- take.1 -- take.2 -- take.3 min :: (Ord a) => a -> a -> a min x y | x <= y = x | otherwise = y -- min.1 -- min.2 (.) :: (a -> b) -> (c -> a) -> c -> b (.) f g x = f (g x) -- compose.1 Es folgt ein Beweis mit struktureller Induktion über xs. zu zeigen: (take k . take l) xs = take (min k l) xs Induktionsbasis: xs == [] (take k . take l) [] = take (min k l) [] ⇐⇒[take.1] (take k . take l) [] = [] ⇐⇒[compose.1] (take k (take l [])) = [] ⇐⇒[take.1] take k [] = [] ⇐⇒[take.1] [] = [] 7 Induktionsschritt: xs −→ (x:xs) Induktionsvoraussetzung: (take u . take v) xs = take (min u v) xs, für alle u, v . Induktionsbehauptung: (take k . take l) (x:xs) = take (min k l) (x:xs) Fall 1: 0 < k ≤ l ⇐⇒ (take k . take l) (x:xs) = take (min k l) (x:xs) ⇐⇒[compose.1] take k (take l (x:xs)) = take k (x:xs) ⇐⇒[take.3] take k (x:(take (l-1) xs)) = x:(take (k-1) xs) ⇐⇒take.3 x:(take (k-1) (take (l-1) xs)) = x:(take (k-1) xs) ⇐⇒compose.1 x:((take (k-1)) . (take (l-1)) xs) = x:(take (k-1) xs) ⇐⇒IV x:(take (k-1) xs) = x:(take (k-1) xs) Fall 2: k > l > 0 ⇐⇒ (take k . take l) (x:xs) = take l (x:xs) ⇐⇒comp.1 take k (take l (x:xs)) = take l (x:xs) ⇐⇒take.3 take k (x:(take (l-1) xs)) = x:(take (l-1) xs) ⇐⇒take.3 x:(take (k-1) (take (l-1) xs)) = x:(take (l-1) xs) ⇐⇒compose.1 x:((take (k-1)) . ((take (l-1) xs)) = x:(take (l-1) xs) ⇐⇒IV x:(take (l-1) xs) = x:(take (l-1) xs) Fall 3: k ≤ 0. Es folgt daraus min k l <= 0. linke Seite = (take k . take l) (x:xs) =compose.1 take k ((take l) (x:xs)) =take.2 [] rechte Seite = take (min k l) (x:xs) =take.2 [] Fall 4: l ≤ 0. Daraus folgt min k l <= 0. linke Seite = (take k . take l) (x:xs) =compose.1 take k ((take l) (x:xs)) =take.2 take k [] =take.1 [] rechte Seite = take (min k l) (x:xs) = [] wie bei Fall 3. 8 Aufgabe 24: Lauflängenkodierung Aufgabe 24a: Bei der Lauflängenkodierung (run-length coding ) wird eine Kette "aaaabbaaa" mit vielen wiederholten Zeichen komprimiert, indem man die Länge jedes Laufes (engl. run) von gleichen Zeichen nimmt: [(4,'a'), (2,'b'), (3,'a')] Schreiben Sie eine Funktion, die diese Kodierung berechnet, und auch die Umkehrfunktion für die Dekodierung. Lösung zu Aufgabe 24a: -- erzeugt aus einer Zeichenkette eine Liste von Tupeln der Form: -- (Häufigkeit des Zeichens, Zeichen) encode :: [Char] -> [(Int, Char)] encode [] = [] encode (x:xs) = [(laenge + 1, x)] ++ encode rest where laenge = length (takeWhile (== x) xs) rest = drop laenge xs -- erzeugt aus einer Liste von Tupeln der Form: (Häufigkeit des Zeichens, Zeichen) -- eine Zeichenkette decode :: [(Int, Char)] -> [Char] decode [] = [] decode (x:xs) = (replicate (fst x) (snd x)) ++ decode xs Aufgabe 24b: Unter der Annahme, dass die Eingabekette keine Ziern enthält, kann man die komprimierte Fassung kompakter als Kette "4a2b3a" darstellen. Erweitern Sie die vorige Aufgabe auf diese Darstellung. Lösung zu Aufgabe 24b: --Eine Variante der Lauflaengenkodierung, die eine Zeichenkette liefert. encodeStr :: String -> String encodeStr "" = "" encodeStr (c:cs) = show (length block) ++ [c] ++ encodeStr rest where block = takeWhile (== c) (c:cs) rest = dropWhile (== c) cs --Die entsprechende Funktion zur Dekodierung. decodeStr :: String -> String decodeStr "" = "" decodeStr str = replicate (read num) c ++ decodeStr xs where num = takeWhile (`elem` ['0'..'9']) str (c:xs) = dropWhile (`elem` ['0'..'9']) str Alternativlösung: auf encode und decode aufbauen und nur die zusätzliche Übersetzung in eine Zeichenkette dazugeben (siehe Datei uebung4.hs). 9