Programmieren in Haskell Felder Peter Steffen Universität Bielefeld Technische Fakultät 01.12.2010 1 Programmieren in Haskell Was wir heute machen Beispiel Die Typklasse Ix Felder in Haskell Funktionstabellierung Binäre Suche Pascalsches Dreieck Hashing (anspruchsvolles Projekt) 2 Programmieren in Haskell Quadratzahlen 0 1 2 3 n 0 1 4 9 · · · n2 squaresList :: Integral a => [a] squaresList = [n^2 | n <- [0..]] Zugriff auf die n-te Quadratzahl mit squaresList!!n, z.B. squaresList!!5 => 25 Laufzeit? Θ(n) 3 Programmieren in Haskell Quadratzahlen mit Feldern (Arrays) squaresArray :: (Integral a, Ix a) => Array a a squaresArray = array (0,99) [(n,n^2) | n <- [0..99]] Zugriff auf die n-te Quadratzahl mit squaresArray!n, z.B. squaresArray!5 => 25 Laufzeit? Θ(1) 4 Programmieren in Haskell Interface der Klasse Ix class Ord a range index inRange rangeSize => :: :: :: :: Ix a where (a,a) -> [a] (a,a) -> a -> Int (a,a) -> a -> Bool (a,a) -> Int range (a,b) liefert eine Liste aller Elemente zwischen a und b: Array> range (1,5) [1,2,3,4,5] index (a,b) c liefert den Index des Wertes c im Bereich (a,b) Array> index (3,5) 3 0 inRange (a,b) c gibt aus, ob sich der Wert c innerhalb des Bereichs (a,b) befindet Array> inRange (1,5) 3 True rangeSize (a,b) liefert die Grösse des Bereichs (a,b) Array> rangeSize (1,5) 5 5 Programmieren in Haskell Funktionen auf Arrays Um Arrays verwenden zu können, muss das Modul Array importiert werden: import Array Funktionen auf Arrays: array :: Ix a => (a,a) -> [(a,b)] -> Array a b -- erstellt ein Array der Dimension (a,b) bounds :: Ix a => Array a b -> (a,a) -- gibt die Dimensionen eines Arrays zurueck assocs :: Ix a => Array a b -> [(a,b)] -- gibt den Inhalt eines Arrays zurueck (!) :: Ix a => Array a b -> a -> b -- liefert den Wert eines Arrays an einer bestimmten Position 6 Programmieren in Haskell array/bounds/assocs/(!) aFewSquares = array (0,4) [(n,n^2) | n <- [0..4]] bounds aFewSquares => (0,4) assocs aFewSquares => [(0,0),(1,1),(2,4),(3,9),(4,16)] aFewSquares!4 => 16 7 Programmieren in Haskell Funktionstabellierung tabulate :: Ix a => (a -> b) -> (a,a) -> Array a b tabulate f bs = array bs [(i, f i) | i <- range bs] 8 Programmieren in Haskell Anwendung: Tabellierung badfib badfib badfib badfib :: Integral a => a -> a 0 = 1 1 = 1 n = badfib (n-2) + badfib (n-1) fib :: (Integral a, Ix a) => a -> a fib n = t!n where t = tabulate f (0,n) f 0 = 1 f 1 = 1 f n = t!(n-2) + t!(n-1) 9 Programmieren in Haskell Listen zu Felder listArray :: Ix a => (a,a) -> [b] -> Array a b -- vordefiniert listArray bs vs = array bs (zip (range bs) vs) zip zip zip zip zip :: [a] -> [b] -> [(a,b)] -- vordefiniert [] [] = [] [] (y:ys) = [] (x:xs) [] = [] (x:xs) (y:ys) = (x,y):zip xs ys zip [1..5] [’a’..’z’] => [(1,’a’),(2,’b’),(3,’c’),(4,’d’),(5,’e’)] 10 Programmieren in Haskell Typ Ordering data Ordering = LT | EQ | GT -- vordefiniert compare :: Ord a => a -> a -> Ordering -- vordefiniert compare a b | a < b = LT | a == b = EQ | a > b = GT 11 Programmieren in Haskell Anwendung: Binäre Suche binarySearch :: (Ord b, Integral a, Ix a) => Array a b -> b -> Bool binarySearch a e = within (bounds a) where within (l,r) = l <= r && let m = (l + r) ‘div‘ 2 in case compare e (a!m) of LT -> within (l, m-1) EQ -> True GT -> within (m+1, r) 12 Programmieren in Haskell Anwendung: Pascalsches Dreieck 0 1 2 3 4 5 6 7 8 13 0 1 1 1 1 1 1 1 1 1 1 2 1 2 3 4 5 6 7 8 1 3 6 10 15 21 28 3 4 5 6 7 8 1 4 1 10 5 1 20 15 6 1 35 35 21 7 1 56 70 56 28 8 1 Programmieren in Haskell Pascalsches Dreieck pascalsTriangle :: Int -> Array (Int,Int) Int pascalsTriangle n = a where a = array ((0,0),(n,n)) ( [((i,j),0) | i <- [0..n], j <- [i+1..n]] ++ [((i,0),1) | i <- [0..n]] ++ [((i,i),1) | i <- [1..n]] ++ [((i,j),a!(i-1,j) + a!(i-1,j-1)) | i <- [2..n], j <- [1..i-1]]) 14 Programmieren in Haskell Anzeigen eines zweidimensionalen Arrays showArray :: (Show a, Ix b, Ix c, Enum b, Enum c) => Array (c,b) a -> IO () showArray arr = let ((lx,ly),(ux,uy)) = bounds arr row r = concat [ show (arr!(r,c)) ++ " " | c <- [ly .. uy]] rows = concat [ row r ++ "\n" | r <- [lx .. ux]] in putStrLn rows Main> showArray (pascalsTriangle 5) 1 0 0 0 0 0 1 1 0 0 0 0 1 2 1 0 0 0 1 3 3 1 0 0 1 4 6 4 1 0 1 5 10 10 5 1 15 Programmieren in Haskell Array-Update (//) :: (Ix a) => Array a b -> [(a, b)] -> Array a b unitMatrix :: (Ix a, Num b) => (a,a) -> Array (a,a) b unitMatrix (l,r) = array bs’ [(ij,0) | ij <- range bs’] // [((i,i),1) | i <- range (l,r)] where bs’ = ((l,l),(r,r)) 16 Programmieren in Haskell Hashing: Einführendes Beispiel Ein Pizza-Lieferservice in Bielefeld speichert die Daten seiner Kunden: Name, Vorname, Adresse und Telefonnummer. Wenn ein Kunde seine Bestellung telefonisch aufgibt, um dann mit der Pizza beliefert zu werden, dann muss er seine Telefonnummer angeben, da er über diese Nummer eindeutig identifiziert werden kann. 17 Programmieren in Haskell Hashing: Einführendes Beispiel Telefonnummer 00000000 00000001 00000002 ... 99999997 99999998 99999999 18 Name Müller Schmidt Schultz ... Meier Neumann Schröder Vorname Heinz Werner Hans ... Franz Herbert Georg PLZ 33615 33615 33602 ... 33609 33612 33647 Straße Unistraße 15 Grünweg 1 Arndtstraße 12 ... Kirchweg 4 Jägerallee 15 Mühlweg 2 Programmieren in Haskell Hashing: Einführendes Beispiel Bielefeld hat ca. 300.000 Einwohner, dann gibt es vielleicht 200.000 Telefonnummern. Davon bestellt jeder fünfte eine Pizza, bleiben 40.000 potentielle Einträge, verteilt auf mehrere Pizza-Lieferservices. Optimistisch geschätzt wird unsere Pizzeria also ca. 10.000 Kunden haben. 19 Programmieren in Haskell Hashing: Einführendes Beispiel Da stellt sich folgende Frage: Wir wissen doch gar nicht, welche Telefonnummern bestellen werden – wie sollen denn dann die Zeilen benannt werden? Unsere Aufgabe ist es, alle 100 Millionen Telefonnummern (denn jede einzelne könnte ja theoretisch bestellen) so abzubilden, dass sie in eine 10.000 Zeilen große Tabelle passen. 20 Programmieren in Haskell Modulo Hierzu machen wir uns jetzt eine mathematische Operation zunutze, die Modulo-Operation: x mod y liefert als Ergebnis den Rest der ganzzahligen Division x/y . Beispielsweise ergibt 117 mod 20 = 17, da 117 = 5 · 20 + 17. 21 Programmieren in Haskell Hash-Funktion Beispiel h(Telefonnummer) = Telefonnummer mod Tabellenlänge oder allgemein: Beispiel h(k) = k mod m mit h für Hashfunktion, k für key und m für Tabellenlänge. 22 Programmieren in Haskell Hash-Funktion Beispiel h(Telefonnummer) = Telefonnummer mod Tabellenlänge oder allgemein: Beispiel h(k) = k mod m mit h für Hashfunktion, k für key und m für Tabellenlänge. 22 Programmieren in Haskell Kollisionen Wir benutzen also diese Hashfunktion, um jedem Schlüssel einen Index (hier eine Zahl zwischen 0 und 9999) in einer verkleinerten Tabelle (der sogenannten Hashtabelle) zuzuordnen und damit eine Menge Platz zu sparen. Leider kann es allerdings passieren, dass in ungünstigen Fällen zwei oder mehr Schlüssel (Telefonnummern) auf denselben Index in der Hashtabelle abgebildet werden, Beispiel z.B. ist 01063852 mod 10000 = 08153852 mod 10000 = 3852. 23 Programmieren in Haskell Allgemeine Definitionen Formal gesehen ist Hashing ein abstrakter Datentyp, der die Operationen insert, delete und lookup auf (dynamischen) Mengen effizient unterstützt. 24 Programmieren in Haskell Direkte Adressierung Hashing ist im Durchschnitt sehr effizient – unter vernünftigen Bedingungen werden obige Operationen in O(1) Zeit ausgeführt (im worst-case kann lookup O(n) Zeit benötigen). Wenn die Menge U aller Schlüssel relativ klein ist, können wir sie injektiv auf ein Feld abbilden; dies nennt man direkte Adressierung 25 Programmieren in Haskell Direkte Adressierung (kein Hashing) 0 U (Universum der Schl"ussel) 0 9 6 2 7 4 1 1 3 4 K (Aktuelle Schl"ussel) 2 5 3 6 5 7 8 8 9 T 26 Programmieren in Haskell Hashing Ist die Menge U aller Schlüssel aber sehr groß (wie im obigen Beispiel des Pizza-Services), so können wir nicht mehr direkt adressieren. Unter der Voraussetzung, dass die Menge K aller Schlüssel, die tatsächlich gespeichert werden, relativ klein ist gegenüber U, kann man die Schüssel effizient in einer Hashtabelle abspeichern. Dazu verwendet man allgemein eine Hashfunktion Definition h : U → {0, 1, . . . , m − 1}, die Schlüssel abbildet auf Werte zwischen 0 und m − 1 (dabei ist m die Größe der Hashtabelle). 27 Programmieren in Haskell Hashing 0 U h(k1) (Universum der Schl"ussel) h(k4) k1 K (Aktuelle Schl"ussel) h(k2) = h(k5) k4 k2 k5 h(k3) k3 m−1 T 28 Programmieren in Haskell Typische Hashfunktion Eine typische Hashfunktion h für U = N ist Beispiel h(k) = k mod m. 29 Programmieren in Haskell Kollisionen da Hashfunktionen nicht injektiv sind, tritt das Problem der Kollision auf: zwei Schlüsseln wird der gleiche Platz in der Hashtabelle zugewiesen. Kollisionen sind natürlich unvermeidbar, jedoch wird eine “gute” Hashfunktion h die Anzahl der Kollisionen gering halten. D.h. h muss die Schlüssel gleichmäßig auf die Hashtabelle verteilen. Außerdem sollte h einfach zu berechnen sein. 30 Programmieren in Haskell Direkte Verkettung k4 k4 k2 k2 k5 k5 31 Programmieren in Haskell Hash-Schnittstelle emptyHash capacity loadFactor insert contains lookup hashList delete update 32 :: :: :: :: :: :: :: :: :: Int -> Hash a Hash a -> Int Fractional b => (Eq a, Hashable (Eq a, Hashable (Eq a, Hashable Hash a -> [a] (Eq a, Hashable (Eq a, Hashable Hash a -> b a) => a -> Hash a -> Hash a a) => a -> Hash a -> Bool a) => a -> Hash a -> a a) => a -> Hash a -> Hash a a) => a -> Hash a -> Hash a Programmieren in Haskell Klasse Hashable Instanzen von Hashable haben eine Hash-Funktion definiert: class Hashable a where hashMap :: Int -> a -> Int -- hash function -- first argument is hash capacity 33 Programmieren in Haskell Ein einfacher Int-Hash instance Hashable Int where hashMap m x = x ‘mod‘ m intHash :: Hash Int intHash = insert 1 $ insert 2 $ insert 120 $ emptyHash 10 allInts :: [Int] allInts = hashList intHash 34 Programmieren in Haskell Ein Kunden-Hash type type type data Phone Name Address Customer = = = = Int String String Customer Phone Name Address deriving Show Zwei Kunden sind gleich gdw. ihre Telefonnummern gleich sind: instance Eq Customer where (==) (Customer p _ _) (Customer q _ _) = p == q 35 Programmieren in Haskell Kunden-Hashfunktion Wir “hashen” über die Telefonnummer: instance Hashable Customer where hashMap m (Customer p _ _) = p ‘mod‘ m 36 Programmieren in Haskell Kunden-Hashfunktion customerHash :: Hash Customer customerHash = insert (Customer 13 "Robert" "Uni") $ insert (Customer 3 "Marc" "Uni") $ emptyHash 10 phone3Customer :: Customer phone3Customer = Hash.lookup (Customer 3 "" "") customerHash updatedCustomerHash :: Hash Customer updatedCustomerHash = update (Customer 3 "Marc" "zu Hause") customerHash updatedPhone3Customer :: Customer updatedPhone3Customer = Hash.lookup (Customer 3 "" "") updatedCustomerHash 37 Programmieren in Haskell Datentyp Hash newtype Hash a = Hash (Array Int [a]) -- representation of hash as array instance Show a => Show (Hash a) where show (Hash a) = show (hashList (Hash a)) 38 Programmieren in Haskell Datentyp Hash emptyHash m = Hash (array (0,m-1) [(i,[]) | i <- [0..m-1]]) capacity (Hash a) = m + 1 where (0,m) = bounds a 39 Programmieren in Haskell Datentyp Hash contains x h@(Hash a) = elem x (a!(hashMap m x)) where m = capacity h insert x h@(Hash a) | contains x h = error "Hash.insert: element already in hash." | otherwise = Hash (a // [(i,x:a!i)]) where m = capacity h i = hashMap m x lookup x h@(Hash a) | contains x h = head $ filter (==x) (a!(hashMap m x)) | otherwise = error "Hash.lookup: hash does not contain element." where m = capacity h 40 Programmieren in Haskell Datentyp Hash hashList (Hash a) = concat $ map snd (assocs a) update x h@(Hash a) | contains x h = Hash (a // [(i,x:(a!i \\ [x]))]) | otherwise = error "Hash.update: hash does not contain element." where m = capacity h i = hashMap m x 41 Programmieren in Haskell Haskell-Module Für die übrigen Funktionen: siehe Modul Hash.lhs. Anwendung: hash test.lhs. 42 Programmieren in Haskell