Aufgabe 2: dynamische Datenstrukturen – doppelt verkettete Liste Aus dem Unterricht ist Ihnen der Datentyp der Linearen Liste bekannt. Eine doppelt verkettete Liste ist nun ein Spezialfall einer solchen Linearen Liste in der jedes Element nicht nur einen Zeiger auf den Nachfolger sondern auch einen Zeiger auf den Vorgänger besitzt. Der Datenzugriff erfolgt ansonsten genau wie bei der Linearen Liste über den Zeiger Aktuell bzw. eine entsprechende Funktion. Gegeben ist das folgende interface: type TElement = class Next: TElement; Prev: TElement; constructor create; end; TIntegerElement = class(TElement) private sInhalt: Integer; public procedure SetInhalt(i: integer); function GetInhalt: integer; constructor create(i: integer); end; TTwoLinkList = class private sAnfang, sEnde, sAktuell: TElement; public constructor create; destructor destroy; { Navigationsroutinen } procedure vor; procedure zurueck; procedure anAnfang; procedure ansEnde; { Informationsroutinen } function Leer: boolean; function Aktuell: TElement; function Anzahl: integer; { Datensatzoperationen } procedure EinfuegenVor(e: TElement); procedure EinfuegenNach(e: TElement); procedure Loeschen; { Urobjekt für Listenelemente { Zeiger auf den Nachfolger { Zeiger auf den Vorgänger } } } { von TElement abgeleiteter Typ } { Inhalt vom Typ integer } { Belegt den Inhalt mit i { Rückgabe: Inhalt { Initialisierung } } } { Liste über Urtyp TElement } { "Zeiger" auf erstes / letztes { aktuelles Element } } { Initialisierung { Speicher aufräumen } } { { { { } } } } Aktuell Aktuell Aktuell Aktuell einen weiter bewegen einen zurück bewegen an den Anfang an das Ende { Rückgabe: Leer: ja/nein? } { Rückgabe: aktuelles Element } { Rückgabe: Anzahl d. Datensätze } { Datensatz vor Aktuell einfügen } { Datensatz nach Aktuell einf. } { Aktuellen Datensatz löschen } a) Erläutern Sie den interface-Teil für die Klassen TElement und TIntegerElement. Begründen Sie insbesondere, wofür die Schlüsselwörter private und public benutzt werden und warum die Verwendung hier sinnvoll ist. b) Geben Sie auszugsweise den implementation-Teil für die Klasse TTwoLinkList an, d. h. implementieren Sie die folgenden Methoden dieser Klasse: destructor destroy; procedure zurueck; procedure ansEnde; function Leer: boolean; procedure EinfuegenNach(e: TElement); { { { { { Speicher aufräumen Aktuell einen zurück bewegen Aktuell an das Ende Rückgabe: Leer: ja/nein? Datensatz nach Aktuell einf. } } } } } Aufgabe 3: dynamische Datenstrukturen – ADT Stack Aus dem Unterricht ist dir der Datentyp der Linearen Liste bekannt. Ein Keller (auch Stack genannt) ist nun ein Spezialfall einer solchen Linearen Liste nach dem „Last in, first out“Prinzip (LIFO), d.h. die Datenaufnahme, der Datenzugriff sowie die Datenentnahme erfolgt nur „vorne“ bzw. „oben“ am sogenannten Top-Element! Gegeben ist das folgende interface: type TElement = class { Urobjekt für Listenelemente } Next: TElement; constructor create; function InhaltAsString: string; virtual; abstract; end; TIntegerElement = class(TElement) { von TElement abgeleiteter Typ } Inhalt: Integer; constructor create(i: integer); function InhaltAsString: string; override; { Konvertierung Inhalt zu string } end; TStack = class private sTop: TElement; public constructor create; destructor destroy; procedure push(i: TElement); procedure pop; function top: TElement; function empty: boolean; end; { Stack über Urtyp TElement } { "Zeiger" auf erstes Element } { { { { { { } } } } } } Konstruktor: Initialisierung Destruktor: Speicher aufräumen neues Element auf Stapel oberstes Element löschen Rückgabe: oberstes Element Stack leer? a) Erläutern Sie den interface-Teil für die Klassen TElement und TIntegerElement. Begründen Sie insbesondere die Vorteile der Methode InhaltAsString. b) Geben Sie den zu diesem interface zugehörigen implementation-Teil an, d. h. implementieren Sie alle Methoden der drei oben aufgeführten Klassen. c) Erweitern Sie den interface- und implementation-Teil insofern, dass auch Zeichenketten und Punktkoordinaten der Form (x | y) ∈ IN × IN auf dem Stack abgelegt werden können. d) Erweitern Sie den Datentyp TStack um eine Methode SaveToFile(Dateiname: string); mit Hilfe derer der gesamte Stackinhalt in einer Textdatei ausgegeben wird. e) Es wäre schön, wenn der Datentyp TStack auch über eine Methode LoadFromFile verfügen würde. Dies ist bei unserer Implementierung des Stack allerdings unmöglich. Begründen Sie. Wie müsste man den Stack umorganisieren, damit eine solche Methode Sinn machen würde? Hinweis: Keine Implementierung, sondern nur eine Beschreibung! Aufgabe 2: dynamische Listenstrukturen – die Datenstruktur Stack (Keller) a) Erläutern Sie die Unterschiede der Datenstruktur eines Stacks im Gegensatz zur Datenstruktur einer Queue (Schlange). Nennen Sie für beide Datenstrukturen jeweils zwei praktische Beispiele. b) Wie muss das Einfügen eines neuen Elements in einen Stack ablaufen? Schreiben Sie einen umgangssprachlichen Algorithmus, welchen man mit Hilfe des Modellprogramms aus dem Unterricht nachvollziehen könnte. Achten Sie darauf, dass Ihr Algorithmus alle Spezialfälle berücksichtigt. Die Ausgangssituation ist wie folgt gegeben: Dabei verweist der Zeiger Top zu jeder Zeit auf das erste (vorderste bzw. oberste) Element. c) Formulieren Sie ebenso einen umgangssprachlichen Algorithmus für das Löschen aus der Datenstruktur Stack. Berücksichtigen Sie auch hier alle Spezialfälle. d) Implementieren Sie mit Hilfe der aus dem Unterricht bekannten „Übersetzungsschablone“ (siehe Anlage II) die Methoden der Datenstruktur TStack. Geben Sie für die Methode push auch eine sinnvolle Oberflächenprozedur an. unit U_stack; interface type TElement = class z: string; Next: TElement; constructor create; end; TStack = class private Zgr_Top: TElement; public constructor create; destructor destroy; procedure push(e: TElement); procedure pop; function top: TElement; function empty: boolean; end; { { { { Urobjekt für Listenelemente Später wird dies allgemein Nachfolger Konstruktor wird verändert } } } } { Stack über Urtyp TElement } { "Zeiger" auf erstes Element } { { { { { { } } } } } } setzt Zgr_Top auf NIL löscht alle vorh. Elemente neues Element auf Stapel oberstes Element löschen Rückgabe: oberstes Element Stack leer? implementation { **** Routinen für TElement **** } constructor TElement.create; begin inherited create; Next:= NIL; end; { **** Routinen für TStack **** } Hier sind Sie gefragt! { Konstruktor } { ererbten Konstruktor aufrufen { und Nachfolger initialisieren } } Aufgabe 2: Dynamische Datenstrukturen − Ring Aus dem Unterricht ist Ihnen der Datentyp der Linearen Liste bekannt. Eine Ring ist nun ein Spezialfall einer solchen Linearen Liste in der das letzte Element wieder auf das erste Element verweist. Somit gibt es keinen ausgezeichneten Anfang, sondern lediglich einen Zeiger auf ein aktuelles Element. Ansonsten erfolgt der Datenzugriff genau wie bei der Linearen Liste. Gegeben ist das folgende interface: type TElement = class private sNext: TElement; public function Next: Telement constructor create; end; { Urobjekt für Ringelemente } { Nachfolger } { Rückgabe des Nachfolgers { Konstruktor } } TRing = class private sAktuell: TElement; public constructor create; destructor destroy; { Navigationsroutinen } procedure vor; procedure zurueck; { Datensatzoperationen } procedure EinfuegenVor(e: TElement); procedure EinfuegenNach(e: TElement); procedure Loeschen; { Informationsroutinen } function Anzahl: integer; function Leer: boolean; function Aktuell: TElement; end; a) Erläutern Sie die Datenstruktur TRing. Gehen Sie auch auf die Bedeutung der Schlüsselwörter private und public ein. Begründen Sie, warum das Attribut sAktuell privat deklariert ist und warum in der Klasse TRing das Attribut sAnfang nicht mehr existiert. b) Geben Sie auszugsweise den implementation-Teil für die Klasse TRing an, d. h. implementieren Sie die folgenden Methoden dieser Klasse: function Leer: boolean; procedure EinfuegenNach(e: TElement); procedure zurueck; function Anzahl: integer; destructor destroy; Aufgabe 1: Objektorientierte Programmierung − dynamische Datenstrukturen Die Firma MEYER & CO möchte seine Mitarbeiterkartei im Computer verwalten. Es gibt drei verschiedene Arten von Mitarbeitern, welche alle in dieser einen Kartei gespeichert werden sollen. Diese sind: Festangestellter, Vorstandsmitglied und Außendienstmitarbeiter. Für alle Mitarbeiter soll der Name und die Adresse gespeichert werden. Für jeden Festangestellten sollen zusätzlich noch folgende drei Informationen gespeichert werden: Grundgehalt ; Stundenlohn pro Stunde ; Arbeitszeit (Stundenanzahl pro Monat). Vorstandsmitglieder haben dagegen keine feste Arbeitszeit, beziehen dafür aber ein festes Gehalt, welches mindestens 3000,− € beträgt. Freie Außendienstmitarbeiter erhalten lediglich ein Grundgehalt von genau 1000,− € zuzüglich einer individuellen Provision. Allen Mitarbeitern soll mit Hilfe der Kartei am Ende des Monats ein Gehalt gezahlt werden, welches sich je nach Mitarbeitertyp unterschiedlich berechnet. Sie sollen in dieser Aufgabe Teile der Mitarbeiterkartei modellieren und implementieren. a) Entwerfen Sie ein UML-Klassendiagramm für die Klassen TMitarbeiter, TFestangestellter, TVorstandsmitglied und TAussendienstler. inklusive aller benötigten Eigenschaften und Methoden. Implementieren Sie die Gehalt-Berechnungsmethode der Klasse TFestangestellter. Implementieren Sie den Konstruktor der Klasse TVorstandsmitglied: constructor TVorstandsmitglied.create(pName, pAdresse: string; pGehalt: double) b) Gegeben ist das nebenstehende Klassendiagramm der Klasse TMitarbeiterkartei. Diese soll alle Mitarbeiter verwalten. Dokumentieren Sie die Klasse TMitarbeiterkartei. Entwerfen Sie ein UML-Klassendiagramm zur Verdeutlichung der Verwaltung der Mitarbeiter durch die Klasse TMitarbeiterkartei unter Verwendung der aus dem Unterricht bekannten Klassen TElement und TLinList (Dokumentation siehe Anlage). Hinweis: Sie können in dem Diagramm die Klassen TElement und TLinList ohne Methoden darstellen. Erläutern und begründen Sie die Beziehungen in Ihrem Diagramm. c) Der Anwender stellt an die Mitarbeiterkartei die Anfrage, welche Gehälter am Ende des Monats insgesamt bezahlt werden müssen. Beschreiben Sie eine Strategie für die Methode Gesamtgehaelter. Implementieren Sie die Methode entsprechend Ihrer Strategie. Achten Sie auf eine sinnvolle Rückgabe, falls die Mitarbeiterkartei noch leer ist. d) In der Mitarbeiterkartei soll bei jedem Mitarbeiter zusätzlich vermerkt werden, mit welchen anderen Mitarbeitern er befreundet ist. Dazu soll eine Klasse TFreunde entwickelt werden, die für einen Mitarbeiter alle Freunde dieses Mitarbeiters verwaltet. Wählen Sie eine aus dem Unterricht bekannte dynamische Datenstruktur, die sich für die Verwaltung der Freunde eignet und begründen Sie Ihre Wahl. Erweitern Sie das in Teil b) entwickelte UML-Klassendiagramm um die Klassen TFreunde und die gewählte Klasse der Datenstruktur für die Verwaltung. e) Die Klasse TFreunde soll um die Methode Stammtischeinladung erweitert werden. Anfrage vorher: nachher: Stammtischeinladung(): string Die Klasse Freunde hat Zugriff auf alle befreundeten Mitarbeiter. Der zurückgegebene string umfasst alle Namen der befreundeten Mitarbeiter. Z. B. könnte die Rückgabe einer Stammtischeinladung lauten: ´Heinz – Otto – Frauke – Fritz´ Implementieren Sie die Methode Stammtischeinladung. MATERIALIEN ZUR AUFGABE 1 Dokumentation der Klasse TElement Es handelt sich um die Klasse für Objekte, die in der Datenstruktur TLinList verwaltet werden sollen. Konstruktor Nachher: create() Ein neues Element mit next = NIL ist erstellt worden. Anfrage Nachher: Next() : TElement Gibt den Zeiger auf das nachfolgende Element zurück. Gab es kein nachfolgendes Element, so liefert Next den Wert nil. Dokumentation der Klasse "LinList": Es handelt sich um eine verkettete Listenstruktur zur Verwaltung beliebiger Objekte einer allgemeinen Basisklasse. In Delphi ist das die Klasse "TObject". Der Zeiger auf das aktuelle Element kann mittels der Aufträge "next" und "previous" zurück sogar vor das erste bzw. hinter das letzte Listenelement bewegt werden. Konstruktor Nachher create Eine leere Liste mit der Länge 0 ist angelegt. Der Positionszeiger steht vor der leeren Liste. Anfrage Nachher isEmpty : boolean Die Anfrage liefert den Wert wahr, wenn die Liste keine Elemente enthält. Anfrage Nachher isBefore : boolean Die Anfrage liefert den Wert wahr, wenn der Positionszeiger vor dem ersten Listenelement oder vor der leeren Liste steht. Anfrage Nachher isBehind : boolean Die Anfrage liefert den Wert wahr, wenn der Positionszeiger hinter dem letzten Listenelement oder hinter der leeren Liste steht. Auftrag Nachher next Der Positionszeiger ist um eine Position in Richtung Listenende weitergerückt, d.h. wenn er vor der Liste stand, wird das Element am Listenanfang zum aktuellen Element, ansonsten das jeweils nachfolgende Listenelement. Stand der Positionszeiger auf dem letzten Listenelement, befindet er sich jetzt hinter der Liste. Befand er sich hinter der Liste, hat er sich nicht bewegt. Auftrag Nachher previous Der Positionszeiger ist um eine Position in Richtung Listenanfang weitergerückt, d.h. wenn er hinter der Liste stand, wird das Element am Listenende zum aktuellen Element, ansonsten das jeweils vorhergehende Listenelement. Stand der Positionszeiger auf dem ersten Listenelement, befindet er sich jetzt vor der Liste. Befand er sich vor der Liste, hat er sich nicht bewegt. Auftrag Nachher toFirst Der Positionszeiger steht auf dem ersten Listenelement. Falls die Liste leer ist befindet er sich jetzt hinter der Liste. Auftrag Nachher toLast Der Positionszeiger steht auf dem letzten Listenelement. Falls die Liste leer ist befindet er sich jetzt vor der Liste. Anfrage Nachher getObject: TObject Die Anfrage liefert den Wert des aktuellen Listenelements bzw. nil, wenn die Liste keine Elemente enthält, bzw. der Positionszeiger vor oder hinter der Liste steht. Auftrag Vorher update (pInhalt : TObject) Die Liste ist nicht leer. Der Positionszeiger steht nicht vor oder hinter der Liste. Der Wert des Listenelements an der aktuellen Position ist durch den neuen Inhalt ersetzt. Nachher Auftrag Vorher Nachher insertBefore (pInhalt : TObject) Der Positionszeiger steht nicht vor der Liste. Ein neues Listenelement mit dem entsprechenden Inhalt ist angelegt und vor der aktuellen Position in die Liste eingefügt worden. Der Positionszeiger steht hinter dem eingefügten Element. Auftrag insertBehind (pInhalt : TObject) Vorher Nachher Der Positionszeiger steht nicht hinter der Liste. Ein neues Listenelement mit dem entsprechenden Inhalt ist angelegt und hinter der aktuellen Position in die Liste eingefügt worden. Der Positionszeiger steht vor dem eingefügten Element. Auftrag Vorher Nachher delete Die aktuelle Position verweist auf ein Listenelement. Das Element an der aktuellen Listenposition wird aus der Listenstruktur entfernt. Das Inhaltsobjekt bleibt erhalten. Das nachfolgende Listenobjekt wird dabei zum neuen aktuellen Element. Der Positionszeiger befindet sich hinter der Liste, falls das letzte Element der Liste gelöscht wurde. Destruktor Nachher Free Die Listenstruktur ist nicht mehr vorhanden. Aufgabe 1: Objektorientierte Programmierung – dynamische Datenstrukturen Das Zwei-Personen-Kartenspiel "Längstes Leben" verläuft nach folgenden Spielregeln. 1. 2. 3. 4. Ein Kartenspiel mit 52 Karten wird gemischt und gleichmäßig an beide Spieler verteilt. Beide Spieler legen ihre Karten alle verdeckt aufeinander. Gleichzeitig legen beide Spieler die oberste Karte offen auf den Tisch. Der Spieler mit dem höheren Kartenwert nimmt sich beide Karten und steckt sie verdeckt unter seinen Kartenstapel. Bei Gleichstand beider Kartenwerte legen beide Spieler eine weitere Karte auf den Tisch, welche nun über den Gesamtgewinn der Karten entscheidet. 5. Haben beide Spieler noch Karten übrig, so setzt sich das Spiel am Punkt 3 fort. Sie sollen im folgenden Teile dieses Spiels dokumentieren, modellieren und implementieren. a) Rechts sehen Sie Karten des Kartenspiels abgebildet. Jede Karte verfügt Informationen über seine Kartenfarbe (Karo, Herz, Pik, Kreuz) und seinen Kartenwert (2, 3, ..., 10, Bube, Dame, König, As). Entwerfen Sie ein UML-Klassendiagramm der Klasse TKarte, so dass alle wichtigen Informationen einer Spielkarte modelliert werden. b) Die Klasse TKarte soll nun um eine Methode hatGroesserenKartenwert erweitert werden. Gegeben ist dazu die folgende Dokumentation: Anfrage hatGroesserenKartenwert(Vergleichskarte: TKarte) vorher: Das Objekt Vergleichskarte existiert nachher: Falls der eigene Kartenwert größer als der Kartenwert der Vergleichskarte ist, so wird der Wert true zurückgegeben, andernfalls wird der Wert false zurückgegeben. Ein Kartenwert ist größer, falls er in der Reihenfolge 2, 3, 4, 5, ..., 10, Bube, Dame, König, As weiter hinten steht. Implementieren Sie die Methode hatGroesserenKartenwert. Hinweis: Implementieren Sie bei Bedarf eine Hilfs-Funktion, die zu einem Kartenwert den Rang innerhalb der Reihenfolge 2, 3, 4, 5, ..., 10, Bube, Dame, König, As berechnet. c) Nachfolgend sehen Sie das UML-Diagramm der Klasse TSpiel. Ein Objekt dieser Klasse ermöglicht die Simulation einer Partie Längstes Leben. Die Klassen TList, TQueue und TElement sind hier ohne Methoden angegeben, entsprechen aber den Klassen, die Sie aus dem Unterricht kennen (siehe Anlage). Die Klasse TKarte entspricht Ihrer in Aufgabenteil a erstellten Klasse. Dokumentieren Sie die Klasse TSpiel. Begründen Sie die Wahl der Klasse TList für das Kartenspiel. Warum bieten sich die Klassen TStack und TQueue hier nicht an? Begründen Sie die Wahl der Klasse TQueue für die Kartenstapel der beiden Spieler. Warum bietet sich die Klasse TStack hier nicht an? Geben Sie die Art der Beziehungen (hat-Beziehung = Aggregation bzw. kennt-Beziehung = Assoziation) zwischen den einzelnen Klassen an und begründen Sie diese. Begründen Sie auch die angegebenen Multiplizitäten. d) Implementieren Sie die Methoden kartenAusteilen und ganzesSpielDurchfuehren der Klasse TSpiel. Hinweis: Die Methode ganzesSpielDurchfuehren muss lediglich in richtiger Reihenfolge und angemessener Häufigkeit die einzelnen Methoden der Klasse TSpiel aufrufen. Zum Schluss sollte dann eine Siegerausgabe mit showMessage(string) erfolgen. Aufgabe 2: dynamische Datenstrukturen In der Anlage finden Sie die Dokumentation der Klasse TList. Diese verwaltet Objekte der Klasse TElement, welche ihrerseits beliebige Inhaltsobjekte aufnehmen können. Wir haben im Unterricht die lineare Liste so kennen gelernt, dass ein Objekt der Klasse TElement einen Zeiger auf seinen Nachfolger besaß, welchen man mittels der Methode getNext() auslesen konnte. Im folgenden sollen Sie Teile der Linearen Liste implementieren. Allerdings sollen jetzt die Elemente einen Zeiger auf den Nachfolger und auf den Vorgänger besitzen, so wie im folgenden Klassendiagramm deutlich wird. a) Erläutern Sie allgemein die Vorteile dieser Datenorganisation gegenüber der aus dem Unterricht bekannten Datenstruktur. Geben Sie die Methoden der Linearen Liste an, welche von der Änderung "profitieren" und begründen Sie Ihre Wahl. b) Implementieren Sie die Methode insertInFrontOf, ohne die Methode insertBehind zu verwenden. Achten Sie darauf, dass sich die Liste so verhält, wie es in der Dokumentation angegeben ist. Dokumentation der Klasse TElement Es handelt sich um die Klasse für Objekte, die in der Datenstruktur TList verwaltet werden sollen. Konstruktor Nachher: create() Ein neues Element mit next = NIL ist erstellt worden. Konstruktor create(pObject: TObject) Nachher: Ein neues Element mit next = NIL und Inhalt pObject ist erstellt worden. Auftrag setNext(pElement: TElement) : TElement Nachher: Setzt den Nachfolger-Zeiger auf das Element pElement. Anfrage Nachher: getNext() : TElement Gibt den Zeiger auf das nachfolgende Element zurück. Gab es kein nachfolgendes Element, so liefert getNext den Wert nil. Auftrag Nachher: setContent(pObject: TObject) : TElement Setzt den Inhalt des Elements auf pObject. Anfrage Nachher: getContent() : TElement Gibt das Inhaltsobjekt zurück. Wurde noch kein Inhalt definiert, so liefert die Methode den Wert nil. Dokumentation der Klasse TList: Objekte der Klasse TList verwalten beliebige Objekte nach einem Listenprinzip. Ein interner Positionszeiger wird durch die Listenstruktur bewegt, seine Position markiert ein aktuelles Objekt. Die Lage des Positionszeigers kann abgefragt, verändert und die Objektinhalte an den Positionen können gelesen oder verändert werden. Konstruktor Nachher create Eine leere Liste mit der Länge 0 ist angelegt. Der Positionszeiger steht vor der leeren Liste. Anfrage Nachher isEmpty : boolean Die Anfrage liefert den Wert wahr, wenn die Liste keine Elemente enthält. Anfrage Nachher isInFrontOf : boolean Die Anfrage liefert den Wert true, wenn der Positionszeiger vor dem ersten Listenelement oder vor der leeren Liste steht, sonst liefert sie den Wert false. Anfrage Nachher isBehind : boolean Die Anfrage liefert den Wert true, wenn der Positionszeiger hinter dem letzten Listenelement oder hinter der leeren Liste steht, sonst liefert sie den Wert false. Auftrag Nachher next Der Positionszeiger ist um eine Position in Richtung Listenende weitergerückt, d.h. wenn er vor der Liste stand, wird das Element am Listenanfang zum aktuellen Element, ansonsten das jeweils nachfolgende Listenelement. Stand der Positionszeiger auf dem letzten Listenelement, befindet er sich jetzt hinter der Liste. Befand er sich hinter der Liste, hat er sich nicht verändert. Auftrag Nachher previous Der Positionszeiger ist um eine Position in Richtung Listenanfang weitergerückt, d.h. wenn er hinter der Liste stand, wird das Element am Listenende zum aktuellen Element, ansonsten das jeweils vorhergehende Listenelement. Stand der Positionszeiger auf dem ersten Listenelement, befindet er sich jetzt vor der Liste. Befand er sich vor der Liste, hat er sich nicht verändert. Auftrag Nachher toFirst Der Positionszeiger steht auf dem ersten Listenelement. Falls die Liste leer ist befindet er sich jetzt hinter der Liste. Auftrag Nachher toLast Der Positionszeiger steht auf dem letzten Listenelement. Falls die Liste leer ist befindet er sich jetzt vor der Liste. Anfrage Nachher getItem: TObject Die Anfrage liefert den Wert des aktuellen Listenelements bzw. nil, wenn die Liste keine Elemente enthält, bzw. der Positionszeiger vor oder hinter der Liste steht. Auftrag Vorher replace (pObject : TObject) Die Liste ist nicht leer. Der Positionszeiger steht nicht vor oder hinter der Liste. Der Wert des Listenelements an der aktuellen Position ist durch pObject ersetzt. Nachher Auftrag Vorher Nachher insertInFrontOf (pObject : TObject) Der Positionszeiger steht nicht vor der Liste. Ein neues Listenelement mit dem entsprechenden Objekt ist angelegt und vor der aktuellen Position in die Liste eingefügt worden. Der Positionszeiger steht hinter dem eingefügten Element. Auftrag Vorher Nachher insertBehind (pObject : TObject) Der Positionszeiger steht nicht hinter der Liste. Ein neues Listenelement mit dem entsprechenden Objekt ist angelegt und hinter der aktuellen Position in die Liste eingefügt worden. Der Positionszeiger steht vor dem eingefügten Element. Auftrag Vorher Nachher . Auftrag Nachher Destruktor Nachher remove Die aktuelle Position verweist auf ein Listenelement. Das aktuelle Listenelement ist gelöscht. Der Positionszeiger steht auf dem Element hinter dem gelöschten Element, bzw. hinter der Liste, wenn das gelöschte Element das letzte Listenelement war. Das Inhaltsobjekt des gelöschten Listenelements existiert weiterhin. addList(pList: TList) Die Liste pList ist der Liste angefügt. Die übergebene Listenstruktur von pList existiert nicht mehr. Free Die Listenstruktur ist nicht mehr vorhanden. Dokumentation der Klasse TQueue Objekte der Klasse TQueue (Schlange) verwalten beliebige Objekte nach dem FirstIn-First-Out-Prinzip, d.h. das zuerst abgelegte Element wird als erstes wieder entnommen. Konstruktor create Nachher: Eine leere Schlange ist erzeugt. Anfrage Nachher: isEmpty: Boolean Die Anfrage liefert den Wert true, wenn die Schlange keine Elemente enthält, sonst liefert sie den Wert false. Auftrag Vorher: Nachher: enqueue (pObject:TObject) Die Schlange ist erzeugt. pObject ist als letztes Element in der Schlange abgelegt. Auftrag Vorher: Nachher: dequeue Die Schlange ist nicht leer. Das vorderste Element ist aus der Schlange entfernt. Das Inhaltsobjekt Anfrage Vorher: Nachher: front: TObject Die Schlange ist nicht leer. Die Anfrage liefert das vorderste Element der Schlange. Die Schlange istunverändert. Destruktor Nachher: destroy Die Schlange existiert nicht mehr. Aufgabe 1: Modellierung Ein Supermarkt möchte ein Programm zur Verwaltung seines Artikelbestands haben. Alle Artikel haben eine Artikelnummer und eine Artikelbezeichnung. Der Markt unterscheidet zwischen Spielwaren, Haushaltswaren und Lebensmitteln, wobei die Lebensmittel nochmals unterteilt sind in Lebensmittel für den menschlichen Verzehr (Nahrungsmittel) und für den tierischen Verzehr (Tiernahrung). Zu den oben angegebenen Informationen sollen noch zusätzliche Informationen in dem Verwaltungsprogramm gespeichert werden: Zu allen Spielwaren soll eine Altersbeschränkung angegeben werden können. Alle Lebensmittel (sowohl Nahrungsmittel als auch Tiernahrung) sollen mit einem Mindesthaltbarkeitsdatum erfasst werden. Bei Artikeln der Kategorie Tiernahrung soll die Tierart als Information festgehalten werden. Das Verwaltungsprogramm soll später Listen aller verwalteten Artikel ausgeben können. Z. B. könnte der Artikelbestand des Supermarkts wie folgt gelistet sein (die in Klammern angegebenen Nummern entsprechen der Artikelnummer): Spielwaren - Fußball (12345), Altersbeschränkung: ab 6 Jahre Haushaltswaren - Schneidemesser (24154) Nahrung - Brot (12546), MHD: 5 Tage Tiernahrung - Hund - Schappi (56879), MHD: 60 Tage Sie sollen im folgenden Teile dieses Verwaltungsprogramms dokumentieren, modellieren und implementieren. a) Entwickeln Sie ausgehend der nachfolgend angegebenen, lückenhaften Klassendefinition eines Artikels ein UML-Diagramm, welches die gegebene Artikel-Struktur modelliert. b) Die Artikel sollen in der Datenstruktur Queue verwaltet werden. Die Software-Entwickler haben sich folgende Klassendefinition zur Verwaltung der Artikel überlegt: Dokumentieren Sie die Klasse Artikelverwaltung. Begründen Sie, dass die gewählte Datenstruktur Queue für die eben dokumentierte Funktionalität nicht besonders geeignet ist. Entwerfen Sie ein UML-Diagramm, welches die Klassen (ohne Angabe von Attributen und Methoden) Artikelverwaltung, Artikel, Queue und Element in Beziehung setzt (letztere beiden Klassen sind Ihnen aus dem Unterricht bekannt). Geben Sie die Art der Beziehungen (hat-Beziehung = Aggregation bzw. kenntBeziehung = Assoziation) zwischen den einzelnen Klassen an und begründen Sie diese. Geben Sie auch die Multiplizitäten der Beziehungen an. c) Die Methode listeAusgeben() der Klasse Artikelverwaltung soll eine Artikellistung aller Artikel so wie im Einleitungstext beschreiben ausgeben. Implementieren Sie zunächst die Methoden ausgeben() aller nicht abstrakt definierten Lebensmittel-Klassen. Die verschiedenen Artikel sollen wie oben gezeigt auf der Console ausgegeben werden. Für die Ausgabe einer Artikellistung aller Artikel auf der Console soll der folgende Algorithmus verwendet werden: • Richte eine neue Hilfsschlange ein. • Solange noch Artikel in der Schlange sind tue: • Hole Artikel aus der Schlange raus und lösche ihn dort. • Gib den Artikel auf der Console aus. • Füge den Artikel in die Hilfsschlange ein. • Nachdem alle Artikel ausgegeben wurde setze Schlange auf Hilfsschlange. Implementieren Sie die Methode listeAusgeben() der Klasse Artikelverwaltung unter Zuhilfenahme des oben angegebenen Algorithmus. Lösung: Aufgabe 1: a) b) Dokumentation: Konstruktor Artikelverwaltung nachher: eine neue Artikelverwaltung ist bereitgestellt. Auftrag hinzufügen(artikel: Artikel) vorher: nachher: Die Artikelverwaltung ist erzeugt. Der Artikel ist hinten an die Listenstruktur angefügt. Auftrag loeschen() vorher: Die Artikelverwaltung ist nicht leer nachher: Der zuerst eingegebene Artikel ist aus der Struktur entfernt. Auftrag listeAusgeben() vorher: Die Artikelverwaltung ist nicht leer nachher: Alle Artikel sind auf der Console ausgegeben. Begründung für die schlechte Eignung der Schlange: Die Datenstruktur ist insbesondere für die Ausgabe der Artikellistung ungeeignet. Hier sollte man sich mit einem Positionsanzeiger durch die Liste bewegen können, um die einzelnen Artikel der Reihe nach ausgeben zu können. Des weiteren ist mit dieser Datenstruktur nur eine Löschung des zu erst eingegebenen Artikels möglich. Es kann jedoch auch sinnvoll sein, einen Artikel in der Mitte der Datenstruktur zu löschen. UML-Diagramm aller beteiligter Klassen: c) Klasse Nahrungsmittel: public void ausgeben() { System.out.println("Nahrung - "+this.getBezeichnung()+ " ("+this.getNummer()+"), MHD: "+ this.getMhd()+" Tage"); } Klasse Tiernahrung: public void ausgeben() { System.out.println("Tiernahrung - "+this.getTierart()+" - "+ this.getBezeichnung()+" ("+this.getNummer()+ "), MHD: "+this.getMhd()+" Tage"); } Klasse Artikelverwaltung: public void listeAusgeben() { Queue hilf = new Queue(); while (schlange.isEmpty() == false) { Artikel a = ((Artikel)schlange.front()); a.ausgeben(); schlange.dequeue(); hilf.enqueue(a); } schlange = hilf; } Aufgabe 2: dynamische Listenstrukturen Bei Warteschlangen kommt es häufig vor, dass einige Elemente aus berechtigten Gründen nicht am Ende der Schlange eingefügt werden sollen, sondern an einem weiter vorne gelegenen Platz eingereiht werden. Dies sind zum Beispiel bestimmte Druckaufträge in einer Druckerwarteschlange oder Patienten mit verschiedenen Dringlichkeiten in einer Arztpraxis. Zur Realisierung einer solchen Schlange werden die einzufügenden Objekte mit einer ganzzahligen Priorität versehen. Je höher die Priorität, desto weiter nach vorne gelangen die Objekte innerhalb der Schlange. Besitzen mehrere Objekte dieselbe Priorität, so soll das neue Objekt hinter die bereits enthaltenen Objekte gleicher Priorität eingereiht werden. Aus diesem Grund werden derartige Schlangen auch als Prioritätenwarteschlange (Priority Queue) bezeichnet. a) In einer Arztpraxis mit anfangs leerem Wartezimmer spielt sich folgendes Szenario ab: • Herr Arendt erhält die Priorität 4 und betritt das Wartezimmer. • Frau Wolff erhält die Priorität 2 und betritt das Wartezimmer. • Frau Fritz erhält die Priorität 5 und betritt das Wartezimmer. • Der erste Patient wird aufgerufen und verlässt das Wartezimmer. • Herr Kluge erhält die Priorität 4 und betritt das Wartezimmer. Stellen Sie die Belegung der Warteschlange nach jedem Einfügen eines Patienten geeignet dar. b) Objekte, die in eine Prioritätenwarteschlange eingefügt werden sollen, sollen von der Klasse PriorityObject abgeleitet sein, d. h., sie sollen stets einer Klasse angehören, die Unterklasse der Klasse PriorityObject ist. Für den Fall des Wartezimmers könnte dies etwa eine Klasse Patient sein. Die Klasse PriorityQueue soll ein Objekt der Klasse Queue haben. Die einzufügenden Objekte werden also intern in der Queue gespeichert. Entwerfen Sie ein UML-Klassendiagramm für die Klassen PriorityQueue, Queue, PriorityObject und Patient. Geben Sie dabei alle Methoden und Beziehungen an. Erläutern Sie kurz die Wirkung aller in PriorityQueue und PriorityObject enthaltenen Methoden. c) Implementieren Sie Ihre Methode zum Löschen der Datenstruktur PriorityQueue. d) Gegeben sei die folgende Methode der Klasse PriorityQueue: public void ___________(PriorityObject newObject) { helpQueue = new Queue(); while ( !this.queue.isEmpty() && ((PriorityObject)this.queue.front()).getPriority() >= newObject.getPriority() ) { PriorityObject helpObject = (PriorityObject)this.queue.front(); this.queue.dequeue(); helpQueue.enqueue(helpObject); } helpQueue.enqueue(newObject); while (!this.queue.isEmpty()) { PriorityObject helpObject = (PriorityObject)this.queue.front(); this.queue.dequeue(); helpQueue.enqueue(helpObject); } this.queue = helpQueue; } Analysieren Sie diese Methode, unterteilen Sie dazu die Methode in Abschnitte. Erläutern Sie anschließend den Zweck dieser Methode. e) In einer Praxisgemeinschaft wird für jeden behandelnden Arzt eine eigene PrioritätenWarteschlange verwaltet. Wird ein Arzt zu einem Notfall gerufen, übernimmt der Kollege dessen Patienten. Implementieren Sie die folgende Methode der Klasse PriorityQueue : insertPriorityQueue(PriorityQueue pQueue), die in eine bestehende Prioritätenwarteschlange eine andere Prioritätenwarteschlange unter Beibehaltung der Ordnung einfügt. Lösung: Aufgabe 2: dynamische Listenstrukturen a) Die Warteschlange entwickelt sich wie folgt: a. Arendt/4 b. Arendt/4 – Wolff/2 c. Fritz/5 – Arendt/4 – Wolff/2 d. Arendt/4 – Wolff/2 e. Arendt/4 – Kluge/4 – Wolff/2 b) siehe rechts Klasse PriorityQueue: isEmpty() : boolean Liefert true, falls die Warteschlange leer ist, ansonsten false. add(PriorityObject pObj) :void Fügt pObj an der richtigen Position in die Warteschlange ein. dequeue() : void Entfernt das Element mit der höchsten Priorität aus der Warteschlange, falls diese nicht leer ist. front() : PiorityObject Liefert das Schlangenelement mit der höchsten Priorität, falls die Schlange nicht leer ist, ansonsten null. Klasse PriorityObject: setPriority(int pPriority) Setzt die Priorität des Objektes. getPriority() : int Liefert die Priorität des Objektes. c) public void dequeue() { queue.dequeue(); } d) Zunächst wird eine neue Schlange helpQueue erzeugt. Danach werden alle Objekte der regulären Schlange in die neue Hilfsschlange eingefügt, deren Priorität größer der Priorität des neu einzufügenden Objekts sind, und aus der regulären Schlange gelöscht. Als nächstes wird das neu einzufügende Objekt in die Hilfsschlange eingereiht. Abschließend werden alle noch in der regulären Schlange verbliebenen Objekte in die Hilfsschlange eingereiht. Ganz zum Schluss wird die Hilfsschlange als reguläre Schlange übernommen. Ein neues Objekt wird gemäß seiner Priorität in die Schlange eingereiht. e) public void insertPriorityQueue(PriorityQueue pQueue){ while (! pQueue.isEmpty() ){ this.add(pQueue.front()); pQueue.dequeue(); } }
© Copyright 2024