Grundlagen der Programmierung 2 Unendliche Listen und Ströme(B) Prof. Dr. Manfred Schmidt-Schauÿ Künstliche Intelligenz und Softwaretechnologie 17. Mai 2006 Beispiel: scanl, scanr scanl berechnet das foldl jeweils der ersten n Elemente: So sieht man, welche Unterliste verarbeitet wird: Prelude> scanl (\x y -> y:x) [] [1..10] [[],[1],[2,1],[3,2,1],[4,3,2,1],[5,4,3,2,1],[6,5,4,3,2,1],[7,6,5,4,3,2,1], scanl (+) 0 [1..] [0,1,3,6,10,15,21,28,36,45,55,66,78,91, ... Grundlagen der Programmierung 2 - 2 - Beispiel: scanr scanr wendet foldr jeweils auf die Restlisten an: Prelude> scanr (:) [] [1..10] [[1,2,3,4,5,6,7,8,9,10],[2,3,4,5,6,7,8,9,10],[3,4,5,6,7,8,9,10], [4,5,6,7,8,9,10],[5,6,7,8,9,10],[6,7,8,9,10],[7,8,9,10], [8,9,10],[9,10],[10],[]] sinnvoll, wenn der foldr-Anteil nur Anfänge benötigt, oder wieder einen Strom erzeugt: scanr (+) 0 [1..] [^CInterrupted. Sinnvolle Verarbeitung: map head (scanr (:) [] Grundlagen der Programmierung 2 [1..]) - 3 - Sortierte Ströme: Mischen Mischen von 2 aufsteigend sortierten Strömen: mische [] ys = ys mische xs [] = xs mische (x:xs) (y:ys) = if x <= y then x: (mische xs (y:ys)) else y: (mische (x:xs) ys) mische [1,3..] [2,4..] [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23, 24,25,26,27,28,29,30,31,32,33,34,35,36,37, 38,39,40,41,42,43,44,45,46,47,48,49,50,51,52, 53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,.... > mische [2,4..] [3,6..] [2,3,4,6,6,8,9,10,12,12 Grundlagen der Programmierung 2 - 4 - Sortierte Ströme: Mischen Mischen der Vielfachen von 2,3,5: [2,4,6,8...] [2,3,4,5,6,6,8,9,10,10,12,12,14,15,15,16,..] [3,6,9,12...] mische [5,10,15,20...] Main> mische (mische (map (2*) [1..]) (map (3*) [1..])) (map (5*) [1..]) [2,3,4,5,6,6,8,9,10,10,12,12,14,15,15,16,18,18,20,20,21,22,24,24,25,26,27, 28,30,30,30,32,33,34,35,36,36,38,39,40,40,42,42,44,45,45,46,48,48,50,50,.. Ausgabe enthält doppelte Elemente. Entfernen: mit nub oder mische umprogrammieren Grundlagen der Programmierung 2 - 5 - Sortierte Ströme: Mischen Entfernen doppelter Zahlen: *Main> nub (mische (mische (map (5*) [1..]) ) (map (2*) [1..]) (map (3*) [1..])) [2,3,4,5,6,8,9,10,12,14,15,16,18,20,21,22,24,25,26,27,28,30,32,33,34,... Grundlagen der Programmierung 2 - 6 - Sortierte Ströme: Mischen ohne Doppelte [2,4,6,8...] [2,3,4,5,6,8,9,10,12,14,15,16,..] [3,6,9,12...] mische [5,10,15,20...] mischeNub [] ys = ys mischeNub xs [] = xs mischeNub (x:xs) (y:ys) = if x == y then (mischeNub xs (y:ys)) else if x <= y then x: (mischeNub xs (y:ys)) else y: (mischeNub (x:xs) ys) *Main> mischeNub (mischeNub (map (2*) [1..]) (map (3*) [1..])) (map (5*) [1..]) [2,3,4,5,6,8,9,10,12,14,15,16,18,20,21,22,24,25,26,27,28,30,32,33,34,35, Grundlagen der Programmierung 2 - 7 - Beispiel Differenz von zwei Strömen, wenn die Eingabeströme aufsteigend sortierte Zahlen enthalten strom_minus xs [] = xs strom_minus [] ys = [] strom_minus (x:xs) (y:ys) = if x == y then strom_minus xs (y:ys) else if x > y then strom_minus (x:xs) ys else x: (strom_minus xs (y:ys)) > strom_minus [1..] (nub (mische (map (2*) [1..]) (mische (map (3*) [1..]) (map (5*) [1..]) ))) [1,7,11,13,17,19,23,29,31,37,41,43, Grundlagen der Programmierung 2 - 8 - Beispiel Schnitt zweier Strömen aus aufsteigend sortierte Zahlen: strom_schnitt xs [] = [] strom_schnitt [] ys = [] strom_schnitt (x:xs) (y:ys) = if x == y then x: strom_schnitt xs else if x > y then strom_schnitt (x:xs) ys else strom_schnitt xs (y:ys) *Main> strom_schnitt [2,4..] [3,6..] [6,12,18,24,30,36,42,48,54,60,66,72,78,84,... Grundlagen der Programmierung 2 - 9 - ys Verallgemeinerung der Ordnung auf den Stromelementen Erfordert Verallgemeinerung der Funktionen: mischeNubGen ord [] ys = ys mischeNubGen ord xs [] = xs mischeNubGen ord (x:xs) (y:ys) = if x ‘ord‘ y && y ‘ord‘x then (mischeNubGen ord xs (y:ys)) else if x ‘ord‘ y then x: (mischeNubGen ord xs (y:ys)) else y: (mischeNubGen ord (x:xs) ys) Für aufsteigende Ströme von Zahlen: mischeNubGen (<=) Für absteigende Ströme von Zahlen: mischeNubGen (>=) Grundlagen der Programmierung 2 - 10 - Primzahlen als Strom primes = 2: [x | x <- [3,5..], and (map (\t-> x ‘mod‘ t /= 0) (takeWhile (\y -> y^2 <= x) primes))] Der Vergleich mit einer durch den Fermatschen Primzahltest erzeugten Liste ergibt: primesDifference = strom_minus (2:[x | x <- [3,5..], fermat_test_naiv x]) primes *Main> primesDifference [561,1105,1729,2465,2821,6601^C Grundlagen der Programmierung 2 - 11 - foldr, foldl auf Strömen foldl ungeeignet, da es für unendliche Ströme nicht terminiert. *Main> foldl (\y x -> x:x:y) [] [1..10] [10,10,9,9,8,8,7,7,6,6,5,5,4,4,3,3,2,2,1,1] *Main> foldl (\y x -> x:x:y) [] [1..] ^CInterrupted. foldr ist gut einsetzbar: *Main> foldr (\x y -> x:x:y) [] [1..10] [1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10] foldr (\x y -> x:x:y) [] [1..] [1,1,2,2,3,3,4,4,5,5,6,6....... *Main> Grundlagen der Programmierung 2 - 12 - Verarbeitung von Files mittels Strömen Funktionen zur Bearbeitung langer Strings: Vordefinierte Funktionen in Haskell: words unwords lines unlines :: :: :: :: String -> [String] [String] -> String String -> [String] [String] -> String Grundlagen der Programmierung 2 - 13 - Beispiele: Files als Ströme *Main> words "abcd efgh eirof kdjn Dcnsajn" ["abcd","efgh","eirof","kdjn","Djcnsajn"] *Main> concat (words "abcd efgh eirof kdjn djcnsajn") "abcdefgheirofkdjndjcnsajn" *Main> unwords (words "abcd efgh eirof kdjn djcnsajn") "abcd efgh eirof kdjn djcnsajn" Grundlagen der Programmierung 2 - 14 - Beispiele: Files als Ströme. lines und unlines: *Main> lines "Habe nun, ach! Philosophie,\n Juristerei und Medizin,\n und leider auch Theologie" ["Habe nun, ach! Philosophie,"," Juristerei und Medizin,", " und leider auch Theologie"] *Main> unlines ["Habe nun, ach! Philosophie,"," Juristerei und Medizin,", " und leider auch Theologie"] "Habe nun, ach! Philosophie,\n Juristerei und Medizin,\n und leider auch Theologie\n" *Main> (map words (lines "Habe nun, ach! Philosophie,\n Juristerei und Medizin,\n und leider auch Theologie")) [["Habe","nun,","ach!","Philosophie,"],["Juristerei","und","Medizin,"], ["und","leider","auch","Theologie"]] Grundlagen der Programmierung 2 - 15 - Beispiele: Files als Ströme. lines und unlines: *Main> unlines (map unwords [["Habe","nun,","ach!","Philosophie,"], ["Juristerei","und","Medizin,"],["und","leider","auch","Theologie"]]) "Habe nun, ach! Philosophie,\nJuristerei und Medizin, \nund leider auch Theologie\n" Grundlagen der Programmierung 2 - 16 - Beispiele: Files Kommt Wort w in einem langen String vor? wordEq w strom = w == take (length w) strom wordIn w [] = False wordIn w (x:strom) = wordEq w (x:strom) || wordIn w strom wordCount w strom = sum (map (\str -> if wordEq w str then 1 else 0) (tails strom)) Grundlagen der Programmierung 2 - 17 - Beispiele: Files; Verwendungsbeispiele: *Main> wordIn "Theo" "Habe nun, ach! Philosophie,\n Juristerei und Medizin,\n und leider auch Theologie" True *Main> wordIn "Theorem" "Habe nun, ach! Philosophie,\n Juristerei und Medizin,\n und leider auch Theologie" False *Main> wordCount "und" "Habe nun, ach! Philosophie,\n Juristerei und Medizin,\n und leider auch Theologie" 2 Grundlagen der Programmierung 2 - 18 - Beispiele: Files Kommt Wort w in einem langen String vor? Wenn sich Worte überlappen dürfen, muss man spezifizieren, was genau gezählt werden soll: *Main> wordCount "aa" "aaaa" 3 Grundlagen der Programmierung 2 - 19 - IO und Files als Ströme fileLesen fu = do putStr "File-Name:?" fname <-getLine contents <-(readFile fname) putStr (show (fu contents)) Grundlagen der Programmierung 2 - 20 - Bemerkungen zur Stromverarbeitung verzögerte Auswertung unterstützt Programmierung kombinierter Anfragen, die den Strom nur einmal lesen. nub (filter p (map f xs)) map f . Grundlagen der Programmierung 2 filter nub - 21 - Bemerkungen zur Stromverarbeitung: Fallen Speicher-Blockaden z.B. durch Definition eines Stromes als Konstante im Programm primes, Grundlagen der Programmierung 2 - 22 - Bemerkungen zur Stromverarbeitung Falle: findeDoppelte strom = findeDoppelteR strom strom 0 findeDoppelteR strom1 [] n = False findeDoppelteR strom1 (x:strom2) n = (take n strom1 == take n (x:strom2)) || findeDoppelteR strom1 strom2 (n+1) *Main> findeDoppelte ([1..10] ++ [1..20]) True *Main> findeDoppelte ([1..10] ++ [1..]) True findeDoppelte ([1..]) In 1 min wird ca. 1 GB interner Speicher blockiert. Grundlagen der Programmierung 2 - 23 - Stromverarbeitung für endliche Mengen Repräsentation von Mengen als aufsteigend sortierte Listen: Operationen: Schnitt, Vereinigung, und Differenz sind einfach und sehr effizient das Ergebnis ist ebenfalls aufsteigend sortierte Liste Grundlagen der Programmierung 2 - 24 - Ströme: Weitere Funktionalitäten Potenzmenge: nicht sinnvoll bei Strömen, aber bei Mengen die Ausgabe ist exponentiell Bei geordneter Ausgabe kann man verschiedene Ordnungen wählen: Nach Kardinalität, lexikographischen Ordnung auf Folgen. Grundlagen der Programmierung 2 - 25 - Ströme: Potenzmenge ---Potenzmenge, lexikographisch potenzmenge xs = []:potenzmengeR xs potenzmengeR [x] = [[x]] potenzmengeR (x:xs) = let pm = (potenzmengeR xs) in : (map (x:) pm) ++ Grundlagen der Programmierung 2 [x] pm - 26 - Ströme: Potenzmenge ---Potenzmenge, Kardinalitaet, dann lexikographisch potenzmengeKL xs = [] : (concatMap (\n -> potenzmengeKLR xs n) [1.. length xs]) potenzmengeKLR [] _ = [] potenzmengeKLR xs 0 = [[]] potenzmengeKLR xs 1 = map (\u-> [u]) xs potenzmengeKLR (x:xs) (n+1) = let pmox = potenzmengeKLR xs (n+1) pmwx = potenzmengeKLR xs n in (map (x:) pmwx) ++ pmox Grundlagen der Programmierung 2 - 27 - Ströme: Kreuzprodukt von Mengen Ausgabe kann nicht die lexikographische Ordnung einhalten, wenn die Ausgabe fair sein soll. Kreuzprodukt in lexikographischer Reihenfolge, wenn die Eingabe sortierte endliche Mengen sind: kreuzprodukt xs ys = [(x,y) | x <- xs, y <- ys] Grundlagen der Programmierung 2 - 28 -