Musterlösung

Werbung
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
Herunterladen