Teillösung zum 7.Aufgabenblatt zur Vorlesung Informatik A (Autor: Florian Brinkmeyer) 1 Pascalsches Dreieck Implementieren Sie die Rekursion zur Berechnung der Binomialkoezienten. 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 einLeerzeichen. Zur Erinnerung: Der Binomialkoezient nk ist für nichtnegative ganze Zahlen n und k rekursiv deniert als: ∀n = 0 : ∀n = k : n 0 n k = 1, ∀0 5 n < k : n−1 = n−1 k−1 + k n k =0 Programmierlösung: lines_count :: Int lines_count = 12 -- verknüpft Berechnungvariante A mit der Ausgabevariante A var1 = showPascalA createPascalA -- verknüpft Berechnungsvariante A mit der Ausgabevariante B var2 = showPascalA createPascalB -- verknüft Berechnungsvariante B mit der Ausgabevariante B var3 = showPascalB -- erste Berechnungsvariante für das Pascalsche Dreieck (A): -- auf die Erfordernisse der Aufgabe zugeschnittene Definition -- des Binomialkoeffizienten 1 binom:: Int -> Int -> Int binom n k |k<0 = 0 |n==k = 1 |otherwise = binom (n-1) k + binom (n-1) (k-1) -- erzeugt die n-te Zeile createLineA:: Int -> [Int] createLineA n = map (binom n) (take (n+1) [0,1..]) -- erzeugt die Werte des Gesamtdreiecks createPascalA:: [[Int]] createPascalA = map createLineA (take lines_count [0,1..]) -- effizientere Berechnungsvariante (B): -- Es wird die aktuelle Zeile unter Rückgriff auf die vorige berechnet. createLineB :: [Int] -> [Int] createLineB oldLine = reverse (1:createLineBHelp oldLine [1]) createLineBHelp :: [Int] -> [Int] -> [Int] createLineBHelp (x:y:xs) newLine = createLineBHelp (y:xs) ((x+y):newLine) createLineBHelp _ newLine = newLine -- erzeugt das Gesamtdreieck createPascalB :: [[Int]] createPascalB = [1]:[createLineB xs | xs<-take (lines_count-1) createPascalB] -- wandelt die Einzelwerte des Dreiecks in Zeichenketten um pascalString :: [[Int]] -> [[String]] pascalString pascal = map (map show) pascal -- linksbündige Ausgabe des Ergebnisses (A): -- Die einzelnen Einträge einer Zeile werden durch ein Leerzeichen getrennt -- und dann zu einem Gesamtstring verbunden. showLineA:: [String] -> String showLineA = concat.(map (++" ")) ----- Nach der Erzeugung des Dreiecks in Zeichenform werden die einzelnen Ausgabezeilen generiert, mit zwei Zeilenumbrüchen ("\n") versehen, zu einem Gesamtstring verknüpft und das Ergebnis wird mit putStrLn ausgegeben. showPascalA :: [[Int]] -> IO() showPascalA pascal = putStrLn (concat [(showLineA xs)++"\n\n" | xs<-pascalString pascal]) -- zentrierte Ausgabevariante (B) -- Als Breite eines Ausgabeblocks wird die maximale Länge -- eines Eintrags verwendet. blockWidth :: Int blockWidth = maximum (map length (concat (pascalString createPascalB))) ----- Übergeben wird die Spalte, in der die Ausgabe der Zeile beginnen soll und für den rekursiven Aufruf auch die gegenwärtige Position. Der Abstand zwischen zwei Einträgen ist ebenfalls blockWidth. Jeder Eintrag wird innerhalb seines Ausgabeblocks zentriert. showLineB:: Int -> Int -> [String] -> String showLineB start pos (x:xs) = take offset [' ',' '..] ++ x ++ showLineB (start+2*blockWidth) (pos+offset+length x) xs where offset = start-pos+center center = (blockWidth-length x) `div` 2 showLineB _ _ [] = "\n" 3 -- Erzeugt der Reihe nach die Ausgabezeilen showPascalBHelp:: Int -> [[String]] -> String showPascalBHelp i (xs:xss) = showLineB start 0 xs ++ showPascalBHelp (i+1) xss where start = (lines_count-i)*blockWidth showPascalBHelp _ [] = [] showPascalB :: IO() showPascalB = putStrLn (showPascalBHelp 1 (pascalString createPascalB)) 2 Vollständige Induktion I a) Beweisen Sie mittels vollständiger Induktion über die Listenlänge, dass für alle Listen xs gilt: length (reverse xs) = length xs, wobei reverse die Funktion ist, die eine Liste umdreht, definiert mittels: reverse [] = [] --reverse.1 reverse (z:zs) = reverse zs ++ [z] --reverse.2 length [] = 0 length (y:ys) = 1 + length ys --length.1 --length.2 Beweis Für den Beweis wird folgende Zusatzannahme verwendet: length (xs++ys) = length xs + length ys Diese lässt sich ebenfalls durch vollständige Induktion beweisen. Induktionsanfang (IA) (Länge xs = 0 => xs = []) length (reverse []) = length [] --reverse.1 Induktionsvoraussetzung (IV) length (reverse xs) = length xs für alle xs der Länge n Induktionsbehauptung (IB) length (reverse (x:xs)) = length (x:xs) Induktionsschritt (IV => IB) = = = = = = = length (reverse (x:xs)) --reverse.2 length (reverse xs ++ [x]) --Zusatzannahme length (reverse xs) + length [x] --Induktionsvoraussetzung length xs + length [x] --Definition von [x] length xs + length x:[] --length.2 length xs + 1 + length [] --length.1 und Termumformung 1 + length xs --length.2 length (x:xs) Es wurde also gezeigt, dass die Behauptung für xs = [] gilt. Ferner wurde gezeigt, dass, wenn sie für beliebige xs der Länge n gilt, daraus folgt, dass sie auch für beliebige xs der Länge n+1 gilt. Nach dem Prinzip der vollständigen Induktion ist damit die Behauptung bewiesen. Beweis der Zusatzannahme Es ist zu zeigen: length (xs++ys) = length xs + length ys Für den Nachweis muss noch der (++)-Operator definiert werden: [] ++ ys = ys --(++).1 (x:xs) ++ ys = x:(xs++ys) --(++).2 Vollständige Induktion über die Listenlänge von xs: Induktionsanfang (IA) length ([]++ys) --(++).1 = length ys = 0 + length ys --length.1 = length [] + length ys Induktionsvoraussetzung (IV) length (xs++ys) = length xs + length ys für alle xs der Länge n Induktionsbehauptung (IB) length ((x:xs)++ys) = length (x:xs) + length ys Induktionsschritt (IV=>IB) = = = = length length length length length ((x:xs)++ys) x:(xs++ys) (xs++ys) + 1 xs + 1 + length ys (x:xs) + length ys --(++).2 --length.2 --Induktionsvoraussetzung und Termumformung --length.2 Vollständige Induktion I b) Beweisen Sie auÿerdem, dass für HaskellListen die folgende Gleichheit auf Funktionenebene gilt, wobei id die Identitätsfunktion ist und der (.)-Operator für Funktionskomposition steht. reverse . reverse = id Beweis reverse . reverse = id <=> (reverse . reverse) xs = xs <=> reverse (reverse xs) = xs für alle xs für alle xs Der Beweis erfolgt durch Induktion über die Listenlänge von xs. Es wird folgende Zusatzannahme verwendet: reverse (xs++ys) = reverse ys ++ reverse xs Diese beweist man ebenfalls durch Induktion. Induktionsanfang (IA) reverse (reverse []) --reverse.1 = reverse [] --reverse.1 = [] 6 Induktionsvoraussetzung (IV) reverse (reverse xs) = xs für alle xs der Länge n Induktionsbehauptung (IB) reverse (reverse (x:xs)) = x:xs Induktionsschritt (IV=>IB) = = = = = = = = reverse (reverse (x:xs)) --reverse.2 reverse (reverse xs ++ [x]) --Zusatzannahme reverse [x] ++ reverse (reverse xs) --Induktionsvoraussetzung reverse [x] ++ xs --Definition von [x] reverse (x:[]) ++ xs --reverse.2 reverse [] ++ [x] ++ xs --reverse.1, Definition von [x] [] ++ x:[] ++ xs --(++).1, (++).2 x:([]++xs) --(++).1 x:xs Beweis der Zusatzannahme Es ist zu zeigen: reverse (xs++ys) = reverse ys ++ reverse xs Beweis durch Induktion über die Listenlänge von xs Hierbei wird folgende Zusatzannahme verwendet: xs ++ [] = xs Diese ist anders als [] ++ xs = xs nicht Teil unserer Definition von (++) und muss eigentlich ebenfalls durch Induktion bewiesen werden. Induktionsanfang (IA) reverse ([]++ys) --(++).1 = reverse ys --Zusatzannahme = reverse ys ++ [] --reverse.1 = reverse ys ++ reverse [] Induktionsvoraussetzung (IV) reverse (xs ++ ys) = reverse ys ++ reverse xs für alle xs der Länge n Induktionsbehauptung (IB) reverse ((x:xs) ++ ys) = reverse ys ++ reverse (x:xs) Induktionsschritt = = = = reverse reverse reverse reverse reverse ((x:xs) ++ ys) (x:(xs++ys)) (xs++ys) ++ [x] ys ++ reverse xs ++ [x] ys ++ reverse (x:xs) --(++).2 --reverse.2 --Induktionsvoraussetzung --reverse.2 Links- und Rechtsfaltung a) Beschreiben und begründen Sie verbal, was folgende Linksfaltung tut. wasTuIch xs1 xs2 = foldl func xs1 xs2 func [] _ = [] func (x:xs) y | x==y = func xs y | otherwise = x: (func xs y) Die Hilfsfunktion 'func' löscht alle Vorkommnisse des Elements, das sie als zweiten Parameter erhält, aus der Liste, die sie als ersten Parameter erhält und liefert die Restliste zurück. Dies wird durch eine Rekursion realisiert. Die Elemente der Liste werden der Reihe nach mit dem zweiten Parameter verglichen und nur im Falle einer Ungleichheit in die Ergebnisliste übernommen. Die Funktion 'wasTuIch' löscht auch bei mehrfachem Auftreten alle Elemente aus xs1, die ebenfalls in xs2 vorkommen. Hierbei wird auf eine Linksfaltung der Funktion 'func' über der Liste xs2 zurückgegrien. Die anfangs noch vollständige Liste xs1 fungiert hierbei als Startwert für die Faltungsoperation. Es werden sukzessive die Elemente von xs2 durchlaufen und jeweils alle Vorkommnisse des aktuellen Elements aus der jeweiligen Restliste von xs1 gelöscht. 8 Links- und Rechtsfaltung b) Was tut die Funktion mystery xs = foldr (++) [] (map sing xs), wobei sing x = [x] für alle x ist? Die Funktion 'sing' bildet aus dem Parameter x eine Liste, die x als einziges Element enthält. 'map sing xs' liefert demnach eine Liste, deren Elemente einelementige Listen sind, die aus den Elementen von xs gebildet wurden. Die abschlieÿende Faltungsoperation entspricht einer Konkatenation und damit einer erneuten Zusammenführung dieser einelementigen Listen, so dass man wieder xs erhält. 'mystery' realisiert demnach für Listen die Identitätsfunktion.