Allgemeine Definitionen Programmieren in Haskell Felder (Arrays) Peter Steffen Universität Bielefeld Technische Fakultät 05.12.2008 1 Programmieren in Haskell Allgemeine Definitionen 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) 2 Programmieren in Haskell Allgemeine Definitionen 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) 3 Programmieren in Haskell Allgemeine Definitionen 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 4 Programmieren in Haskell Allgemeine Definitionen Beispiel data Abc = A | B | C deriving (Show,Eq,Ord) instance Ix Abc where range (A,A) = [A] range (A,B) = [A,B] range (A,C) = [A,B,C] range (B,B) = [B] range (B,C) = [B,C] range (C,C) = [C] range _ = [] index (l,u) a = find 0 a (range (l,u)) where find i a (x:xs) | a == x = i | a > x = find (i+1) a xs find i a _ = error "Ix.index: Index out of range." inRange (l,u) a = l <= a && a <= u 5 Programmieren in Haskell Allgemeine Definitionen range index index inRange (A,C) => [A,B,C] (A,C) B => 1 (B,C) A => Program error: Ix.index: Index out of range. (B,C) A => False Ix-Instanzen sind auch ableitbar (für Aufzählungstypen und für Ein-Konstruktor-Typen deren Argument-Typen Instanzen von Ix sind): data Abc = A | B | C deriving (Show,Eq,Ord,Ix) 6 Programmieren in Haskell Allgemeine Definitionen Funktionen auf Indextypen range :: Ix a => (a,a) -> [a] -- liefert die Liste aller Elemente zwischen a und b index :: Ix a => (a,a) -> a -> Int -- liefert den Index eines Wertes im Bereich (a,b) inRange :: Ix a => (a,a) -> a -> Bool -- bestimmt, ob sich ein Wert c im Bereich (a,b) befindet rangeSize :: Ix a => (a,a) -> Int -- gibt die Groesse des Bereichs (a,b) zurueck 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 7 Programmieren in Haskell Allgemeine Definitionen 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 8 Programmieren in Haskell Allgemeine Definitionen Funktionstabellierung tabulate :: Ix a => (a -> b) -> (a,a) -> Array a b tabulate f bs = array bs [(i, f i) | i <- range bs] 9 Programmieren in Haskell Allgemeine Definitionen 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) 10 Programmieren in Haskell Allgemeine Definitionen 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’)] 11 Programmieren in Haskell Allgemeine Definitionen 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 12 Programmieren in Haskell Allgemeine Definitionen 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) 13 Programmieren in Haskell Allgemeine Definitionen Anwendung: Pascalsches Dreieck 0 1 2 3 4 5 6 7 8 14 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 Allgemeine Definitionen 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]]) 15 Programmieren in Haskell Allgemeine Definitionen 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 16 Programmieren in Haskell Allgemeine Definitionen 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)) 17 Programmieren in Haskell Allgemeine Definitionen 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. 18 Programmieren in Haskell Allgemeine Definitionen Hashing: Einführendes Beispiel Telefonnummer 00000000 00000001 00000002 ... 99999997 99999998 99999999 19 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 Allgemeine Definitionen 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. 20 Programmieren in Haskell Allgemeine Definitionen 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. 21 Programmieren in Haskell Allgemeine Definitionen 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. 22 Programmieren in Haskell Allgemeine Definitionen 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. 23 Programmieren in Haskell Allgemeine Definitionen 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. 23 Programmieren in Haskell Allgemeine Definitionen 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. 24 Programmieren in Haskell Allgemeine Definitionen Allgemeine Definitionen Formal gesehen ist Hashing ein abstrakter Datentyp, der die Operationen insert, delete und lookup auf (dynamischen) Mengen effizient unterstützt. 25 Programmieren in Haskell Allgemeine Definitionen 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 26 Programmieren in Haskell Allgemeine Definitionen 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 27 Programmieren in Haskell Allgemeine Definitionen 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). 28 Programmieren in Haskell Allgemeine Definitionen 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 29 Programmieren in Haskell Allgemeine Definitionen Typische Hashfunktion Eine typische Hashfunktion h für U = N ist Beispiel h(k) = k mod m. 30 Programmieren in Haskell Allgemeine Definitionen 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. 31 Programmieren in Haskell Allgemeine Definitionen Direkte Verkettung k4 k4 k2 k2 k5 k5 32 Programmieren in Haskell Allgemeine Definitionen Hash-Schnittstelle emptyHash capacity loadFactor insert contains lookup hashList delete update 33 :: :: :: :: :: :: :: :: :: 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 Allgemeine Definitionen 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 34 Programmieren in Haskell Allgemeine Definitionen 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 35 Programmieren in Haskell Allgemeine Definitionen 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 36 Programmieren in Haskell Allgemeine Definitionen Kunden-Hashfunktion Wir “hashen” über die Telefonnummer: instance Hashable Customer where hashMap m (Customer p _ _) = p ‘mod‘ m 37 Programmieren in Haskell Allgemeine Definitionen 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 38 Programmieren in Haskell Allgemeine Definitionen 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)) 39 Programmieren in Haskell Allgemeine Definitionen Datentyp Hash emptyHash m = Hash (array (0,m-1) [(i,[]) | i <- [0..m-1]]) capacity (Hash a) = m + 1 where (0,m) = bounds a 40 Programmieren in Haskell Allgemeine Definitionen 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 41 Programmieren in Haskell Allgemeine Definitionen 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 42 Programmieren in Haskell Allgemeine Definitionen Haskell-Module Für die übrigen Funktionen: siehe Modul Hash.lhs. Anwendung: hash test.lhs. 43 Programmieren in Haskell