Programmieren in Haskell Felder (Arrays) Programmieren in Haskell 1 Was wir heute machen • Motivationsbeispiel • Die Typklasse Ix • Felder in Haskell • Funktionstabellierung • Binäre Suche • Pascalsches Dreieck • Hashing (anspruchsvolles Projekt) Programmieren in Haskell 2 Quadratzahlen 0 1 2 3 0 1 4 9 ··· n 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) Programmieren in Haskell 3 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) Programmieren in Haskell 4 Die Klasse Ix in der Typhierarchie Enum Eq Show / \ / Ord Num / \ / \ Ix Real Fractional / Integral Programmieren in Haskell 5 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 Programmieren in Haskell 6 • 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 Programmieren in Haskell 7 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 Programmieren in Haskell 8 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) Programmieren in Haskell 9 Funktionen auf Indextypen Programmieren in Haskell 10 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 Programmieren in Haskell 11 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 Programmieren in Haskell 12 Funktionstabellierung tabulate :: Ix a => (a -> b) -> (a,a) -> Array a b tabulate f bs = array bs [(i, f i) | i <- range bs] Programmieren in Haskell 13 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) Programmieren in Haskell 14 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’)] Programmieren in Haskell 15 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 Programmieren in Haskell 16 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) Programmieren in Haskell 17 Anwendung: Pascalsches Dreieck 0 1 2 3 4 5 6 7 8 0 1 Programmieren in Haskell 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 7 1 7 21 35 35 21 7 1 8 1 8 28 56 70 56 28 8 1 1 18 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]]) Programmieren in Haskell 19 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 Programmieren in Haskell 20 Array-Update (//) :: (Ix a) => Array a b -> [(a, b)] -> Array a b unitMatrix :: (Ix a, Num b) => (a,a) -> Array (a,a) b unitMatrix bs@(l,r) = array bs’ [(ij,0) | ij <- range bs’] // [((i,i),1) | i <- range bs] where bs’ = ((l,l),(r,r)) Programmieren in Haskell 21 Anwendung: Hashing • Warum Hashes? (-> schneller Zugriff UND platzsparend) • Abstrakter Datentyp Hash (Schnittstelle) • Hash-Implementierung als Haskell-Modul Programmieren in Haskell 22 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 Programmieren in Haskell 23 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 Programmieren in Haskell 24 Direkte Verkettung k4 k4 k2 k2 k5 k5 Programmieren in Haskell 25 Hash-Schnittstelle emptyHash capacity loadFactor insert contains lookup hashList delete update Programmieren in Haskell :: :: :: :: :: :: :: :: :: 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 26 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 Programmieren in Haskell 27 Annahmen über Hashes (fällt uns da noch mehr ein?) • hashList (emptyHash m) == [] for any hash capacity m • contains x (insert x h) == True, if contains x h == False • lookup x h == x, if contains x h == True • (bag . hashList) (delete x (insert x h)) == (bag . hashList) h, if contains x h == False (Die Funktion bag wandelt eine Liste in eine Multimenge um.) Programmieren in Haskell 28 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 Programmieren in Haskell 29 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 (das ist keine Feststellung, sondern eine Definition): instance Eq Customer where (==) (Customer p _ _) (Customer q _ _) = p == q Programmieren in Haskell 30 Kunden-Hashfunktion Wir "hashen"über die Telefonnummer: instance Hashable Customer where hashMap m (Customer p _ _) = p ‘mod‘ m Programmieren in Haskell 31 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 Programmieren in Haskell 32 Das Hash-Modul Programmieren in Haskell 33 module Hash( Hash, Hashable, emptyHash, capacity, loadFactor, Hash.insert, contains, Hash.lookup, hashList, Hash.delete, update ) where Programmieren in Haskell -- We export only type constructor Hash, not data constructor Ha -- Thus the client cannot select the hash representation. Note: -- would export both by writing Hash(Hash). -- qualified because of ambiguity with Data.List.insert -- qualified because of ambiguity with Hugs.Prelude.lookup -- qualified because of ambiguity with Data.List.delete 34 Zwei Module müssen wir importieren: import Array import List Programmieren in Haskell 35 Datentyp Hash newtype Hash a = Hash (Array Int [a]) -- representation of hash as array instance Show (Hash a) where show h = "Hash" -- we don’t want to show anything here Programmieren in Haskell 36 emptyHash m = Hash (array (0,m-1) [(i,[]) | i <- [0..m-1]]) capacity (Hash a) = m + 1 where (0,m) = bounds a Programmieren in Haskell 37 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 Programmieren in Haskell 38 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 Programmieren in Haskell 39 Für die übrigen Funktionen (und sowieso): siehe Modul Hash.lhs. Anwendung: hash_test.lhs. Programmieren in Haskell 40