Kapitel 8: Felder

Werbung
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 [email protected](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 [email protected](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 [email protected](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 [email protected](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
Herunterladen