Teillösung zum 7.Aufgabenblatt zur Vorlesung Informatik A

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