Kapitel 4: Programmiermethodik Programmieren in Haskell 1 Spezifikation sort [8, 3, 5, 3, 6, 1] ⇒ [1, 3, 3, 5, 6, 8] sort "hello world" ⇒ " dehllloorw" sort ["Bein", "Anfall", "Anna"] ⇒ ["Anfall", "Anna", "Bein"] sort [(1, 7), (1, 3), (2, 2)] ⇒ [(1, 3), (1, 7), (2, 2)] Programmieren in Haskell 2 sort :: (Ord a) => [a] -> OrdList a Programmieren in Haskell 3 sort :: (Ord a) => [a] -> OrdList a ordered ordered ordered ordered :: (Ord a) [] [a] (a1:a2:as) Programmieren in Haskell => [a] -> Bool = True = True = a1 <= a2 && ordered (a2:as) 3 sort :: (Ord a) => [a] -> OrdList a ordered ordered ordered ordered :: (Ord a) [] [a] (a1:a2:as) => [a] -> Bool = True = True = a1 <= a2 && ordered (a2:as) Spezifikation 1: Für alle Listen x :: [τ ] muß gelten: ordered (sort x) Programmieren in Haskell = True . (1) 3 sort :: (Ord a) => [a] -> OrdList a ordered ordered ordered ordered :: (Ord a) [] [a] (a1:a2:as) => [a] -> Bool = True = True = a1 <= a2 && ordered (a2:as) Spezifikation 1: Für alle Listen x :: [τ ] muß gelten: ordered (sort x) = True . (1) Reicht das? Programmieren in Haskell 3 sort :: (Ord a) => [a] -> OrdList a ordered ordered ordered ordered :: (Ord a) [] [a] (a1:a2:as) => [a] -> Bool = True = True = a1 <= a2 && ordered (a2:as) Spezifikation 1: Für alle Listen x :: [τ ] muß gelten: ordered (sort x) = True . (1) Reicht das? Nein: \x -> [] Programmieren in Haskell 3 Multimengen ∅ die leere Multimenge, *a+ die einelementige Multimenge, die genau ein Vorkommen von a enthält, x ] y die Vereinigung der Elemente von x und y; das „+“ im Vereinigungszeichen deutet an, daß sich die Vorkommen in x und y akkumulieren. Programmieren in Haskell ∅]x = x (2) x]∅ = x (3) x]y = y]x (4) (x ] y) ] z = x ] (y ] z) (5) 4 bag :: [a] -> Bag a bag [] = ∅ bag (a:as) = *a+ ] bag as Spezifikation 2: Für alle Listen x :: [τ ] muß gelten: ordered (sort x) = True ∧ bag (sort x) = bag x . Programmieren in Haskell (6) 5 Strukturelle Rekursion auf Listen translate :: [Codon] -> Protein translate [] = [] translate (triplet:triplets) | aminoAcid == Stp = [] | otherwise = aminoAcid:translate triplets where aminoAcid = genCode triplet length :: [a] -> Int length [] = 0 length (a:as) = 1 + length as Programmieren in Haskell -- vordefiniert 6 Rekursionsbasis: [] Rekursionsschritt: (a:as) Schema der strukturellen Rekursion auf Listen: f f [] f (a : as) where s Programmieren in Haskell :: = = = [σ] -> τ e1 e2 f as 7 Sortieren durch Einfügen insertionSort insertionSort [] insertionSort (a : as) where s Programmieren in Haskell :: = = = (Ord a) => [a] -> OrdList a e1 e2 insertionSort as 8 Sortieren durch Einfügen insertionSort insertionSort [] insertionSort (a : as) where s :: = = = (Ord a) => [a] -> OrdList a e1 e2 insertionSort as insertionSort :: (Ord a) => [a] -> OrdList a insertionSort [] = [] insertionSort (a:as) = insert a (insertionSort as) Programmieren in Haskell 8 Erweitertes Rekursionsschema: g g i [] g i (a : as) where s insert insert a [] insert a (a’ : as) where s Programmieren in Haskell :: = = = :: = = = σ1 -> [σ2 ] -> τ e1 e2 g e3 as (Ord a) => a -> OrdList a -> OrdList a e1 e2 insert e3 as 9 insert a [] insert a (a’ : as) | a <= a’ | otherwise where s = [] = = = e21 e22 insert e3 as insert :: (Ord a) => a -> [a] -> [a] insert a [] = [a] insert a (a’:as) | a <= a’ = a:a’:as | otherwise = a’:insert a as Programmieren in Haskell 10 isort (8 : 3 : 5 : 3 : 6 : 1 : []) ⇒ ins 8 (isort (3 : 5 : 3 : 6 : 1 : [])) (Def. isort) ⇒ ins 8 (ins 3 (isort (5 : 3 : 6 : 1 : []))) (Def. isort) ⇒ ... ⇒ ins 8 (ins 3 (ins 5 (ins 3 (ins 6 (ins 1 []))))) ⇒ ins 8 (ins 3 (ins 5 (ins 3 (ins 6 (1 : []))))) (Def. ins) ⇒ ins 8 (ins 3 (ins 5 (ins 3 (1 : ins 6 [])))) (Def. ins) ⇒ ins 8 (ins 3 (ins 5 (1 : ins 3 (ins 6 [])))) (Def. ins) ⇒ ... ⇒ 1 : ins 8 (ins 3 (ins 5 (ins 3 (ins 6 [])))) Programmieren in Haskell (Def. isort) (Def. ins) 11 Strukturelle Rekursion auf Bäumen data Tree a = Nil | Leaf a | Br (Tree a) (Tree a) deriving Show Rekursionsbasis (Nil) Das Problem wird für den leeren Baum gelöst. Rekursionsbasis (Leaf a) Das Problem wird für das Blatt Leaf a gelöst. Rekursionsschritt (Br l r) Um das Problem für den Baum Br l r zu lösen, werden rekursiv Lösungen für l und r bestimmt, die zu einer Lösung für Br l r erweitert werden. Programmieren in Haskell 12 Schema der strukturellen Rekursion of Bäumen: f f Nil f (Leaf a) f (Br l r) where sl sr Programmieren in Haskell :: = = = = = Tree σ -> τ e1 e2 e3 f l f r 13 size size size size :: Tree a -> Integer Nil = 1 (Leaf _) = 1 (Br l r) = size l + size r depth depth depth depth :: Tree a -> Integer Nil = 0 (Leaf _) = 0 (Br l r) = max (depth l) (depth r) + 1 Programmieren in Haskell 14 Das allgemeine Rekursionsschema data T a1 . . . am = C1 t11 . . . t1n1 | ... | Cr tr1 . . . trnr Seien li1 ,. . . , lipi mit 1 6 li1 < li2 < · · · < lipi 6 ni die Positionen, an denen der Konstruktor Ci rekursiv ist Programmieren in Haskell 15 Allgemeines Schema der strukturellen Rekursion: f f (C1 x11 . . . x1n1 ) where s11 ... s1p1 ... f (Cr xr1 . . . xrnr ) where sr1 ... srpr Programmieren in Haskell :: = = T σ1 . . . σm -> τ e1 f x1l11 = f x1l1p1 = = er f xrlr1 = f xrlrpr 16 Verstärkung der Rekursion reverse’’ :: [a] -> [a] -- vordefiniert reverse’’ [] = [] reverse’’ (a:as) = reverse’’ as ++ [a] Programmieren in Haskell 17 Verstärkung der Rekursion reverse’’ :: [a] -> [a] -- vordefiniert reverse’’ [] = [] reverse’’ (a:as) = reverse’’ as ++ [a] Spezifikation: reel reel x y Programmieren in Haskell :: = [a] -> [a] -> [a] reverse x ++ y 17 Rekursionsbasis (x = []): reel [] y Programmieren in Haskell = reverse [] ++ y (Spezifikation) = [] ++ y (Def. reverse) = y (Def. (++)) 18 Rekursionsschritt (x = a:as): reel (a:as) y Programmieren in Haskell = reverse (a:as) ++ y (Spezifikation) = (reverse as ++ [a]) ++ y (Def. reverse) = reverse as ++ ([a] ++ y) (Ass. (++)) = reverse as ++ (a:y) (Def. (++)) = reel as (a:y) (Spezifikation) 19 reel :: [a] -> [a] -> [a] reel [] y = y reel (a:as) y = reel as (a:y) Programmieren in Haskell 20 reel :: [a] -> [a] -> [a] reel [] y = y reel (a:as) y = reel as (a:y) reverse’’’ :: [a] -> [a] reverse’’’ as = reel as [] Programmieren in Haskell 20 Strukturelle Induktion auf Listen Induktionsbasis ([]): Wir zeigen die Aussage zunächst für die leere Liste []. Induktionsschritt (a:as): Wir nehmen an, daß die Aussage für die Liste as gilt, und zeigen, daß sie unter dieser Voraussetzung auch für a:as gilt. Beweisregel: Φ([]) (∀a, as) Φ(as) =⇒ Φ(a:as) (∀x) Φ(x) Programmieren in Haskell 21 Spezifikation von insert: ordered x = True =⇒ ordered (insert \(a\) \(x\)) = True (7) bag (insert a x) = *a+ ] bag x (8) Programmieren in Haskell 22 Φ(x) ⇐⇒ (∀a) bag (insert a x) = *a+ ] bag x (9) Induktionsbasis (x = []): bag (insert a []) Programmieren in Haskell = bag [a] = *a+ ] bag [] (Def. insert) (Def. bag) 23 ⇐⇒ Φ(x) (∀a) bag (insert a x) = *a+ ] bag x Induktionsschritt (x = a’:as): Fall a <= a’ = True: bag (insert a (a’:as)) Programmieren in Haskell = bag (a:a’:as) = *a+ ] bag (a’:as) (Def. insert) (Def. bag) 24 ⇐⇒ Φ(x) (∀a) bag (insert a x) = *a+ ] bag x Induktionsschritt (x = a’:as): Fall a <= a’ = False: bag (insert a (a’:as)) = bag (a’:insert a as) = *a’+ ] bag (insert a as) = = = Programmieren in Haskell *a’+ ] (*a+ ] bag as) *a+ ] (*a’+ ] bag as) *a+ ] bag (a’:as) (Def. insert) (Def. bag) (I.V.) (Ass., Komm. ]) (Def. bag) 25 Φ(x) ⇐⇒ (∀a) ordered x = True =⇒ ordered (insert \(a\) \(x\)) = True Induktionsbasis (x = []): ordered (insert a []) Programmieren in Haskell = ordered [a] (Def. insert) = True (Def. ordered) 26 Induktionsschritt (x = a’:as): 1. x = a1 :as ∧ a<=a1 = True 2. x = a1 :as ∧ a<=a1 = False ∧ as = [] 3. x = a1 :as ∧ a<=a1 = False ∧ as = a2 :as0 Programmieren in Haskell 27 Φ(x) ⇐⇒ (∀a) ordered x = True =⇒ ordered (insert \(a\) \(x\)) = True Teilfall a<=a2 = True: ordered (insert a (a1 :as)) Programmieren in Haskell = ordered (a1 :insert a as) (Def. insert) = ordered (a1 :a:as) (Def. insert) = a1 <=a && a<=a2 && ordered as (Def. ordered) = True (Vor.) 28 Φ(x) ⇐⇒ (∀a) ordered x = True =⇒ ordered (insert \(a\) \(x\)) = True Teilfall a<=a2 = False: ordered (insert a (a1 :as)) = ordered (a1 :insert a as) (Def. insert) = ordered (a1 :a2 :insert a as0 ) (Def. insert) = a1 <=a2 && ordered (a2 :insert a as0 ) (Def. ordered) = ordered (a2 :insert a as0 ) = ordered (insert a as) (Def. insert) = True (Vor. und I.V.) Programmieren in Haskell (Vor. undDef. (&&)) 29 Strukturelle Induktion auf Bäumen Induktionsbasis (Nil): Wir zeigen die Aussage für den leeren Baum Nil. Induktionsbasis (Leaf a): Wir zeigen die Aussage für das Blatt Leaf a. Induktionsschritt (Br l r): Wir nehmen an, daß die Aussage für den Teilbaum l und für den Teilbaum r gilt, und zeigen, daß sie unter dieser Voraussetzung auch für den Baum Br l r gilt. Programmieren in Haskell 30 Strukturelle Induktion auf Bäumen Induktionsbasis (Nil): Wir zeigen die Aussage für den leeren Baum Nil. Induktionsbasis (Leaf a): Wir zeigen die Aussage für das Blatt Leaf a. Induktionsschritt (Br l r): Wir nehmen an, daß die Aussage für den Teilbaum l und für den Teilbaum r gilt, und zeigen, daß sie unter dieser Voraussetzung auch für den Baum Br l r gilt. Φ(Nil) (∀a) Φ(Leaf a) (∀l, r) Φ(l) ∧ Φ(r) =⇒ Φ(Br l r) (∀t) Φ(t) Programmieren in Haskell 30 Φ(t) Programmieren in Haskell ⇐⇒ size t 6 2^depth t 31 Φ(t) ⇐⇒ size t 6 2^depth t Induktionsbasis (t = Nil): size Nil = 1 (Def. size) = 2^0 (Def. (^)) = 2^depth Nil Programmieren in Haskell (Def. depth) 31 Φ(t) ⇐⇒ Induktionsbasis (t = Nil): size t 6 2^depth t Induktionsbasis (t = Leaf a): size Nil size (Leaf a) = 1 (Def. size) = 1 (Def. size) = 2^0 (Def. (^)) = 2^0 (Def. (^)) = 2^depth Nil (Def. depth) = 2^depth (Leaf a) Programmieren in Haskell (Def. depth) 31 ⇐⇒ Φ(t) size t 6 2^depth t Induktionsschritt (t = Br l r): size (Br l r) Programmieren in Haskell = size l + size r (Def. size) 6 2^depth l + 2^depth r 6 2 * 2^(max (depth l) (depth r)) (Eig. max) = 2^(max (depth l) (depth r) + 1) (Eig. (^)) = 2^depth (Br l r) (I.V.) (Def. depth) 32 Das allgemeine Induktionsschema data T a1 . . . am = C1 t11 . . . t1n1 | . . . |Cr tr1 . . . trnr Programmieren in Haskell 33 Das allgemeine Induktionsschema data T a1 . . . am = C1 t11 . . . t1n1 | . . . |Cr tr1 . . . trnr Seien li1 ,. . . , lipi mit 1 6 li1 < li2 < · · · < lipi 6 ni die Positionen, an denen der Konstruktor Ci rekursiv ist Programmieren in Haskell 33 Das allgemeine Induktionsschema data T a1 . . . am = C1 t11 . . . t1n1 | . . . |Cr tr1 . . . trnr Seien li1 ,. . . , lipi mit 1 6 li1 < li2 < · · · < lipi 6 ni die Positionen, an denen der Konstruktor Ci rekursiv ist (∀x11 . . . x1n1 ) Φ(x1l11 ) ∧ · · · ∧ Φ(x1l1p1 ) =⇒ Φ(C1 x11 . . . x1n1 ) ... (∀xr1 . . . xrnr ) Φ(xrlr1 ) ∧ · · · ∧ Φ(xrlrpr ) =⇒ Φ(Cr xr1 . . . xrnr ) (∀x) Φ(x) Programmieren in Haskell 33 Spezialfall: Natürliche Induktion data Natural = Zero | Succ Natural Φ(Zero) (∀n) Φ(n) =⇒ Φ(Succ n) (∀n ∈ Nat) Φ(n) Programmieren in Haskell 34 undepth undepth undepth undepth :: Tree a -> Integer Nil = 0 (Leaf _) = 0 (Br l r) = min (undepth l) (undepth r) + 1 Programmieren in Haskell 35 undepth undepth undepth undepth :: Tree a -> Integer Nil = 0 (Leaf _) = 0 (Br l r) = min (undepth l) (undepth r) + 1 Sei t ein Braun-Baum, dann gilt depth t - undepth t ∈ {0, 1} . Programmieren in Haskell (10) 35 Induktionsbasis (t = Nil): depth Nil - undepth Nil = 0−0 = 0 ∈ {0, 1} (Def. depth, undepth) Induktionsbasis (t = Leaf a): depth (Leaf a) - undepth (Leaf a) = 0−0 = 0 ∈ {0, 1} Programmieren in Haskell (Def. depth, undepth) 36 Induktionsschritt (t = Br l r): depth (Br l r) - undepth (Br l r) = max (depth l) (depth r) + 1 (min (undepth l) (undepth r) + 1) (Def. depth, undepth) = Programmieren in Haskell 37 Verstärkung der Induktion Programmieren in Haskell 2n 6 size t < 2n+1 =⇒ undepth t = n , (11) 2n−1 < size t 6 2n =⇒ depth t = n . (12) 38 Verstärkung der Induktion 2n 6 size t < 2n+1 =⇒ undepth t = n , (11) 2n−1 < size t 6 2n =⇒ depth t = n . (12) Induktionsschritt (t = Br l r): Da t ein Braun-Baum ist, gilt size l = b 12 size tc und size r = d 12 size te. Damit folgt für den linken Teilbaum 2n 6 size t < 2n+1 Programmieren in Haskell =⇒ 2n−1 6 size l < 2n (Arithmetik) =⇒ undepth l = n − 1 (I.V.) 38 und entsprechend für den rechten 2n 6 size t < 2n+1 =⇒ 2n−1 6 size r 6 2n (Arithmetik) =⇒ undepth r > n − 1 (I.V.) Insgesamt erhalten wir undepth (Br l r) Programmieren in Haskell = min (undepth l) (undepth r) + 1 = n (Def. undepth) (obige Rechnungen) 39 Sortieren durch Fusionieren [8, 3, 5, 3, 6, 1] [8, 3, 5] [3, 6, 1] [3, 5] 8 3 5 [6, 1] 3 [3, 5] [3, 5, 8] 6 1 [1, 6] [1, 3, 6] [1, 3, 3, 5, 6, 8] Programmieren in Haskell 40 mergeSort :: (Ord a) => [a] -> OrdList a mergeSort = sortTree . build (.) :: (b -> c) -> (a -> b) -> (a -> c) (f . g) a = f (g a) Programmieren in Haskell 41 Phase 2: Sortieren eines Binärbaums sortTree sortTree sortTree sortTree Programmieren in Haskell :: (Ord a) Nil = (Leaf a) = (Br l r) = => Tree a -> OrdList a [] [a] merge (sortTree l) (sortTree r) 42 Phase 2: Sortieren eines Binärbaums sortTree sortTree sortTree sortTree merge merge merge merge | | :: (Ord a) Nil = (Leaf a) = (Br l r) = :: (Ord a) => [] bs (a:as) [] (a:as) (b:bs) a <= b otherwise Programmieren in Haskell => Tree a -> OrdList a [] [a] merge (sortTree l) (sortTree r) OrdList a -> OrdList a -> OrdList a = bs = a:as = a:merge as (b:bs) = b:merge (a:as) bs 42 merge’ :: (Ord a) => OrdList a -> OrdList a -> OrdList a merge’ [] = id merge’ (a:as) = insert where insert [] = a:as insert (b:bs) | a <= b = a:merge’ as (b:bs) | otherwise = b:insert bs Programmieren in Haskell 43 Programmieren in Haskell ordered x ∧ ordered y =⇒ ordered (merge x y) (13) bag (merge x y) = bag x ] bag y (14) 44 bag bag bag bag :: Tree a -> Bag a Nil = ∅ (Leaf a) = *a+ (Br l r) = bag l ] bag r Programmieren in Haskell 45 bag bag bag bag :: Tree a -> Bag a Nil = ∅ (Leaf a) = *a+ (Br l r) = bag l ] bag r Programmieren in Haskell ordered (sortTree t) (15) bag (sortTree t) = bag t (16) 45 bag (build x) = bag x Programmieren in Haskell (17) 46 bag (build x) = bag x (17) build :: [a] -> Tree a build [] = Nil build [a] = Leaf a build as = Br (build (take k as)) (build (drop k as)) where k = length as ‘div‘ 2 Programmieren in Haskell 46 Phase 1: Konstruktion von Braun-Bäumen type Braun a = Tree a build’ :: [a] -> Braun a build’ [] = Nil build’ (a:as) = extend a (build as) Programmieren in Haskell 47 Phase 1: Konstruktion von Braun-Bäumen type Braun a = Tree a build’ :: [a] -> Braun a build’ [] = Nil build’ (a:as) = extend a (build as) extend :: a -> Braun a -> Braun a extend a Nil = Leaf a extend a (Leaf b) = Br (Leaf b) (Leaf a) Programmieren in Haskell 47 Phase 1: Konstruktion von Braun-Bäumen type Braun a = Tree a build’ :: [a] -> Braun a build’ [] = Nil build’ (a:as) = extend a (build as) extend :: a -> Braun a -> Braun a extend a Nil = Leaf a extend a (Leaf b) = Br (Leaf b) (Leaf a) extend a (Br l r) = Br r (extend a l) Programmieren in Haskell 47 1 0 0 (a) (b) 3 2 0 4 (f) Programmieren in Haskell 0 1 3 1 0 2 2 (c) 1 2 3 (d) 0 4 1 5 (e) 3 1 5 1 5 0 (g) 4 2 6 0 4 2 6 3 7 (h) 48 Programmieren in Haskell bag (extend a t) = bag (build x) = *a+ ] bag t bag x (18) (19) braun t =⇒ braun (extend a t) (20) braun (build x) (21) 49 Wohlfundierte Rekursion build :: [a] -> Tree a build [] = Nil build [a] = Leaf a build as = Br (build (take k as)) (build (drop k as)) where k = length as ‘div‘ 2 Programmieren in Haskell 50 Ist das Problem einfach, wird es mit ad-hoc Methoden gelöst. Anderenfalls wird es in einfachere Teilprobleme aufgeteilt, diese werden nach dem gleichen Prinzip gelöst und anschließend zu einer Gesamtlösung zusammengefügt. Programmieren in Haskell 51 Die Relation „≺“ heißt wohlfundiert, wenn es keine unendliche absteigende Kette · · · ≺ xn ≺ · · · ≺ x2 ≺ x1 gibt. Zum Beispiel auf N: x≺y x<y . (22) length x < length y . (23) ⇐⇒ Auf Listen: x≺y Programmieren in Haskell ⇐⇒ 52 leaves leaves leaves leaves leaves leaves :: Tree a -> [a] Nil (Leaf a) (Br Nil r) (Br (Leaf a) r) (Br (Br l’ r’) r) Programmieren in Haskell = = = = = [] [a] leaves r a : leaves r leaves (Br l’ (Br r’ r)) 53 leaves leaves leaves leaves leaves leaves :: Tree a -> [a] Nil (Leaf a) (Br Nil r) (Br (Leaf a) r) (Br (Br l’ r’) r) weight weight weight weight :: Tree a -> Integer Nil = 0 (Leaf a) = 0 (Br l r) = 1+2*(size l - 1) + weight r Programmieren in Haskell = = = = = [] [a] leaves r a : leaves r leaves (Br l’ (Br r’ r)) 53 v t Programmieren in Haskell u =⇒ t u v 54 t≺u Programmieren in Haskell ⇐⇒ weight t < weight u . 55 t≺u ⇐⇒ weight t < weight u . weight (Br (Br l’ r’) r) Programmieren in Haskell = 2*size (Br l’ r’) + weight r - 1 (Def. weight) = 2*size l’ + 2*size r’ + weight r - 1 (Def. size) > 2*size l’ + 2*size r’ + weight r - 2 (Arithmetik) = 2*size l’ + weight (Br r’ r) - 1 (Def. weight) = weight (Br l’ (Br r’ r)) (Def. weight) 55 Wohlfundierte Induktion (∀y) ((∀z ≺ y) Φ(z)) =⇒ Φ(y) (∀x) Φ(x) Programmieren in Haskell 56