1 - Testen von Mengen Zunächst importieren wir die QuickCheck-Bibliothek: 1 import Test.QuickCheck Für die Ausgabe von QuickCheck benötigen wir zunächst eine Instanz von Show: 2 3 data Set a = Set [a] deriving Show Um Mengen raten zu können müssen wir eine Instanz der Typklasse Arbitrary für den Datentyp Set angeben. Hierbei raten wir einfach eine geeignete Liste: 4 5 6 7 instance Arbitrary a => Arbitrary (Set a) where arbitrary = do xs <- arbitrary return (Set xs) Wir rekapitulieren noch einmal die angebotenen Funktionen: 8 9 empty :: Set a empty = Set [] 10 11 12 isEmpty :: Set a -> Bool isEmpty (Set xs) = null xs 13 14 15 insert :: a -> Set a -> Set a insert x (Set xs) = Set (x:xs) 16 17 18 member :: Eq a => a -> Set a -> Bool member x (Set xs) = elem x xs 19 20 21 22 23 24 25 delete :: Eq a => a -> Set a -> delete x (Set xs) = Set (remove where remove _ [] = remove y (z:zs) | y == z = | otherwise = Set a x xs) [] zs remove y zs 26 27 28 union :: Set a -> Set a -> Set a union (Set xs) (Set ys) = Set (xs ++ ys) 29 30 31 32 33 34 35 36 37 38 intersect :: Ord a => Set a -> Set a -> Set a intersect (Set s1) (Set s2) = Set (merge s1 s2) where merge [] ys = ys merge xs [] = xs merge (x:xs) (y:ys) = case compare x y of LT -> x : y : merge xs ys EQ -> y : merge xs ys GT -> y : x : merge xs ys 39 40 41 size :: Set a -> Int size (Set xs) = length xs Nun können wir damit beginnen, Eigenschaften zu definieren. 42 43 prop_isEmpty_empty :: Bool prop_isEmpty_empty = isEmpty empty 1 44 45 46 prop_member_empty :: Int -> Bool prop_member_empty x = not (member x empty) 47 48 49 prop_size_empty :: Bool prop_size_empty = size empty == 0 50 51 52 prop_isEmpty_insert :: Int -> Set Int -> Bool prop_isEmpty_insert x s = not (isEmpty (insert x s)) 53 54 55 prop_member_insert :: Int -> Set Int -> Bool prop_member_insert x s = member x (insert x s) 56 57 58 prop_member_insert2 :: Int -> Int -> Set Int -> Property prop_member_insert2 x y s = x /= y ==> member y (insert x s) == member y s 59 60 61 prop_size_insert :: Int -> Set Int -> Bool prop_size_insert x s = size (insert x s) == size s + (if member x s then 0 else 1) 62 63 64 prop_isEmpty_delete :: Int -> Set Int -> Property prop_isEmpty_delete x s = isEmpty s ==> isEmpty (delete x s) 65 66 67 prop_member_delete :: Int -> Set Int -> Bool prop_member_delete x s = not (member x (delete x s)) 68 69 70 prop_member_delete2 :: Int -> Int -> Set Int -> Property prop_member_delete2 x y s = x /= y ==> member y (delete x s) == member y s 71 72 73 prop_size_delete :: Int -> Set Int -> Bool prop_size_delete x s = size (delete x s) == size s - (if member x s then 1 else 0) 74 75 76 prop_isEmpty_union :: Set Int -> Set Int -> Bool prop_isEmpty_union s1 s2 = isEmpty (union s1 s2) == isEmpty s1 && isEmpty s2 77 78 79 prop_member_union :: Int -> Set Int -> Set Int -> Bool prop_member_union x s1 s2 = (member x s1 || member x s2) == member x (union s1 s2) 80 81 82 prop_size_union :: Set Int -> Set Int -> Bool prop_size_union s1 s2 = size (union s1 s2) == size s1 + size s2 - size (intersect s1 s2) 83 84 85 prop_isEmpty_intersect :: Set Int -> Set Int -> Property prop_isEmpty_intersect s1 s2 = isEmpty s1 || isEmpty s2 ==> isEmpty (intersect s1 s2) 86 87 88 prop_member_intersect :: Int -> Set Int -> Set Int -> Bool prop_member_intersect x s1 s2 = (member x s1 && member x s2) == member x (intersect s1 s2) 89 90 91 92 prop_size_intersect :: Set Int -> Set Int -> Bool prop_size_intersect s1 s2 = s >= 0 && s <= size s1 && s <= size s2 where s = size (intersect s1 s2) 93 94 95 96 97 98 99 100 main :: IO () main = do putStr "prop_isEmpty_empty : " quickCheck prop_isEmpty_empty putStr "prop_member_empty : " quickCheck prop_member_empty putStr "prop_size_empty : " 2 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 quickCheck prop_size_empty putStr "prop_isEmpty_insert : " quickCheck prop_isEmpty_insert putStr "prop_member_insert : " quickCheck prop_member_insert putStr "prop_member_insert2 : " quickCheck prop_member_insert2 putStr "prop_size_insert : " quickCheck prop_size_insert putStr "prop_isEmpty_delete : " quickCheck prop_isEmpty_delete putStr "prop_member_delete : " quickCheck prop_member_delete putStr "prop_member_delete2 : " quickCheck prop_member_delete2 putStr "prop_size_delete : " quickCheck prop_size_delete putStr "prop_size_delete : " quickCheck prop_size_delete putStr "prop_isEmpty_union : " quickCheck prop_isEmpty_union putStr "prop_member_union : " quickCheck prop_member_union putStr "prop_size_union : " quickCheck prop_size_union putStr "prop_isEmpty_intersect: " quickCheck prop_isEmpty_intersect putStr "prop_member_intersect : " quickCheck prop_member_intersect putStr "prop_size_intersect : " quickCheck prop_size_intersect Wir sehen, dass nicht alle Tests erfolgreich sind: Main> main prop_isEmpty_empty prop_member_empty prop_size_empty prop_isEmpty_insert prop_member_insert prop_member_insert2 prop_size_insert 4 Set [1,-3,1,4] prop_isEmpty_delete prop_member_delete 5 Set [5,-4,-4,4,5] prop_member_delete2 -1 0 Set [0,2] prop_size_delete -2 Set [1] prop_size_delete -2 : : : : : : : OK, passed 100 tests. OK, passed 100 tests. OK, passed 100 tests. OK, passed 100 tests. OK, passed 100 tests. OK, passed 100 tests. Falsifiable, after 3 tests: : OK, passed 100 tests. : Falsifiable, after 5 tests: : Falsifiable, after 1 tests: : Falsifiable, after 0 tests: : Falsifiable, after 4 tests: 3 Set [0,2,2] prop_isEmpty_union : Set [] Set [0,2] prop_member_union : prop_size_union : Set [] Set [-2] prop_isEmpty_intersect: Set [] Set [-1] prop_member_intersect : -2 Set [] Set [-2,0,2] prop_size_intersect : Set [1,-2] Set [-2,-2] Falsifiable, after 2 tests: OK, passed 100 tests. Falsifiable, after 1 tests: Falsifiable, after 0 tests: Falsifiable, after 4 tests: Falsifiable, after 0 tests: 2 - Vorstandsgerangel 1 2 3 4 5 % Auflistung der Kandidaten kandidat(meier ). kandidat(mueller ). kandidat(schulz ). kandidat(schroeder). 6 7 8 % Verschiedenheit verschieden(K1, K2) :- kandidat(K1), kandidat(K2), K1 \= K2. 9 10 11 alleVerschieden(vorstand(V,K,S)) :verschieden(V,K), verschieden(V,S), verschieden(K,S). 12 13 14 15 16 % Enthaltensein im Vorstand imVorstand(P,vorstand(P,_,_)). imVorstand(P,vorstand(_,P,_)). imVorstand(P,vorstand(_,_,P)). 17 18 19 nichtImVorstand(P,vorstand(V,K,S)) :verschieden(P,V), verschieden(P,K), verschieden(P,S). 20 21 22 allesKandidaten(vorstand(V,K,S)) :kandidat(V), kandidat(K), kandidat(S). 23 24 % Bedingungen 25 26 27 28 29 % Entweder Meier oder Mueller meierOderMueller(V) :- imVorstand(meier ,V) , nichtImVorstand(mueller,V). meierOderMueller(V) :- imVorstand(mueller,V) , nichtImVorstand(meier ,V). meierOderMueller(V) :- nichtImVorstand(mueller,V), nichtImVorstand(meier ,V). 30 31 32 33 34 % Mueller nur wenn Schulz Vorsitzender muellerNurWennSchulz(vorstand(schulz,K,S)) :imVorstand(mueller,vorstand(schulz,K,S)). muellerNurWennSchulz(V) :- 4 35 nichtImVorstand(mueller,V) . 36 37 38 39 % Schroeder nur wenn Meier schroederNurWennMeier(V) :- imVorstand(schroeder,V), imVorstand(meier,V). schroederNurWennMeier(V) :- nichtImVorstand(schroeder,V). 40 41 42 43 44 45 % Meier nur wenn Schulz nicht Schriftführer meierOhneSchulz(vorstand(V,K,schulz)) :nichtImVorstand(meier,vorstand(V,K,schulz)). meierOhneSchulz(vorstand(_,_,S)) :verschieden(S,schulz). 46 47 48 49 50 51 % Schulz nur wenn Schröder nicht Vorsitzender schulzOhneSchroeder(vorstand(schroeder,K,S)) :nichtImVorstand(schulz,vorstand(schroeder,K,S)). schulzOhneSchroeder(vorstand(V,_,_)) :verschieden(V,schroeder). 52 53 54 55 56 57 58 59 60 % Lösen loese(V) :- allesKandidaten(V), alleVerschieden(V), meierOderMueller(V), muellerNurWennSchulz(V), schroederNurWennMeier(V), meierOhneSchulz(V), schulzOhneSchroeder(V). Als Lösungen ergeben sich: ?- loese(V). V = vorstand(meier, schulz, schroeder) ; V = vorstand(schulz, meier, schroeder) ; V = vorstand(schulz, schroeder, meier) ; false. 3 - Sprache eines Automaten Wir geben noch einmal den Datentypen sowie drei Testautomaten an. 1 2 3 data State = State [(Char, State)] -- transitions Bool -- final state? 4 5 6 7 8 9 10 11 -- accepts any word that contains "baa" containsBAA :: State containsBAA = let q0 = State [('a', q0), q1 = State [('a', q2), q2 = State [('a', q3), q3 = State [('a', q3), in q0 ('b', ('b', ('b', ('b', 12 13 14 15 16 17 -- accepts any number of repetitions of "ab" manyAB :: State manyAB = let q0 = State [('a', q1)] True q1 = State [('b', q0)] False in q0 5 q1)] q1)] q1)] q3)] False False False True 18 19 20 21 22 23 24 25 26 27 28 29 30 -- accepts only the word haskell :: State haskell = let q0 = State q1 = State q2 = State q3 = State q4 = State q5 = State q6 = State q7 = State q8 = State in q0 "haskell" [('h', [('a', [('s', [('k', [('e', [('l', [('l', [('l', [] q1)] q2)] q3)] q4)] q5)] q6)] q7)] q8)] False False False False False False False False True In der Implementierung nutzen wir eine Hilfsfunktion, die eine Liste von Paaren durchsucht, wobei jedes Paar aus einem Zustand und dem Wortprefix besteht, durch das man zu diesem Zustand gelangt ist. 31 32 33 34 language :: State -> [String] language s = lang [(s, [])] where lang :: [(State, String)] -> [String] Ist die Liste leer, so gibt es keine betrachteten Zustände mehr, wir fügen keine Wörter hinzu. 35 lang [] = [] Für die nicht-leere Liste übernehmen wir zunächst die Wörter für die Endzustände, bevor wir die Wörter anhand der jeweiligen Transitionen verlängern und die Folgezustände berücksichtigen. Dies kann man sehr elegant mittels zweier List-Comprehensions ausdrücken, natürlich geht dies aber auch mittels map und filter (1. Comprehension) bzw. concatMap (2. Comprehension). 36 37 lang ps = [ w | (State _ True, w) <- ps] ++ lang [ (s', w ++ [c]) | (State ts _, w) <- ps, (c, s') <- ts ] Allerdings ist diese Lösung etwas ineffizient, da bei jeder Verlängerung eines Wortes um einen Buchstaben das gesamte Wort durchlaufen werden muss. Dadurch ergeben sich für ein Wort der Länge n insgesamt (n − 1) + (n − 2) + · · · + 1 Aufrufe von (++), was einer quadratischen Laufzeit entspricht. Ein beliebter Trick ist hier, die Liste zunächst verkehrt herum aufzubauen, um sie erst ganz am Ende umzudrehen, womit sich eine lineare Laufzeit ergibt. 38 39 40 41 42 43 language' :: State -> [String] language' s = lang' [(s, [])] where lang' [] = [] lang' ps = [ reverse w | (State _ True, w) <- ps] ++ lang' [ (s', c:w) | (State ts _, w) <- ps, (c, s') <- ts ] Zuguterletzt testen wir unsere Implementierung noch: ghci> take 10 $ language containsBAA ["baa","abaa","baaa","baab","bbaa","aabaa","abaaa","abaab","abbaa","baaaa"] ghci> take 10 $ language manyAB ["","ab","abab","ababab","abababab","ababababab","abababababab","ababababababab","abababababababab","ab ghci> take 10 $ language haskell ["haskelll"] Der letzte Aufruf würde übrigens nicht terminieren, wenn wir nicht den Spezialfall von lang für die leere Liste implementiert hätten. Der Laufzeitunterschied zwischen language und language' kann für große Wörter durchaus signifikant werden: 6 ghci> :set +s ghci> language manyAB !! 10000 ... (20.74 secs, 18178318376 bytes) ghci> language' manyAB !! 10000 ... (0.07 secs, 36044200 bytes) Man sieht also, dass es sich durchaus lohnt über Laufzeiten von Funktionen nachzudenken. 4 - Testen von Warteschlangen Zunächst importieren wir die QuickCheck-Bibliothek: 1 import Test.QuickCheck Anschließend fügen wir die Show- und Arbitrary-Instanzen hinzu. 2 3 data Queue a = Queue [a] [a] deriving Show 4 5 6 7 8 9 instance Arbitrary a => Arbitrary (Queue a) where arbitrary = do xs <- arbitrary ys <- arbitrary return (Queue xs ys) Wir rekapitulieren noch einmal die Implementierung: 10 11 12 13 -- smart queue :: queue [] queue xs constructor [a] -> [a] -> Queue a ys = Queue (reverse ys) ys ys = Queue xs ys 14 15 16 17 -- empty queue emptyQueue :: Queue a emptyQueue = queue [] [] 18 19 20 21 -- Is a queue empty? isEmptyQueue :: Queue a -> Bool isEmptyQueue (Queue _ ys) = null ys 22 23 24 25 -- add to a queue enqueue :: a -> Queue a -> Queue a enqueue x (Queue xs ys) = queue xs (x:ys) 26 27 28 29 30 -- get next element next :: Queue a -> a next (Queue (x:_) _) = x next _ = error "Queue.next: empty queue" 31 32 33 34 35 -- remove first element dequeue :: Queue a -> Queue a dequeue (Queue (_:xs) ys) = queue ys xs dequeue _ = error "Queue.dequeue: empty queue" 36 37 -- size of a queue 7 38 39 size :: Queue a -> Int size (Queue xs ys) = length xs + length ys 40 41 42 43 -- invariant a queue should fulfill invariant :: Queue a -> Bool invariant (Queue xs ys) = not (null xs) || null ys Es folgen die zu testenden Eigenschaften: 44 45 prop_queue :: [Int] -> [Int] -> Bool prop_queue xs ys = invariant (queue xs ys) 46 47 48 prop_size_queue :: [Int] -> [Int] -> Bool prop_size_queue xs ys = length xs + length ys == size (queue xs ys) 49 50 51 prop_emptyQueue :: Bool prop_emptyQueue = invariant emptyQueue 52 53 54 prop_isEmptyQueue_emptyQueue :: Bool prop_isEmptyQueue_emptyQueue = isEmptyQueue emptyQueue 55 56 57 prop_size_emptyQueue :: Bool prop_size_emptyQueue = size emptyQueue == 0 58 59 60 prop_enqueue :: Int -> Queue Int -> Property prop_enqueue x q = invariant q ==> invariant (enqueue x q) 61 62 63 prop_isEmptyQueue_enqueue :: Int -> Queue Int -> Property prop_isEmptyQueue_enqueue x q = invariant q ==> not (isEmptyQueue (enqueue x q)) 64 65 66 prop_size_enqueue :: Int -> Queue Int -> Property prop_size_enqueue x q = invariant q ==> size (enqueue x q) == 1 + size q 67 68 69 prop_dequeue :: Queue Int -> Property prop_dequeue q = invariant q && not (isEmptyQueue q) ==> invariant (dequeue q) 70 71 72 73 prop_size_dequeue :: Queue Int -> Property prop_size_dequeue q = invariant q && not (isEmptyQueue q) ==> size (dequeue q) == size q - 1 74 75 76 prop_next_enqueue_empty :: Int -> Bool prop_next_enqueue_empty x = next (enqueue x emptyQueue) == x 77 78 79 80 prop_next_enqueue_Nonempty :: Int -> Queue Int -> Property prop_next_enqueue_Nonempty x q = invariant q && not (isEmptyQueue q) ==> next q == next (enqueue x q) 81 82 83 84 85 86 87 88 89 90 91 92 main :: IO () main = do putStr "prop_queue : " quickCheck prop_queue putStr "prop_size_queue : " quickCheck prop_size_queue putStr "prop_emptyQueue : " quickCheck prop_emptyQueue putStr "prop_isEmptyQueue_emptyQueue: " quickCheck prop_isEmptyQueue_emptyQueue putStr "prop_size_emptyQueue : " 8 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 quickCheck prop_size_emptyQueue putStr "prop_enqueue : quickCheck prop_enqueue putStr "prop_isEmptyQueue_enqueue : quickCheck prop_isEmptyQueue_enqueue putStr "prop_size_enqueue : quickCheck prop_size_enqueue putStr "prop_dequeue : quickCheck prop_dequeue putStr "prop_size_dequeue : quickCheck prop_size_dequeue putStr "prop_next_enqueue_empty : quickCheck prop_next_enqueue_empty putStr "prop_next_enqueue_Nonempty : quickCheck prop_next_enqueue_Nonempty " " " " " " " Wir sehen, dass nicht alle Tests erfolgreich sind: *Main> main prop_queue : prop_size_queue : [] [1] prop_emptyQueue : prop_isEmptyQueue_emptyQueue: prop_size_emptyQueue : prop_enqueue : prop_isEmptyQueue_enqueue : prop_size_enqueue : 0 Queue [] [] prop_dequeue : prop_size_dequeue : prop_next_enqueue_empty : prop_next_enqueue_Nonempty : OK, passed 100 tests. Falsifiable, after 2 tests: OK, passed 100 tests. OK, passed 100 tests. OK, passed 100 tests. OK, passed 100 tests. OK, passed 100 tests. Falsifiable, after 0 tests: OK, OK, OK, OK, passed passed passed passed 100 100 100 100 tests. tests. tests. tests. 5 - Europameisterschaft 2016 Die Relation spieltFuer(Spieler, Land): 1 2 3 4 5 6 7 8 spieltFuer(bale, wal). spieltFuer(boateng, ger). spieltFuer(buffon, ita). spieltFuer(ibrahimovic, swe). spieltFuer(kroos, ger). spieltFuer(lewandowski, pol). spieltFuer(pogba, fra). spieltFuer(rooney, eng). Die Relation spieltIn(Land, Gruppe): 9 10 11 12 13 spieltIn(eng, spieltIn(ger, spieltIn(fra, spieltIn(ita, spieltIn(pol, gruppeB). gruppeC). gruppeA). gruppeE). gruppeC). 9 14 15 spieltIn(swe, gruppeE). spieltIn(wal, gruppeB). Die Relation vorrundenGegner(LandA, LandB): 16 17 18 19 20 21 vorrundenGegner(ger, vorrundenGegner(pol, vorrundenGegner(eng, vorrundenGegner(wal, vorrundenGegner(ita, vorrundenGegner(swe, pol). ger). wal). eng). swe). ita). Die Relation spieltInGruppe(Spieler, Gruppe): 22 spieltInGruppe(S, G) :- spieltFuer(S, L), spieltIn(L, G). und spieltGegen(Spieler1, Spieler2): 23 24 25 spieltGegen(S1, S2) :spieltFuer(S1, L1), spieltFuer(S2, L2), vorrundenGegner(L1, L2). Die Anfragen (mit swipl): % Laden der Datei ?- ['4_EM.pl']. % 4_EM.pl compiled 0.00 sec, 24 clauses true. % Spielt Lewandowski für Deutschland? ?- spieltFuer(lewandowski, ger). false. % Spielt Kroos für Deutschland? ?- spieltFuer(kroos, ger). true. % Spielt England in der Gruppe C? ?- spieltInGruppe(eng, gruppeC). false. % In welcher Gruppe spielt Wales? ?- spieltIn(wal, Gruppe). Gruppe = gruppeB. % Für welches Land spielt Rooney? ?- spieltFuer(rooney, Land). Land = eng. % Welche Spieler spielen für Deutschland? ?- spieltFuer(Spieler, ger). Spieler = boateng ; Spieler = kroos. % Gegen welche Spieler spielt Rooney? ?- spieltGegen(rooney, Gegner). Gegner = bale ; false. 10 % Welche Spieler der Gruppe C sind bekannt? ?- spieltInGruppe(Spieler, gruppeC). Spieler = boateng ; Spieler = kroos ; Spieler = lewandowski ; false. % Spielen Kroos und Bale in der Vorrunde gegeneinander? ?- spieltGegen(kroos, bale). false. % Welche Spieler in welchen Gruppen sind bekannt? ?- spieltInGruppe(S, G). S = bale, G = gruppeB ; S = boateng, G = gruppeC ; S = buffon, G = gruppeE ; S = ibrahimovic, G = gruppeE ; S = kroos, G = gruppeC ; S = lewandowski, G = gruppeC ; S = pogba, G = gruppeA ; S = rooney, G = gruppeB. 11