Algorithmen und Programmieren 1 Funktionale Programmierung - Musterlösung zu Übung 2 Dozent: Prof. Dr. G. Rote Tutoren: J. Fleischer, T. Haimberger, N. Lehmann, C. Pockrandt, A. Steen 25.10.2011 Ziele dieser Übung Mit Tupeln, Listen und Zeichenketten sicher in Haskell umgehen und die Idee der Polymorphie verstehen. In dieser Übung sollte man folgende Konzepte geübt und verstanden haben: • Guards • Patternmatching • if-then-else • Tupel • Listen Listengeneratoren Die Funktion: sum • Polymorphie • Strings in Haskell Die Funktion: putStr 1 Aufgabe 7: Uhrzeiten Uhrzeiten wie 10:30 Uhr oder 23:59 Uhr sollen als geordnete Paare (10,30) beziehungsweise (23,59) dargestellt werden. Schreiben Sie eine Funktion, die die Dauer zwischen zwei Uhrzeiten in Stunden und Minuten berechnet (negativ, falls die zweite Uhrzeit vor der ersten liegt). Sie sollten das Problem in Teilprobleme zerlegen und geeignete Hilfsfunktionen denieren. Lösung zu Aufgabe 7: -- Gibt den Abstand zwischen 2 Uhrzeiten an. timeDist::(Int,Int) -> (Int,Int) -> (Int,Int) timeDist (a,b) (c,d) | (chkTimeFrmt a b) && (chkTimeFrmt c d) = min2tupel(tupel2min (c,d) - tupel2min (a,b)) | otherwise = error "Keine gültiges Uhrzeit-Format (hh,mm)!" -- wandelt Zeittupel in Minuten ab 0 Uhr um. tupel2min::(Int,Int) -> Int tupel2min (x,y) = x*60+y -- wandelt Minuten ab 0 Uhr in Zeittupel um. min2tupel::Int->(Int,Int) min2tupel z | z >= 0 = (div z 60, mod z 60) | otherwise = (-(div (-z) 60), -(mod (-z) 60)) -- Prüft, ob die Eingabe ein gültiges Uhrzeitformat ist. chkTimeFrmt::Int->Int->Bool chkTimeFrmt h m | h >= 0 && h <24 && m >= 0 && m <60 = True | otherwise = False Aufgabe 8: Uhrzeiten Im angelsächsischen Raum werden Uhrzeiten in einem 12-Stunden-Bereich angegeben und mit dem Zusatz a.m. (Vormittag), p.m. (Nachmittag), noon oder midnight versehen: 0:00 = 12:00 midnight, 0:13 = 12:13 a.m., 10:30 = 10:30 a.m., 12:00 = 12:00 noon, 12:55 = 12:55 p.m., 13:20 = 1:20 p.m., 23:59 = 11:59 p.m.. Die Stundenzahl ist also immer zwischen 1 und 12. Schreiben Sie eine Funktion, die dieses Format (als Zeichenkette) berechnet. Wenn die Eingabe keine gültige Uhrzeit darstellt, soll der Text ungültig ausgegeben werden. Lösung zu Aufgabe 8: angelsaechsisch angelsaechsisch angelsaechsisch angelsaechsisch :: (Int,Int) -> String (0,0) = "12:00 midnight" (12,0) = "12:00 noon" (a,b) | a < 0 || a >= 24 || b < 0 || | a == 0 = "12:" ++ minuten | a < 12 = show a ++ ":" ++ | a == 12 = "12:" ++ minuten | otherwise = show a ++ ":" ++ where minuten = if b < 10 then "0" ++ show b else show b 2 b >= 60 = "ungültig" ++ " a.m." minuten ++ " a.m" ++ " p.m." minuten ++ " p.m." Aufgabe 9: Multiplikationstabelle Erstellen Sie eine Tabelle für das kleine Einmaleins (die Produkte aller Zahlenpaare zwischen 1 und 10) als Zeichenkette. Die einzelnen Zeilen sind mit \ n abgeschlossen. Das reduzierte Beispiel rechts zeigt, wie so eine Tabelle aussehen könnte, wenn man sie mit putStr ausgibt. Die Spalten ihrer Tabelle müssen vertikal ausgerichtet sein. (Hinweis: die Funktion show wandelt eine Zahl in eine Zeichenkette um.) Lösung zu Aufgabe 9: kleines1x1 :: String kleines1x1 = kopfzeile ++ trennzeile ++ aneinander [ zeile i | i <- [1..10]] -- fügt eine Liste von Strings zu einem einzigen String zusammen aneinander :: [String] -> String aneinander l = [ c | s<-l, c<-s ] -- Formatiert eine Zahl mit führenden Leerzeichen zu Gesamtlänge 4 -- Die Zahl hat höchstens 3 Zeichen. pad :: Int -> String pad y | length ys == 1 = " " ++ ys | length ys == 2 = " " ++ ys | length ys == 3 = " " ++ ys where ys = show y kopfzeile :: String kopfzeile = " |" ++ aneinander [ pad j | j <- [1..10]] ++ "\n" trennzeile :: String trennzeile = "-----+" ++ aneinander [ "----" | j <- [1..10]] ++ "\n" zeile :: Int -> String zeile i = pad i ++ " |" ++ aneinander [ pad (i*j) | j <- [1..10]] ++ "\n" Die Ausgabe erfolgt mit: putStr kleines1x1 Aufgabe 10: Bedingte Ausdrücke Schreiben Sie die folgende Funktion als einen einzigen (nicht geschachtelten) if-then-else-Ausdruck: test x | | | y z x <= y = True y <= z = False otherwise = x < z Lösung zu Aufgabe 10: Betrachten wir die Funktion test : 1) Die Funktion test erhält drei Eingabeparameter. 2) Die Funktion test prüft den ersten Guard: 2+) Falls x kleiner oder gleich y ist, wird True ausgegeben. 2−) Falls x gröÿer ist als y, so ist die Bedingung für den 1. Guard nicht erfüllt und der nächste Guard wird überprüft. 3) Die Funktion test überprüft den zweiten Guard: 3+) Falls y kleiner oder gleich z ist, wird False ausgegeben. 3−) Falls y gröÿer ist als z, so ist die Bedingung für den 1. Guard nicht erfüllt und der nächste Guard 3 wird überprüft. 4) Die Funktion test überprüft den dritten Guard: 4+) (otherwise = True), dieser Fall tritt also immer ein! Die Funktion überprüft nun ob x kleiner als z ist und gibt ein booleschen Wert als Ergebnis zurück. Betrachten wir erneut die Funktion test : Der dritte Guard wird nur dann überprüft, wenn die ersten beiden Guards nicht ausgeführt worden sind. Wenn die ersten beiden Guards allerdings nicht zutrafen ist x > z . (Folgt transitiv aus: (x > y) ∧ (y > z) =⇒ (x > z)) Der Ausdruck x < z im dritten Guard ist in unserer Funktion also eine Kontradiktion, die Codezeile | otherwise = x < z wird daher immer False ergeben. test::Ord a => a -> a -> a -> Bool test x y z = if (x<=y) then True else False Zusatzfrage (0 Punkte): Kommt man auch ganz ohne if-then-else-Ausdruck aus? Lösung zu Zusatzfrage: test2::Ord a => a -> a -> a -> Bool test2 x y z = x<=y Aufgabe 11: Perfekte Zahlen a) Schreiben Sie eine Funktion, die zu einer Zahl n die Liste ihrer Teiler berechnet. Lösung zu Aufgabe 11a: -- Berechnet eine Liste aller echten Teiler des Eingabeparameters. teilerVonN::Int->[Int] teilerVonN n = [x|x <- [1..n], (mod n x) == 0, x /= n] b) Bestimmen Sie jede Zahl zwischen 1 und 1000, bei der die Summe ihrer Teiler gröÿer ist als die Zahl selbst. (Hinweis: Die Funktion sum berechnet die Summe der Elemente einer Liste) Lösung zu Aufgabe 11b: -- Berechnet die Summe aller echten Teiler des Eingabeparameters. sumTeilerVonN::Int->Int sumTeilerVonN n = sum (teilerVonN n) -- Erstellt eine Liste aller Zahlen, bei der die Summe -- ihrer Teiler gröÿer ist als die Zahl selbst. biggerThanSumTeilerVonN::[Int] biggerThanSumTeilerVonN = [z | z <- [1..1000], z < sumTeilerVonN z] Es kommen 246 Zahlen als Lösung heraus. c) Bestimmen Sie alle perfekten Zahlen zwischen 1 und 1000: die Zahlen, die gleich der Summe Ihrer Teiler sind. Lösung zu Aufgabe 11c: -- Bestimmt alle perfekte Zahlen zwischen 1 und 1000. perfekteZahlen::[Int] perfekteZahlen = [z | z <- [1..1000] , z == sumTeilerVonN z] Das Ergebnis ist [6,28,496]. Es ist unbekannt, ob es ungerade perfekte Zahlen gibt. 4