Programmieren in Haskell Felder (Arrays) Peter Steffen Universität Bielefeld Technische Fakultät 05.12.2008 1 Programmieren in Haskell
Quadratzahlen 0 1 2 3 n 0 1 4 9 n 2 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
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
Interface der Klasse Ix class Ord a => Ix a where range :: (a,a) -> [a] index :: (a,a) -> a -> Int inrange :: (a,a) -> a -> Bool rangesize :: (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
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
range (A,C) => [A,B,C] index (A,C) B => 1 index (B,C) A => Program error: Ix.index: Index out of range. inrange (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
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
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
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
Anwendung: Tabellierung badfib :: Integral a => a -> a badfib 0 = 1 badfib 1 = 1 badfib 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
Listen zu Felder listarray :: Ix a => (a,a) -> [b] -> Array a b -- vordefiniert listarray bs vs = array bs (zip (range bs) vs) zip :: [a] -> [b] -> [(a,b)] -- vordefiniert zip [] [] = [] zip [] (y:ys) = [] zip (x:xs) [] = [] zip (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
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
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
Anwendung: Pascalsches Dreieck 0 1 2 3 4 5 6 7 8 0 1 1 1 1 2 1 2 1 3 1 3 3 1 4 1 4 6 4 1 5 1 5 10 10 5 1 6 1 6 15 20 15 6 1 7 1 7 21 35 35 21 7 1 8 1 8 28 56 70 56 28 8 1 14 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]]) 15 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 16 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)) 17 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. 18 Programmieren in Haskell
Hashing: Einführendes Beispiel Telefonnummer Name Vorname PLZ Straße 00000000 Müller Heinz 33615 Unistraße 15 00000001 Schmidt Werner 33615 Grünweg 1 00000002 Schultz Hans 33602 Arndtstraße 12............... 99999997 Meier Franz 33609 Kirchweg 4 99999998 Neumann Herbert 33612 Jägerallee 15 99999999 Schröder Georg 33647 Mühlweg 2 19 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. 20 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. 21 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. 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. 23 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. 23 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. 24 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. 25 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 26 Programmieren in Haskell
Direkte Adressierung (kein Hashing) 9 U (Universum der Schl"ussel) 0 6 7 4 0 1 2 3 1 K (Aktuelle Schl"ussel) 5 2 8 3 4 5 6 7 8 9 T 27 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). 28 Programmieren in Haskell
Hashing 0 U (Universum der Schl"ussel) h(k1) h(k4) K (Aktuelle Schl"ussel) k2 k4 k3 k1 k5 h(k2) = h(k5) h(k3) T m 1 29 Programmieren in Haskell
Typische Hashfunktion Eine typische Hashfunktion h für U = N ist Beispiel h(k) = k mod m. 30 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. 31 Programmieren in Haskell
Direkte Verkettung k4 k4 k5 k2 k2 k5 32 Programmieren in Haskell
Hash-Schnittstelle emptyhash :: Int -> Hash a capacity :: Hash a -> Int loadfactor :: Fractional b => Hash a -> b insert :: (Eq a, Hashable a) => a -> Hash a -> Hash a contains :: (Eq a, Hashable a) => a -> Hash a -> Bool lookup :: (Eq a, Hashable a) => a -> Hash a -> a hashlist :: Hash a -> [a] delete :: (Eq a, Hashable a) => a -> Hash a -> Hash a update :: (Eq a, Hashable a) => a -> Hash a -> Hash a 33 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 34 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 35 Programmieren in Haskell
Ein Kunden-Hash type Phone = Int type Name = String type Address = String data Customer = 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
Kunden-Hashfunktion Wir hashen über die Telefonnummer: instance Hashable Customer where hashmap m (Customer p ) = p mod m 37 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 38 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)) 39 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 40 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 41 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 42 Programmieren in Haskell
Haskell-Module Für die übrigen Funktionen: siehe Modul Hash.lhs. Anwendung: hash test.lhs. 43 Programmieren in Haskell