Programmieren in Haskell Felder

Werbung
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
Herunterladen