Programmieren in Haskell Abstrakte Datentypen
Einführung Man unterscheidet zwei Arten von Datentypen: konkrete Datentypen: beziehen sich auf eine konkrete Repräsentation in der Sprache. Beispiele: Listen, Bäume abstrakte Datentypen: nicht an eine konkrete Repräsentation gebunden. Werden definiert durch Operationen, die auf diesem Datentyp arbeiten. Werden durch eine Schnittstelle definiert. Die konkrete Repräsentation der Daten ist nicht sichtbar.
Abstrakte Datentypen werden durch eine Schnittstelle definiert die konkrete Implementierung des Datentyps ist für den Benutzer des Datentyps nicht sichbar die Implementierung des Datentyps kann verändert werden, ohne dass sich der Code der diesen Datentyp verwendet, ändern muss In Haskell werden abstrakte Datentypen durch Module unterstützt.
Module in Haskell Ein Haskell-Modul ist eine Datei, welche wie folgt eingeleitet wird: module <Name> (<Exportliste>) where Nur die Datentypen und Funktionen, die in <Exportliste> angegeben werden, sind nach aussen hin sichtbar Wenn <Exportliste> weggelassen wird, sind alle Definitionen nach aussen sichtbar.
Exportieren von Datentypen data List a = Nil Cons a (List a) module Liste (List) where Dies exportiert nur den Datentyp List, nicht aber die Konstruktoren Nil und Cons. Diese können in dem importierenden Modul nicht verwendet werden module Liste (List(Nil, Cons)) where exportiert auch die Konstruktoren alternativ: module Liste (List(..)) where exportiert alle Konstruktoren eines Datentyps
Beispiel: Stack Ein Stack ist ein Datentyp, der eine Menge von gleichartigen Elementen aufnehmen kann. Er unterstützt drei Operationen: push x s: Legt ein neues Element x auf den Stack s pop s: Entfernt das oberste Element vom Stack top s: Liefert das oberste Element des Stacks s, ohne dieses zu entfernen Last In First Out (LIFO)-Strategie: Das letzte Element, was auf den Stack gelegt wurde, ist das erste, was wieder heruntergenommen wird.
Stack-Schnittstelle in Haskell module Stack (Stack,push,pop,top,emptyStack,stackEmpty) where emptystack:: Stack a stackempty:: Stack a -> Bool push :: a-> Stack a -> Stack a pop :: Stack a -> Stack a top :: Stack a -> a emptystack liefert einen neuen, leeren Stack stackempty überprüft, ob der Stack leer ist push legt ein Element auf den Stack pop entfernt das oberste Element vom Stack top liefert das oberste Element vom Stack, ohne es zu entfernen
Implementierung (I) data Stack a = EmptyStk Stk a (Stack a) emptystack = EmptyStk stackempty EmptyStk = True stackempty _ = False push x s = Stk x s pop EmptyStk = error "pop from an empty stack" pop (Stk _ s) = s top EmptyStk = error "top from an empty stack" top (Stk x _) = x
Implementierung (II) newtype Stack a = Stk [a] emptystack = Stk [] stackempty (Stk []) = True stackempty (Stk _ ) = False push x (Stk xs) = Stk (x:xs) pop (Stk []) = error "pop from an empty stack" pop (Stk (_:xs)) = Stk xs top (Stk []) = error "top from an empty stack" top (Stk (x:_)) = x
Darstellung Problem: Da die Implementierungen jeweils nach aussen hin nicht sichtbar sind, kann der Stack auch nicht angezeigt werden. Lösung: Definition einer eigenen show-funktion Version 1: instance (Show a) => Show (Stack a) where show EmptyStk = "-" show (Stk x s) = show x ++ " " ++ show s Version II: instance (Show a) => Show (Stack a) where show (Stk []) = "-" show (Stk (x:xs)) = show x ++ " " ++ show (Stk xs)
Beispiel: Queue Eine Queue (Schlange) ist vergleichbar mit einem Stack, nur dass sie nach einer FIFO (First In First Out)-Strategie arbeitet Das erste Element, das einer Queue hinzugefügt wurde, ist auch das erste, das wieder entfernt wird Eine Queue stellt die Operationen enqueue, dequeue und front bereit
Queue-Schnittstelle in Haskell module Queue (Queue,emptyQueue,queueEmpty,enqueue,dequeue,front) where emptyqueue :: Queue a queueempty :: Queue a -> Bool enqueue :: a -> Queue a -> Queue a dequeue :: Queue a -> Queue a front :: Queue a -> a emptyqueue liefert eine neue Queue queueempty überprüft, ob eine Queue leer ist enqueue fügt einer Queue ein neues Element hinzu dequeue entfernt das erste Element aus der Queue front liefert das erste Element der Queue
Implementierung (I) newtype Queue a deriving Show = Q [a] emptyqueue = Q [] queueempty (Q []) = True queueempty (Q _ ) = False enqueue x (Q q) = Q (q ++ [x]) dequeue (Q (_:xs)) = Q xs dequeue (Q []) = error "dequeue: empty queue" front (Q (x:_)) = x front (Q []) = error "front: empty queue" Problem: enqueue benötigt O(n) Zeit!
Implementierung (II) newtype Queue a = Q ([a],[a]) queueempty (Q ([],[])) = True queueempty _ = False emptyqueue = Q ([],[]) enqueue x (Q ([],[])) = Q ([x],[]) enqueue y (Q (xs,ys)) = Q (xs,y:ys) dequeue (Q ([],[])) = error "dequeue:empty queue" dequeue (Q ([],ys)) = Q (tail(reverse ys), []) dequeue (Q (x:xs,ys)) = Q (xs,ys) front (Q ([],[])) front (Q ([],ys)) = error "front:empty queue" = last ys front (Q (x:xs,ys)) = x Alle Operationen laufen in O(1) Zeit, nur wenn die erste Liste leer ist, benötigen dequeue und front O(n) Zeit
Priority Queues Eine Priority Queue ist eine Queue, bei der jedes Element mit einer Priorität verknüpft ist Die dequeue-operation entfernt dann das Element mit der größten Priorität
Schnittstelle module PQueue (PQueue,emptyPQ,pqEmpty,enPQ,dePQ,frontPQ) wher emptypq :: (Ord a) => PQueue a pqempty :: (Ord a) => PQueue a -> Bool enpq :: (Ord a) => a -> PQueue a -> PQueue a depq :: (Ord a) => PQueue a -> PQueue a frontpq :: (Ord a) => PQueue a -> a emptypq liefert eine neue Queue pqempty überprüft, ob eine Queue leer ist enpq fügt einer Queue ein neues Element hinzu depq entfernt das erste Element aus der Queue frontpq liefert das erste Element der Queue
Implementierung newtype PQueue a deriving Show = PQ[a] emptypq = PQ [] pqempty (PQ []) = True pqempty _ = False enpq x (PQ q) = PQ (insert x q) where insert x [] = [x] insert x r@(e:r') x > e = x:r otherwise = e:insert x r' depq (PQ []) = error "depq:empty priority queue" depq (PQ (x:xs)) = PQ xs frontpq (PQ [])= error "frontpq:empty priority queue" frontpq (PQ(x:xs)) = x
Mengen Eine Menge ist eine ungeordnete Sammlung von unterschiedlichen Elementen Ein Element kann auf Mitgliedschaft in einer Menge hin überprüft werden, kann einer Menge hinzugefügt oder aus einer Menge entfernt werden
Mengen-Schnittstelle in Haskell module Set (Set,emptySet,setEmpty,inSet,addSet,delSet) where emptyset :: Set a setempty :: Set a -> Bool inset :: (Eq a) => a -> Set a -> Bool addset :: (Eq a) => a -> Set a -> Set a delset :: (Eq a) => a -> Set a -> Set a emptyset erzeugt eine neue Menge setempty überprüft, ob die Menge leer ist inset überprüft, ob ein Element in einer Menge enthalten ist addset fügt ein Element der Menge hinzu delset entfernt ein Element aus der Menge
Implementierung newtype Set a = St [a] emptyset = St [] setempty (St []) = True setempty _ = False inset x (St xs) = elem x xs addset x (St a) = St (x:a) delset x (St xs) = St (filter (/= x) xs)
Was haben wir heute gesehen? abstrakte Datentypen Stacks Queues Priority Queues Mengen
schöne Semesterferien!