in in WS 2013/2014 Robert AG Praktische Informatik 8. Januar 2014
Themen-Vorschau in : as-s und lazy s Client-Server-Programmierung Module in Holy Lists
in in matching (alias Mustervergleich) kennen wir bereits aus Funktionsgleichungen aus case-ausdrücken Ein (Muster) ist ein Ausdruck bestehend aus Variablen, der anonymen Variablen _ ( Joker ) Konstruktoren aller Zahlen, Characters... also keine Funktionen, die nicht Konstruktoren sind... und keine Variable mehrfach, ausgenommen _
Ergonomie des in matching leistet zweierlei Fallunterscheidung in übersichtlicher Form Nutzung der Reihenfolge bei überlappenden Mustern Bindung von Muster-Variablen an Substrukturen des geprüften Werts 1 leaves ( Leaf a) = [a] 2 leaves ( Br ( Leaf a) y) = a : leaves y 3 leaves (Br (Br x y) z) = leaves (Br x (Br y z)) in
As-s 1 Manchmal will man Muster verwenden und einen Namen für den (unzerlegten) Argumentwert haben 2 suffixes (x:xs) = (x:xs ): suffixes xs erzeugt eine überflüssige (:) Operation, vergeudet Zeit und verschwendet Platz. Besser mit as-pattern suffixes z@(x:xs) = z: suffixes xs Nun ist das schon vorhandene x:xs unter dem Namen z zu haben. in Nebenbei: Welche Komplexität hat die Berechnung von suffixes x? Und welche die Ausgabe des Ergebnisses?
und Auswertungreihenfolge in Vergleich eines Musters mit einem Wert erfordert, dass der Wert ausgerechnet wird, aber nur soweit es für den Vergleich nötig ist (Laziness) Verwendung von Mustern hat also Auswirkung auf die Berechenungsreihenfolge
Abweisbare und nicht abweisbare Muster a.k.a. refutable versus irrefutable patterns Ein Muster ohne Konstruktoren (also x, _ ) passt auf jedem Wert und heißt unabweisbar Ein Muster mit Konstruktoren heißt abweisbar Ist eine Funktion mit unabweisbaren Mustern definiert, ist sie immer ein Redex. Beispiele: 1 square x = x * x 2 double f = f. f 3 five _ = 5 NB: Man kann alle Funktionen auf der linken Seite mit unabweisbaren Mustern schreiben, wenn man stattdessen case-ausdrücke auf der rechten Seite verwendet. in
und Terminierung (1) Erinnerung: Die Auswertungsstrategie in einer funktionalen Sprache hat keinen Einfluss auf den ggf. berechneten Wert, wohl aber auf die Terminierung der Berechnung. Ein Mustervergleich kann positive oder negativ ausgehen, oder divergieren Alle Muster in einer Gleichung werde top-down, links-rechts verglichen Geht ein Vergleich negativ aus, ist die aktuelle Gleichung nicht anwendbar, und es wird die nachfolgende Gleichung versucht Ruft der Vergleich eine nicht-endende Berechnung hervor, divergiert er und das Ergebnis der Gesamtrechnung ist undefiniert. Passen alle Muster einer Gleichung, wird sie angewendet in
Bottom in Hier eine Definition des undefinierten Werts 1 > bot = bot bot, ausgesprochen bottom ist der gänzlich undefinierte Wert. Seine Berechnung terminiert nicht, und produziert auch keinen Konstruktor. Was wäre ein teilweise undefinierter Wert? Nebenbei: Welchen Typ hat bot?
und Terminierung (2) in Vergleiche 1 take 0 _ = [] 2 take _ [] = [] 3 take n (x:xs) = x : take (n -1) xs und mit vertauschten Zeilen 1 take _ [] = [] 2 take 0 _ = [] 3 take n (x:xs) = x : take (n -1) xs Einen Unterschied gibt es nur, wenn eines der Argumente undefiniert ist...
Unterschiedliches Terminierungsverhalten in 1 take 0 bot ==> [] 2 take 0 bot = = > undefiniert 3 4 take bot [] = = > undefiniert 5 take bot [] ==> [] Der Prelude implementiert die Version take.
Lazy s in a.k.a. Muster mit verzögertem Vergleich Problem: Man möchte eine Funktion mit einem Muster definieren und zwar für ein Argument, das durch die Anwendung der Funktion erst berechnet wird Zwar steht fest, dass das Muster erfüllt sein wird, jedoch zum Zeitpunkt der Funktionsaufrufs muss für den Mustervergleich die gleiche Funktion wieder aufgerufen werden... Wo kommt so etwas überhaupt vor?
Client-Server-Anwendungen Client-Server-Anwendungen sind parallel laufende Programme, die über Ströme kommunizieren Nachrichten an einander Requests und Responses werden wechselseitig erzeugt und gelesen Sie werden in Strömen (streams) verwaltet. Ströme sind in einfach lazy Listen, in andern Sprachen weden sie als spezieller Datentyp zur Verfügung gestellt. in
Zahlen raten als client-server Programm (1) Ratespiel: Finde eine Zahl n aus dem Intervall [0..N] durch Fragen der Art Ist n z? mit geschickt gewählten Vergleichszahlen z. Rollenverteilung: Client: Erzeugt Fragen und merkt sich das Ergebnis. Seine Strategie: Intervall-Halbierung; sie führt garantiert in O(log(N)) Schritten zum Ziel. Server: Kennt n und beantwortet Fragen Ist n z? mit True oder False. Der Server antwortet wahrheitsgemäß. (In einer interesssanteren Variante des Spiels darf er einmal lügen.) Kommunikation zwischen Client und Server über zwei Ströme: questions und answers. in
Zahlen raten als client-server Programm (2) Eine Frage hat die Form (l,g,u) und bedeutet ausführlich: Ich weiß dass n [l..u] und frage ob n g. g ist dabei die Intervallmitte, und wird, je nach Antwort, für die nächste Frage ins linke oder rechte Halbintervall verlegt. Das Ende erkennt der Client daran, dass das Intervall sich nicht mehr ändert. 1 > minn = 0 2 > maxn = 1024 3 > t a k e d i f f [ ] = [ ] 4 > t a k e d i f f [ x ] = [ x ] in 5 > t a k e d i f f ( x : y : xs ) = x : i f y == x then [ ] e l s e t a k e d i f f ( y : xs ) takediff nimmt den Präfix einer Liste bis zur ersten Wiederholung
Zahlen raten als client-server Programm (2) 1 Wir definieren die Prozesse ask und reply, die über die Listen questions und answers kommunizieren. in 2 > g u e s s n = t a k e d i f f (map ( \ (_, g, _) > g ) q u e s t i o n s ) where 3 > a nswers = r e p l y q u e s t i o n s 4 > r e p l y ( (_, g, _ ) : qs ) = ( n <= g ) : r e p l y qs 5 6 > q u e s t i o n s = ask i n i t i a l answers where 7 > i n i t i a l = ( minn, (maxn + minn ) div 2, maxn) 8 > ask ( l, g, u ) ( a : as ) = ( l, g, u ) : ask newguess as where 9 > newguess = i f a then ( l, ( l + g ) div 2, g ) 10 > e l s e ( g+1, ( g+1 + u ) div 2, u ) Diese Version funktioniert nicht warum?
Verklemmung! Der Aufruf von ask (l,g,u) (a:as) stellt eine Frage ((l,g,u)), die von reply beantwortet wird (a) fragt jedoch in seinem rechten Muster bereits nach dieser Antwort ((a:as)), um aus ihr die nächste Frage (newguess) zu generieren Das pattern matching beim Aufruf von ask kommt zu frueh: es verlangt ein Ergbnis von reply, bevor der gegebene Aufruf von ask das erste Element von questions generiert hat. reply seinerseits verlangt ein Ergebnis von ask... in
Zahlen raten als client-server Programm (3) 1 Vermeidung des übereifrigen Musters: 2 > a nswers = r e p l y q u e s t i o n s 3 > r e p l y ( (_, g, _ ) : qs ) = ( n <= g ) : r e p l y qs 4 in 5 > q u e s t i o n s = ask i n i t i a l answers where 6 > i n i t i a l = ( minn, (maxn + minn ) div 2, maxn) 7 > ask ( l, g, u ) as = ( l, g, u ) : ask newguess ( t a i l as ) where 8 > a = head as 9 > newguess = i f a then ( l, ( l + g ) div 2, g ) 10 > e l s e ( g+1, ( g+1 + u ) div 2, u ) ask kann nun starten, ohne zuerst nach der Zerlegung der answers-liste as zu fragen. Also kann die Frage ausgegeben und daraus die erste Antwort erzeugt werden, bevor mit head as nach ihr gefragt wird
Zahlen raten als client-server Programm (4) Der gleiche Effekt mit lazy pattern ~(a:as) 1 Otherwise, the same as s o l u t i o n ( 1 ) 2 in 3 > guess n = t a k e d i f f (map ( \ (_, g, _) > g ) q u e s t i o n s ) where 4 5 > a nswers = r e p l y q u e s t i o n s 6 > r e p l y ( (_, g, _ ) : qs ) = ( n <= g ) : r e p l y qs 7 8 > q u e s t i o n s = ask i n i t i a l answers where 9 > i n i t i a l = ( minn, (maxn + minn ) div 2, maxn) 10 > ask ( l, g, u ) ~( a : as ) = ( l, g, u ) : ask newguess as where 11 > newguess = i f a then ( l, ( l + g ) div 2, g ) 12 > e l s e ( g+1, ( g+1 + u ) div 2, u ) der lazy pattern ~(a:as) wird beim Aufruf als erfüllt angenommen erst wenn a oder as gebraucht wird, findet der Abgleich statt.
Lazy s in, ~(...) sind unabweisbar. Ihre Überprüfung findet nur statt, wenn eine Komponente daraus gebraucht wird Scheitert sie, bricht die Rechnung ab. Eine weitere Mustergleichung nach einer Gleichung mit einem lazy macht also keinen Sinn.
Top level pattern binding: immer lazy Werden Muster nicht auf Argumentposition, sondern in der Definition von Konstanten benutzt, sind sie automatisch lazy: fibp@(1:tfibp) = 1:1:[a+b (a,b) <- zip fibp tfibp ] Hier haben wir einen as-, der eine konstante, unendliche Liste definiert,...... die Liste der Fibonacci-Zahlen. Das Muster bindet die Gesamtliste an den Namen fibp, zugleich ihren tail an den Namen tfibp die linke Seite fragt, ob die Liste nicht leer ist, aber dank der laziness des Musters wird dies erst geprüft, wenn tfibp rechts benutzt wird Vergleiche: let a@(1:as) = 1:[] in 2:a let a@(0:as) = 1:[] in 2:a in
Infos und Tipps zu den Prüfungen in Die Veranstaltungen des Moduls Algorithmen und Datenstrukturen werden gemeinsam in einer Modulabschluss-Prüfung geprüft. wissenschaftlicher Sinn einer Prüfung: Feststellen, ob ausreichende Grundlagen für weiterführende Veranstaltungen vorhanden sind. wissenschaftlich unsinnig, aber systemimmanent: Leistungsvergleich, Noten, peinliche Buchführung über den Ausbildungsgang zum Ausweis von Unterschieden Voraussetzung der Zulassung zur Prüfung sind 50% der Punkte aus den Übungen zu A&D+.
Mündliche Prüfung zum Modul A&D Welche Veranstaltungen werden geprüft? A&D Vorlesung in A&D + Übung in der UNIX-Umgebung Wer prüft? Prüfer Beisitzer Wie lange? 20 Minuten Wann? siehe Termine Was muss mitgebracht werden? Personalausweis bzw. Pass Studentenausweis in
Prüfungsziel in Test, inwieweit Veranstaltungsinhalte verstanden werden und angewendet werden können. Verschiedene Arten von Leistungen Reproduktion Transfer (konkret) Transfer (abstrakt) Prüfen auf eine möglichst gute Note hin.
Termine Robert 2. und 4. Februar-Woche, und ggf. später Alex Sczyrba 3. Februar-Woche, 3. März-Woche Terminvereinbarung bei Stefanie Lanfermann (M3-127) (Frau Lanfermann meldet die Prüfung im Dekanat an) weitere Prüfer: Prof. Dr. Stoye (mail an Heike Samuel) Prof. Dr. Hammer (14.,17.,18.2.) (mail an Gisela Weitekemper) Prof. Dr. Nattkemper (mail an Heike Samuel) Dr. Thies Pfeiffer (mail an Stefanie Lanfermann) in
in Materialien Vorlesungsfolien Übungsunterlagen Skript Prüfungsprotokolle, dabei jeweils beachten: Motivation Hintergrund Übungsgruppen Sprechstunden
Ablauf in Ausweiskontrolle Prüfer stellt die Fragen Beisitzer protkolliert hauptsächlich Einstiegsfrage nach 20 Minuten: Ende Student geht vor die Tür Prüfer und Beisitzer beraten sich kurz Student wird wieder hereingebeten Mitteilung der Note und Feedback
10 einfache Regeln zum Versemmeln einer Prüfung (G.Sauthoff) 1 erst 3 Tage vor dem Prüfungstermin mit der beginnen 2 alleine Lernen, obwohl das eigentlich noch nie gut geklappt hat 3 bei der das Skript ignorieren 4 Verzicht auf eine aktive Bearbeitung der Übungen 5 strikt vermeiden, ein -Programm selbst zu schreiben 6 Inhalte auswendig lernen 7 möglichst unausgeschlafen in die Prüfung kommen 8 auf die Frage nach Unklarheiten vor dem Beginn der Prüfung am besten sehr lang und breit erklären, wie schlecht vorbereitet man doch ist 9 kommunizieren, dass man die Begriffe in den Veranstaltungstiteln nicht erklären kann 10 trotz Krankheit zur Prüfung erscheinen in
Weitere Angebote in ZSB bietet Workshops an eher allgemeine Hinweise vieles ist wahrscheinlich schon bekannt eher sinnvoll bei extremer Prüfungsangst o.ä.
in In der Software-Entwicklung unterscheidet zwei Arten von : konkrete beziehen sich auf eine konkrete Repräsentation in der verwendeten Programmiersprache. Beispiele: Int, Char, Listen, Bäume; auch polymorphe sind konkret! abstrakte sind nicht an eine konkrete Repräsentation gebunden. Wie kann man dann überhaupt etwas definieren?
in Ein abstrakter Datentyp (ADT) ist, unabhängig von einer Programmiersprache, eine (nicht weiter spezifizierte) Wertemenge mit Operationen auf dieser Wertemenge, die bestimmte Axiome (Gleichungen) erfüllen die Menge der Operationen ist die Schnittstelle (API) nach außen konkrete Repräsentation der Daten in der Implementierung ist nicht nach außen sichtbar (Kapselung)
Warum? mit ADTs erhöht die Wiederverwendbarkeit von Programmen Beispiel: ADT Verzeichnis in Operationen auf Verzeichnissen: Erzeugen, Einfuegen, Loeschen, Sortieren, Bereinigen Programmierer, die Verzeichnisse verwenden, kennen NUR diese Operationen Verzeichnisse können konkret Listen, Bäume, Arrays,... sein, ggf. mit Zusatzoperationen Die konkrete Implementierung kann geändert werden, ohne dass andere Programme davon betroffen sind
spielen eine bedeutende Rolle in der Software-Entwicklung Alle modernen Programmiersprachen unterstützen ihre Verwendung Sie dienen der Spezifikation der nach außen sichtbaren Operationen und Eigenschaften Manchmal werden die Operationen mit Effizienzvorgaben verknüpft In werden abstrakte durch Module und durch Typklassen unterstützt. In Java/C++ geschieht dies durch Klassen ud Interfaces in
Mini-ADT Llist in Wir spezifizieren einen ADT Llist a analog zu Listen, dessen length-funktion die Komplexität O(1) haben soll. Operationen: empty : Llist a in O(1) (1) lcons : a Llista Llist a in O(1) (2) lhead : Llist a a in O(1) (3) ltail : Llist a Llist a in O(1) (4) llength : Llist a Int in O(1) (5) lapp : Llist a Llist a Llist a in O(n) (6)
Eigenschaften Eigenschaften der Operationen: lhead(lcons(x, xs)) = x (7) ltail(lcons(x, xs)) = xs (8) llength(lcons(x, empty)) = 1 (9) llength(lapp(xs, ys)) = llength(xs) + llength(ys)(10) lapp(x, empty) = x (11) und so weiter... in Können wir das spontan implementieren?
Module in Aus einigen Beispielen bekannt: Modulname Imports-Deklarationen Definitionen des Moduls 1 > module Meinmodul 2 > where 3 > import Data. List 4 in 5 > splits n = [ splitat k [1.. n] k <- [1..n -1] ]
Modulaufbau Was bisher verschwiegen wurde in Ein -Modul ist eine Datei, welche wie folgt eingeleitet wird: module <Name> (<Exportliste>) where Nur die und Funktionen, die in <Exportliste> angegeben werden, sind nach außen hin sichtbar Wenn <Exportliste> weggelassen wird, sind alle Definitionen nach außen sichtbar.
Exportieren von 1 module Liste ( List ) where 2 3 data List a = Nil 4 Cons a ( List a) 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 in
ADT-Beispiel: Holy Lists in Anhängen eines Elements am Ende einer Liste erfordert O(n) Schritte. Holy Lists erlauben diese Operation in O(1). Spezifikation: 1 > module Hlist (l2h,h2l, lcons, rcons ) where 2 > l2h :: [a] -> Hlist a -- in O(n) 3 > h2l :: Hlist a -> [a] -- in O(n) 4 > lcons :: a - > Hlist a - > Hlist a -- in O (1) 5 > rcons :: Hlist a - > a - > Hlist a -- in O (1) Holy Lists Stack Queue Set Multi-Set
Holy Lists: Eigenschaften in Einige der erwarteten Eigenschaften: 1 l2h. h2l = id ( Konvertierung ) 2 h2l. l2h = id ( " ) 3 4 reverse ( h2l ( rcons as a)) = a: h2l as Implementierung??? Zum Nachdenken über die Feiertage! Tipp: holy kommt von hole Listen mit Loch am Ende... Holy Lists Stack Queue Set Multi-Set
in Schöne Feiertage und Guten Rutsch Dieser Foliensatz wird noch weiter ergänzt im neuen Jahr. Holy Lists Stack Queue Set Multi-Set
ADT Holy Lists 1 type Hlist a in 2 empty :: Hlist a -- klassisch 3 cons :: a - > Hlist a - > Hlist a 4 append :: Hlist a - > Hlist a - > Hlist a 5 6 hcons :: Hlist a - > a - > Hlist a -- neu!! 7 8 l2h :: [a] -> Hlist a -- Konversion 9 h2l :: Hlist a -> [a] Holy Lists Stack Queue Set Multi-Set
Axiome für ADT Holy Lists in Forderungen an die Implementierung: Alle Eigenschaften von (:), [], (++) sollen auch für cons, empty, append gelten. h2l. l2h == id hcons h a == l2h (h2l h ++ [a]) Konvertierung (l2h l) und (h2l h) in O(n) hcons h a in O(1) Die letzte Anforderung ist die eigentliche Herausforderung! Holy Lists Stack Queue Set Multi-Set
Lösungsvorschlag Implementierung siehe Datei hlist.lhs Auszug: in 1 Hlist - Listen mit effizientem Anfuegen vorne und h 2 Append - Funktion ebenfalls in O (1) 3 4 Specification 5 6 > module Hlist (l2h,h2l, lcons, rcons ) where 7 > l2h :: [a] -> Hlist a -- in O(n) 8 > h2l :: Hlist a -> [a] -- in O(n) 9 > lcons :: a - > Hlist a - > Hlist a -- in O (1) uns so weiter... Holy Lists Stack Queue Set Multi-Set
ADT-Beispiel: Stack Ein Stack ( Stapel oder auch Keller ) ist ein Datentyp, der eine Menge von gleichartigen Elementen aufnehmen kann. Er unterstützt fünf Operationen: emptystack: Liefert einen Stack ohne Inhalt stackempty s: Fragt ob ein Stack s leer ist 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. in Holy Lists Stack Queue Set Multi-Set
Stack-Schnittstelle in 1 module Stack ( Stack,push,pop,top, 2 emptystack, stackempty ) where 3 4 emptystack :: Stack a 5 stackempty :: Stack a - > Bool 6 push :: a - > Stack a - > Stack a 7 pop :: Stack a - > Stack a 8 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 in Holy Lists Stack Queue Set Multi-Set
Stack-Implementierung (1) in 1 > module Stack ( Stack, emptystack, stackempty, pop, push, top ) 2 > data Stack a = St [ a ] 3 4 > emptystack : : Stack a 5 > emptystack = St [ ] 6 7 > stackempty : : Stack a > Bool 8 > stackempty ( St [ ] ) = True 9 > stackempty ( St _) = F a l s e 10 11 > push : : a > Stack a > Stack a 12 > push x ( St xs ) = St ( x : xs ) Der Datentyp stack wird exportiert, aber nicht der Konstruktor St! NUR mittels Emptystack und push können Stacks erzeugt werden. Alternative: module Stack (Stack(St), emptystack... oder auch module Stack (Stack(..), emptystack... Holy Lists Stack Queue Set Multi-Set
Stack-Implementierung (2) in 14 > pop : : Stack a > Stack a 15 > pop ( St [ ] ) = e r r o r " pop : Stack empty! " 16 > pop ( St ( x : xs ) ) = St xs 17 18 > top : : Stack a > a 19 > top ( St [ ] ) = e r r o r " top : Stack empty! " 20 > top ( St ( x : xs ) ) = x Inspektion und Abbau von Stacks Holy Lists Stack Queue Set Multi-Set
Sicherheit... in Welche Rolle spielt der Konstruktor St? unterhalb von St stehen immer nur einfache Listen eigentlich kann man ihn ganz weglassen, oder? wie z.b. in... Holy Lists Stack Queue Set Multi-Set
Schlichte Stacks... in 1 > module Stack2 ( Stack, emptystack, stackempty, pop, push, top ) 2 > type Stack a = [ a ] 3 4 > emptystack : : Stack a 5 > emptystack = [ ] 6 7 > stackempty : : Stack a > Bool 8 > stackempty [ ] = True 9 > stackempty _ = F a l s e 10 11 > push : : a > Stack a > Stack a 12 > push x xs = x : xs 13 14 > pop : : Stack a > Stack a 15 > pop [ ] = e r r o r " pop : Stack empty! " 16 > pop ( x : xs ) = xs 17 18 > top : : Stack a > a 19 > top [ ] = e r r o r " top : Stack empty! " 20 > top ( x : xs ) = x Holy Lists Stack Queue Set Multi-Set
Der Unterschied... in Den Unterschied sieht man mit Hugs an Ausdrücken wie push y emptystack emptystack ++ "nonono" Auf den schlichten Stacks sind normale Listenoperationen ausführbar das will man gerade nicht, auch wenn es manchmal praktisch wäre, so wie im Falle von show. Holy Lists Stack Queue Set Multi-Set
Konvertierung in Die erste Lösung ist also die bessere. Ggf. brauchen wir Konvertierungsfunktionen zwischen Listen und Stacks. 1 > l2s :: [a] -> Stack a 2 > s2l : Stack a -> [a] Wo würde man die implementieren? Holy Lists Stack Queue Set Multi-Set
Konvertierung wo? in Im Modul Stack geht es einfach und effizient: 1 > l2s l = St l 2 > s2l (St l) = l außerhalb geht es auch recht einfach, aber weniger effizient: 1 > l2s = foldr push emptystack l 2 > s2l s = if s == emptystack then [] 3 > else top s : s2l pop s Das liegt daran, dass wir außen nichts über die interne Implementierung wissen. Holy Lists Stack Queue Set Multi-Set
Beispiel: Queue in Warteschlangen realisiert der ADT Queue. Eine Queue (Schlange) arbeitet (im Gegensatz zum Stack) nach dem FIFO (First In First Out)-Prinzip 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, sowie emptyqueue und queueempty. Holy Lists Stack Queue Set Multi-Set
Queue-Schnittstelle in 1 module Queue ( Queue, emptyqueue, queueempty, 2 enqueue, dequeue, front ) where 3 4 emptyqueue :: Queue a 5 queueempty :: Queue a - > Bool 6 enqueue :: a - > Queue a - > Queue a 7 dequeue :: Queue a - > Queue a 8 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 in Holy Lists Stack Queue Set Multi-Set
Queue Implementierung (0) in Vorüberlegung: Warum kann es nicht effizient werden, Queues einfach als Listen zu implementieren? Also z.b. als data Queue a = Q [a]? Holy Lists Stack Queue Set Multi-Set
Queue Implementierung (1) in Moduldeklaration: 1 > module Queue ( Queue, emptyqueue, queueempty, 2 > enqueue, dequeue, f r o n t ) where 3 4 > emptyqueue : : Queue a 5 > queueempty : : Queue a > Bool 6 > enqueue : : a > Queue a > Queue a 7 > dequeue : : Queue a > Queue a 8 > f r o n t : : Queue a > a Auch hier wird kein Konstruktor exportiert! Holy Lists Stack Queue Set Multi-Set
Queue Implementierung (1) in Aufbau einer Queue: 10 > data Queue a = Q [ a ] [ a ] d e r i v i n g (Show, Eq) 11 12 > emptyqueue = Q [ ] [ ] 13 > queueempty (Q [ ] [ ] ) = True 14 > queueempty _ = F a l s e 15 16 > enqueue a (Q back f r o n t ) = Q ( a : back ) f r o n t Holy Lists Stack Queue Set Multi-Set
Queue Implementierung (3) 17 Abbau einer Queue: in 18 > dequeue (Q [ ] [ ] ) = e r r o r " dequeue : queue empty! " 19 > dequeue (Q back [ ] ) = dequeue (Q [ ] ( r e v e r s e back ) ) 20 > dequeue (Q back ( a : f r o n t ) ) = Q back f r o n t 21 22 > f r o n t (Q [ ] [ ] ) = e r r o r " f r o n t : queue empty! " 23 > f r o n t (Q back [ ] ) = f r o n t (Q [ ] ( r e v e r s e back ) ) 24 > f r o n t (Q back ( a : f r o n t ) ) = a Natürlich verwenden wir eine O(n)-Implementierung von reverse Holy Lists Stack Queue Set Multi-Set
Queue: Effizienz? 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 Effizienzbetrachtung: Welchen Rechenaufwand haben die einzelnen Operationen? Was ist der Aufwand, wenn n Eintraege erzeugt, gelesen und entfernt werden? Amortisierte Effizienz von O(1) für dequeue. in Holy Lists Stack Queue Set Multi-Set
Varianten Was ändert sich, wenn man front und dequeue zusammenlegt als frondeq:: Queue a -> (a, Queue a)? 1 > frondeq ( Q [] []) = error " front : empty queue!" 2 > frondeq (Q back []) = frondeq (Q [] ( reverse back )) 3 > frondeq ( Q back ( a: front )) = (a, Q back front ) Jedes Element wird nur einmal gelesen und nur einmal revertiert. Nochmals die Sinnfrage: Warum kann man ohne abstrakte den Typ Queue nicht korrekt implementieren? in Holy Lists Stack Queue Set Multi-Set
Beispiel: Mengen in Mengen sind ein recht schwieriger Datentyp. 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 Was ist daran schwierig? Holy Lists Stack Queue Set Multi-Set
Mengen-Schnittstelle in 1 module Set (Set, emptyset, setempty, inset, 2 addset, delset ) where 3 4 emptyset :: Set a 5 setempty :: Set a - > Bool 6 inset :: (Eq a) => a -> Set a -> Bool 7 addset :: (Eq a) => a -> Set a -> Set a 8 delset :: (Eq a) => a -> Set a -> Set a emptyset erzeugt eine leere 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 in Holy Lists Stack Queue Set Multi-Set
Mengen-Implementierung in Siehe Datei set.lhs Holy Lists Stack Queue Set Multi-Set
Multi-Set Synonyme: Multimenge Multi-Set Bag die Elementpositionen spielen keine Rolle wie bei Set Unterschied zu Listen Elemente können vielfach enthalten sein Unterschied zu Set wie bei Listen in Holy Lists Stack Queue Set Multi-Set
Multimenge die leere Multimenge, a die einelementige Multimenge, die genau ein Vorkommen von a enthält, x y die Vereinigung der Elemente von x und y; das + im Vereinigungszeichen deutet an, dass sich die Vorkommen in x und y akkumulieren. x = x = x y = x x y x (x y) z = x (y z) in Holy Lists Stack Queue Set Multi-Set
Multimenge in 1 bag :: [a] -> Bag a 2 bag [] = 3 bag (a:as) = a bag as Beobachtung Eine Liste x enthält alle Elemente von y, falls bag x = bag y. In diesem Fall heißt x Permutation von y. Holy Lists Stack Queue Set Multi-Set
r Datentyp Multimenge 1 module Bag ( Bag, emptybag, bagempty, inbag, 2 addbag, delbag, appendbag, 3 eqbag ) where 4 import List 5 6 emptybag :: Bag a 7 bagempty :: Bag a - > Bool 8 inbag :: Eq a = > a - > Bag a - > Bool 9 addbag :: Eq a = > a - > Bag a - > Bag a 10 delbag :: Eq a = > a - > Bag a - > Bag a 11 appendbag :: Bag a - > Bag a - > Bag a 12 eqbag :: Eq a = > Bag a - > Bag a - > Bool in Holy Lists Stack Queue Set Multi-Set
r Datentyp Multimenge emptybag erzeugt eine neue Multimenge bagempty überprüft, ob eine Multimenge leer ist inbag überprüft, ob ein Element in der Multimenge enthalten ist addbag fügt ein Element einer Multimenge hinzu delbag löscht ein Element aus einer Multimenge appendbag vereinigt zwei Multimengen Was soll man von diesen Operationen halten? headbag gibt das erste Element aus der Multimenge aus tailbag entfernt das erste Element aus der Multimenge Siehe Datei bag.lhs in Holy Lists Stack Queue Set Multi-Set
ADT-Fazit in ADTs sind wesentlich für die nachhaltige Software-Entwicklung. Einzelne Programmteile können unabhängig voneinander entwickelt verbessert und erweitert ausgetauscht werden und haben klare Schnittstellen (auch für das Verstehen des Programms) Holy Lists Stack Queue Set Multi-Set