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 • Ein lineares Sortierverfahren • Hashing (ansprochsvolles Projekt) Programmieren in Haskell 2 Quadratzahlen 0 1 2 3 0 1 4 9 ··· Programmieren in Haskell n n2 3 Quadratzahlen 0 1 2 3 0 1 4 9 ··· n n2 squaresList :: Integral a => [a] squaresList = [n^2 | n <- [0..]] Programmieren in Haskell 3 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 Programmieren in Haskell 3 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? Programmieren in Haskell 3 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]] Programmieren in Haskell 4 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 Programmieren in Haskell 4 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? Programmieren in Haskell 4 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 “Minimal complete instance”: range, index, inRange Programmieren in Haskell 6 Annahmen über Ix-Instanzen inRange (l,u) i == elem i (range (l,u)) range (l,u) !! index (l,u) i == i, when inRange (l,u) i map (index (l,u)) (range (l,u))) == [0..rangeSize (l,u)-1] rangeSize (l,u) == length (range (l,u)) Programmieren in Haskell 7 Beispiel data Abc = A | B | C deriving (Show,Eq,Ord) Programmieren in Haskell 8 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 Programmieren in Haskell 9 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 range index inRange rangeSize array bounds assocs (!) Programmieren in Haskell :: :: :: :: :: :: :: :: Ix Ix Ix Ix Ix Ix Ix Ix a a a a a a a a => => => => => => => => (a,a) (a,a) (a,a) (a,a) (a,a) Array Array Array -> [a] -> a -> Int -> a -> Bool -> Int -> [(a,b)] -> Array a b a b -> (a,a) a b -> [(a,b)] a b -> a -> b 10 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 11 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 12 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 13 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 14 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 15 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 16 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 17 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 18 Randbemerkung: Binomialkoeffizienten (x + y)n = n X k=0 n k Programmieren in Haskell ! = ! n k n−k x y k 8 < n! (n−k)!k! 06k6n : 0 06n<k , 19 Akkumulierende Felder accumArray :: Ix a => (b -> c -> b) -> b -> (a,a) -> [(a,c)] -> Array a b -- vordefiniert Der Ausdruck accumArray (*) e bs vs ergibt das Feld a, wobei das Feldelement a!i gleich (· · ·((e*c1 )*c2 )· · ·)*ck ist, wenn vs dem Index i nacheinander die Werte c1 , . . . , ck zuordnet. Beachte: Ist die Operation (*) nicht kommutativ, spielt die Reihenfolge der Elemente in vs eine Rolle. Programmieren in Haskell 20 Anwendung: Ein lineares Sortierverfahren countingSort :: Ix a => (a,a) -> [a] -> [a] countingSort bs x = [ a | (a,n) <- assocs t, i <- [1..n]] where t = accumArray (+) 0 bs [(a,1) | a <- x, inRange bs a] Programmieren in Haskell 21 Anwendung: Ein lineares Sortierverfahren countingSort :: Ix a => (a,a) -> [a] -> [a] countingSort bs x = [ a | (a,n) <- assocs t, i <- [1..n]] where t = accumArray (+) 0 bs [(a,1) | a <- x, inRange bs a] Effizienz? Programmieren in Haskell 21 Anwendung: Ein lineares Sortierverfahren countingSort :: Ix a => (a,a) -> [a] -> [a] countingSort bs x = [ a | (a,n) <- assocs t, i <- [1..n]] where t = accumArray (+) 0 bs [(a,1) | a <- x, inRange bs a] Effizienz? Wenn das Intervall bs die Größe m und x die Länge n hat, sortiert countingSort in Θ(m + n). Programmieren in Haskell 21 Und Listen von Listen? listSort :: (Ix a) => (a, a) -> [[a]] -> [[a]] listSort bs xs | drop 8 xs == [] = isort xs | otherwise = [[] | [] <- xs] ++ [a:x | (a, ys) <- assocs t, x <- listSort bs ys] where t = accumArray (\y b -> b:y) [] bs [(a,x) | (a:x) <- xs] Programmieren in Haskell 22 Und Listen von Listen? listSort :: (Ix a) => (a, a) -> [[a]] -> [[a]] listSort bs xs | drop 8 xs == [] = isort xs | otherwise = [[] | [] <- xs] ++ [a:x | (a, ys) <- assocs t, x <- listSort bs ys] where t = accumArray (\y b -> b:y) [] bs [(a,x) | (a:x) <- xs] listSort (’A’,’z’) ["bla","blub","hallo","welt","Marc","Robert","Hund", "Katze","Maus","eins","zwei","drei","wunderbar"] => ["Hund","Katze","Marc","Maus","Robert","bla","blub","drei","eins","hallo", "welt","wunderbar","zwei"] Programmieren in Haskell 22 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 23 Anwendung: Hashing • Warum Hashes? (-> schneller Zugriff UND platzsparend) • Abstrakter Datentyp Hash (Schnittstelle) • Hash-Implementierung als Haskell-Modul Programmieren in Haskell 24 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 25 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 26 Direkte Verkettung k4 k4 k2 k2 k5 k5 Programmieren in Haskell 27 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 28 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 29 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 30 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 31 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 32 Kunden-Hashfunktion Wir "hashen"über die Telefonnummer: instance Hashable Customer where hashMap m (Customer p _ _) = p ‘mod‘ m Programmieren in Haskell 33 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 34 Das Hash-Modul Programmieren in Haskell 35 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 36 Zwei Module müssen wir importieren: import Array import List Programmieren in Haskell 37 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 38 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 39 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 40 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 41 Für die übrigen Funktionen (und sowieso): siehe Modul Hash.lhs. Anwendung: hash_test.lhs. Programmieren in Haskell 42