HTTP://WWW.WIKIPAINTINGS.ORG/EN/FRIEDENSREICH-HUNDERTWASSER/YOU-ARE-A-GUEST-OF-NATURE-BEHAVE Abstrakte Datentypen OOPM, Ralf Lämmel
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 562 Motivation abstrakter Datentypen Was haben wir bisher an Typen gesehen? Primitive Typen, Felder und Verbunde Spezifische abgeleitete Datentypen (Complex,...) Spezifische, zeigerbasierte Datenstrukturen Was fehlt uns noch an Typen? Allgemeinere Typen: Bäume, Keller, Schlangen,... Melanie Gallo
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 563 Beispiele für Bäume Eine Verzeichnisstruktur... oder denken Sie an Hierarchien in Betrieben. Die Package- Hierarchie in unserer Programmsammlung.
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 564 Beispiele für Keller public static int ggt(int, int); 0: iload_0 1: iload_1 2: if_icmpeq 24 5: iload_0 6: iload_1 7: if_icmple 17 10: iload_0 11: iload_1 12: isub 13: istore_0 14: goto 0 17: iload_1 18: iload_0 19: isub 20: istore_1 21: goto 0 24: iload_0 25: ireturn Operanden-Keller in der JVM. Webseiten-Keller im Browser.
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 565 Beispiele für (Warte-)Schlangen Melanie http://thepcweb.com/wp-content/uploads/2008/06/printqueues.gif Wartenschlangen für Drucker. Wartenschlangen für App-Updates.
Der abstrakte Datentyp Keller LIFO -- Last In First Out Operationen Push: Element auf Stapel legen IsEmpty: Test auf leeren Keller Pop: Oberstes Element vom Stapel entfernen Top: Oberstes Element auf dem Stapel anfragen http://en.wikipedia.org/wiki/file:data_stack.svg (C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 566
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 567 Der abstrakte Datentyp Keller LIFO -- Last In First Out Operation top() push(1) 1 push(2) 2 push(3) 3 pop() 2 pop() 1 pop() -- http://en.wikipedia.org/wiki/file:data_stack.svg
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 568 Beispielcode zur Benutzung eines Kellers UnboundedIntStack s = new UnboundedIntStack(); s.push(1); System.out.println(s.top()); s.push(2); System.out.println(s.top()); s.push(3); System.out.println(s.top()); while (!s.isempty()) { System.out.println(s.top()); s.pop(); top() push(1) 1 push(2) 2 push(3) 3 pop() 2 pop() 1 pop() -- Siehe package data.lifo
Schnittstelle für den abstrakten Datentyp Keller public interface IntStack { void push(int item); boolean isempty(); int top(); void pop(); (C) Ralf Lämmel, OOPM, Universität Koblenz-Landau Siehe package data.lifo 569
Implementation eines unbeschränkten Kellers public class UnboundedIntStack implements IntStack { private IntListEntry top = null; public void push(int item) { IntListEntry e = new IntListEntry(); e.item = item; e.next = top; top = e; Beliebig viele Einträge können in den Keller eingetragen werden. public boolean isempty() { return top == null; public int top() { return top.item; public void pop() { top = top.next; (C) Ralf Lämmel, OOPM, Universität Koblenz-Landau Siehe package data.lifo 570
Schnittstelle für den abstrakten Datentyp Keller public interface IntStack { void push(int item); boolean isempty(); int top(); void pop(); Könnte man nicht auch beide Operationen zu einer machen? (C) Ralf Lämmel, OOPM, Universität Koblenz-Landau Siehe package data.lifo 571
Alternative Schnittstelle für den abstrakten Datentyp Keller public interface IntStack { void push(int item); boolean isempty(); int pop(); So macht man das gern in der Praxis. Das ergibt aber keine gute Trennung zwischen Abfragen und Änderungen :-). (C) Ralf Lämmel, OOPM, Universität Koblenz-Landau Siehe package data.lifo 572
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 573 Unterteilung von Operationen Konstruktoren bauen Daten auf. new, Push, Enqueue Mutationen ändern die Daten ab. Pop, Dequeue Beobachter befragen die Daten. Top, Front
Ein beschränkter Keller public class BoundedIntStack implements IntStack { private IntListEntry top = null; private int length = 0; private int limit = 0; public BoundedIntStack(int limit) { this.limit = limit; public void push(int item) {... if (length==limit) return; IntListEntry e = new IntListEntry(); e.item = item; e.next = top; top = e; length++; (C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 574... public boolean isempty() { return top == null; public int top() { return top.item; public void pop() { length--; top = top.next; Im Sinne einer Ressourcenbeschränkung können Einträge bis zu einer gewissen Schranke eingekellert werden. Siehe package data.lifo
Der abstrakte Datentyp Schlange FIFO -- First In First Out Operationen Enqueue: Element in die Schlange einfügen IsEmpty: Test auf leere Schlange Dequeue: Erstes Element aus der Schlange entfernen Front: Erstes Element in der Schlange anfragen http://en.wikipedia.org/wiki/file:data_queue.svg (C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 575
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 576 Der abstrakte Datentyp Schlange FIFO -- First In First Out Operation front() enqueue(1) 1 enqueue(2) 1 enqueue(3) 1 dequeue() 2 dequeue() 3 dequeue() -- http://en.wikipedia.org/wiki/file:data_queue.svg
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 577 Beispielcode zur Benutzung einer Schlange SimpleIntQueue q = new SimpleIntQueue(); q.enqueue(1); System.out.println(q.front()); q.enqueue(2); System.out.println(q.front()); q.enqueue(3); System.out.println(q.front()); while (!q.isempty()) { System.out.println(q.front()); q.dequeue(); front() enqueue(1) 1 enqueue(2) 1 enqueue(3) 1 dequeue() 2 dequeue() 3 dequeue() -- Siehe package data.fifo
Schnittstelle für den abstrakten Datentyp Schlange Melanie Gallo public interface IntQueue { void enqueue(int item); boolean isempty(); int front(); void dequeue(); Siehe package data.fifo (C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 578
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 579 Testen der FIFO-Eigenschaft import org.junit.test; import static org.junit.assert.assertequals; public class Demo { @Test public void FifoQueueOk() { SimpleIntQueue x = new SimpleIntQueue(); x.enqueue(1); x.enqueue(2); assertequals(1, x.front());
Eine Schlangenimplementation (Teil 1/2) public class SimpleIntQueue implements IntQueue { private IntListEntry first = null; private IntListEntry last = null; public void enqueue(int item) {... (C) Ralf Lämmel, OOPM, Universität Koblenz-Landau IntListEntry e = new IntListEntry(); e.item = item; e.next = null; if (first==null) first = e; if (last!=null) last.next = e; last = e; Siehe package data.fifo 580 Verglichen zu den Operationen des Kellers, sind die Operationen der Schlange etwas schwieriger zu implementieren, weil das Einfügen und Entfernen an verschiedenen Enden erfolgt.
Eine Schlangenimplementation (Teil 2/2) public class SimpleIntQueue implements IntQueue {... public boolean isempty() { return first == null; public int front() { return first.item; public void dequeue() { if (first==last) last = null; first = first.next; (C) Ralf Lämmel, OOPM, Universität Koblenz-Landau Siehe package data.fifo 581
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 582 Keller versus Schlange Fangfrage: Gelten die folgenden Entsprechungen? push isempty top pop Antwort: Nein! = enqueue = isempty = front = dequeue Wie würden Sie den konstruktiven Beweis führen? Keller verwendet LIFO. Schlange verwendet FIFO.
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 583 Effektive Unterscheidung von Keller und Schlange push isempty top pop = enqueue = isempty = front = dequeue Implementiere die gleiche Schnittstelle in beiden ADTs. Implementiere fehlende Operationen gemäß den Gleichungen. Teste LIFO für den Keller: Ok Teste FIFO für den Keller: Fail Teste FIFO für die Schlange: Ok Ein Gedankenexperiment, welches nicht prüfungsrelevant ist. Teste LIFO für die Schlange: Fail
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 584 Implementation beider Schnittstellen public interface IntContainer extends IntStack, IntQueue { public class UnboundedIntStack implements IntContainer {... SAME CODE AS BEFORE... // Let's confuse Stack and Queue. public void enqueue(int item) { push(item); public int front() { return top(); public void dequeue() { pop(); Ein Gedankenexperiment, welches nicht prüfungsrelevant ist. public class SimpleIntQueue implements IntContainer {... SAME CODE AS BEFORE... // Let's confuse Stack and Queue. public void push(int item) { enqueue(item); public int top() { return front(); public void pop() { dequeue();
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 585 Testen der Unterschiede public class Demo { private void testlifo(intcontainer x) { x.push(1); x.push(2); assertequals(2, x.top()); private void testfifo(intcontainer x) { x.enqueue(1); x.enqueue(2); assertequals(1, x.front()); Ein Gedankenexperiment, welches nicht prüfungsrelevant ist. @Test public void LifoStackOk() { testlifo(new UnboundedIntStack()); @Test(expected=java.lang.AssertionError.class) public void FifoStackFail() { testfifo(new UnboundedIntStack()); @Test public void FifoQueueOk() { testfifo(new SimpleIntQueue()); @Test(expected=java.lang.AssertionError.class) public void LifoQueueFail() { testlifo(new SimpleIntQueue());
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 586 Einschub: Warum sprechen wir von abstrakten Datentypen? Konkrete Datentypen = Datenstrukturen Diese werden durch eine Implementation beschrieben. Abstrakte Datentypen = Schnittstelle + Eigenschaften Anwenden können effektiv von Implementation abstrahieren. Eigenschaften können verschieden beschrieben werden: Text, Illustration, Testfälle (diese Vorlesung) Algebraische Spezifikation (nächste Vorlesung)
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 587 Bäume Wurzel Java types Unterbaum Primitive types Reference types... boolean char short int long float double String... Array types... Wrapper types...... int[] Integer Blatt Wurzel und Blätter sind spezielle Knoten. Alle Knoten speichern eine Information.
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 588 Bäume Java types Primitive types Reference types... boolean char short int long float double String... Array types... Wrapper types...... int[] Integer Eine andere Darstellungsform: Hier wird die Baumstruktur durch Einrückung verdeutlicht. - Java types ---- Primitive types ------- boolean ------- char ------- short ------- int ------- long ------- float ------- double ---- Reference types ------- String ------- Array types ---------- int[] ----------... ------- Wrapper types ---------- Integer ----------...
Baumkonstruktion StringTree t1 = new StringTree("Java types", new StringTree[] { new StringTree("Primitive types", new StringTree[] { new StringTree("boolean", new StringTree[] {), new StringTree("char", new StringTree[] {), new StringTree("short", new StringTree[] {), new StringTree("int", new StringTree[] {), new StringTree("long", new StringTree[] {), new StringTree("float", new StringTree[] {), new StringTree("double", new StringTree[] {)), new StringTree("Reference types", new StringTree[] { new StringTree("String", new StringTree[] {), new StringTree("Array types", new StringTree[] { new StringTree("int[]", new StringTree[] {), new StringTree("...", new StringTree[] {)), new StringTree("Wrapper types", new StringTree[] { new StringTree("Integer", new StringTree[] {), new StringTree("...", new StringTree[] {)))); - Java types ---- Primitive types ------- boolean ------- char ------- short ------- int ------- long ------- float ------- double ---- Reference types ------- String ------- Array types ---------- int[] ----------... ------- Wrapper types ---------- Integer ----------... Wir verwenden einen geeigneten Konstruktor, um die Knoteninfo und die Unterbäume zu beschreiben. (C) Ralf Lämmel, OOPM, Universität Koblenz-Landau Siehe package data.tree 589
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 590 Implementation public class StringTree { private String info = null; private StringTree[] subtrees = null; public StringTree(String info, StringTree[] subtrees) { this.info = info; this.subtrees = subtrees; // What public operations are needed?...
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 591 Ausgabe von Bäumen public class StringTree {... Wir schmieren the print-methode in die Klasse des ADT!? public void print() { print(0); public void print(int indent) { System.out.print(' '); for (int i=0; i<3*indent; i++) System.out.print('-'); System.out.print("- "); System.out.println(info); indent++; for (int i=0; i<subtrees.length; i++) subtrees[i].print(indent); Siehe package data.tree - Java types ---- Primitive types ------- boolean ------- char ------- short ------- int ------- long ------- float ------- double ---- Reference types ------- String ------- Array types ---------- int[] ----------... ------- Wrapper types ---------- Integer ----------...
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 592 Bäume mit Zugriffsoperation public class StringTree { Ist diese Repräsentation immer zweckmässig? private String info = null; private StringTree[] subtrees = null; public StringTree(String info, StringTree[] subtrees) { this.info = info; this.subtrees = subtrees; Brauchen wir noch weitere Methoden? Ist die Feld- Sicht adäquat? public String getinfo() { return info; public StringTree[] getsubtrees() { return subtrees; Verbesserte Abstraktion: Wir können nun Methoden wie print() außerhalb der Klasse programmieren.
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 593 Beispiel einer Methode, welche auf ADT-Methoden aufbaut public class DemoStringTree { public static void print(stringtree t) { print(t, 0); public static void print(stringtree t, int indent) { System.out.print(' '); for (int i=0; i<3*indent; i++) System.out.print('-'); System.out.print("- "); System.out.println(t.getInfo()); indent++; for (int i=0; i<t.getsubtrees().length; i++) print(t.getsubtrees()[i],indent);
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 594 Binärbäume x y z w * Jeder Knoten hat maximal zwei Unterbäume. * Es wird der linke und der rechte Teilbaum unterschieden.
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 595 Anwendung von Binärbäumen: Binäre Suchbäume 4 2 5 1 3 Auf allen Ebenen sind die Werte im linken (rechten) Teilbaum kleiner (größer) als der Wert an der Wurzel. Vergleiche diese Bäume mit sortierten Feldern, auf deren Basis man binär suchen kann. 1 2 3 4 5
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 596 Wozu benötigt man binäre Suchbäume? Dies wird im Folgenden illustriert. Binäre Suche ist einfach zu formulieren. Sortierte Struktur ist einfach zu ändern. Siehe weiterführende Veranstaltungen. 4 2 5 1 3
Baumkonstruktion - 4 ---- 2 ------- 1 ------- 3 ---- 5 BinIntTree t1 = new BinIntTree(4, new BinIntTree(2, new BinIntTree(1,null,null), new BinIntTree(3,null,null)), new BinIntTree(5,null,null)); (C) Ralf Lämmel, OOPM, Universität Koblenz-Landau Siehe package data.tree 597
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 598 Implementation public class BinIntTree { private int info; private BinIntTree left, right; public BinIntTree(int info, BinIntTree left, BinIntTree right) { this.info = info; this.left = left; this.right = right; public int getinfo() { return info; public BinIntTree getleft() { return left; public BinIntTree getright() { return right; // Further instance methods are conceivable....
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 599 Anzahl der Knoten - 4 ---- 2 ------- 1 ------- 3 ---- 5 public class BinIntTree {... SAME CODE AS BEFORE... /** @return the number of nodes in the tree */ public int nodes() { return 1 + (left==null? 0 : left.nodes()) + (right==null? 0 : right.nodes()); 5 Übung: Machen Sie die ADT- Implementation effizienter so dass die Knotenanzahl in konstanter Zeit zur Verfügung steht.
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 600 Inorder-Serialisierung public class BinIntTree { - 4 ---- 2 ------- 1 ------- 3 ---- 5... SAME CODE AS BEFORE... /** @return inorder serialization of the tree */ public int[] inorder() { int[] a = new int[nodes()]; inorder(a,0); return a; [1,2,3,4,5] private int inorder(int[] a, int i) { i = left==null? i : left.inorder(a, i); a[i++] = info; i = right==null? i : right.inorder(a, i); return i; Wir verwenden hier Instanzmethoden. Übung: Entfernen Sie die Methoden aus der Klasse.
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 601 Suche im Binärbaum public class BinIntTree { - 4 ---- 2 ------- 1 ------- 3 ---- 5... SAME CODE AS BEFORE... public boolean find(int x) { return (x < info)? left!=null && left.find(x) : (x > info)? right!=null && right.find(x) : true;
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 602 Spezifikation von Eigenschaften abstrakter Datentypen mittels Testfällen Identifiziere Sequenzen von Operationen: Demonstration des Zusammenspiels der Operationen. Bereite Testfall vor mit Konstruktoren und optional Mutationen. Formuliere Zusicherungen in Rückgriff auf die Beobachter. Normalfälle: Sequenzen, die sinnvollen Beobachtungen führen. Fehlerfälle: Nicht anwendbare Operation am Ende einer Sequenz. Grenzfälle:?
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 603 Testen eines Kellers Ableitung von Testfällen aus folgenden Geschichten Initialer (leerer) Keller Nichtleerer Keller Aus leerem Keller durch push(...) entstanden. Keller mit verbleibenden Elementen nach pop(). Anwendung der Operationen in diesen Situationen.
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 604 Normalfall /* * A newly created stack is empty. */ @Test public void testempty() { IntStack s = new UnboundedIntStack(); asserttrue(s.isempty());
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 605 Fehlerfälle /* * An empty stack throws on pop. */ @Test(expected=java.lang.NullPointerException.class) public void testpopwhenempty() { IntStack s = new UnboundedIntStack(); s.pop(); /* * An empty stack throws on top. */ @Test(expected=java.lang.NullPointerException.class) public void testtopwhenempty() { IntStack s = new UnboundedIntStack(); s.top();
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 606 Normalfall /* * A stack is non-empty after push. * Also, top returns the pushed item. */ @Test() public void testpush() { IntStack s = new UnboundedIntStack(); int item = 1; s.push(item); assertfalse(s.isempty()); assertequals(item,s.top());
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 607 Normalfall /* * When an item is popped off the stack, * then the previously pushed item becomes top-of-stack. */ @Test() public void testpop() { IntStack s = new UnboundedIntStack(); int item1 = 1; int item2 = 2; s.push(item1); s.push(item2); assertequals(item2,s.top()); s.pop(); assertequals(item1,s.top());
(C) Ralf Lämmel, OOPM, Universität Koblenz-Landau Zusammenfassung Abstrakte Datentypen: Keller, Schlangen, Bäume Verwendung einfach verketteter Listen in der Implementierung Weitere Illustration von Verkapselung zur Abstraktion Weitere Illustration des Testens zur Spezifikation Ausblick Algebraische Spezifikation von Datentypen Einführung in die Objektorientierung Generizität von Datentypen