Kurs 1575, Klausur vom 7.2.1998, Musterlösung Sie wollen die Tause von Büchern in Ihrem heimischen Bücherregal lich systematisch erfassen. Dazu schreiben Sie sich von jedem Buch Autorenname, Titel sowie ein beschreibes Schlagwort auf. Sie möchten nun Ihre Bücherliste unter drei Aspekten sortiert dargestellt haben jeweils in einer Liste - : nach Autorennamen, nach Titeln und nach Schlagworten. Als erfahrener Pascalprogrammierer wird das kein Problem für Sie sein. Sie treffen folge Entwurfsentscheidungen: Jeder der drei Begriffe kann jeweils in einer Zeichenkette der Länge 30 untergebracht werden. Da die Anzahl Ihrer Bücher sich im Laufe der Zeit ändern wird, ist zur Darstellung der Bücherliste in Ihrem Programm eine dynamische Datenstruktur angebracht. Da die Liste nach drei Kriterien sortiert werden soll, ist es sinnvoll, sie nach diesen drei Kriterien zu verketten. Es ergibt sich also als Datenstruktur eine dreifach verkettete lineare Liste (oder vielleicht besser: Knoten, die in drei einfach verketteten Listen verkettet sind). Ein Knoten in dieser Liste enthält also drei Zeichenkettenkomponenten, die die drei Begriffe Autorenname, Titel und Schlagwort aufnehmen, sowie drei (Vorwärts-) Zeigerkomponenten für die sortierte Verkettung bezüglich Autorenname, Titel und Schlagwort. Bei geschickter Wahl der Datenstruktur können die Prozeduren, die zum Aufbau der Liste sowie zu ihrer Ausgabe benötigt werden, so parametrisiert werden, daß sie für jede der drei gewünschten Sortierungen nur einmal geschrieben werden müssen. Dazu faßt man die Zeichenketten- und die Zeigerkomponenten eines Knotens jeweils in einem (dreielementigen) Array zusammen, deren Indextyp ein Aufzählungstyp mit den Ausprägungen Autor, Titel und Schlagwort ist (oder auf die feine englische Art: Author, Title und Keyword). Damit ergeben sich folge Pascal-Typenvereinbarungen: type Index = (Author, Title, Keyword); Text30 = packed array[1..30] of char; ListPointer = ^ListElement; ListElement = record Entry: array[index] of Text30; Next: array[index] of ListPointer ; ListStart = array[index] of ListPointer; Eine Variable vom Typ ListStart wird dazu dienen, auf den Kopf der Liste zu weisen. Die Eingabedaten für Ihr Programm sehen so aus, daß die drei Begriffe, die zu einem Buch gehören, jeweils in einer Eingabezeile stehen in der Reihenfolge Autor, Titel, Schlagwort, - und jeweils durch ein Komma (das nicht mehr zum Text gehört) beet werden. Beliebig viele Leerzeichen dürfen vor und nach den Begriffen stehen und sollen vom Programm überlesen werden. Also könnte die Eingabe z.b. so aussehen: 1
Conan-Doyle, Der Hund von Baskerville, Krimi, Marx, Das Kapital, Sachbuch, Zoller, Windows 95 im Detail, Sachbuch, Goethe, Die Wahlverwandtschaften, Roman, Heine, Deutschland-ein Wintermaerchen, Gedicht, Goethe, Goetz von Berlichingen, Drama, Die Ausgabe, die Sie sich wünschen, hätte ungefähr folges Aussehen: Autorenliste Conan-Doyle Der Hund von Baskerville Krimi Goethe Die Wahlverwandtschaften Roman Goethe Goetz von Berlichingen Drama Heine Deutschland-ein Wintermaerchen Gedicht Marx Das Kapital Sachbuch Zoller Windows 95 im Detail Sachbuch Titelliste Das Kapital Marx Sachbuch Der Hund von Baskerville Conan-Doyle Krimi Deutschland-ein Wintermaerchen Heine Gedicht Die Wahlverwandtschaften Goethe Roman Goetz von Berlichingen Goethe Drama Windows 95 im Detail Zoller Sachbuch Schlagwortliste Drama Goethe Goetz von Berlichingen Gedicht Heine Deutschland-ein Wintermaerchen Krimi Conan-Doyle Der Hund von Baskerville Roman Goethe Die Wahlverwandtschaften Sachbuch Marx Das Kapital Sachbuch Zoller Windows 95 im Detail Eine grafische Darstellung der gewünschten Datenstruktur finden Sie am Ende dieser Klausur. Das Programm wird Funktionen/Prozeduren benötigen : Funktion zum Erzeugen eines neuen Knotens function NewElement: ListPointer; Prozedur zum Aufbau der Liste(n) durch sortiertes Einfügen eines neuen Knotens hinter einem gefundenen Knoten procedure InsertAfter (var Start: ListStart; NewElem: ListPointer; TypeOfList: Index); Diese Prozedur benutzt (ruft auf) eine Prozedur zum Finden des Knotens, hinter dem eingefügt werden soll procedure FindPos (var PrevPointer: ListPointer; Start: ListStart; NewElem: ListPointer; TypeOfList: Index); Prozedur zum Ausdrucken der Liste: procedure PrintList ( StartPointer: ListPointer; TypeOfList: Index); 2
Aufgabe 1: Schreiben Sie das Hauptprogramm zum Aufbau sowie zur Ausgabe der verketteten Listen. Nehmen Sie an, daß die oben skizzierten Funktion und Prozeduren zur Verfügung ständen. (Benotung: Maximal 15 Punkte) program Literat(input,output); type Index = (Author, Title, Keyword); Text30 = packed array[1..30] of char; ListPointer = ^ListElement; ListElement = record Entry: array[index] of Text30; Next: array[index] of ListPointer ; ListStart = array[index] of ListPointer; var Start: ListStart; NewElem: ListPointer; function NewElement: ListPointer;... {NewElement}; procedure FindPos (var PrevPointer: ListPointer; Start: ListStart; NewElem: ListPointer; TypeOfList: Index);... {FindPos}; procedure InsertAfter (var Start: ListStart; NewElem: ListPointer; TypeOfList: Index);... {InsertAfter}; procedure PrintList ( StartPointer: ListPointer; TypeOfList: Index);... {PrintList}; {Hauptprogramm} reset(input); rewrite(output); writeln; Start[Author]:=nil; Start[Title]:=nil; Start[Keyword]:=nil; while not eof do NewElem:=NewElement; InsertAfter(Start, NewElem, Author); InsertAfter(Start, NewElem, Title); InsertAfter(Start, NewElem, Keyword); ; writeln('autorenliste'); writeln; Printlist (Start[Author], Author); writeln('titelliste'); writeln; Printlist (Start[Title], Title); writeln('schlagwortliste'); writeln; Printlist (Start[Keyword], Keyword); 3
. 4
Aufgabe 2: Schreiben Sie die Funktion function NewElement: ListPointer; Die Funktion erzeuge einen neuen Knoten für die Liste und lese die Daten für ein Buch in die entsprechen Zeichenkettenkomponenten des Knotens ein. (Benotung: Maximal 20 Punkte) function NewElement: ListPointer; var NewElem: ListPointer; procedure ReadText30 (var Text: Text30); var pos, i: integer; z: char; pos:=1; read(z); while (z=' ') do read(z); while (z<>',') do Text[pos]:=z; pos:=pos+1; read(z) ; for i:=pos to 30 do Text[i]:=' ' {ReadText30}; new(newelem); ReadText30(NewElem^.Entry[Author]); ReadText30(NewElem^.Entry[Title]); ReadText30(NewElem^.Entry[Keyword]); readln; NewElem^.Next[Author]:=nil; NewElem^.Next[Title]:=nil; NewElem^.Next[Keyword]:=nil; NewElement:=NewElem {NewElement}; 5
Aufgabe 3: Schreiben Sie die Prozedur procedure FindPos (var PrevPointer: ListPointer; Start: ListStart; NewElem: ListPointer; TypeOfList: Index); Die Prozedur finde n am Anfang Start der Liste von der Art TypeOfList den Knoten (auf den der Zeiger PrevPointer als Ergebnis zeigt), hinter dem der Knoten mit dem Zeiger NewElem einzufügen wäre. (Benotung: Maximal 20 Punkte) procedure FindPos (var PrevPointer: ListPointer; Start: ListStart; NewElem: ListPointer; TypeOfList: Index); var gefunden: boolean; Pointer: ListPointer; gefunden:=false; PrevPointer:=nil; Pointer:=Start[TypeOfList]; while ((Pointer<>nil) and (not gefunden)) do if Pointer^.Entry[TypeOfList] <= NewElem^.Entry[TypeOfList] then PrevPointer:=Pointer; Pointer:=PrevPointer^.Next[TypeOfList] else gefunden:=true {Suche nach Vorgaengerknoten} {FindPos}; 6
Aufgabe 4: Schreiben Sie die Prozedur procedure InsertAfter (var Start: ListStart; NewElem: ListPointer; TypeOfList: Index); Die Prozedur füge den Knoten, auf den Zeiger NewElem weist, in die Liste ein. Die Art der Liste (Autoren-, Titel- bzw. Schlagwortliste) ist durch TypeOfList parametrisiert. Die Prozedur bedient sich zum Auffinden der Einfügeposition der (global definierten) Prozedur FindPos (s. Aufgabe 3). Beachten Sie auch den Sonderfall der leeren Liste. (Benotung: Maximal 15 Punkte) procedure InsertAfter (var Start: ListStart; NewElem: ListPointer; TypeOfList: Index); var PrevPointer: ListPointer; FindPos(PrevPointer, Start, NewElem, TypeOfList); if PrevPointer=nil then {Einfuegen am Anfang der Liste} NewElem^.Next[TypeOfList]:=Start[TypeOfList]; Start[TypeOfList]:=NewElem else { Einfuegen im Innern oder am Ende der Liste} NewElem^.Next[TypeOfList]:=PrevPointer^.Next[TypeOfList]; PrevPointer^.Next[TypeOfList]:=NewElem {InsertAfter}; 7
Aufgabe 5: Schreiben Sie die Prozedur procedure PrintList ( StartPointer: ListPointer; TypeOfList: Index); zur Ausgabe der drei Listenarten Autorennamen, Titel, Schlagwort (sortiert nach Autorennamen) Titel, Autorennamen, Schlagwort (sortiert nach Titeln) Schlagwort, Titel, Autorenname (sortiert nach Schlagworten). Die Listenart ist parametrisiert durch TypeOfList. Das Layout der Ausgabe soll etwa dem Beispiel auf S.2 folgen. (Benotung: Maximal 10 Punkte) procedure PrintList ( StartPointer: ListPointer; TypeOfList: Index); while (StartPointer<>nil) do case TypeOfList of Author: write ( StartPointer^.Entry[Title],' ', Title: write (StartPointer^.Entry[Title],' ', Keyword:write (StartPointer^.Entry[Keyword],' ', StartPointer^.Entry[Title]) {case}; writeln; StartPointer:=StartPointer^.Next[TypeOfList] {PrintList}; 8
Aufgabe 6: Ihr Programm ist so erfolgreich, daß Sie es bis nach Australien exportieren wollen. Auf der südlichen Hälfte der Erdhalbkugel läuft aber alles, wie Sie wissen, anders herum. Deshalb müssen Sie Ihrem Programm noch eine Variante der Ausgabeprozedur PrintList hinzufügen. Diese Variante heiße PrintInvers und drucke die Listen in umgekehrter Reihenfolge. Sie erreicht das auf rekursive Art. procedure PrintInvers ( StartPointer: ListPointer; TypeOfList: Index); (Benotung: Maximal 20 Punkte) procedure PrintInvers ( StartPointer: ListPointer; TypeOfList: Index); if (StartPointer<>nil)then PrintInvers(StartPointer^.Next[TypeOfList], TypeOfList); case TypeOfList of Author: write ( StartPointer^.Entry[Title],' ', Title: write (StartPointer^.Entry[Title],' ', Keyword:write (StartPointer^.Entry[Keyword],' ', StartPointer^.Entry[Title]) {case}; writeln {PrintListInvers}; Das Hauptprogramm muß entsprech ergänzt werden:... writeln('autorenliste invers'); writeln; PrintInvers (Start[Author], Author); writeln('titelliste invers'); writeln; PrintInvers (Start[Title], Title); writeln('schlagwortliste invers'); writeln; PrintInvers (Start[Keyword], Keyword);. 9
Hier noch einmal das komplette Programm: program Literat(input,output); type Index = (Author, Title, Keyword); Text30 = packed array[1..30] of char; ListPointer = ^ListElement; ListElement = record Entry: array[index] of Text30; Next: array[index] of ListPointer ; ListStart = array[index] of ListPointer; var Start: ListStart; NewElem: ListPointer; function NewElement: ListPointer; var NewElem: ListPointer; procedure ReadText30 (var Text: Text30); var pos, i: integer; z: char; pos:=1; read(z); while (z=' ') do read(z); while (z<>',') do Text[pos]:=z; pos:=pos+1; read(z) ; for i:=pos to 30 do Text[i]:=' ' {ReadText30}; new(newelem); ReadText30(NewElem^.Entry[Author]); ReadText30(NewElem^.Entry[Title]); ReadText30(NewElem^.Entry[Keyword]); readln; NewElem^.Next[Author]:=nil; NewElem^.Next[Title]:=nil; NewElem^.Next[Keyword]:=nil; NewElement:=NewElem {NewElement}; procedure FindPos (var PrevPointer: ListPointer; Start: ListStart; NewElem: ListPointer; TypeOfList: Index); var gefunden: boolean; Pointer: ListPointer; gefunden:=false; PrevPointer:=nil; Pointer:=Start[TypeOfList]; while ((Pointer<>nil) and (not gefunden)) do if Pointer^.Entry[TypeOfList] <= NewElem^.Entry[TypeOfList] then PrevPointer:=Pointer; Pointer:=PrevPointer^.Next[TypeOfList] else gefunden:=true {Suche nach Vorgaengerknoten} {FindPos}; procedure InsertAfter (var Start: ListStart; NewElem: ListPointer; TypeOfList: Index); 10
var PrevPointer: ListPointer; FindPos(PrevPointer, Start, NewElem, TypeOfList); if PrevPointer=nil then {Einfuegen am Anfang der Liste} NewElem^.Next[TypeOfList]:=Start[TypeOfList]; Start[TypeOfList]:=NewElem else { Einfuegen im Innern oder am Ende der Liste} NewElem^.Next[TypeOfList]:=PrevPointer^.Next[TypeOfList]; PrevPointer^.Next[TypeOfList]:=NewElem {InsertAfter}; procedure PrintList ( StartPointer: ListPointer; TypeOfList: Index); while (StartPointer<>nil) do case TypeOfList of Author: write ( StartPointer^.Entry[Title],' ', Title: write (StartPointer^.Entry[Title],' ', Keyword:write (StartPointer^.Entry[Keyword],' ', StartPointer^.Entry[Title]) {case}; writeln; StartPointer:=StartPointer^.Next[TypeOfList] {PrintList}; procedure PrintInvers ( StartPointer: ListPointer; TypeOfList: Index); if (StartPointer<>nil)then PrintInvers(StartPointer^.Next[TypeOfList], TypeOfList); case TypeOfList of Author: write ( StartPointer^.Entry[Title],' ', Title: write (StartPointer^.Entry[Title],' ', Keyword:write (StartPointer^.Entry[Keyword],' ', StartPointer^.Entry[Title]) {case}; writeln {PrintListInvers}; {Hauptprogramm} assign(input,'d:\k1575\nklaus98\literat.dat'); assign(output,'d:\k1575\nklaus98\literat.out'); reset(input); rewrite(output); writeln; Start[Author]:=nil; Start[Title]:=nil; Start[Keyword]:=nil; while not eof do 11
NewElem:=NewElement; InsertAfter(Start, NewElem, Author); InsertAfter(Start, NewElem, Title); InsertAfter(Start, NewElem, Keyword); ; writeln('autorenliste'); writeln; Printlist (Start[Author], Author); writeln('titelliste'); writeln; Printlist (Start[Title], Title); writeln('schlagwortliste'); writeln; Printlist (Start[Keyword], Keyword); writeln('autorenliste invers'); writeln; PrintInvers (Start[Author], Author); writeln('titelliste invers'); writeln; PrintInvers (Start[Title], Title); writeln('schlagwortliste invers'); writeln; PrintInvers (Start[Keyword], Keyword);. 12
Versuch, die Datenstruktur grafisch darzustellen. Start Grafische Darstellung eines Knotens der Liste: [Author] [Title] [Keyword] Entry[Author] Entry[Title] Entry[Keyword] Next[Author] Next[Title] Next[Keyword] Conan-Doyle Marx Zoller Goethe Heine Goethe Der Hund von Baskerville Das Kapital Windows 95 im Detail Die Wahlverwandtschaften Deutschland-ein Wintermaerchen Goetz von Berlichingen Krimi Sachbuch Sachbuch Roman Gedicht Drama 13