Informatik A Übung 6 Musterlösung (Robert Gottwald, Thimo Wellner) 1. Haskell und Rekursion I (4 Punkte) Implementieren Sie die Rekursion zur Berechnung der Binominialkoeffizienten. Geben Sie die ersten 12 Zeilen des Pascalschen Dreieckes auf dem Bildschirm aus. Die einzelnen Zeilen sollten linksbündig sein, danach jeweils eine Leerzeile, zwischen den Einträgen einer Zeile jeweils ein Leerzeichen. Lösung: --Aufgabe 1 binomial :: Int -> Int -> Integer binomial _ 0 = 1 binomial n k | n>k = (binomial (n-1) (k-1)) + (binomial (n-1) k) | n==k = 1 | otherwise = 0 Hierbei war wichtig, die Funktion wirklich rekursiv zu schreiben, da sonst auch schnell mit der Fakultätsfunktion die Darstellungsgrenze des Int-Bereichs erreicht gewesen wäre. drawPascal :: Int -> IO() drawPascal n = putStr (drawHelper 0 n) where pascalLine n = concat (map (++" ") (map show (map (binomial n) [0..n]))) drawHelper i n | i==n = "" | otherwise = (pascalLine i) ++ "\n" ++ (drawHelper (i+1) n) (Eine weitere Implementierung ist im pad-spline unter http://pad.spline.de/MTDAb6W1iF zu finden, geschrieben von Thimo Wellner.) 2. Palindrome und Rekursion II (4+2 Punkte) (a) Definieren Sie rekursiv(!) eine Funktion isPalind, die feststellt, ob ein String ein Palindrom ist. Zur Erinnerung, ein Palindrom ist ein String, der von vorn bzw. hinten gelesen dasselbe ergibt. Um es ein bisschen anspruchsvoller zu machen soll bei Buchstaben nicht zwischen Groß- und Kleinschreibung unterschieden werden. Also „Rentner" ist ein Palindrom. Lösung: --Aufgabe 2 import Char –- notwendig, da chr und ord nur in dieser Bibliothek stehen. --Hilfsfunktion lcase :: Char -> Char lcase c = if elem c (['A'..'Z']++['Ä','Ö','Ü']) then (chr (32+ord c)) else c –- wandelt große Buchstaben in kleine um. isPalind isPalind isPalind isPalind :: String -> Bool [] = True [x] = True (x:xs) = (lcase x) == (lcase (last xs)) && (isPalind (init xs)) (b) Wie viele Palindrome der Länge n ≥ 0 über einem Alphabet Σ der Größe s ≥ 1 gibt es eigentlich? Stellen Sie eine Formel auf und beweisen Sie diese! Lösung: Für gerade n gibt es s(n /2) Palindrome, für ungerade n s(((n −1)/ 2)+1) . Das sind für beide Fälle gerade s⌈ n /2 ⌉ . Beweis: Induktion über n: Anker: Für n = 0 gibt es ein einziges Palindrom, das leere Wort. s⌈ 0 /2 ⌉=s 0=1 Für n = 1 gibt es s Palindrome, die einzelnen Symbole in Σ. s⌈ 1/ 2 ⌉=s1 =s IV: IB: IS: 3. Für ein beliebiges, aber festes n gilt: Die Anzahl aller Palindrome über n mit s ist s⌈ n /2 ⌉ . Für n' = n+2 gilt: Die Anzahl aller Palindrome ist s⌈ n ' /2 ⌉=s⌈ (n+2)/2 ⌉=s ⌈(n)/2+1 ⌉ Betrachtet man sich die Palindrome mit Wortlänge n, so ist ersichtlich, dass man daraus Palindrome der Wortlänge n' = n+2 machen kann, in dem man vorne einen beliebigen Buchstaben aus Σ anfügt. Der Buchstabe am Ende des Wortes jedoch muss derselbe sein, um die palindromische Eigenschaft zu bewahren. Somit gibt es ⌈ n /2 ⌉ ⌈ n/ 2 ⌉+1 ⌈(n)/ 2+1⌉ ⌈(n+2)/ 2 ⌉ ⌈ n '/ 2 ⌉ s ∗s=s =s =s =s Palindrome der Länge n' = (n+2). Buchstabenfrequenz (6 Punkte) Schreiben Sie eine Funktion frequency :: String -> [(Char,Int)], die feststellt, welche Buchstaben wie oft in einem String vorkommen. Dabei sollen Klein- und Großbuchstaben nicht unterschieden werden. Die Häufigkeiten beziehen sich auf die Gesamtzahl der Buchstaben im String, die anderen Zeichen sollen ignoriert werden. Definieren Sie sich ggf. Hilfsfunktionen. Lösung: --Aufgabe 3 frequency :: String -> [(Char,Int)] frequency a = frequencyHelper a [] where frequencyHelper [] cs = cs --Hilfsfunktion, die sich den aktuellen Zählstand --in einer Liste merkt frequencyHelper (x:xs) cs = frequencyHelper xs (countChar (lcase x) cs) where countChar x [] = [(x,1)] --zählt das Zeichen x in dem übergebenen --Zählstand um 1 hoch countChar x ((c,n):cs) = if x==c then ((c,n+1):cs) else ((c,n):(countChar x cs)) 4. Simultanes Min-Max (4 Punkte) Schreiben Sie eine rekursive Funktion zur simultanen Bestimmung des Minimums und des Maximums in einer Liste von ganzen Zahlen. Analysieren Sie, wie viele Vergleiche zwischen Listenelementen Ihr Verfahren macht bei einer Liste der Länge n. Sie sollten mit weniger als 2n -2 Vergleichen auskommen. Dies erreicht man schon, wenn man getrennt Minimum und Maximum mit jeweils n - 1 Vergleichen bestimmt. Lösung: Die Idee für die schnellste Implementierung ist, sich jeweils zwei Werte der Liste gleichzeitig anzuschauen und sie zuerst zu vergleichen. Das kleinere von beiden kann nicht Maximum sein, das größere nicht Minimum, wodurch man statt 4 Vergleichen (min, max) für beide Werte nur 3 Vergleiche macht. --Aufgabe 4 minMax :: [Int] -> (Int,Int) minMax [] = error "empty list" minMax [x] = (x,x) minMax (x:y:xs) | x<y = minMaxHelper xs [] (x,y) | otherwise = minMaxHelper xs [] (y,x) where minMaxHelper :: [Int] -> [(Int,Int)] -> (Int,Int) -> (Int,Int) minMaxHelper [] [] (a,b) = (a,b) minMaxHelper [] ((a,b):ms) (l,g) = minMaxHelper [] ms ((min a l),(max b g)) --wenn die Eingabeliste verarbeitet wurde dann wird das kleinste der Minima und das größte der Maxima bestimmt minMaxHelper [x] ms lg = minMaxHelper [] ((x,x):ms) lg --bestimmt erst den größeren von zwei Paaren und fügt ihn an Liste ms an minMaxHelper (x:y:xs) ms lg | x<y = minMaxHelper xs ((x,y):ms) lg | otherwise = minMaxHelper xs ((y,x):ms) lg