Dateiverwaltung komplett
Wenn der Speicherplatz im Computer nicht mehr reicht, muß man auf das Diskettenlaufwerk ausweichen. Wir zeigen, wie man Daten sinnvoll und effektiv verwalten kann.
Die einfachste Methode, Dateien innerhalb eines Basic-Programmes zu verwalten, ist, den Text (im Fall einer Datei für Texte) einfach in DATA-Zeilen beziehungsweise dimensionierten Stringfeldern abzulegen.
Zunächst wollen wir einmal von einem ganz einfachen Beispiel ausgehen: Wie auch in späteren Kapiteln dieses Kurses stellen wir uns hierzu eine Aufgabe beziehungsweise geben uns eine Datei vor, an der wir anschließend arbeiten werden.
Gehen wir einmal von folgender Datei aus:
| 01 | BANANE |
| 02 | ORANGE |
| 03 | TRAUBE |
| 04 | APFEL |
| 05 | BIRNE |
| 06 | COCA COLA |
| 07 | ERDBEERE |
| 08 | MANDARINE |
| 09 | |
| 10 | HIMBEERE |
| 11 | PFLAUME |
| 12 | BIRNE |
| 13 | BROMBEERE |
Zugegeben, dies ist keine besonders glückliche Datei (nicht etwa, weil sie 13 Elemente enthält), sondern vielmehr eine ungeordnete Liste von Früchten. Einige von Ihnen wundern sich bestimmt auch schon, was Amerikas bestverkauftes Getränk in dieser Liste zu suchen hat – dazu jedoch später.
Lassen Sie mich einmal kurz aufzählen, was für eine ideale Datei charakteristisch ist:
- Die Datei bezieht sich auf genau einen Bereich, beziehungsweise ist in einzelne Gruppen unterteilt. Das heißt, in einer Datei, mit der man zum Beispiel Videokassetten ordnen und verwalten will, haben Elemente wie Stromabrechnung oder Zeitschrifteninhaltsverzeichnisse nichts zu suchen. Genauso ist Coca Cola in unserer Beispielsdatei, mit der wir arbeiten wollen und die sich auf das Verwalten von Früchten bezieht, total fehl am Platz.
- Eine Datei kann in einzelne Gruppen unterteilt werden. Wenn man zum Beispiel eine Datei zum Verwalten eines Haushaltes eröffnen möchte, kann man sie in die Gruppen Stromabrechnung, Wasserabrechnung, Telefongebühren etc. unterteilen. Diese einzelnen Gruppen stehen dann in festgelegten Bereichen der Haushaltsdatei und dürfen sich nicht überschneiden.
- Es bietet sich an, die Dateien nach bestimmten Kriterien wie zum Beispiel alphabetisch zu sortieren.
Unsere Beispielsdatei sieht auf den ersten Blick zwar wie eine relativ vernünftige Aufzählung von Früchten aus, ist jedoch noch lange keine ideale Datei in bezug auf die obigen drei Definitionen.
Aufbau einer Datei
Hier einige Betrachtungen zum Aufbau unserer Früchtedatei:
— Die Datei besteht aus 13 SÄTZEN. Unter einem SATZ verstehen wir eine Information (zum Beispiel APFEL in unserer Früchtedatei), die an einer bestimmten Stellen der Datei zu finden ist. Entprechend unserer Beispielsdatei könnte man also sagen »SATZ Nummer 1 heißt BANANE« oder »BANANE steht in SATZ 1 der Früchtedatei« beziehungsweise »BANANE steht an erster STELLE in der Früchtedatei«.
— In der Früchtedatei sind drei Beerenarten vertreten – ERDBEERE, HIMBEERE und BROMBEERE.
— Satz 9 der Datei ist leer beziehungsweise an Stelle 9 der Früchtedatei ist eine Lücke.
— In SATZ 6 steht ein Element (COCA COLA), das nicht in diese Datei gehört (da COCA COLA ein Getränk ist und in unserer Früchtedatei nur Früchte vorkommen sollen).
— Die Datei ist weder alphabetisch noch nach einem anderen erkennbaren Kriterium sortiert beziehungsweise geordnet.
Bevor wir mit unserer Datei arbeiten können, müssen wir sie zunächst einmal in den Computer bringen.
Wir legen die »Früchte« hierzu in DATA-Zeilen ab und lesen sie dann in ein dimensioniertes Stringfeld D$ ein (Listing 1).
10000 rem fruechte-datei deffinieren ------------------------------------------- 10010 sz=13 :rem anzahl der saetze 10020 dim d$(sz):rem string-feld 10030 for i=1 to sz 10040 : read d$(i) 10050 nexti 10100 data banane 10105 data orange 10110 data traube 10115 data apfel 10120 data birne 10125 data coca cola 10130 data erdbeere 10135 data mandarine 10140 data 10145 data himbeere 10150 data pflaume 10155 data birne 10160 data brombeere 10190 return
10000 REM FRUECHTE-DATEI DEFFINIEREN ------------------------------------------- 10010 SZ=13 :REM ANZAHL DER SAETZE 10020 DIM D$(SZ):REM STRING-FELD 10030 FOR I=1 TO SZ 10040 : READ D$(I) 10050 NEXT I 10100 DATA BANANE 10105 DATA ORANGE 10110 DATA TRAUBE 10115 DATA APFEL 10120 DATA BIRNE 10125 DATA COCA COLA 10130 DATA ERDBEERE 10135 DATA MANDARINE 10140 DATA 10145 DATA HIMBEERE 10150 DATA PFLAUME 10155 DATA BIRNE 10160 DATA BROMBEERE 10190 RETURN
Mit der Anweisung GOSUB 10000 wird unsere Früchtedatei definiert und kann dann bearbeitet werden.
Erste Gehversuche
Wir wollen nun einige Experimente mit unserer Datei durchführen.
Das Unterprogramm zur Definition ist immer das gleiche. Sie müssen lediglich jeweils die anderen Programmzeilen anpassen.
SATZ lesen
Unser erstes »Experiment« ist so einfach, daß sich jegliche Erklärungen erübrigen. Bitte ergänzen Sie nun um die Zeilen aus Listing 2.
10 gosub10000: rem datei deffinieren 100 rem steuerprogramm --------------------------------------------------------- 110 input"welchen satz ";sa 120 ifsa<0orsa>szthenprint"dieser satz existiert nicht!":goto110 130 print:printd$(sa) 140 goto100
10 GOSUB 10000: REM DATEI DEFFINIEREN 100 REM STEUERPROGRAMM --------------------------------------------------------- 110 INPUT"WELCHEN SATZ ";SA 120 IF SA<0OR SA>SZ THENPRINT"DIESER SATZ EXISTIERT NICHT!":GOTO 110 130 PRINT:PRINTD$(SA) 140 GOTO 100
Dieses Steuerprogramm verlangt also die Eingabe von Satzzahlen und gibt daraufhin den entsprechenden Satz aus.
SATZ suchen
Hier geht es darum, einen bestimmten SATZ innerhalb der Datei zu finden. Man gibt als Suchbegriff zum Beispiel ERDBEERE ein und erhält als Antwort SATZ 7 – also der gesuchte Begriff ERDBEERE steht in SATZ 7 der Früchtedatei. Das Steuerprogramm hierfür finden Sie in Listing 3.
10 gosub10000: rem datei deffinieren 100 rem steuerprogramm --------------------------------------------------------- 110 x$="":input"welchen begriff soll ich suchen ";x$ 120 for sa=1 to sz 130 : if x$=d$(sa) thenprint"der gesuchte begriff steht in satz ";sa 140 next sa 150 goto110
10 GOSUB 10000: REM DATEI DEFFINIEREN 100 REM STEUERPROGRAMM --------------------------------------------------------- 110 X$="":INPUT"WELCHEN BEGRIFF SOLL ICH SUCHEN ";X$ 120 FOR SA=1 TO SZ 130 : IF X$=D$(SA) THENPRINT"DER GESUCHTE BEGRIFF STEHT IN SATZ ";SA 140 NEXT SA 150 GOTO 110
Auch dieses Programm ist noch sehr einfach. Nach Eingabe des gesuchten Begriffs als X$ durchläuft eine Schleife SA die gesamte Datei D$(1) bis D$(SZ) – wobei SZ der höchste SATZ der Datei ist – und überprüft bei jedem Schritt, ob X$ gleich D$(SA) ist. Wird eine Übereinstimmung gefunden, so erhält man den entsprechenden Satzwert SA.
Im Falle einer idealen Datei würde dieses Steuerprogramm optimal funktionieren. Bei unserer momentan noch etwas chaotischen Datei kann es jedoch noch zu Schwierigkeiten kommen. Betrachten wir einmal folgende Fälle:
— Suchen Sie doch einmal den Begriff »PFIRSICH«. Selbstverständlich werden Sie keine Antwort erhalten, da diese Frucht in unserer Früchtedatei nicht aufgeführt wird. Unser Steuerprogramm kann also insofern verbessert werden, daß es mit »Dieser Begriff ist in der Früchtedatei nicht enthalten!« antwortet. Diese Änderung läßt sich noch einfach vornehmen, aber es gilt auch noch ein anderes Problem zu lösen:
— Geben wir als Suchbegriff »BIRNE« ein, so erhalten wir zwei Antworten. Einmal, daß der Begriff in SATZ 5 steht und einmal in SATZ 12. Diese Antwort des Steuerprogramms ist völlig korrekt, da der Begriff beziehungsweise die Frucht BIRNE in unserer Früchtedatei zweimal auftritt – nämlich in den SÄTZEN 5 und 12.
Allerdings ist es ziemlich unsinnig, bei unserer Früchtedatei eine Frucht zweimal aufzuführen. Warum habe ich die Frucht BIRNE eigentlich zweimal aufgeführt, sollten Sie sich jetzt fragen. Ganz einfach – wenn man sehr lange Listen eintippen muß (zum Beispiel Ersatzteile-Bestandsliste für einen Kfz-Betrieb) kann es sehr leicht passieren, daß man einen Satz versehentlich mehrmals eintippt. Spätestens das Dateiverwaltungs-Programm sollte dann soviel »Intelligenz« (Anführungszeichen deshalb, da dies noch lange nichts mit KI (künstliche Intelligenz) zu tun hat) haben, um den Fehler zu erkennen und auf ihn aufmerksam zu machen. Es gibt selbstverständlich auch Dateiverwaltungen, bei denen es sinnvoll ist, Begriffe doppelt auszuführen (zum Beispiel Objekt- und Wortschatztabellen für Abenteuerspiele) – dazu jedoch später.
Wir wollen unser derzeitiges Steuerprogramm also noch so verbessern, daß es einen Hinweis gibt, wenn ein Begriff mehrmals auftaucht.
Versuchen Sie zur Übung einmal diese beiden Verbesserungen am Steuerprogramm selbst durchzuführen, bevor Sie die Lösung abtippen. Die Lösung finden Sie in Listing 4.
10 gosub10000: rem datei deffinieren 100 rem steuerprogramm --------------------------------------------------------- 110 x$="":input"welchen begriff soll ich suchen ";x$ 120 ic=0:for sa=1 to sz 130 : if x$=d$(sa) thenprint"der gesuchte begriff steht in satz ";sa:ic=ic+1 140 next sa 142 ific=0thenprint"dieser begriff ist in der fruechtedatei nicht enthalten!" 144 ific>1thenprint"achtung, der begriff ";x$;" kommt";ic;"mal vor!" 150 goto110
10 GOSUB 10000: REM DATEI DEFFINIEREN 100 REM STEUERPROGRAMM --------------------------------------------------------- 110 X$="":INPUT"WELCHEN BEGRIFF SOLL ICH SUCHEN ";X$ 120 IC=0:FOR SA=1 TO SZ 130 : IF X$=D$(SA) THENPRINT"DER GESUCHTE BEGRIFF STEHT IN SATZ ";SA:IC=IC+1 140 NEXT SA 142 IF IC=0 THENPRINT"DIESER BEGRIFF IST IN DER FRUECHTEDATEI NICHT ENTHALTEN!" 144 IF IC>1 THENPRINT"ACHTUNG, DER BEGRIFF ";X$;" KOMMT";IC;"MAL VOR!" 150 GOTO 110
Dieses Steuerprogramm ist schon relativ brauchbar. Es kann Begriffe innerhalb unserer Datei suchen, erkennt, wenn ein gesuchter Begriff in der Datei überhaupt nicht vorkommt und es teilt uns mit, wenn ein Begriff in der Datei mehrmals enthalten ist (zum Beispiel BIRNE).
Bisher haben wir bei unseren Beispielprogrammen noch keinen Wert auf eine schöne Darstellung beziehungsweise Formatierung der Textausgaben auf dem Bildschirm geachtet. Im Laufe des Kurses wollen wir uns jedoch auch diesem Problem zuwenden.
Wenn man bei unserer Früchtedatei den Begriff BIRNE sucht, so erhalten wir die Antwort »Achtung, der Begriff kommt 2mal vor!«.
Auf dem Monitor sieht dieser Satz allerdings nicht sehr gut dargestellt aus, wenn man einmal das Wort »vor« am Ende des Satzes betrachtet. Das »v« des Wörtchens steht am Ende einer Bildschirmzeile, während das »or« zu Beginn der nächsten Zeile steht.
Viel deutlicher noch wird das Problem einer formatierten Textausgabe im Beispiel von Listing 5.
10 i$="abcdefghijklmnopqrstuvwxyz"
20 x$="":print"{clr}"
30 x$=x$+"bei der buchstabenfolge "
40 x$=x$+left$(i$,int(23*rnd(1))+1)
50 x$=x$+" handelt es sich um einen teil des alphabets."
60 printx$
70 print"{down}{down}bitte eine taste druecken!"
80 poke198,0:wait198,1
90 goto20
10 I$="ABCDEFGHIJKLMNOPQRSTUVWXYZ" 20 X$="":PRINT"{CLR}" 30 X$=X$+"BEI DER BUCHSTABENFOLGE " 40 X$=X$+LEFT$ (I$,INT (23*RND (1))+1) 50 X$=X$+" HANDELT ES SICH UM EINEN TEIL DES ALPHABETS." 60 PRINTX$ 70 PRINT"{2DOWN}BITTE EINE TASTE DRUECKEN!" 80 POKE 198,0:WAIT 198,1 90 GOTO 20
Dieses Programm macht nichts anderes, als einfach einen zufällig erzeugten Text auf dem Bildschirm auszugeben. Das heißt, Anfang und Ende des Textes sind immer gleich, der mittlere Teil stellt jedoch eine zufällige, aus dem Alphabet ausgewählte Buchstabenfolge dar. Durch die zufällige Auswahl des mittleren Textteils variiert die Gesamtlänge des Textes stark – es kommt so zu einer unkontrollierten, unformatierten Bildschirmausgabe. Einige Computer haben in ihrem Basic PRINT-Anweisungen mit »Intelligent Space« zur Verfügung, mit deren Hilfe es sehr einfach ist, Text formatiert auf den Bildschirm zu bringen. Eine derartige Routine ist im Basic des Commodore 64 leider nicht enthalten. Sie läßt sich jedoch mit nur geringem Aufwand als Basic-Unterprogramm programmieren, wie es Listing 6 zeigt.
20000 rem formatierte textausgabe ---------------------------------------------- 20010 ifx$=""then20090 20020 iflen(x$)<40thenprintx$:goto20090 20030 iflen(x$)=40thenprintx$;:goto20090 20040 i$=left$(x$,41):ic=0:fori=1tolen(i$):ifmid$(i$,i,1)=" "thenic=i 20050 nexti:ific=0thenprintx$:goto20090 20060 i$=left$(i$,ic-1):iflen(i$)=40thenprinti$; 20070 iflen(i$)<40thenprinti$ 20080 x$=right$(x$,len(x$)-ic):goto20010 20090 return
20000 REM FORMATIERTE TEXTAUSGABE ---------------------------------------------- 20010 IF X$="" THEN20090 20020 IF LEN (X$)<40 THENPRINTX$:GOTO 20090 20030 IF LEN (X$)=40 THENPRINTX$;:GOTO 20090 20040 I$=LEFT$ (X$,41):IC=0:FOR I=1 TOLEN (I$):IF MID$ (I$,I,1)=" " THENIC=I 20050 NEXT I:IF IC=0 THENPRINTX$:GOTO 20090 20060 I$=LEFT$ (I$,IC-1):IF LEN (I$)=40 THENPRINTI$; 20070 IF LEN (I$)<40 THENPRINTI$ 20080 X$=RIGHT$ (X$,LEN (X$)-IC):GOTO 20010 20090 RETURN
Nun wollen wir dieses Unterprogramm in unser letztes Beispielsprogramm einbauen. Hierzu müssen lediglich folgende Änderungen durchgeführt werden:
60 GOSUB 20000
Wenn Sie das Programm nun erneut starten, werden alle Textausgaben formatiert auf den Bildschirm gebracht. Was die formatierte Textausgabe anbelangt, so wollen wir es zunächst bei diesem Unterprogramm belassen.
Eine MERGE-Routine
Wie Sie sicherlich bereits bemerkt haben, kommen in diesem Kurs Routinen vor, auf die wir ständig zurückgreifen werden. Um sich viel Tipparbeit zu ersparen, bietet es sich daher an, die einzelnen Routinen einzeln abzuspeichern, um sie bei Bedarf wieder laden zu können. Hierbei ist es nützlich, wenn Sie mit einem MERGE-Befehl arbeiten, der es erlaubt, einzelne Programmteile nacheinander zusammen »zu laden«. Sie können hierzu Basic-Erweiterungen à la G-Basic, Simons Basic oder auch Toolkits à la KFC Super 64 verwenden. Mit fortlaufendem Kurs werden wir jedoch soviel Speicherplatz benötigen beziehungsweise das Betriebssystem des C 64 derartig »umkrempeln« und erweitern, daß es sich mit keiner dieser Basic-Erweiterungen mehr verträgt. Es bietet sich sodann die folgende MERGE-Routine, die Sie im Direkt-Modus durchführen können, an (sowohl für Disklaufwerk als auch Datasette).
Dieser Direktmodus-MERGE geht so:
A. Programm A laden. Dann notieren Sie sich den Inhalt der Speicherzellen 43 und 44 – also PEEK(43) und PEEK(44).
B. Geben Sie nun im Direktmodus ein:
POKE 43,(PEEK(45)+256*PEEK(46)-2)AND255
(RETURN)
POKE 44,(PEEK(45)+256*PEEK(46) -2/256)
(RETURN)
C. Programm B laden. Dann die notierten Werte wieder in die Speicherzellen 43 und 44 POKEn.
Fertig – beide Programme sind jetzt gemeinsam im Speicher.
Bei diesem Vorgang ist zu beachten, daß zuerst das Programm mit den niedrigeren Zeilennummern geladen wird (Programm A) – ansonsten kann es zu Schwierigkeiten kommen beziehungsweise das MERGEn klappt einfach nicht.
Wir werden deshalb schon sehr bald auf die beste Dateiverwaltungsmethode zu sprechen kommen, die auf dem C 64 überhaupt möglich ist: Relative Dateien mit Hilfe des Diskettenlaufwerks 1541.
Würde man auf Disk eine Datei mit 2000 Sätzen anlegen und in dieser dann einen Begriff suchen, der an letzter Stelle der Datei steht, so würde man bei Verwenden unserer bisherigen Suchroutine eine viel größere Suchzeit als 14 Sekunden in Kauf nehmen müssen. Auch eine Programmierung in Assembler würde keine befriedigenden Suchzeiten ergeben, da bei relativen Dateien auf Disk noch ein anderer Faktor eine wesentliche Rolle spielt: Die Zugriffzeit des Floppy-Laufwerks – also die Zeit, die das Disketten-Laufwerk braucht, um auf bestimmte Sätze einer relativen Datei zuzugreifen. Diese Zugriffszeit, die vom Floppy-Laufwerk beansprucht wird, läßt sich auch mit Assembler nicht beeinflussen. Auch Hypra-Load-Fans werden enttäuscht sein, da Hypra-Load wie auch alle anderen ähnlichen Programme (Ausnahme: Parallelübertragung à la SpeedDos etc.) die Zugriffszeit auf relative Dateien in keinster Weise beeinflussen. Hier auch gleich eine Anmerkung für alle, die ihr altes Betriebssystem durch ein Betriebssystem mit Hypra-Load ersetzt haben (beziehungsweise Umschaltplatine im Computer haben): Hypra-Load beansprucht den Datasetten-Puffer von 828-1019. Diesen Bereich werde ich ab und zu für kleine MP-Routinen beanspruchen. Es ist daher unbedingt erforderlich, daß Sie dann das alte Betriebssystem im Computer haben beziehungsweise auf dieses umschalten!
Nun aber zurück zum Sortieren.
Bevor wir unsere erste Sortierroutine programmieren, müssen wir uns noch einiges an Theorie zu Gemüte führen.
Betrachten wir hierzu noch einmal unsere Früchtedatei. Bisher haben wir immer nur einzelne Begriffe gesucht. Was aber nun, wenn wir aus unserer Früchtedatei alle Früchte heraussuchen wollen, die einer Beerensorte angehören? Wir müßten als Antwort dann ERDBEERE, HIMBEERE und BROMBEERE erhalten. Woher in aller Welt soll unser C 64 aber wissen, was Beeren sind beziehungsweise welche Früchte als Beeren bezeichnet werden können?
Die einfachste Lösung wäre die folgende:
Alle Beerenarten in unserer Datei haben die Endung BEERE gemeinsam. Es liegt nun nahe, beim Suchvorgang einfach zu überprüfen, ob die letzten 5 Zeichen jedes Früchtenamens gleich »BEERE« ist; also:
IF RIGHT$(D$,5)=X$ THEN
wobei X$= "BEERE" ist.
Um die Suchroutine (Listing 4) entsprechend zu ändern, müssen Sie lediglich Zeile 130 modifizieren;
130 : IF RIGHT$(D$(SA),LEN(X$))=X$THENPRINTD$(SA):IC=IC+1
Die erste Hürde bei der Einführung von Dateiverwaltungsprogrammierung wäre nun geschafft. Sie wissen jetzt, wie man einfache Dateien innerhalb von Basic tabellenartig verwaltet, und Sie haben die einfachste und schlechteste Suchroutine kennengelernt, die es gibt.
Ohne Sortieren geht es nicht
Bisher haben wir unsere Früchtedatei von Anfang bis Ende durchsucht, um schließlich eine bestimmte Frucht zu finden oder zu erfahren, daß sie in der Datei gar nicht beziehungsweise mehrmals auftritt. Das ging bisher auch ziemlich schnell, in Bruchteilen von Sekunden. Was aber nun, wenn wir riesige Dateien mit mehreren tausend Sätzen verwalten wollen?
Die »Schwäche« unserer bisherigen Suchroutine wird bei dem Demo-Programm in Listing 7 besonders deutlich:
10 dim x$(2000) 20 x$(2000)="mike" 30 rem suchroutine 35 ti$="000000":rem stoppuhr auf null 40 fori=1to2000 50 :ifx$(i)="mike"thenprint"mike steht in satz";i:print"benoetigte zeit:";ti$ 60 nexti
10 DIM X$(2000) 20 X$(2000)="MIKE" 30 REM SUCHROUTINE 35 TI$="000000":REM STOPPUHR AUF NULL 40 FOR I=1 TO2000 50 :IF X$(I)="MIKE" THENPRINT"MIKE STEHT IN SATZ";I:PRINT"BENOETIGTE ZEIT:";TI$ 60 NEXT I
Hier wird in einer Datei mit 2000 Sätzen der Name »Mike« gesucht, der unglücklicherweise an letzter Stelle der Datei steht. Das Programm sucht 14 Sekunden, bis es den Namen endlich gefunden hat. Ein kleiner Tip am Rande: SYS 65499 bewirkt das gleiche wie der Befehl TIS="000000".
Diese Zeit ist sogar noch akzeptabel, wenn man bedenkt, daß sich auf unsere Früchtedatei keine andere vernünftige beziehungsweise schnellere Suchroutine als die obig vorgestellte anwenden läßt.
Warum? Ganz einfach – vernünftige Suchroutinen lassen sich nur bei Dateien anwenden, die nach einem bestimmten Kriterium sortiert sind (zum Beispiel nach der alphabetischen Grundordnung).
Wir müssen unsere Früchtedatei also zunächst sortieren, bevor wir eine bessere Suchroutine programmieren können.
»Warum programmieren wir die obige Suchroutine nicht einfach in Maschinensprache?« könnte jetzt einer rufen! Er hätte auch recht – die obige Routine wäre in Assembler derartig schnell, daß wir uns über die Programmierung von Suchroutinen keine Gedanken machen müßten.
Es muß also noch andere Gründe geben, warum ich das Zeitproblem nicht einfach mit einer Maschinensprache-Routine unter den Tisch kehre und stattdessen einen Umweg über die Programmierung von Sortieralgorithmen mache. Und der ist wie folgt: Sie müssen sich lediglich vor Augen halten, daß wir es im Moment nur mit einfachen Dateien innerhalb von Basic zu tun haben. Diese Dateien sind zwar sehr gut zu handhaben, aber ihre Verwaltungskapazität ist zu stark eingeschränkt – nämlich auf 38 KByte Basic-RAM beim C 64.
Wenn Sie das Programm nun starten und als Suchbegriff »BEEREN« eingeben, so gibt die Suchroutine alle Sätze aus, die mit »BEEREN« enden – also alle Beerensorten. Genausogut können Sie als Suchbegriff »COLA« eingeben und erhalten dann alle in der Datei vorkommenden Cola-Sorten aufgelistet. Diese Suchmethode funktioniert im Moment zwar ganz gut, ist jedoch viel zu sehr eingeschränkt, da sich Dateien nur in ganz seltenen Fällen nach ihren Satzendungen sortieren lassen. Angenommen, Sie möchten eine Datei programmieren, in der Computerprogramme verwaltet werden (zum Beispiel für ein Softwarehaus). Man müßte dann nach verschiedenen Kategorien einteilen: Zum Beispiel Programmart, Preislage, Computertyp etc.
Ein solches Programm für einen Software-Händler müßte folgende Bedingungen aufweisen:
- Feststellen, ob ein bestimmtes Programm geführt wird.
- Datei nach bestimmten Kriterien sortieren:
— Programmart
— Preislage etc. - Lagerbestand kontrollieren – also wieviele Exemplare eines Programmes noch vorrätig sind etc.
Man müßte die Dateiverwaltung in Form einer Tabelle aufbauen, die so aussehen könnte:
| 1. Schlüssel Programmname |
2. Schlüssel Art |
3. Schlüssel Preis |
4. Schlüssel Vorrat |
|---|---|---|---|
| vizawrite | 1 | 200.00 | 24 |
| wordpro | 1 | 1155.50 | 13 |
| space invaders | 2 | 39.00 | 67 |
| jumpman | 2 | 249.00 | 34 |
| simons basic | 3 | 99.00 | 65 |
| g-basic | 3 | 3140.00 | 45 |
| gordon saga | 4 | 39.00 | 89 |
| deadline | 4 | 99.00 | 34 |
Die einzelnen Spalten beziehungsweise Kategorien einer solchen Tabelle/Datei bezeichnet man als Schlüssel. Wenn ich die Software-Datei zum Beispiel nach dem 3. Schlüssel sortiere, so sortiere ich nach dem Kriterium Preis. Beim 1. Schlüssel – dem Programmnamen – würde ich alphabetisch sortieren.
Bevor man nun zur Programmierung übergeht, muß man sich noch einige andere Gedanken über den Aufbau der Tabelle machen. Man muß so zum Beispiel genau festlegen, aus wieviel Zeichen der Programmname maximal bestehen darf. Auch für alle anderen »Schlüssel« muß genau festgelegt werden, welche Länge sie einnehmen dürfen. Betrachten wir einmal den zweiten Schlüssel: Um zwischen 9 Programmarten unterscheiden zu können, benötige ich ein Zeichen Platz. Lege ich jedoch 2 Zeichen als maximale Länge fest, so kann ich bis zu 99 (also zweistellige Zahlen) Arten unterscheiden. Unsinnig wäre es sicherlich, 4 Stellen zu reservieren, da man wohl kaum seine Programme in 9999 verschiedene Arten einteilen will. Auch wenn ich momentan nur 9 Programmarten unterscheide (also nur 1 Zeichen Platz benötige), entscheide ich mich für 2 Zeichen Reservierung, damit meine Datei möglichst ausbaufähig bleibt.
Für den 2. Schlüssel muß ich noch eine weitere Tabelle anlegen:
1 = Textverarbeitungsprogramm
2 = Actionsspiel
3 = Basic-Erweiterung
4 = Abenteuerspiel
Nach einigen Überlegungen lege ich schließlich folgendes fest:
| Programmnamenlänge | : max. 15 Zeichen |
| 2. Schlüssel | : max. 2 Zeichen |
| 3. Schlüssel | : max. 6 Zeichen |
| 4. Schlüssel | : max. 2 Zeichen |
Die Tabelle, wie sie später im Computer sein soll, ist damit festgelegt wie in Bild 1.

Lassen Sie mich noch einmal kurz zu unserer Früchtedatei zurückkehren. Diese Datei läßt sich besonders einfach sortieren, da sie aus nur einem Schlüssel besteht (dem Fruchtnamen).
Zum Sortieren braucht man einen möglichst guten Sortier-Algorithmus – am besten in Form eines Basic-Unterprogramms, das Sie später einfach in Ihrem eigenen Programm einbauen können.
Ich halte es nicht für sinnvoll, in diesem Kurs lange und breit auf die Funktion der einzelnen Sortieralgorithmen einzugehen beziehungsweise diese vorzustellen. Der schnellste Sortieralgorithmus ist der Quicksport. Ihn wollen wir immer dann im Kurs anwenden, wenn irgendetwas sortiert werden muß. Wer sich für die Grundlagen der Sortieralgorithmen interessiert, den möchte ich auf den Kurs »Effektives Programmieren« hinweisen, der im Jahre 1985 in der Zeitschrift 64'er veröffentlicht worden ist. Dort erfahren Sie alles Wissenswerte über die Programmierung und Funktion dieser Algorithmen.
Listing 8 zeigt das Unterprogramm zum Quicksort-Algorithmus.
30000 rem quicksort ----------------------------------------------------------- 30001 dimlg(100),rg(100):z=0:lg(1)=1:rg(1)=sz 30010 z=z+1:iflg(z)>=rg(z)then30120 30020 x=lg(z):y=rg(z) 30030 vg$=d$(int((x+y)/2)) 30040 if x>y then30100 30050 if d$(x)<vg$thenx=x+1:goto30050 30060 if d$(y)>vg$theny=y-1:goto30060 30070 if x>ythen30100 30080 s$=d$(x):d$(x)=d$(y):d$(y)=s$ 30090 x=x+1:y=y-1:goto30040 30100 rg(z+1)=y:lg(z+1)=lg(z):gosub30010 30110 lg(z+1)=x:rg(z+1)=rg(z):gosub30010 30120 z=z-1:return
30000 REM QUICKSORT ----------------------------------------------------------- 30001 DIM LG(100),RG(100):Z=0:LG(1)=1:RG(1)=SZ 30010 Z=Z+1:IF LG(Z)>=RG(Z) THEN30120 30020 X=LG(Z):Y=RG(Z) 30030 VG$=D$(INT ((X+Y)/2)) 30040 IF X>Y THEN30100 30050 IF D$(X)<VG$ THENX=X+1:GOTO 30050 30060 IF D$(Y)>VG$ THENY=Y-1:GOTO 30060 30070 IF X>Y THEN30100 30080 S$=D$(X):D$(X)=D$(Y):D$(Y)=S$ 30090 X=X+1:Y=Y-1:GOTO 30040 30100 RG(Z+1)=Y:LG(Z+1)=LG(Z):GOSUB 30010 30110 LG(Z+1)=X:RG(Z+1)=RG(Z):GOSUB 30010 30120 Z=Z-1:RETURN
Der Quicksort ist wie bereits gesagt der schnellste, aber auch der am schwierigsten verständliche Algorithmus zum Sortieren. Für den Verlauf dieses Kurses ist es lediglich notwendig, daß Sie wissen, wie das Quicksort-Unterprogramm angewendet wird beziehungsweise welche Parameter man ihm übergeben muß.
Beachten Sie bitte die beiden DIMs in Zeile 30001. Wenn Sie innerhalb eines Programmes den Quicksort mehrmals verwenden wollen, also das Unterprogramm mit GOSUB30000 aufrufen, müssen Sie diese beiden DIMs an einer anderen Stelle im Programm (am besten am Anfang) unterbringen, daß es nicht zu einem REDIM'D ARRAY ERROR kommt.
Das Unterprogramm benötigt folgende Parameter:
— das Feld, das sortiert werden soll, muß D$(1)-D$(?) sein. Sie können im Unterprogramm natürlich auch D$ durch etwas anderes ersetzen!
— In der Variablen SZ steht die Anzahl der Sätze der Datei/des Feldes.
— LG(1) und RG(1) geben Anfang und Ende des Bereiches des Feldes, das sortiert werden soll, an.
Ist LG(1)=1 und RG(1)=SZ, so heißt das, daß das gesamte Feld D$ sortiert werden soll. Ist LG(1)=10 und RG(1)=40, so wird vom 10ten bis zum 40sten Satz sortiert etc.
Das Beispielprogramm in Listing 9 zeigt, wie sich unsere Früchtedatei mit Hilfe des Quicksort-Unterprogramms alphabetisch sortieren läßt.
10 gosub10000:rem tabelle deffinieren
20 print"{clr}tabelle vor dem sortieren:
30 fori=1tosz
40 :printi,d$(i)
50 nexti
60 print"{down}[return]":poke198,0:wait198,1
65 print"bitte warten!"
70 gosub30000:rem d$(1)-d$(sz) sortieren
80 print"{clr}tabelle nach dem sortieren:
90 fori=1tosz
100 :printi,d$(i)
110 nexti
120 end
10 GOSUB 10000:REM TABELLE DEFFINIEREN 20 PRINT"{CLR}TABELLE VOR DEM SORTIEREN: 30 FOR I=1 TOSZ 40 :PRINTI,D$(I) 50 NEXT I 60 PRINT"{DOWN}[RETURN]":POKE 198,0:WAIT 198,1 65 PRINT"BITTE WARTEN!" 70 GOSUB 30000:REM D$(1)-D$(SZ) SORTIEREN 80 PRINT"{CLR}TABELLE NACH DEM SORTIEREN: 90 FOR I=1 TOSZ 100 :PRINTI,D$(I) 110 NEXT I 120 END
An diesem Programm braucht nicht viel erklärt werden – einfach Abtippen und RUN.
Interessant ist vielleicht, was mit unserem Satz Nummer 9 der Früchtedatei passiert ist (der leere Satz). Er steht nach dem Sortiervorgang an erster Stelle der Datei. Wenn Sie möchten, dann können Sie leere Sätze als eine Art NOPs betrachten – einfache Platzhalter, in die man später noch etwas einbauen kann. Bei Assembler-Programmierung sind solche Platzhalter unentbehrlich, bei Dateiverwaltung jedoch relativ überflüssig, da gute Dateiverwaltungen Befehle enthalten, innerhalb einer Datei automatisch zwischen zwei Sätzen einen Leerraum zu schaffen. Vor dem Sortieren bietet es sich folglich an, die leeren Sätze aus der Datei zu eliminieren.
Man durchläuft die gesamte Datei hierbei mit einer Schleife und schreibt immer dann, wenn man auf einen leeren Satz findet, den letzten Satz dieser Datei in den leeren Satz. Danach wird SZ, die Variable, die angibt, wieviele Sätze die Datei enthält, um den Wert 1 erniedrigt.
Das geht so wie in Listing 10.
20000 rem leere saetze aus einer datei eliminieren ----------------------------- 20020 i=0: rem zaehler auf null 20030 i=i+1:ifi=szthenreturn 20040 :ifd$(i)=""thend$(i)=d$(sz):sz=sz-1:goto40 20050 goto20030
20000 REM LEERE SAETZE AUS EINER DATEI ELIMINIEREN ----------------------------- 20020 I=0: REM ZAEHLER AUF NULL 20030 I=I+1:IF I=SZ THENRETURN 20040 :IF D$(I)="" THEND$(I)=D$(SZ):SZ=SZ-1:GOTO 40 20050 GOTO 20030
Sie müssen zum Aufruf des Unterprogramms lediglich Listing 9 um folgende Zeile ergänzen:
11 GOSUB20000:REM LEERE SAETZE WEG
Nun werden vor dem Sortiervorgang alle leeren Sätze aus der Datei entfernt. Wir sind nun in der Lage, jede (sich komplett im Computer befindliche) Datei zu sortieren. Daher eröffnen sich uns auch völlig neue Suchroutinen:
Die binäre und die index-sequentielle Suchmethode.
Für unsere folgenden Experimente gibt die Früchtedatei nicht mehr genug her. Wir wenden uns deshalb nun der Software-Datei zu, die über mehrere Schlüssel verfügt.
Zunächst wird die Datei wieder in einem Unterprogramm ab 10000 definiert, wie es Listing 11 zeigt.
10000 rem fruechte-datei deffinieren ------------------------------------------- 10010 sz=8 :rem anzahl der saetze 10020 dim d$(sz):rem string-feld 10030 for i=1 to sz 10040 : read d$(i) 10050 nexti 10100 data"vizawrite 01200.0024" 10105 data"wordpro 01155.5013" 10110 data"space invaders 02 39.0067" 10115 data"jumpman 02 49.0034" 10120 data"simons basic 03 99.0065" 10125 data"g-basic 03140.0045" 10130 data"gordon saga 04 39.0089" 10135 data"deadline 04 99.0034" 10140 return
10000 REM FRUECHTE-DATEI DEFFINIEREN ------------------------------------------- 10010 SZ=8 :REM ANZAHL DER SAETZE 10020 DIM D$(SZ):REM STRING-FELD 10030 FOR I=1 TO SZ 10040 : READ D$(I) 10050 NEXT I 10100 DATA "VIZAWRITE{6SPACE}01200.0024" 10105 DATA "WORDPRO{8SPACE}01155.5013" 10110 DATA "SPACE INVADERS 02 39.0067" 10115 DATA "JUMPMAN{8SPACE}02 49.0034" 10120 DATA "SIMONS BASIC{3SPACE}03 99.0065" 10125 DATA "G-BASIC{8SPACE}03140.0045" 10130 DATA "GORDON SAGA{4SPACE}04 39.0089" 10135 DATA "DEADLINE{7SPACE}04 99.0034" 10140 RETURN
Eigentlich haben wir unsere Dateien bisher immer doppelt im Speicher stehen: einmal innerhalb von DATA-Zeilen und zum anderen in einem dimensionierten Stringfeld. Wir gehen bisher also ziemlich verschwenderisch mit dem geringen Speicher unseres C 64 um (gering in Anbetracht der Tatsache, daß es heute schon Computer zu Homecomputer-Preisen gibt, die auf 512 KByte RAM aufgestockt werden können!). Es wird also höchste Zeit, sich um eine bessere Unterbringung unserer Tabellen zu bemühen.
In der Tat – sind die DATA-Zeilen erst einmal in ein Stringfeld eingelesen, werden sie nicht mehr weiter benötigt und blockieren lediglich freien Speicherplatz.
Oh, soeben habe ich einen kleinen Fehler in Listing 11 entdeckt: Die REM-Zeile 10000 darf nicht länger Früchte-Datei heißen, sondern muß jetzt Software-Datei lauten. Wir müssen Zeile 10000 deshalb folgendermaßen abändern:
10000 REM SOFTWARE-DATEI DEFINIEREN
Vielleicht werden sich einige (mit gutem Recht) wundern, warum ich hier lange und breit auf diesen lächerlichen »Fehler« eingehe und nicht einfach mein Manuskript verbessere........ kein Kommentar, denn jetzt kommt wieder ein kleiner (aber äußerst interessanter) Tip am Rande:
Ärger beim »Replace«'n
Selbstverständlich gehe ich jetzt davon aus, daß Sie alle Listings, die Sie im Rahmen dieses Kurses abtippen, auf Diskette speichern, um diese später wieder verwenden zu können. Nehmen wir einmal an, daß Sie auch das falsche Listing 11 bereits gespeichert haben. Worauf ich hinaus will, ist das folgende: Wenn Sie das »falsche« Listing bereits gespeichert haben, so werden Sie es sicherlich verbessern und anschließend mit dem Befehl
SAVE"@:LISTING 11",8
wieder speichern. Wenn Ihnen nicht klar ist, was das »@« im Filename soll, so sind Sie noch nicht »reif« genug für den folgenden Tip – lesen Sie schleunigst Ihr Bedienerhandbuch zur VC 1541!
Wer die Anweisung kennt und schon des öfteren angewandt hat, hat sicherlich auch schon unerklärbare Probleme damit gehabt. So kann es zum Beispiel vorkommen, daß der Befehl einfach nicht funktioniert und die rote LED am Laufwerk ähnlich einer extrem miserablen 1-Kanal-Lichtorgel im 4/4-Takt zu blinken beginnt. Die Erklärung dieses Fehlers im Betriebssystem ist sehr kompliziert. Für den Anwender ist nur eines wichtig: Niemals den @ verwenden! Sicherer ist es, mit »Scratch« zu löschen und dann noch einmal zu speichern. Also: Niemals SAVE " @..."
Nun aber schnell zurück zu unserem aktuellen Problem. Laden Sie bitte Listing 11, falls Sie es nicht mehr im Speicher haben. Nach Eingabe von CLR und PRINT FRE(0)+65000 erhalten Sie die Antwort, daß 37928 Byte Basic RAM frei sind. Nach Starten des Programmes mit GOSUB 10000 im Direktmodus und Eingabe von PRINT FRE(0) erhalten Sie die Antwort, daß jetzt nur noch 37880 Bytes Basic RAM frei sind. Es wird also Basic-RAM benötigt, um ein Stringfeld zu füllen. Es macht bei unserem Programm zwar nur ein paar Bytes aus, aber unsere Tabelle ist ja extrem klein. Bei großen Dateien kann ein sehr großer Unterschied vorkommen.
Es bieten sich nun mehr Lösungsmöglichkeiten an.
Man kann so zum Beispiel darauf verzichten, die DATAs in ein Stringfeld einzulesen. Man benötigt dann eine modifizierte RESTORE-Routine. Im Basic des C 64 wird mittels RESTORE der DATA-Zeiger einfach auf die erste DATA-Zeile zurückgesetzt. Unter modifizierte RESTORE-Funktion versteht man nun die Möglichkeit, den DATA-Zeiger auf eine bestimmte DATA-Zeile zu setzen – RESTORE(X), wobei X für die DATA-Zeile steht, auf die der Zeiger gesetzt werden soll. Von dieser Möglichkeit, die sich hervorragend zum Programmieren von Adventures anbietet, die komplett im Basic-RAM stehen (worauf Datasetten-Benutzer angewiesen sind), habe ich in meinem ersten Adventure-Programmierkurs im zweiten 64'er Sonderheft regen Gebrauch gemacht. Das entsprechende Maschinenprogramm für die modifizierte RESTORE-Routine finden Sie in diesem Heft.
Für Dateiverwaltung, KI-Programme und disketten-orientierte Adventures ist die RESTORE-Methode jedoch absolut ungeeignet. Die Tabelle kann innerhalb von Basic nicht ausgebaut werden – und gerade dies ist bei Dateiverwaltung und selbstlernenden KI-Programmen unvermeidlich (schon gut, es gibt Basic-Zeilengeneratoren, aber wir wollen uns mit effektvolleren Methoden vertraut machen!).
Eine wesentlich effektvollere Methode besteht darin, die DATA-Zeilen durch sequentielle Dateien zu ersetzen.
Wir ergänzen deshalb Listing 11 nun um ein Steuerprogramm, das D$(1) bis D$(SZ) als sequentielle Datei auf Disk ablegt (Listing 12).
10 rem d$(1)-d$(sz) als seq-file ----------------------------------------------- 20 gosub10000: rem tabelle deffinieren 30 open2,8,2,"daten-file,s,w" 40 fori=1tosz 50 :print#2,d$(i) 60 nexti 65 close2 70 print"*** fertig ***" 80 end
10 REM D$(1)-D$(SZ) ALS SEQ-FILE ----------------------------------------------- 20 GOSUB 10000: REM TABELLE DEFFINIEREN 30 OPEN 2,8,2,"DATEN-FILE,S,W" 40 FOR I=1 TOSZ 50 :PRINT#2,D$(I) 60 NEXT I 65 CLOSE 2 70 PRINT"*** FERTIG ***" 80 END
Die Software-Datei ist jetzt unter dem Filenamen »Daten-File« als SEQ-File auf Diskette gespeichert. Unter einem sequentiellen File versteht man eine Datenkette, die auf Diskette gespeichert ist – also ein String beziehungsweise Element nach dem anderen. Eine so gespeicherte Tabelle kann ganz einfach wieder in den Computer in ein dimensioniertes Feld geladen werden (Listing 13).
10000 rem fruechte-datei deffinieren ------------------------------------------- 10010 sz=8 :rem anzahl der saetze 10020 dim d$(sz):rem string-feld 10030 open2,8,2,"daten-file,s,r" 10040 fori=1tosz 10050 :input#2,d$(i) 10060 nexti 10070 close2 10080 return
10000 REM FRUECHTE-DATEI DEFFINIEREN ------------------------------------------- 10010 SZ=8 :REM ANZAHL DER SAETZE 10020 DIM D$(SZ):REM STRING-FELD 10030 OPEN 2,8,2,"DATEN-FILE,S,R" 10040 FOR I=1 TOSZ 10050 :INPUT#2,D$(I) 10060 NEXT I 10070 CLOSE 2 10080 RETURN
Die hier vorgestellten Beispielprogramme sollen uns zunächst genügen. Es gibt noch einige weitere, wesentlich bessere Methoden, Dateien und Tabellen als sequentielle Files zu speichern. Tatsächlich ist es auch nicht besonders sinnvoll, jeden einzelnen Satz einer Tabelle mit mehreren Schlüsseln als einfachen String zu speichern. Wie ich Ihnen bereits in der Einleitung zum Kurs mitteilte, möchte ich das Thema Dateiverwaltung speziell auf die Bereiche KI und fortgeschrittene Adventure-Programmierung beziehen. SEQ-Files sind auch mit der Datasette möglich. Beim Programmieren von Adventures und KI sind sie jedoch nur äußerst begrenzt sinnvoll einsetzbar. Wie es der Name schon sagt, werden sequentielle Dateien kettenförmig gespeichert. Es ist somit nicht möglich, einen bestimmten Teil der Datei so mir nichts, dir nichts zu verändern beziehungsweise in der Mitte der Datei eine Lücke zu schaffen, um nachträglich etwas einzutragen. Zur Bearbeitung muß eine SEQ-Datei immer komplett in den Computer geladen werden. Dann, nach Beendigung der Arbeit, wird sie wieder auf Diskette gespeichert. Eine sequentielle Datei läßt sich nur dann effektiv modifizieren, wenn sie so klein (relativ betrachtet) ist, daß sie auf einmal in den Speicher des C 64 paßt. Dadurch ist die maximale Kapazität einer solchen Datei sehr eingeschränkt.
Für kleinere Dateien sind sequentielle Dateien jedoch empfehlenswert, da sie sehr einfach zu handhaben sind. Für die späteren KI-Programme und Adventures werden SEQ-Files nur eine kleine Nebenrolle spielen. Ich möchte daher nicht auf alle Möglichkeiten der sequentiellen Datenspeicherung eingehen, zumal zu diesem Thema auch genügend geschrieben worden ist.
Jetzt wird es aber höchste Zeit, einige weitere Experimente zum Sortieren und Suchen an unserer neuen Datei durchzuführen. Als Ausgangsprogramm verwenden Sie bitte immer Listing 11. Für unsere Beispiele lohnt es sich nicht, die Datei jedesmal als SEQ-file laden zu müssen.
Sortieren mit dem Quicksort
Wie wir anhand des Demoprogramms zur Suchzeit (Listing 7) festgestellt haben, ist unsere erste Suchmethode – Datei vom ersten bis zum Zielelement durchsuchen – viel zu langsam, wenn eine große Datei vorliegt, und das gesuchte Element ziemlich am Ende der Datei steht. Wir waren uns auch einig, daß bessere Suchmethoden nur dann sinnvoll sind, wenn die Datei alphabetisch beziehungsweise nach einem anderen Kriterium sortiert ist. Besonders deutlich kommen die verschiedenen Sortierkriterien anhand einer Personendatei zum Vorschein. Wenn ich zum Beispiel wissen will, ob eine bestimmte Person in der Datei aufgeführt ist, so muß ich die Datei alphabetisch ordnen (Namen), um ein schnelles Suchprogramm schreiben zu können. Will ich jedoch wissen, wer an einem bestimmten Tag alles Geburtstag hat, so muß ich die Datei nach den Geburtstagen sortieren. Das Sortieren von Geburtstagen ist schon etwas komplizierter als das Sortieren nach dem Alphabet.
Beispiel:
Tobi 21.11.66
Borgsi 30.05.67
Olaf 20.03.67
Als String lassen sich die Geburtstage nicht so ohne weiteres sortieren. Ein kleiner Trick hilft jedoch:
Tobi 661121
Borgsi 670530
Olaf 670320
Die Geburtstage werden durch einfaches »Umdrehen« in sechsstellige Zahlen verwandelt. Die höchste Zahl kommt jetzt dem jüngsten der drei zu, die niedrigste dem ältesten.
Jetzt können die »Geburtstage« einfach als Strings mit dem Quicksort oder einem anderen Sortieralgorithmus sortiert werden.
Zurück zur Software-Datei. Wie wir bereits festgestellt haben, sind verbesserte Suchmethoden nur dann möglich, wenn die Datei irgendwie (nach irgendeinem Schlüssel) sortiert ist. Bei unserer Software-Datei bietet sich der 1. Schlüssel – also der Programmname – an. Listing 14 zeigt, wie die Datei mit dem Quicksort alphabetisch geordnet wird (1. Schlüssel).
10 gosub10000:rem datei deffinieren 20 print"datei ungeordnet:" 30 fori=1tosz:printd$(i):nexti 40 gosub30000:rem 1.schluessel sortieren 50 print"datei nach 1.schluessel sortiert:" 60 fori=1tosz:printd$(i):nexti 70 end 10000 rem fruechte-datei deffinieren ------------------------------------------- 10010 sz=8 :rem anzahl der saetze 10020 dim d$(sz):rem string-feld 10030 for i=1 to sz 10040 : read d$(i) 10050 nexti 10100 data"vizawrite 01200.0024" 10105 data"wordpro 01155.5013" 10110 data"space invaders 02 39.0067" 10115 data"jumpman 02 49.0034" 10120 data"simons basic 03 99.0065" 10125 data"g-basic 03140.0045" 10130 data"gordon saga 04 39.0089" 10135 data"deadline 04 99.0034" 10140 return 30000 rem quicksort ----------------------------------------------------------- 30001 dimlg(100),rg(100):z=0:lg(1)=1:rg(1)=sz 30010 z=z+1:iflg(z)>=rg(z)then30120 30020 x=lg(z):y=rg(z) 30030 vg$=left$(d$(int((x+y)/2)),15) 30040 if x>y then30100 30050 if left$(d$(x),15)<vg$thenx=x+1:goto30050 30060 if left$(d$(y),15)>vg$theny=y-1:goto30060 30070 if x>ythen30100 30080 s$=d$(x):d$(x)=d$(y):d$(y)=s$ 30090 x=x+1:y=y-1:goto30040 30100 rg(z+1)=y:lg(z+1)=lg(z):gosub30010 30110 lg(z+1)=x:rg(z+1)=rg(z):gosub30010 30120 z=z-1:return
10 GOSUB 10000:REM DATEI DEFFINIEREN 20 PRINT"DATEI UNGEORDNET:" 30 FOR I=1 TOSZ:PRINTD$(I):NEXT I 40 GOSUB 30000:REM 1.SCHLUESSEL SORTIEREN 50 PRINT"DATEI NACH 1.SCHLUESSEL SORTIERT:" 60 FOR I=1 TOSZ:PRINTD$(I):NEXT I 70 END 10000 REM FRUECHTE-DATEI DEFFINIEREN ------------------------------------------- 10010 SZ=8 :REM ANZAHL DER SAETZE 10020 DIM D$(SZ):REM STRING-FELD 10030 FOR I=1 TO SZ 10040 : READ D$(I) 10050 NEXT I 10100 DATA "VIZAWRITE{6SPACE}01200.0024" 10105 DATA "WORDPRO{8SPACE}01155.5013" 10110 DATA "SPACE INVADERS 02 39.0067" 10115 DATA "JUMPMAN{8SPACE}02 49.0034" 10120 DATA "SIMONS BASIC{3SPACE}03 99.0065" 10125 DATA "G-BASIC{8SPACE}03140.0045" 10130 DATA "GORDON SAGA{4SPACE}04 39.0089" 10135 DATA "DEADLINE{7SPACE}04 99.0034" 10140 RETURN 30000 REM QUICKSORT ----------------------------------------------------------- 30001 DIM LG(100),RG(100):Z=0:LG(1)=1:RG(1)=SZ 30010 Z=Z+1:IF LG(Z)>=RG(Z) THEN30120 30020 X=LG(Z):Y=RG(Z) 30030 VG$=LEFT$ (D$(INT ((X+Y)/2)),15) 30040 IF X>Y THEN30100 30050 IF LEFT$ (D$(X),15)<VG$ THENX=X+1:GOTO 30050 30060 IF LEFT$ (D$(Y),15)>VG$ THENY=Y-1:GOTO 30060 30070 IF X>Y THEN30100 30080 S$=D$(X):D$(X)=D$(Y):D$(Y)=S$ 30090 X=X+1:Y=Y-1:GOTO 30040 30100 RG(Z+1)=Y:LG(Z+1)=LG(Z):GOSUB 30010 30110 LG(Z+1)=X:RG(Z+1)=RG(Z):GOSUB 30010 30120 Z=Z-1:RETURN
1110 : ifsu$=mid$(d$(sa),1,len(su$))thenprint"su$ steht in satz ";sa:goto2000
1120 : ifsu$<mid$(d$(sa),1,len(su$))thensa=sa-(2^(n-1)):goto1200
2000 rem su$ ist gefunden - auswertung -----------------------------------------
2010 print"{clr}{rvon}";left$(d$(sa),15);"{rvof}":print
2020 print"programmart: ";
2030 i=val(mid$(d$(sa),16,2))
2040 ifi=1thenprint"textverarbeitung"
2050 ifi=2thenprint"actionspiel"
2060 ifi=3thenprint"basic-erweiterung"
2070 ifi=4thenprint"adventure"
2080 print"preis: ";mid$(d$(sa),18,6);" dm"
2085 print"bestand: ";val(right$(d$(sa),2));"stueck"
2090 goto1000
1110 : IF SU$=MID$ (D$(SA),1,LEN (SU$)) THENPRINT"SU$ STEHT IN SATZ ";SA:GOTO 2000 1120 : IF SU$<MID$ (D$(SA),1,LEN (SU$)) THENSA=SA-(2^(N-1)):GOTO 1200 2000 REM SU$ IST GEFUNDEN - AUSWERTUNG ----------------------------------------- 2010 PRINT"{CLR,RVSON}";LEFT$ (D$(SA),15);"{RVOFF}":PRINT 2020 PRINT"PROGRAMMART: "; 2030 I=VAL (MID$ (D$(SA),16,2)) 2040 IF I=1 THENPRINT"TEXTVERARBEITUNG" 2050 IF I=2 THENPRINT"ACTIONSPIEL" 2060 IF I=3 THENPRINT"BASIC-ERWEITERUNG" 2070 IF I=4 THENPRINT"ADVENTURE" 2080 PRINT"PREIS: ";MID$ (D$(SA),18,6);" DM" 2085 PRINT"BESTAND: ";VAL (RIGHT$ (D$(SA),2));"STUECK" 2090 GOTO 1000
Bitte beachten Sie unbedingt die Änderungen des Quicksort-Unterprogramms in den Zeilen 30030, 30050 und 30060. Diese Zeilen müssen immer dann modifiziert werden, wenn nicht ein ganzer String, sondern nur ein Teilbereich des Strings als Sortierargument gelten soll. Beim 1. Schlüssel unserer Software-Datei sind dies die jeweils ersten 15 Zeichen vom Gesamtstring.
Wir stellen uns nun folgende Aufgabe: Nach Eingabe des Programmnamens gibt das Programm Art, Preis und Lagerbestand zu dem Programm aus.
Eine einfache Lösung wäre es wieder, die ganze Datei nach dem Programmnamen zu durchsuchen. Da unsere Programmnamen jedoch alphabetisch geordnet sind, können wir mit einer anderen Suchmethode arbeiten, mit der binären Suchmethode.
Die binäre Suchmethode
Betrachten wir einmal den geordneten Schlüssel der Software-Datei:
1 DEADLINE
2 G-BASIC
3 GORDON SAGA
4 JUMPMAN
5 SIMONS BASIC
6 SPACE INVADERS
7 VIZAWRITE
8 WORDPRO
Wir wollen nun das Programm GORDON SAGA suchen, um festzustellen, in welchem Satz (Satznummer) es steht.
Wir setzen zunächst einen Zeiger auf den mittleren Satz der Datei. Die Satzanzahl SZ beträgt 8. Der mittlere Satz SA errechnet sich als SA=INT(SZ/2). Im ersten Durchgang wird der Zeiger also auf 4 gesetzt.
Nun untersuchen wir, ob unser Suchbegriff GORDON SAGA gleich Satz 4, kleiner als Satz 4 oder größer als Satz 4 ist. Wir kommen zu dem Ergebnis, daß der Suchsatz kleiner als Satz 4 ist. Der Suchsatz muß also in der ersten Hälfte der Datei stehen (im Bereich Satz 1 - Satz 3).
Wir halbieren den Restbereich und haben als neue Mitte Satz 2. GORDON SAGA ist jedoch größer als Satz 2. Unser Suchsatz kann also nur noch im Bereich von Satz 3 - Satz 3 stehen – also in Satz 3. Damit haben wir ihn gefunden.
Die binäre Suche besteht also darin, Bereiche nach oben beziehungsweise nach unten hin zu halbieren, bis der Suchsatz gefunden ist. Das Schema in Bild 2 macht die binäre Suchmethode noch viel deutlicher. SU$ ist der Begriff (String), der gesucht werden soll. D$(SA) ist der Inhalt (String) des Satzes mit der Nummer SA beziehungsweise des SAten Satzes der Datei.

Man setzt also jeweils einen Zeiger in die Mitte des Bereiches, in dem sich der gesuchte Begriff befinden muß. Am Anfang des Suchvorgangs ist dies logischerweise die Mitte der gesamten Datei. Danach wird untersucht, ob er Begriff gefunden wurde beziehungsweise ob der sich im Bereich unterhalb oder überhalb des Zeigers befindet. Stellt man wie in unserem Bild beim 1. Vergleich fest, daß sich der gesuchte Begriff unterhalb des Zeigers befindet, so wird der neue Zeiger auf die Mitte des unteren Bereiches etc. gesetzt. Was jedoch, wenn ein Suchbegriff überhaupt nicht in einer Datei vorkommt?
Ganz einfach, man kann bei einer Datei mit bekannter Satzanzahl SZ genau berechnen, wieviele Vergleiche maximal gemacht werden müssen, um einen Begriff zu finden, der in der Datei enthalten ist. Die binäre Suchmethode beruht schließlich auf dem Halbieren von Bereichen. Gehen wir wieder einmal von der Software-Datei aus. Sie enthält genau 8 Elemente. Nach dem ersten Suchversuch wird die Datei halbiert. Es kommen dann nur noch 4 Elemente zur Auswahl, in deren Mitte der zweite Zeiger positioniert ist. Nach dem zweiten Versuch kommen nur noch 2 Elemente in Frage und spätestens nach dem 4. Versuch schließlich nur noch eine.
Spätestens beim 4. Versuch (Halbierung) muß ein Suchbegriff somit in der Software-Datei gefunden werden – andernfalls kommt er nicht vor.
Die Formel, mit der sich die maximal nötige Suchvorgangs-Anzahl berechnen läßt, lautet so:
N=INT(LOG(SZ)/LOG(2))+1
Nach N-Versuchen muß ein Begriff also gefunden sein, wenn er in einer Datei mit SZ-Sätzen enthalten ist.
Am besten läßt sich die binäre Suchroutine anhand eines entsprechenden Unterprogrammes (Listing 15) erklären.
1000 rem binaere suchroutine --------------------------------------------------- 1005 inputsu$ :rem suchbegriff 1010 n=int(log(sz)/log(2))+1 :rem maximal-formel 1040 sa=(2^n)/2:rem mitte der ges.dat 1050 n=n-1 :rem 1.abfrage 1100 : 1110 : ifsu$=d$(sa)thenprint"su$ steht in satz ";sa:end:rem su$ gefunden 1120 : ifsu$<d$(sa)thensa=sa-(2^(n-1)):goto1200 :rem ''halbieren'' 1125 : ifsa+(2^(n-1))>szandn>-1thenn=n-1:goto1125 :rem ''ueberlauf'' 1130 : sa=sa+(2^(n-1)) :rem ''halbieren'' 1200 : 1210 :n=n-1 :rem naechst. halbieren 1220 :ifn<0thenprint"su$ gibt's nicht! in 1220":end :rem su$ nicht in datei 1225 print"sa=";sa;" n=";n :rem unnoetig !! 1230 :goto1100 :rem naechst. such-vers.
1000 REM BINAERE SUCHROUTINE --------------------------------------------------- 1005 INPUTSU$ :REM SUCHBEGRIFF 1010 N=INT (LOG (SZ)/LOG (2))+1 :REM MAXIMAL-FORMEL 1040 SA=(2^N)/2:REM MITTE DER GES.DAT 1050 N=N-1 :REM 1.ABFRAGE 1100 : 1110 : IF SU$=D$(SA) THENPRINT"SU$ STEHT IN SATZ ";SA:END :REM SU$ GEFUNDEN 1120 : IF SU$<D$(SA) THENSA=SA-(2^(N-1)):GOTO 1200 :REM ''HALBIEREN'' 1125 : IF SA+(2^(N-1))>SZAND N>-1 THENN=N-1:GOTO 1125 :REM ''UEBERLAUF'' 1130 : SA=SA+(2^(N-1)) :REM ''HALBIEREN'' 1200 : 1210 :N=N-1 :REM NAECHST. HALBIEREN 1220 :IF N<0 THENPRINT"SU$ GIBT'S NICHT! IN 1220":END :REM SU$ NICHT IN DATEI 1225 PRINT"SA=";SA;" N=";N :REM UNNOETIG !! 1230 :GOTO 1100 :REM NAECHST. SUCH-VERS.
Das Unterprogramm setzt folgendes voraus:
Das Feld, in dem gesucht werden soll, steht in D$(0)-D$(SZ), wobei SZ die Nummer des höchsten Satzes beziehungsweise die Anzahl der Sätze darstellt.
Dokumentation:
1010 Eingabe des Suchbegriffs SU$.
1020 Berechnung der maximal nötigen Suchvorgänge: N
1030 SA, der Zeiger auf die Datei wird zu Beginn des Suchvorgangs in die Mitte der Datei gesetzt. Warum berechnet sich die Mitte nicht einfach mit SA=SZ/2? Ganz einfach: Zum Halbieren »der Restbereiche« verwenden wir Potenzen von 2 (wird gleich erläutert). Probleme würde es geben, wenn eine Datei aus einer ungeraden Anzahl von Sätzen bestünde. Man könnte dann schlecht halbieren. Eine »ungerade Datei« einfach mit einem »Pseudozusatzsatz« zu versehen, würde auch nichts bringen – früher oder später kämen beim Halbieren ungerade SA-Positionen zustande – und die darf's nicht geben. Die Formel SA=(2xaN)/2 gewährleistet jedoch, daß Nmal halbiert werden kann, wobei die Restbereiche stets aus einer geraden Anzahl von Elementen bestehen.
1040 Nach jedem Suchvorgang (Halbierung) wird die Variable N um 1 erniedrigt. Ist sie kleiner als 0, so kommt der Suchbegriff in der Datei nicht vor.
1050 – 1100 Halbierungsvorgang
1060 Prüfen, ob D$(SA) gleich SU$ ist. Wenn ja, so ist der Begriff gefunden worden und die Suche wird abgebrochen.
1070 Wenn SU$ kleiner als D$(SA) ist, so heißt dies, daß sich SU$ (falls es vorkommt) nur in dem Bereich über dem Zeiger SA befinden kann. Der Zeiger muß also auf die Mitte dieses Bereiches gesetzt werden. Das Versetzen geschieht mit der Potenzfunktion: SA=SA-(2*a(N-1)).
Dadurch, daß N nach jedem Suchbegriff um 1 verkleinert wird, werden die Restbereiche und somit die Verschiebungsdistanz des Zeigers auch immer kleiner.
1080–1090 Diese Zeilen treten dann in Kraft, wenn SU$ größer als D$(SA) ist. Die Verschiebungsformel lautet dann: SA=SA+(2*(N-1)), da der Zeiger jetzt in den unteren Bereich verschoben werden muß. Um einen Überlauf zu verhindern, ist Zeile 1090 nötig. In Zeile 1030 idealisierten wir unsere Datei so, daß nie ungerade Zeigerwerte auftreten können. Die Datei wird also größer angenommen, als sie eigentlich ist. Das Vergrößern der Datei ist die einzige Möglichkeit zum Idealisieren. Würde man sie verkleinern, so gingen Sätze verloren beziehungsweise würden beim Suchen außer acht gelassen werden. Die Zeile 1080 ruft solange rekursiv auf, bis der Zeiger SA sich wieder im erlaubten Bereich von 0 bis SZ befindet – falls er diesen Bereich verlassen hat. Es ist erlaubt, den Zeiger außerhalb des erlaubten Bereiches zu positionieren; es ist jedoch klar, daß ein Suchbegriff außerhalb der Datei gefunden werden kann. Auch kann es nur im Fall SU$>D$(SA) zu einer Positionierung außerhalb des Datenbereiches kommen.
1110 Nächsten Suchvorgang vorbereiten
1120 Ist N kleiner als 0, so befindet sich SU$ nicht in der Datei.
1130 kann jederzeit entfernt werden.
Dies hört sich alles schön und gut an, ich weiß jedoch, daß sich viele schwer tun, wenn sie das erste Mal mit binären Suchsystemen konfrontiert werden. Ich rate Ihnen deshalb, falls Sie Schwierigkeiten haben, sich das obige Programm einmal Schritt für Schritt durchzudenken. Nehmen Sie gegebenenfalls ein Blatt Papier und eine kleine Datei her und schreiben Sie den gesamten Programmablauf (wo sich der Zeiger nach jedem Suchvorgang befindet) in Form von einfachen Schemen auf. Hat man es erstmal verstanden, dann ist es ganz einfach.
Bitte ergänzen Sie jetzt Listing 14 mit Listing 15. Außerdem sind folgende Änderungen vorzunehmen:
Zeile 70 löschen.
Ergänzen beziehungsweise ändern Sie auch die Zeilen, wie in Listing 14b angegeben.
Die binäre Suchmethode ist sehr schnell. Um eine Datei von 1000 Sätzen nach einem Begriff zu durchsuchen, sind maximal 10 Halbierungen notwendig, um festzustellen, in welchem Satz der Begriff steht, beziehungsweise daß er nicht vorkommt.
Erhöhen kann man die Suchgeschwindigkeit noch durch Einsatz von index-sequentiellen Dateien, wie wir sie bei Parser 4.0 kennenlernen werden.
Das Programm, das Sie jetzt im Computer haben, simuliert eine einfache Dateiverwaltung für Software.
Damit haben Sie den ersten Einstieg in die Dateiverwaltung auch schon geschafft. Wir können uns nun Dateien auf Disk zuwenden, die für die Programmierung von Adventures und KI ein großes Hilfsmittel darstellen.
Deutscher Zeichensatz im Commodore 64
Bevor ich mich der Programmierung von relativen Dateien widme, möchte ich schnell noch dieses Kapitel einfügen. Wer professionelle deutsche Text-Adventure schreiben will (und das können Sie am Ende dieses Kurses!), sollte keineswegs auf einen deutschen Zeichensatz verzichten.
Auch bei allen anderen Programmen mit deutschen Texten macht ein deutscher Zeichensatz einen besseren Eindruck. Natürlich gehen bei einer Zeichensatzänderung immer ein paar KByte-RAM flöten (der Zeichensatz muß schließlich vom ROM ins RAM kopiert werden), aufgrund guter Programmiertechniken, die sehr platzsparend sind, können wir diesen Verlust jedoch getrost verschmerzen.
Auch bei relativen Dateien bringt ein neuer Zeichensatz erhebliche Vorteile, wie wir schon bald sehen werden.
Zunächst müssen wir festlegen, wo im RAM der neue Zeichensatz liegen soll.
Bild 3 zeigt den Speicherbelegungsplan des C 64, wie er Ihnen wahrscheinlich bereits von anderen Kursen aus dem 64'er-Magazin her bekannt ist. Uns interessieren nur der linierte Bereich und das Basic-RAM. Im Laufe des Kurses werden wir den gesamten linierten RAM-Bereich hernehmen (für Maschinenprogramme, Dateien etc.). Den Basic-RAM versuche ich weitgehend für Ihre eigenen Ideen und Routinen zur freien Verfügung zu halten. Sie sollten sich nicht wundern, warum bestimmte Routinen (zum Beispiel Zeichensatz) in bestimmte Speicherbereiche geschrieben werden, denn alle Routinen sollen sich am Ende des Kurses bei vollausgelastetem RAM miteinander vertragen. Grübeln Sie also nicht lange nach, wenn ich eine Routine in einen zunächst ungeeignet anmutenden RAM-Bereich schreibe.

Unseren deutschen Zeichensatz bringen wir im Bereich 57344 - 61440 unter. Da der VIC jedoch nur ganze RAM-Blöcke von 16 KByte verwalten kann, müssen wir auch den Videobereich (Bildschirm) verschieben. Ihn legen wir nach 52223 - 53248. Bitte beachten Sie, daß hierbei auch die Sprite-Pointer (2040 - 2047) nach 53239 - 53246 verschoben werden (Bild 4)!

Bei dem Listing 16 handelt es sich um einen menüfreundlichen Zeichensatzeditor, der speziell für den Kurs abgerichtet ist (also für RAM-Bereich 57344-61440).
Dokumentation zum Listing 16
Der Zeichensatzeditor ist lediglich ein Utility zum Kurs. Ich beschränke mich daher auf das Wesentliche.
Interessant ist im Prinzip nur das Abändern des Listings zum Erstellen für Zeichensätze in andere RAM-Bereiche als die Vorgeschlagenen. Ich bin mir jedoch ziemlich sicher, daß der Zeichensatz im Bereich von 57344 - 61440 sehr gut untergebracht ist, wie ich Ihnen schon bald zeigen werde.
Um den Editor für andere Bereiche umzuschrauben, müssen lediglich folgende Zeilen abgewandelt werden:
55 IZ = Startadresse im RAM (ist momentan 57344)
122 siehe Listing
124 siehe Listing
201 VIDEO = 52223
1030 und 1040 siehe Listing
2010 siehe Listing
Es müssen also nur Anfangs- und Endadressen vom ZS-Bereich und gegebenenfalls vom Video modifiziert werden.
Bedienungsanleitung
Nach dem Programmstart meldet sich das Hauptmenü mit drei Auswahlmöglichkeiten:
NEUEN Z.SATZ ERSTELLEN: Durch diese Wahl wird der Zeichensatz vom ROM in den RAM-Bereich ab 57344 kopiert.
ALTEN Z.SATZ ÄNDERN: Ein bereits bestehender Zeichensatz kann geladen werden, um weiter verändert zu werden.
DEUTSCHEN Z.SATZ GENERIEREN: Der Editor erstellt hier automatisch einen deutschen Zeichensatz, den Sie anschließend speichern können. Bild 5 zeigt Ihnen, welche Zeichen wie geändert wurden. Ich bin mir sicher, daß Sie sich schnell an die neue Tastaturbelegung gewöhnen werden. Daß auch Zeichen wie ", und : neu definiert wurden, hat seinen Grund: den erfahren Sie im nächsten Kapitel!

0 rem *****************************
1 rem zeichensatz-editor (c) 1985
2 rem *****************************
3 ifi=1then100
4 video=52223:rem start bildschirm
5 poke53280,0:poke53281,0:print"{yel}"
10 rem modifizierte peek-funktion ------
11 fori=704to751:readx:pokei,x:next:poke785,192:poke786,2:rem usr-vektor
12 data165,20,72,165,21,72,32,247,183,165,1,72,165,21,201,208,144,7,201,224
13 data176,3,169,49,44,169,52,120,133,1,160,0,177,20,168,104,133,1,88,104,133
14 data21,104,133,20,76,162,179
30 rem menue ---------------------------
31 print"{clr}{wht}{rvon}zeichensatz-editor i{rvof}"
32 print"{down}von michael nickles.
34 print"{down}{down}{down} {rvon}n{rvof}euen zeichensatz erstellen"
35 print"{down} {rvon}a{rvof}lten zeichensatz aendern "
36 print"{down} {rvon}d{rvof}eutschen zeichensatz generieren "
37 geti$:ifi$<>"n"andi$<>"a"andi$<>"d"then37
38 ifi$="n"then50
39 ifi$="d"then50
40 rem deutschen zeichensatz laden -----
41 input"{down}{down}{down}filename ";i$
42 ifi<>1theni=1:loadi$,8,1
44 poke648,204:poke53272,56:poke56576,0:print"{clr}"
50 rem z.s. vom rom ins ram ab 57344 ---
51 print"{down}bitte warten!"
53 poke56334,peek(56334)and254:poke1,peek(1)and251: rem interrupt aus
55 iz=57344
60 fori=53248to57344
62 :pokeiz,peek(i)
64 :iz=iz+1
66 nexti
70 poke1,peek(1)or4:poke56334,peek(56334)or1
80 ifi$="d"then3000
100 rem start --------------------------
105 poke648,204:poke53272,56:poke56576,0:print"{clr}{wht}";
106 print"bitte waehlen sie:
110 print"{down}{down} gross-grafik z.satz {rvon}1{rvof}"
115 print"{down} gross-klein z.satz {rvon}2{rvof}"
116 print"{down} z.satz abspeichern {rvon}3{rvof}
118 print"{down} crazy characters {rvon}4{rvof}
120 geti$:ifi$<>"1"andi$<>"2"andi$<>"3"andi$<>"4"then120
122 ifi$="1"then an=57344 :printchr$(142)
124 ifi$="2"then an=57344+256*8:printchr$(14)
125 ifi$="3"then 1000
127 ifi$="4"then 2000
130 print"{down}{down}welches zeichen wollen sie aendern?"
131 print"angabe bitte im bildschirmcode!
132 input"{down}zeichen ";nr:z=nr
200 rem maske ausgeben -----------------
201 video=52223
205 print"{clr}zeichen: ":pokevi+10,z
210 z=an+z*8
229 print"{down}{down} ";
230 fori1=ztoz+7
235 printi1;" ";
240 :b=usr(i1)
245 : fori2=7to0step-1
250 : if band2^i2 thenprint"*";:goto260
255 : print".";
260 : nexti2
270 :print" "b:print" ";
275 nexti1
300 rem editor-programm ----------------
310 xx=vi+135:xp=peek(xx):pokexx,32
330 joy=56320:rem port 2
331 ta=203:t1=653
335 ifpeek(joy)=127andpeek(ta)=64thenw=0
340 ifpeek(joy)=126orpeek(ta)=7andpeek(t1)=1thenw=-40
342 ifpeek(joy)=125orpeek(ta)=7andpeek(t1)=0thenw=+40
344 ifpeek(joy)=123orpeek(ta)=2andpeek(t1)=1thenw=-1
346 ifpeek(joy)=119orpeek(ta)=2andpeek(t1)=0thenw=+1
350 ifpeek(joy)>111andpeek(ta)<>60then360
352 ifxp=46thenxp=42:goto355
354 ifxp=42thenxp=46:goto355
355 pokexx,xp:ifpeek(joy)<=111orpeek(ta)=60then355
360 :ifpeek(ta)=1thenpokexx,xp:goto400
370 pokexx,xp
375 xx=xx+w:ifpeek(xx)=32thenxx=xx-w
380 xp=peek(xx):pokexx,32
390 goto330
400 rem neues zeichen berechnen
405 print"{down}bitte warten!"
410 xx=vi+135
430 for i1=xx to xx+7*40step40
435 :im=0:iz=7
445 : for i2=i1 to i1+7
450 : ifrv=0thenifpeek(i2)=42thenim=im+2^iz
451 : ifrv=1thenifpeek(i2)=46thenim=im+2^iz
455 : iz=iz-1
460 : nexti2
470 :pokez,im:z=z+1
475 nexti1
476 ifrv=1thenrv=0:goto100
479 ifnr>127then100
480 print"revers-aenderung erwuenscht? (j/n)
482 geti$:ifi$<>"j"andi$<>"n"then482
484 ifi$="n"then100
485 z=an+(nr+128)*8:rv=1:goto400
490 goto100
1000 rem z.satz abspeichern ------------
1000 rem speichern auf disk -----------
1005 print"{clr}{rvon}zeichensatz auf disk speichern{rvof}":poke198,0
1006 input"{down}{down}filename";i$
1010 print"{down}bitte diskette einlegen!
1020 print"[return]":poke198,0:wait198,1
1030 aa=57344
1040 ea=61439
1050 open1,8,1,i$
1060 hb=int(aa/256):lb=aa-hb*256
1070 print#1,chr$(lb);chr$(hb);
1080 fori=aatoea
1090 :print#1,chr$(usr(i));
1100 nexti
1110 close1
1120 print"{down}{down}{down}*** fertig ***":run
2000 rem crazy characters --------------
2010 fori=57344to61439
2020 :x=usr(i):m=0
2030 : for i1=0 to 7
2040 : if xand2^i1 then m=m+2^(7-i1)
2050 : nexti1
2060 :pokei,m
2070 :nexti
2080 goto100
3000 rem deutschen zeichensatz automatisch generieren --------------------------
3010 data60264,102,24,60,102,126,102,102,0 :rem grosses ae
3015 data60336,102,60,102,102,102,102,60,0 :rem grosses oe
3020 data60328,102,0,102,102,102,102,60,0 :rem grosses ue
3025 data60288,102,0,60,6,62,102,62,0 :rem kleines ae
3030 data60360,0,102,0,60,102,102,60,0 :rem kleines oe
3035 data60352,0,102,0,102,102,102,62,0 :rem kleines ue
3040 data60272,60,102,102,108,110,102,108,96:rem scharfes s
3045 data59392,102,102,102,0,0,0,0,0 :rem anfuehrungszeichen
3050 data59640,0,0,0,0,0,24,24,48 :rem komma
3055 data59632,0,0,24,0,0,24,0,0 :rem doppelpunkt
3060 data-1
3100 readx:ifx=-1then100
3110 fori=xtox+7
3120 : reada:pokei,a :rem normal
3130 : pokei+2048,a:rem revers
3140 nexti
3150 goto3100
0 REM ***************************** 1 REM ZEICHENSATZ-EDITOR (C) 1985 2 REM ***************************** 3 IF I=1 THEN100 4 VIDEO=52223:REM START BILDSCHIRM 5 POKE 53280,0:POKE 53281,0:PRINT"{YELLOW}" 10 REM MODIFIZIERTE PEEK-FUNKTION ------ 11 FOR I=704 TO751:READ X:POKE I,X:NEXT :POKE 785,192:POKE 786,2:REM USR-VEKTOR 12 DATA 165,20,72,165,21,72,32,247,183,165,1,72,165,21,201,208,144,7,201,224 13 DATA 176,3,169,49,44,169,52,120,133,1,160,0,177,20,168,104,133,1,88,104,133 14 DATA 21,104,133,20,76,162,179 30 REM MENUE --------------------------- 31 PRINT"{CLR,WHITE,RVSON}ZEICHENSATZ-EDITOR I{RVOFF}" 32 PRINT"{DOWN}VON MICHAEL NICKLES. 34 PRINT"{3DOWN,5SPACE,RVSON}N{RVOFF}EUEN ZEICHENSATZ ERSTELLEN" 35 PRINT"{DOWN,5SPACE,RVSON}A{RVOFF}LTEN ZEICHENSATZ AENDERN{SPACE,SPACE}" 36 PRINT"{DOWN,5SPACE,RVSON}D{RVOFF}EUTSCHEN ZEICHENSATZ GENERIEREN{SPACE,SPACE}" 37 GET I$:IF I$<>"N"AND I$<>"A"AND I$<>"D" THEN37 38 IF I$="N" THEN50 39 IF I$="D" THEN50 40 REM DEUTSCHEN ZEICHENSATZ LADEN ----- 41 INPUT"{3DOWN}FILENAME ";I$ 42 IF I<>1 THENI=1:LOAD I$,8,1 44 POKE 648,204:POKE 53272,56:POKE 56576,0:PRINT"{CLR}" 50 REM Z.S. VOM ROM INS RAM AB 57344 --- 51 PRINT"{DOWN}BITTE WARTEN!" 53 POKE 56334,PEEK (56334)AND 254:POKE 1,PEEK (1)AND 251: REM INTERRUPT AUS 55 IZ=57344 60 FOR I=53248 TO57344 62 :POKE IZ,PEEK (I) 64 :IZ=IZ+1 66 NEXT I 70 POKE 1,PEEK (1)OR 4:POKE 56334,PEEK (56334)OR 1 80 IF I$="D" THEN3000 100 REM START -------------------------- 105 POKE 648,204:POKE 53272,56:POKE 56576,0:PRINT"{CLR,WHITE}"; 106 PRINT"BITTE WAEHLEN SIE: 110 PRINT"{2DOWN,5SPACE}GROSS-GRAFIK Z.SATZ{2SPACE,RVSON}1{RVOFF}" 115 PRINT"{DOWN,5SPACE}GROSS-KLEIN{2SPACE}Z.SATZ{2SPACE,RVSON}2{RVOFF}" 116 PRINT"{DOWN,5SPACE}Z.SATZ ABSPEICHERN{3SPACE,RVSON}3{RVOFF} 118 PRINT"{DOWN,5SPACE}CRAZY CHARACTERS{5SPACE,RVSON}4{RVOFF} 120 GET I$:IF I$<>"1"AND I$<>"2"AND I$<>"3"AND I$<>"4" THEN120 122 IF I$="1" THEN AN=57344 :PRINTCHR$ (142) 124 IF I$="2" THEN AN=57344+256*8:PRINTCHR$ (14) 125 IF I$="3" THEN 1000 127 IF I$="4" THEN 2000 130 PRINT"{2DOWN}WELCHES ZEICHEN WOLLEN SIE AENDERN?" 131 PRINT"ANGABE BITTE IM BILDSCHIRMCODE! 132 INPUT"{DOWN}ZEICHEN ";NR:Z=NR 200 REM MASKE AUSGEBEN ----------------- 201 VIDEO=52223 205 PRINT"{CLR}ZEICHEN: ":POKE VI+10,Z 210 Z=AN+Z*8 229 PRINT"{2DOWN,4SPACE}"; 230 FOR I1=Z TOZ+7 235 PRINTI1;"{3SPACE}"; 240 :B=USR (I1) 245 : FOR I2=7 TO0STEP -1 250 : IF BAND 2^I2 THENPRINT"*";:GOTO 260 255 : PRINT"."; 260 : NEXT I2 270 :PRINT"{3SPACE}"B:PRINT"{4SPACE}"; 275 NEXT I1 300 REM EDITOR-PROGRAMM ---------------- 310 XX=VI+135:XP=PEEK (XX):POKE XX,32 330 JOY=56320:REM PORT 2 331 TA=203:T1=653 335 IF PEEK (JOY)=127AND PEEK (TA)=64 THENW=0 340 IF PEEK (JOY)=126OR PEEK (TA)=7AND PEEK (T1)=1 THENW=-40 342 IF PEEK (JOY)=125OR PEEK (TA)=7AND PEEK (T1)=0 THENW=+40 344 IF PEEK (JOY)=123OR PEEK (TA)=2AND PEEK (T1)=1 THENW=-1 346 IF PEEK (JOY)=119OR PEEK (TA)=2AND PEEK (T1)=0 THENW=+1 350 IF PEEK (JOY)>111AND PEEK (TA)<>60 THEN360 352 IF XP=46 THENXP=42:GOTO 355 354 IF XP=42 THENXP=46:GOTO 355 355 POKE XX,XP:IF PEEK (JOY)<=111OR PEEK (TA)=60 THEN355 360 :IF PEEK (TA)=1 THENPOKE XX,XP:GOTO 400 370 POKE XX,XP 375 XX=XX+W:IF PEEK (XX)=32 THENXX=XX-W 380 XP=PEEK (XX):POKE XX,32 390 GOTO 330 400 REM NEUES ZEICHEN BERECHNEN 405 PRINT"{DOWN}BITTE WARTEN!" 410 XX=VI+135 430 FOR I1=XX TO XX+7*40STEP 40 435 :IM=0:IZ=7 445 : FOR I2=I1 TO I1+7 450 : IF RV=0 THENIF PEEK (I2)=42 THENIM=IM+2^IZ 451 : IF RV=1 THENIF PEEK (I2)=46 THENIM=IM+2^IZ 455 : IZ=IZ-1 460 : NEXT I2 470 :POKE Z,IM:Z=Z+1 475 NEXT I1 476 IF RV=1 THENRV=0:GOTO 100 479 IF NR>127 THEN100 480 PRINT"REVERS-AENDERUNG ERWUENSCHT? (J/N) 482 GET I$:IF I$<>"J"AND I$<>"N" THEN482 484 IF I$="N" THEN100 485 Z=AN+(NR+128)*8:RV=1:GOTO 400 490 GOTO 100 1000 REM Z.SATZ ABSPEICHERN ------------ 1000 REM SPEICHERN AUF DISK ----------- 1005 PRINT"{CLR,RVSON}ZEICHENSATZ AUF DISK SPEICHERN{RVOFF}":POKE 198,0 1006 INPUT"{2DOWN}FILENAME";I$ 1010 PRINT"{DOWN}BITTE DISKETTE EINLEGEN! 1020 PRINT"[RETURN]":POKE 198,0:WAIT 198,1 1030 AA=57344 1040 EA=61439 1050 OPEN 1,8,1,I$ 1060 HB=INT (AA/256):LB=AA-HB*256 1070 PRINT#1,CHR$ (LB);CHR$ (HB); 1080 FOR I=AA TOEA 1090 :PRINT#1,CHR$ (USR (I)); 1100 NEXT I 1110 CLOSE 1 1120 PRINT"{3DOWN}*** FERTIG ***":RUN 2000 REM CRAZY CHARACTERS -------------- 2010 FOR I=57344 TO61439 2020 :X=USR (I):M=0 2030 : FOR I1=0 TO 7 2040 : IF XAND 2^I1 THEN M=M+2^(7-I1) 2050 : NEXT I1 2060 :POKE I,M 2070 :NEXT I 2080 GOTO 100 3000 REM DEUTSCHEN ZEICHENSATZ AUTOMATISCH GENERIEREN -------------------------- 3010 DATA 60264,102,24,60,102,126,102,102,0 :REM GROSSES AE 3015 DATA 60336,102,60,102,102,102,102,60,0 :REM GROSSES OE 3020 DATA 60328,102,0,102,102,102,102,60,0 :REM GROSSES UE 3025 DATA 60288,102,0,60,6,62,102,62,0 :REM KLEINES AE 3030 DATA 60360,0,102,0,60,102,102,60,0 :REM KLEINES OE 3035 DATA 60352,0,102,0,102,102,102,62,0 :REM KLEINES UE 3040 DATA 60272,60,102,102,108,110,102,108,96:REM SCHARFES S 3045 DATA 59392,102,102,102,0,0,0,0,0 :REM ANFUEHRUNGSZEICHEN 3050 DATA 59640,0,0,0,0,0,24,24,48 :REM KOMMA 3055 DATA 59632,0,0,24,0,0,24,0,0 :REM DOPPELPUNKT 3060 DATA -1 3100 READ X:IF X=-1 THEN100 3110 FOR I=X TOX+7 3120 : READ A:POKE I,A :REM NORMAL 3130 : POKE I+2048,A:REM REVERS 3140 NEXT I 3150 GOTO 3100
Nach der Wahl von einem der drei Hauptmenüpunkten gelangen Sie ins nächste Menü. Sie haben nun folgende Möglichkeiten:
| 1: GROSS-GRAFIK Z.SATZ: | Dies müssen Sie wählen, wenn Sie ein Zeichen des GROSS/GRAFIK-Zeichensatzes ändern wollen. |
| 2: GROSS-KLEIN Z.SATZ: | Wie oben, jedoch GROSS/KLEIN-Zeichensatz. |
Nach Wahl von Punkt 1 oder 2 müssen Sie den Bildschirmcode des Zeichens angeben, das Sie ändern wollen (Tabelle siehe C 64-Handbuch). Nun kann das Zeichen mittels Joystick oder Cursor-Tasten + SPACE editiert werden. Sind Sie mit der Änderung fertig, so muß die RETURN-Taste gedrückt werden. Sie werden dann gefragt, ob auch das entsprechende Revers-Zeichen geändert werden soll. Dann gelangen Sie wieder zurück ins Menü.
| 3: Z.SATZ Speichern: | Der neue Zeichensatz wird auf Disk gespeichert. |
| 4: Crazy Characters: | Eine Spezialfunktion des Editors (benötigt viel Rechenzeit!). Speichern Sie den mit Crazy-Characters erzeugten Zeichensatz einmal unter dem Filenamen »CRAZY« ab. Wenn Sie nun das Listing17 und 18 eingeben und starten, werden Sie schnell verstehen, warum ein Spiegel zum unentbehrlichen Programmierhilfsmittel werden kann! |
PROGRAMM : RUECKWAERTS C400 C4BD ----------------------------------- C400 : A9 C0 8D 88 02 20 44 E5 7C C408 : A2 15 A0 C4 78 8E 14 03 48 C410 : 8C 15 03 58 60 A2 00 A0 4F C418 : 27 BD 00 C0 99 00 CC BD 7E C420 : 28 C0 99 28 CC BD 50 C0 91 C428 : 99 50 CC BD 78 C0 99 78 B9 C430 : CC BD A0 C0 99 A0 CC BD 68 C438 : C8 C0 99 C8 CC BD F0 C0 E0 C440 : 99 F0 CC BD 18 C1 99 18 62 C448 : CD BD 40 C1 99 40 CD BD 8A C450 : 68 C1 99 68 CD BD 90 C1 9D C458 : 99 90 CD BD B8 C1 99 B8 D6 C460 : CD BD E0 C1 99 E0 CD BD CF C468 : 08 C2 99 08 CE BD 30 C2 5A C470 : 99 30 CE BD 58 C2 99 58 3F C478 : CE BD 80 C2 99 80 CE BD F2 C480 : A8 C2 99 A8 CE BD D0 C2 A8 C488 : 99 D0 CE BD F8 C2 99 F8 F3 C490 : CE BD 20 C3 99 20 CF BD 13 C498 : 48 C3 99 48 CF BD 70 C3 65 C4A0 : 99 70 CF BD 98 C3 99 98 5C C4A8 : CF BD C0 C3 99 C0 CF E8 AF C4B0 : 88 E0 28 F0 03 4C 19 C4 51 C4B8 : 4C 31 EA 01 08 1F
10 rem crazy-loader 30 ifi2=0theni2=1:load"crazy",8,1 40 ifi2=1theni2=2:poke648,204:poke53272,56:poke56576,0 60 ifi1=0theni1=1:load"rueckwaerts",8,1 70 ifi1=1theni1=2:sys50176:sys64764
10 REM CRAZY-LOADER 30 IF I2=0 THENI2=1:LOAD "CRAZY",8,1 40 IF I2=1 THENI2=2:POKE 648,204:POKE 53272,56:POKE 56576,0 60 IF I1=0 THENI1=1:LOAD "RUECKWAERTS",8,1 70 IF I1=1 THENI1=2:SYS 50176:SYS 64764
Bitte erzeugen Sie nun mittels dem Menüpunkt »DEUTSCHEN Z.SATZ GENERIEREN« einen deutschen Zeichensatz und speichern Sie diesen unter dem Filenamen »D.Z.SATZ« auf Disk – wir werden ihn später benötigen!
Laden des Zeichensatzes
So wird der deutsche Zeichensatz innerhalb von Basic-Programmen geladen;
10 IFI=0THENI=1:LOAD"D.Z.SATZ",8,1
20 POKE648,204:POKE53272,56:POKE56576,0
30 END
Ist ein neuer Zeichensatz aktiviert und ein Programm wird mittels RUN/STOP + RESTORE unterbrochen, so stürzt das Programm ab, der Cursor ist verschwunden. Der Cursor ist allerdings noch da – Sie können ihn momentan nur nicht sehen! Geben Sie nun bitte »blind« folgendes im Direktmodus ein:
POKE 648,204 (RETURN)
POKE 53272,56 (RETURN)
POKE 56576,0 (RETURN)
Der Cursor ist wieder da! Am besten, Sie schreiben sich diese POKEs gleich auf einen Aufkleber und bringen diesen am C 64 an.
Relative Dateien auf der 1541
Um es gleich zu sagen: Dies ist KEIN Kurs für Disketten-Laufwerke. Ich werde in diesem Kapitel nicht erklären, wie das Floppy-Laufwerk 1541 seine Daten speichert, wie es in Ihrem RAM/ROM aussieht oder gar wie man den Schreib-Lesekopf im 4/4-Takt hin- und herschlagen läßt (als 4. Musikstimme, wie es bei einigen verrückten Programmen schon vorgekommen sein soll). Es geht mir in diesem Kapitel nur darum, daß Sie lernen, spielend leicht mit relativen Dateien umzugehen. Durch den Umgang mit solchen Dateien eröffnen sich völlig neue Programmiermöglichkeiten. Wer erst einmal mit relativen Dateien gearbeitet hat, wird schon bald nicht mehr auf sie verzichten wollen. In der Tat, erst durch relative Dateien ist es möglich, die Speicherkapazität der 1541 voll und ganz auszureizen. Im Bedienungshandbuch für das Diskettenlaufwerk wird kaum auf relative Dateien eingegangen. Auch die großen Bücher über Floppy-Laufwerke geben nicht alle Tips und Tricks zum Umgang mit relativen Dateien preis, sondern handeln alles ganz simpel und logisch ab. Der Anfänger schaut jedoch spätestens dann dumm aus der Wäsche (auch mir ging dies anfangs so), wenn ein FILE DATA ERROR auftaucht oder Satzteile in einer relativen Datei beim Einlesen plötzlich nicht mehr auftauchen. Im Prinzip ist der Umgang mit relativen Dateien jedoch sehr einfach, wenn man erst mal durchgeblickt hat; es gibt nur sehr wenige Kommandos, die nötig sind, um mit relativen Dateien umzugehen: positionieren, lesen und schreiben.
Was ist eine relative Datei?
Am ehesten kann man eine relative Datei mit einem dimensionierten Stringfeld auf Disk vergleichen. Ein Stringfeld im Computer vom Format X$(1)-X$(N) entspricht in etwa einer relativen Datei mit N-Sätzen. Genauso wie ein Stringfeld erst einmal mittels DIM definiert werden muß, muß eine relative Datei erst einmal auf Disk angelegt werden, bevor man mit ihr arbeiten kann.
Anlegen einer relativen Datei
Beim Öffnen einer relativen Datei kommt es lediglich auf die Angabe von drei Parametern an: Dateiname, Satzlänge und Satzanzahl der Datei. Listing 19 zeigt, wie eine relative Datei eröffnet wird.
10 rem eroeffnen einer rel.datei ----------------------------------------------- 20 f$="test" :rem dateiname 30 sl=40 :rem satzlaenge 40 sz=50 :rem satzanzahl 50 hb=int(sz/255):lb=sz-hb*255 60 open15,8,15 :rem floppykanal oeffnen 70 print#15,"i0" :rem initialisieren 80 open1,8,3,f$+",l,"+chr$(sl) :rem datei oeffnen 90 print#15,"p";chr$(3);chr$(lb);chr$(hb);chr$(1) 100 print#1,chr$(255) 110 close1:close15 :rem kanaele schliessen
10 REM EROEFFNEN EINER REL.DATEI ----------------------------------------------- 20 F$="TEST" :REM DATEINAME 30 SL=40 :REM SATZLAENGE 40 SZ=50 :REM SATZANZAHL 50 HB=INT (SZ/255):LB=SZ-HB*255 60 OPEN 15,8,15 :REM FLOPPYKANAL OEFFNEN 70 PRINT#15,"I0" :REM INITIALISIEREN 80 OPEN 1,8,3,F$+",L,"+CHR$ (SL) :REM DATEI OEFFNEN 90 PRINT#15,"P";CHR$ (3);CHR$ (LB);CHR$ (HB);CHR$ (1) 100 PRINT#1,CHR$ (255) 110 CLOSE 1:CLOSE 15 :REM KANAELE SCHLIESSEN
Und so geht das vor sich:
| 20 - 40 | Festlegen der 3 Parameter |
| 50 | Die Satzanzahl SZ wird in Lower und Higher Byte unterteilt. |
| 60 | Öffnen des Floppy-Befehlskanals |
| 70 | Initialisieren der Disk. Dies empfiehlt sich immer dann, wenn eine neue Disk eingelegt beziehungsweise eine Seite gewechselt wird. |
| 80 | Eine relative Datei mit Namen FS und der Satzlänge SL wird geöffnet. |
| 90 | Der relative Datei-Zeiger wird auf den höchsten Satz der Datei positioniert. |
| 100 | In den letzten beziehungsweise höchsten Satz der Datei wird CHR$(255) geschrieben. |
| 110 | Floppy-Datenkanal und relative Datei werden geschlossen. |
Nach dem Programmstart kann es eine Weile dauern – dies hängt natürlich von der Größe der Datei ab. Am Ende blinkt die rote LED des Diskettenlaufwerks. Dies zeigt einen Fehler Nr. 50 – »RECORD NOT PRESENT« an. Das Auftauchen dieses Fehlers nach dem Anlegen einer relativen Datei (unmittelbar danach) ist okay. Sie brauchen sich darum keine Sorgen zu machen.
Sie haben bestimmt schon entdeckt, worin sich relative Dateien von dimensionierten Stringfeldern im Speicher unterscheiden: Die Satzlänge, also die maximale Zeichenanzahl, die ein String haben darf, muß genau festgelegt sein. Bei dimensionierten Stringfeldern kann jeder String bis zu 255 Zeichen lang sein. Dies geht natürlich auch bei relativen Dateien, wenn man als maximale Satzlänge SL den Wert 255 angibt. Dies ist jedoch nur selten empfehlenswert, denn: Je kürzer beziehungsweise kleiner die maximal erlaubte Satzlänge SL ist, umso mehr Sätze lassen sich auf einer Disk-Seite unterbringen.
Man sollte sich also am Anfang genau überlegen, wie groß jeder Satz maximal sein muß. Bei der Software-Datei hätte man sich schließlich für die Satzlänge SL=25 entschieden. Man muß auch von Anfang an wissen, wieviele Sätze man benötigt. Dies stimmt nicht ganz, denn man kann relative Dateien prinzipiell auch offen lassen – indem man beim Anlegen der Datei auf den letztmöglichen Satz positioniert und in ihn den Wert 255 schreibt. Davon ist jedoch abzuraten, da offene relative Dateien viel längere Zugriffszeiten erfordern – im Programmablauf also viel mehr Zeit kosten.
Seien Sie beim Anlegen der Dateien stets großzügig. Hat man eine Datei fertig bearbeitet, so kann man leicht eine neue Datei eröffnen, deren Satzlänge und Satzgröße nur so groß ist wie erforderlich. Dann müssen nur noch die Daten aus der ersten Datei in diese Datei übertragen werden. Dies geht ganz einfach mit dem Editor, den Sie in diesem Kapitel noch finden werden.
Lesen/Beschreiben einer relativen Datei
Bevor man eine relative Datei beschreiben kann, muß natürlich erst einmal eine vorhanden sein. Bitte legen Sie eine leere Disk beziehungsweise Disk mit mindestens 10 freien Blöcken ins Laufwerk und starten Sie dann Listing 19 mittels RUN. Es wird sodann (bitte etwas Geduld) eine relative Datei mit dem Filenamen »TEST« und 50 Sätzen mit einer Länge von je 40 Zeichen angelegt.
Will man auf beziehungsweise von einer relativen Datei schreiben oder lesen, so sind folgende Punkte notwendig: Positionieren, Lesen und Schreiben.
Am deutlichsten wird die, wenn man relative Dateien einmal mit Stringfeldern innerhalb von Basic (X$(1)-X$(N)) vergleicht.
Ein Stringfeld innerhalb von Basic wird einfach mittels
DIM X$(N)
angelegt, wobei N für die Anzahl der Sätze steht. Jeder Satz darf maximal 255 Zeichen umfassen.
Eine relative Datei hingegen muß erst etwas umständlicher angelegt werden. Wie beim Stringfeld muß auch hierbei die Anzahl der Sätze und außerdem auch noch die maximale Länge jedes Satzes angegeben werden.
Will man einen Satz SA von einem Stringfeld lesen beziehungsweise wissen, was im SAten Satz steht, so geht man ganz einfach so vor:
PRINT X$(SA)
Bei relativen Dateien geht dies nicht so einfach. Hier muß erst auf den gewünschten Satz SA positioniert werden, bevor er gelesen werden kann. Also:
Positioniere Zeiger auf Satz SA. Lese Satz SA.
Analog verhält es sich natürlich mit dem Schreiben. Es bietet sich an, die Routinen Schreiben, Lesen und Positionieren als Unterprogramme zu gestalten (Listing 20).
9000 rem zeiger positionieren -------------------------------------------------- 9001 : 9010 hb%=sa/256:lb%=sa-hb%*256 9020 print#15,"p";chr$(3);chr$(lb%);chr$(hb%);chr$(1) 9030 input#15,a:ifa=50thenprint"satznummer zu hoch" 9040 return 9100 rem satz lesen ------------------------------------------------------------ 9101 : 9110 ifa=50thenreturn 9120 input#1,a$ 9130 return 9200 rem satz schreiben -------------------------------------------------------- 9201 : 9210 ifa=50thenreturn 9220 print#1,a$ 9230 return
9000 REM ZEIGER POSITIONIEREN -------------------------------------------------- 9001 : 9010 HB%=SA/256:LB%=SA-HB%*256 9020 PRINT#15,"P";CHR$ (3);CHR$ (LB%);CHR$ (HB%);CHR$ (1) 9030 INPUT#15,A:IF A=50 THENPRINT"SATZNUMMER ZU HOCH" 9040 RETURN 9100 REM SATZ LESEN ------------------------------------------------------------ 9101 : 9110 IF A=50 THENRETURN 9120 INPUT#1,A$ 9130 RETURN 9200 REM SATZ SCHREIBEN -------------------------------------------------------- 9201 : 9210 IF A=50 THENRETURN 9220 PRINT#1,A$ 9230 RETURN
Wie funktionieren die drei Unterprogramme?
Mittels GOSUB 9000 wird der Zeiger auf den Satz SA der relativen Datei positioniert. Die Syntax zum Positionieren des Zeigers ab zum Beispiel Satz 23 einer relativen Datei würde so lauten:
SA=23:GOSUB 9000
9010 Hier wird SA in High- und Lowbyte zerlegt. Es ist also möglich, relative Dateien mit bis zu 65000 Sätzen (Satzlänge entsprechend klein) anzulegen.
9020 Über den Floppy-Befehlskanal wird der Positionierbefehl der Syntax
PRINT#15,"P";CHR$(Kanal);CHR$(LOWBYTE);CHR$(HIGHBYTE) ;CHR$(Zeichen)
dem Floppy-Laufwerk mitgeteilt. »Zeichen« gibt an, auf welches beziehungsweise wievieltes Zeichen im Satz positioniert werden soll. Will man den ganzen Satz einlesen (was meist der Fall ist), so muß man auf das erste Satzzeichen positionieren, also CHR$(1). Danach wird der Fehlerkanal abgefragt mittels
INPUT#15,A
um festzustellen, ob der Satz SA überhaupt vorhanden ist. Nimmt A den Wert 50 an, so bedeutet dies einen RECORD NOT PRESENT ERROR. Dies ist zum Beispiel dann der Fall, wenn wir bei unserer Test-Datei Satz Nummer 70 lesen wollten (die TEST-Datei besteht nämlich nur aus 50 Sätzen).
Mittels GOSUB 9100 wird der Inhalt des Satzes in AS eingelesen, auf den der Zeiger gerade deutet. Will man zum Beispiel wissen,was im 20ten Satz einer Datei steht, so gilt die folgende Syntax:
SA=20 : GOSUB 9000 : GOSUB 9100
Das Lesen geschieht mittels INPUT # 1,A$, wenn der Satz beziehungsweise die maximal erlaubte Satzlänge der relativen Datei 88 Zeichen nicht überschreitet (Größe des INPUT-Puffers). Wird mit Sätzen gearbeitet, die größer als 88 Zeichen sind, so muß Zeichen für Zeichen mittels GET-Befehl eingelesen werden. Das Lesen funktioniert also wie bei sequentiellen Dateien und hat auch die gleichen Einschränkungen.
Mittels GOSUB 9200 wird der String A$ in den Satz SA der relativen Datei geschrieben. Will man zum Beispiel »SUPER-MIKE« in den 34ten Satz einer relativen Datei schreiben, dann geht das so:
A$="SUPERMIKE" : SA=34 : GOSUB 9000 : GOSUB 9200
Das Listing 21 stellt in Kombination mit den drei Unterprogrammen einen ganz einfachen Editor für relative Dateien dar.
10 open 15,8,15 :rem floppykanal oeffnen 15 print#15,"i0" 20 open 1,8,3,"test": rem relative datei oeffnen 100 print"lesen, schreiben, ende ?" 110 get i$:ifi$<>"l"andi$<>"s"and i$<>"e"then 110 120 if i$="l" then 1000 130 if i$="s" then 2000 140 ifi$="e" then 3000 1000 rem lesen------------ 1010 input "satznummer ";sa 1020 gosub 9000: rem positionieren 1030 gosub 9100: rem lesen 1040 print a$ : rem anzeigen 1050 goto 100 2000 rem schreiben-------- 2010 input "satznummer ";sa 2020 input a$ :rem eingabesatz 2030 gosub 9000:rem positionieren 2040 gosub 9200:rem schreiben 2050 goto 100 3000 rem ende------------- 3010 close1:close15 3020 end
10 OPEN 15,8,15 :REM FLOPPYKANAL OEFFNEN 15 PRINT#15,"I0" 20 OPEN 1,8,3,"TEST": REM RELATIVE DATEI OEFFNEN 100 PRINT"LESEN, SCHREIBEN, ENDE ?" 110 GET I$:IF I$<>"L"AND I$<>"S"AND I$<>"E" THEN 110 120 IF I$="L" THEN 1000 130 IF I$="S" THEN 2000 140 IF I$="E" THEN 3000 1000 REM LESEN------------ 1010 INPUT "SATZNUMMER ";SA 1020 GOSUB 9000: REM POSITIONIEREN 1030 GOSUB 9100: REM LESEN 1040 PRINT A$ : REM ANZEIGEN 1050 GOTO 100 2000 REM SCHREIBEN-------- 2010 INPUT "SATZNUMMER ";SA 2020 INPUT A$ :REM EINGABESATZ 2030 GOSUB 9000:REM POSITIONIEREN 2040 GOSUB 9200:REM SCHREIBEN 2050 GOTO 100 3000 REM ENDE------------- 3010 CLOSE 1:CLOSE 15 3020 END
Dieser Editor ist zum Arbeiten mit der Test-Datei gedacht. Eine Dokumentation ist überflüssig, da alles bereits erklärt worden ist.
Schreiben Sie nun einmal ein paar Sätze in die Test-Datei beziehungsweise lesen Sie einmal aus der Test-Datei. Sie werden schnell feststellen, daß beim Lesen oder Schreiben nicht immer auf Disk zugegriffen wird – das Diskettenlaufwerk verhält sich still. Dies hat seinen guten Grund: Die Daten werden nicht augenblicklich auf die Diskette geschrieben, sondern zunächst erst in einem festgelegten RAM-Bereich innerhalb der 1541 gepuffert. Erst zum Beispiel bei Überlauf wird dieser Puffer auf Disk gespeichert. Bei sehr kleinen Dateien wie unserer Test-Datei kann es also passieren, daß sich alles nur im Puffer abspielt. Deshalb ist es unbedingt notwendig, nach Beendigung des gesamten Schreib-Lesevorgangs beziehungsweise dem Arbeiten mit der relativen Datei, die Datei zu schließen – dann wird nämlich der Puffer gespeichert. Ansonsten kann es passieren, daß Daten verloren gehen. Zum Schließen der relativen Datei genügt CLOSE 15. Dies kann auch im Direktmodus durchgeführt werden – zum Beispiel dann, wenn sie mittels RUN/STOP den Editor abgebrochen haben. Also nie vergessen: Nach dem Beenden des Schreibens auf eine relative Datei IMMER die Datei mittels CLOSE 15 (beziehungsweise Floppykanal) schließen. Beim Schließen des Befehlskanals des Diskettenlaufwerks werden übrigens auch alle anderen Kanäle geschlossen. Die Datei muß also nicht extra geschlossen werden – das Schließen des Floppy-Befehlskanals (den man immer mit OPEN 15,8,15 öffnet und mit CLOSE 15 schließt) reicht vollkommen aus.
Probleme mit relativen Dateien
Bisher hörte sich alles ganz logisch und einfach an, ich möchte es jedoch keinesfalls versäumen, Sie auch auf die Probleme aufmerksam zu machen, die beim Arbeiten mit relativen Dateien immer wieder auftauchen und denen Anfänger oft hilflos gegenüberstehen. Besonders traurig ist die Tatsache, daß bei den folgenden Problemen auch die großen Bücher zur 1541 in keinster Weise helfen!
Besitzer des C 128 haben es beim Umgang mit relativen Dateien schon etwas einfacher, denn ihnen steht ein komfortableres Basic als das des C 64 zur Verfügung. Es gibt zwar Basic-Erweiterungen für den C 64 (zum Beispiel DISK-Basic vom 5. Sonderheft der 64'er), die jedoch viel zu viel Speicher in Anspruch nehmen. Wenn man die einzelnen Probleme der relativen Dateiprogrammierung jedoch kennt, so kommt man auch ganz gut mit den drei Unterprogrammen von Listing 20 zurecht. Nun zu den Problemen beziehungsweise Schwierigkeiten:
Der Dateizeiger
Was beim Programmieren oft mißachtet wird, ist die Tatsache, daß der Dateizeiger (der auf jeweils einen Satz positioniert werden kann) der relativen Datei nicht ruht – er versetzt sich nach jedem Zugriff automatisch eine weiter.
Beispiel: Satz 5 soll geändert werden.
Zunächst wollen wir wissen, was eigentlich in Satz 5 steht. Also:
SA=5 : GOSUB 9000 : GOSUB 9100 : PRINT A$.
So schön, so gut, wir wissen jetzt, was in Satz 5 steht. Wo steht jedoch jetzt der Zeiger? Er hat sich automatisch um 1 erhöht. Das heißt, er zeigt jetzt auf Satz 6. Würde man nun erneut GOSUB 9100 : PRINT A$ eingeben, so erhielte man den Inhalt von Satz 6 und der Zeiger würde auf Satz 7 verschoben werden.
Wenn wir also den Inhalt von Satz 5 ändern wollen, so genügt es NICHT
A$="neuer Inhalt" : GOSUB 9100
einzugeben.
Vielmehr muß es
A$="neuer Inhalt" : SA=5 : GOSUB 9000 : GOSUB 9200
lauten.
Es muß also vor dem Schreiben erst wieder positioniert werden. Denken Sie also stets daran, vor dem Schreiben immer auf den Satz zu positionieren, in den geschrieben werden soll.
Eine andere Aufgabe: Die Sätze 30 - 40 einer relativen Datei sollen gelesen und mittels PRINT ausgegeben werden. Die Lösung ist ganz einfach:
FOR SA=30 TO 40
: GOSUB 9000
: GOSUB 9100
: PRINT A$
NEXT SA
Diese Lösung funktioniert zwar perfekt, es geht jedoch auch viel einfacher und schneller:
SA=30 : GOSUB 9000
FOR I=30 TO 40
: GOSUB 9100
: PRINT A$
NEXT I
Die zweite Lösung, die vom automatischen Vorrücken des Dateizeigers Nutzen zieht, benötigt nur halb soviel Zeit, um die 10 Sätze auszugeben wie die erste Lösung. Es lohnt sich also stets, das automatische Vorrücken des Zeigers beim Programmieren zu beachten.
Steuerzeichen
Versuchen Sie einmal mittels dem Editor in den 10. Satz der TEST-Datei den Satz beziehungsweise den Inhalt »LOLO, PETER UND MIKE GINGEN IN DEN WALD« zu schreiben. Sie werden schnell merken, daß dies nicht geht, da die INPUT-Routine des C 64 keine Kommas zuläßt – es kommt ein EXTRA IGNORED. Hier hilft nur eine verbesserte INPUT-Routine, die das Listing 22 darstellt:
50000 rem input-routine ---------------
50001 :
50002 sl=40
50005 sl=40
50020 print"{down}":be$="":poke 198,0:poke211,0:poke214,22:sys58732:print"{yel}{CBM-@}";
50030 getx$:ifpeek(203)=1then50120
50040 if x$=""then 50030
50050 if len(be$)=0 and asc(x$) =20then 50030
50060 i=asc(x$):ifi<32ori>133andi<159then ifi<>20 then 50030
50070 iflen(be$)=slandi<>20then50030
50075 ifx$=" "thenx$="{rght}"
50080 be$=be$+x$
50090 printchr$(20);x$;"{CBM-@}";
50100 if i=20thenbe$=left$(be$,len(be$)-2):goto 50030
50110 goto 50030
50120 print"{left} "
50140 return
50000 REM INPUT-ROUTINE --------------- 50001 : 50002 SL=40 50005 SL=40 50020 PRINT"{DOWN}":BE$="":POKE 198,0:POKE 211,0:POKE 214,22:SYS 58732:PRINT"{YELLOW}@"; 50030 GET X$:IF PEEK (203)=1 THEN50120 50040 IF X$="" THEN 50030 50050 IF LEN (BE$)=0 AND ASC (X$) =20 THEN 50030 50060 I=ASC (X$):IF I<32OR I>133AND I<159 THEN IF I<>20 THEN 50030 50070 IF LEN (BE$)=SLAND I<>20 THEN50030 50075 IF X$=" " THENX$="{RIGHT}" 50080 BE$=BE$+X$ 50090 PRINTCHR$ (20);X$;"@"; 50100 IF I=20 THENBE$=LEFT$ (BE$,LEN (BE$)-2):GOTO 50030 50110 GOTO 50030 50120 PRINT"{LEFT,SPACE}" 50140 RETURN
Die Routine wird einfach mittels GOSUB 50000 aufgerufen. Im String BE$ steht dann der Eingabesatz. Interessant im Listing ist lediglich Zeile 50020.
Mittels
POKE 211,SPALTE:POKE 214,ZEILE:SYS 58732
kann der Cursor an eine beliebige Stelle des Bildschirms positioniert werden.
In Zeile 50005 muß mittels der Variablen SL festgelegt werden, wie groß die maximale Länge des Eingabestrings sein darf.
Erweitern Sie bitte den Editor (Listing 21) um Listing 22.
Außerdem muß noch Zeile 2020 so geändert werden, daß die neue INPUT-Routine aufgerufen wird:
2020 GOSUB 50000 : A$=BE$
Jetzt können Sie »LOLO, PETER UND MIKE IN DEN WALD« in Satz 10 schreiben. Enttäuschung wird jedoch breit, wenn Sie anschließend Satz 10 wieder lesen wollen.
Sie erhalten beim Lesen nur »LOLO«. Alles, was nach dem Komma steht, einschließlich dem Komma ist verschwunden. Das Komma läßt sich innerhalb relativer Dateien also nicht so einfach verwenden, wie wir es gerne hätten. Wir können jedoch auch nicht auf das Komma verzichten, wenn wir Texte schreiben wollen.
Das gleiche Problem taucht bei Verwendung eines Doppelpunktes auf. Ganz zu schweigen von den ewigen Problemen mit den Gänsefüßchen beziehungsweise Anführungszeichen innerhalb von Strings.
Wie bei den meisten Problemen gibt es allerdings auch hier eine einfache Lösung. Der Editor wird einfach um folgende Zeile ergänzt:
2035 A$=CHR$(34)+A$+CHR$(34)
Bevor A$ abgespeichert wird, wird es also einfach in Anführungszeichen gesetzt. Jetzt können alle Sonderzeichen verwendet werden. Auch beim Einlesen bereiten die beiden Anführungszeichen zu Beginn und am Ende von A$ keine Schwierigkeiten – sie tauchen nicht auf. Die Sache hat wie immer natürlich ihren Haken: Die maximal erlaubte Satzlänge beträgt jetzt nicht mehr 40, sondern nur noch 38 Zeichen, da die Anführungszeichen zwei Zeichen Platz benötigen. Es wird LOLO, PETER UND MIKE also wieder nicht gelingen, endlich in den Wald zu gelangen. Abhilfe würde hier ganz einfach das Anlegen einer neuen TEST-Datei mit 42 Zeichen Satzlänge schaffen.
Hier sind jedoch auch Überlegungen dem Tun vorauszustellen. Nehmen wir einmal an, auf einer Diskette wäre Platz für maximal 1000 Sätze mit je 40 Zeichen. Da wir jedoch 42 brauchen, um Strings mit 40 Zeichen darzustellen, würden wir nur noch zirka 952 Sätze auf der Diskette unterbringen. Unsere jetzige Lösung ist somit zwar einfach, jedoch nicht sehr effektiv, da sie viel zu verschwenderisch mit dem Disk-Speichervolumen umgeht.
Sicherlich werden sich einige von Ihnen daran erinnern, daß ich im letzten Kapitel behauptete, daß ein deutscher Zeichensatz auch enorme Vorteile beim Programmieren von relativen Dateien mit sich bringt. Einige haben sich sicherlich auch schon gewundert, warum ich neben den deutschen Umlauten und »ß« auch noch »"«, »,«, »:« und »;« nochmals definiert habe.
Komma ist nicht gleich Komma
Wenn wir unseren deutschen Zeichensatz im Computer haben, so können wir am Aussehen des Kommas auf dem Bildschirm nicht unterscheiden, ob es sich um CHR$(44) (dem Originalkomma) oder um CHR$(95) (Pfeil nach links, der als Komma umdefiniert wurde) handelt. Unser Computer hingegen kann es.
Geben Sie im Direktmodus einmal folgendes ein:
PRINT """ und drücken REURN
Als Antwort erscheint nichts.
Geben Sie nun
PRINT""" ein.
Das mittlere Anführungszeichen müssen Sie allerdings durch Drücken der @-Taste eingeben (also das definierte Anführungszeichen).
Diesmal erscheint als Antwort tatsächlich ein Anführungszeichen. Der umdefinierte Zeichensatz machts möglich! Erweitern Sie den Editor bitte um folgende Zeilen:
1 IFI=0THENI=1:LOAD"D.Z.SATZ",8,1
2 POKE648,204:POKE53272,56:POKE56576,0:
Jetzt wird zu Beginn des Programmes der deutsche Zeichensatz (den Sie hoffentlich bereits mit dem Zeicheneditor automatisch erzeugt und als D.Z.SATZ auf Diskette gespeichert haben!) geladen.
Auf Zeile 2035 können wir nun verzichten – wir brauchen die Anführungszeichen links und rechts von A$ nicht mehr. Das heißt, in einer Datei mit Satzlänge von 40 Zeichen können jetzt auch wirklich Sätze mit 40 und nicht mit 38 Zeichen untergebracht werden.
Jetzt können LOLO, PETER UND MIKE auch endlich in den Wald gehen (natürlich ohne das Bier zu vergessen). Auf die Frage, was LOLO, PETER UND MIKE eigentlich überhaupt im Wald wollen beziehungsweise wozu sie das Bier mitgenommen haben, werde ich später noch zu sprechen kommen!
Bei Eingabe von Sätzen dürfen Sie von nun an jedoch nicht vergessen, daß die Tastatur in bezug auf Komma, Doppelpunkt und Anführungszeichen nicht mehr stimmt – wenn Sie Texte für relative Dateien schreiben wollen. Anstelle der Komma-Taste muß jetzt die Pfeil-links-Taste betätigt werden, wenn ein »Pseudokomma« gewünscht wird etc. Sie werden sich sicher schnell mit der neuen Tastaturbelegung und dem deutschen Zeichensatz anfreunden.
Durch die enormen Vorteile, die sich uns jetzt bieten, gibt es wirklich kein Argument mehr, das gegen die Verwendung eines deutschen Zeichensatzes spricht.
Wenn man sich für die Verwendung des deutschen Zeichensatzes anstelle der Lösung mit den beiden Anführungszeichen entscheidet, müssen noch einige Dinge berücksichtigt werden:
SPACEs
Spaces – Leerräume am Anfang eines Satzes sind nicht ohne weiteres möglich. Angenommen, wir schreiben in Satz 30 den String »HALLO«. Lesen wir den Satz 30 anschließend, so erhalten wir die Antwort »HALLO«. Die 10 Spaces werden vom Computer zu Beginn des Satzes einfach ignoriert. Es ist jedoch unsinnig, ein neues Space zu definieren, auch wenn dies eine Lösung wäre. Wir verwenden anstelle von Space (CHR$(32) und CHR$(160)) einfach CHR$(29) – Cursor nach rechts. Hierfür sorgt Zeile 50075 im Editor. Zu beachten ist hierbei, daß, wenn später mit Strings gearbeitet werden soll, die aus einer mit dieser Methode geführten relativen Datei stammen, die man zum Auffinden von Wortzwischenräumen nicht nach » «, sondern nach CHR$(29) suchen muß!!!
Im Gegensatz zu »,«, »:« und »"« kann zum Erzeugen eines Spaces jedoch ruhig die Space-Taste gedrückt werden (und nicht die Cursor-nach-rechts-Taste). Verwirrt? Probieren Sie einfach mal alles am Computer aus, dann wird alles schnell klar!
Leersätze
Versuchen Sie doch mal einfach, einen Leersatz zu schreiben. Also einfach RETURN drücken. Soll der Satz wieder gelesen werden, so steigt der Computer aus – er findet nichts mehr und verhält sich still. Es ist nicht möglich, in relative Dateien Leersätze " " zu schreiben, die nicht in Anführungszeichen stehen. Da wir uns jedoch für die modifizierte Zeichensatz-Lösung entschieden haben, müssen wir erneut einen kleinen Kunstgriff machen: Wir schreiben anstelle von Leersätzen einfach Sätze, die mit einem Doppelpunkt beginnen, denn wir haben ja erfahren, daß alles, was nach einem Komma beziehungsweise Doppelpunkt steht, als nichtig interpretiert wird. Wir müssen den Editor nur um folgende Zeile ergänzen:
50130 IF BE$="" THEN BE$=":"
Damit wäre auch schon das Wichtigste über relative Dateien gesagt. Wer die Vorzüge dieser Dateien erst einmal schätzen gelernt hat, wird sie schon bald für unentbehrlich halten.
Einschränkung
Das Disketten-Laufwerk 1541 hat nur drei Datenkanäle zur Verfügung. Eine relative Datei beansprucht zwei dieser Kanäle, das heißt, es kann jeweils nur eine relative Datei geöffnet sein. Will man mit zwei oder mehreren relativen Dateien arbeiten, so muß beachtet werden, daß immer die eine Datei zuerst geschlossen werden muß, wenn man eine andere öffnen will. Die Datenübertragung von einer relativen Datei zu einer anderen ist somit nur mittels eines Puffers möglich.
Neben einer relativen Datei kann auch noch eine sequentielle Datei geöffnet werden, da diese Dateiart nur einen Kanal benötigt. Man kann also maximal drei sequentielle Dateien oder eine relative und eine sequentielle gleichzeitig geöffnet haben.
Der Relative-Dateien-Editor
Im folgenden stelle ich Ihnen meinen Relative-Dateien-Editor vor, der uns für den Rest des Kurses als unentbehrliches Werkzeug dienen wird.
Der Editor hat sich im Laufe der Monate allmählich aus dem Editor entwickelt. Er ist keineswegs so komfortabel wie ein Textverarbeitungssystem, was bei Relative-Datei-Editoren auch nie der Fall sein wird beim C 64. Ich habe großen Wert auf Anwenderfreundlichkeit des Programmes gelegt: Der Editor hat nur eine einzige Menüzeile, von der sich jederzeit alle nur erdenklichen Zwischenmenüs aufrufen lassen. Der Editor eignet sich hervorragend zum Erstellen von Texten von Adventure-Spielen. Er eignet sich auch prima zum Erstellen von Wortschätzen – wichtig für Parsertechnik, wie wir bald sehen werden. Der Editor arbeitet auch zusammen mit dem deutschen Zeichensatz. Am besten ist es, wenn Sie den Editor gemeinsam mit dem Zeichensatzeditor und dem deutschen Zeichensatz auf eine Diskette speichern. Im Laufe des Kurses werde ich Ihnen noch weitere Programme für diese Utility-Disk vorstellen. Zunächst sollten Sie jedoch erst einmal das Listing 23 zum Editor abtippen:
0 rem *********************************
1 rem * texteditor v.iii.4 *
2 rem * <c> 1985 *
3 rem * von michael nickles *
4 rem *********************************
8 ifim=1thenpoke648,204:poke53272,56:poke56576,0:print"{clr}":goto20
9 poke53280,0:poke53281,0:print"{clr}{wht}";
10 m%=300:dim m$(m%),m(m%):rem puffer
15 dimlg(100),rg(100) :rem q.sort
19 rem anfangsmenue ------------------------------------------------------------
20 print"{clr}{rvon} texteditor - version 3 {rvof}";
21 print"{rvon} <c> 1985 von michael nickles {rvof}";
22 print"{down}{down} alte datei editieren - 'a'
23 print" neue datei eroeffnen - 'n'
24 print"{down} d.z.satz laden - 'd'
25 geti$:ifpeek(203)=1theni$="a"
26 ifi$<>"a"andi$<>"n"andi$<>"d"then25
27 ifi$="d"thenim=1:load"d.z.satz",8,1
28 poke198,0:input"{down}{down}dateiname *{left}{left}{left}";d$:poke198,0
29 input"satzlaenge 40{left}{left}{left}{left}";sl:ifsl<1orsl>254thenprint"geht nicht !":goto30
30 ifi$="a"then40
31 input"satzanzahl ";sn:hb=int(sn/255):lb=sn-hb*255
32 print"bitte leere disk einlegen <return>":poke198,0:wait198,1
33 open15,8,15
35 print#15,"i0":open1,8,3,d$+",l,"+chr$(sl)
36 print#15,"p";chr$(3);chr$(lb);chr$(hb);chr$(1)
37 print#1,chr$(255)
38 close1:close15
40 open 15,8,15
41 print#15,"i0":open 1,8,3,d$:print
50 rem zwischenmenue -----------------------------------------------------------
51 print"{rvon}s / l / v / o / d / i / z / f / ende {rvof}";
52 geti$:ifi$=""then52
53 ifi$="e"thenprint"{clr}";:goto9900
54 ifi$="v"thenprint"{clr}";:goto6000
55 ifi$="o"thenprint"{clr}";:goto10000
56 ifi$="d"thenprint"{clr}";:goto7000
57 ifi$="i"thenprint"{clr}";:goto30000
58 ifi$="z"thenprint"{clr}";:goto8000
59 ifi$="f"thenprint"{clr}";:goto61000
60 ifi$<>"s"andi$<>"l"then52
61 input"satznr";sa:if sa<0 or sa>63000 then60
64 ifi$="l"thenprint"{clr}";:goto2000
66 ifi$="s"thenprint"{clr}";:mo=1:goto3000
70 goto 50
2000 rem lesen -----------------------------------------------------------------
2001 :print"{clr}{rvon}";sa;"{left}-";str$(sa+20);" {rvof}"
2005 ia=sa
2007 for sa=ia to ia+20
2010 : gosub9000:gosub9100
2025 : ifa=50thensa=ia+21:goto2045
2030 : iflen(a$)<40thenprinta$
2035 : iflen(a$)>=40thenprinta$;
2045 next:print
2046 ifa=50thenprint"satznummer zu hoch!"
2050 goto 50
3000 rem schreiben -------------------------------------------------------------
3005 print"eingabesatz //=keine eingabe"
3010 gosub9000 :rem positionieren
3020 ifa=50thenprint"satznummer zu hoch!":goto50 :rem fehlerkanalabfr.
3030 gosub50000:a$=be$ :rem input-rout.
3040 ifa$="//"thenmo=0:goto50:rem ende
3050 gosub9200 :rem a$ schreiben
3060 sa=sa+1 :rem naechster satz
3070 goto3010 :rem next (loop)
6000 rem versetzen -------------------------------------------------------------
6002 print"{clr}{rvon}versetzen{rvof}"
6010 print"{down}bitte geben sie anfang und ende des be- reiches an, den sie ";
6015 print"versetzen wollen !"
6020 poke198,0:input"{down}anfang";an
6021 poke198,0:input"ende";en:ifan>enthenprint"das ist unsinn !":goto6020
6023 sa=an:gosub9000:ifa=50thenprint"anfangssatz existiert nicht!":goto50
6024 sa=en:gosub9000:ifa=50thenprint"endsatz zu hoch!":goto50
6026 print"{down}quelldatei = ";d$
6028 poke198,0:input"zieldatei ";z$:if z$=""then z$=d$:print"{up}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}";d$
6029 print"{down}bitte anfang des neuen bereichs angeben!";:poke198,0:inputnb:print
6030 sa=nb:gosub9000:ifa=50thenprint"neuer anfangssatz zu hoch!":goto50
6031 sa=nb+en-an:gosub9000:ifa=50thenprint"bereich zu gross!":goto50
6032 print"bitte warten!"
6036 sa=an:im=0:ifan=enthenprint"*** fertig ***":poke198,0:wait198,1:goto50
6038 gosub9000:gosub9100:ifa=50then6100
6040 m$(sa-an)=a$
6045 ifsa=enthen6100
6050 ifsa-an=m%thenim=1:goto6100:rem *
6060 sa=sa+1:goto6038
6100 rem transport
6110 print"bitte disk mit zieldatei einlegen !":poke198,0:wait198,1
6120 close1:print#15,"i0":open1,8,3,z$
6200 sa=nb:gosub9000:ifa=50then50
6205 ii=en-an:ifim=1thenii=m%:rem *
6215 fori=0toii
6220 a$=m$(i):ifa$=""thena$=":"
6225 gosub9200:rem schreiben
6240 sa=sa+1:nexti:ifim<>0then6260
6250 print"bitte disk mit quelldatei einlegen !":poke198,0:wait198,1:goto50
6260 an=an+m%:nb=nb+m%:print"bitte disk mit quelldatei einlegen !"
6270 poke198,0:wait198,1:close1:print#15,"i0":open1,8,3,d$:goto6036:rem *
7000 rem printer-routine -------------------------------------------------------
7001 print"{clr}{rvon}printer-routine mps 801{rvof}
7002 input"{down}anfang";an:input"ende";en:ifan>enthen7000
7005 sa=an:gosub9000:ifa=50thenprint"anfangssatz existiert nicht!":goto50
7006 sa=en:gosub9000:ifa=50thenprint"endsatz zu hoch!":goto50
7009 an=an-1:print"{down}bitte waehlen sie:{down}
7010 print,"normale ausgabe ..1
7011 print,"d.z.s. ausgabe ...2
7012 print,"eigene routine ...3
7013 geti$:ifval(i$)<1orval(i$)>3then7013
7014 ifi$="2"then7100
7015 ifi$="3"then7500
7020 rem normale ausgabe --------------
7021 open3,4,7:forsa=antoen:gosub9000:gosub9100
7025 ifa=50thenprint"satznummer zu hoch!":sa=en+1:goto7045
7035 print#3,right$(" "+str$(sa),3);" ";
7036 fori=1tolen(a$)
7037 :ifmid$(a$,i,1)="{rght}"thenprint#3," ";:goto7040
7038 :print#3,mid$(a$,i,1);
7040 nexti:print#3
7045 nextsa:close3:print:goto50
7100 rem d.z.satz ausgabe -------------
7101 open3,4,7:forsa=antoen:gosub9000:gosub9100
7102 ifa=50thensa=en+1:print"satznummer zu hoch!":goto7170
7110 print#3,right$(" "+str$(sa),4);" ";
7120 fori=1tolen(a$):ifa$=""then7160
7125 im=asc(mid$(a$,i,1))
7126 ifim>64andim<91orim>96andim<123then7160
7130 ifim=64thenprint#3,chr$(34);:goto7170
7135 ifim=173thenprint#3,"Ae";:goto7170
7140 ifim=174thenprint#3,"ss";:goto7170
7145 ifim=176thenprint#3,"ae";:goto7170
7150 ifim=181thenprint#3,"Ue";:goto7170
7152 ifim=182thenprint#3,"Oe";:goto7170
7154 ifim=184thenprint#3,"ue";:goto7170
7156 ifim=185thenprint#3,"oe";:goto7170
7158 ifim=94thenprint#3,":"; :goto7170
7159 ifim=95thenprint#3,","; :goto7170
7160 print#3,chr$(im);
7170 nexti:print#3,chr$(13);
7175 nextsa:close3:print:goto50
7500 rem eigene routine ---------------
7510 print"{down}sie haben noch keine eigene printer- routine deffiniert!":goto50
8000 rem text zentrieren -------------------------------------------------------
8001 i$="":fori=1to10:i$=i$+"{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}":nexti
8002 remsl=27
8005 remdimm$(300):goto8100
8010 print"{rvon}zentrieren{rvof}{down}"
8015 print"bitte geben sie anfang und ende des bereiches an, der zentriert";
8017 print" werden soll!"
8020 input"anfang";an:input"ende";en:ifan>enthen50
8025 sa=an:gosub9000:ifa=50thenprint"anfangssatz existiert nicht!":goto50
8030 sa=en:gosub9000:ifa=50thenprint"endsatz zu hoch!":goto50
8040 print"{down}links-rechts buendig zentrieren - '1'
8045 print"{down}in die mitte zentrieren - '2'
8050 getii$:ifii$<>"1"andii$<>"2"then8050
8060 ifii$="2"then8600
8099 rem links-rechts buendig ---------
8100 print"{down}bitte warten!"
8105 forsa=antoen
8110 : gosub9000:gosub9100:printa$:printlen(a$)
8120 : ifright$(a$,1)=chr$(29)thena$=left$(a$,len(a$)-1):goto8120
8122 : ifright$(a$,1)=chr$(32)thena$=left$(a$,len(a$)-1):goto8122
8123 : ifright$(a$,1)=chr$(160)thena$=left$(a$,len(a$)-1):goto8123
8130 : iflen(a$)=slthen8520
8200 : lu=sl-len(a$):print"lu=";lu
8220 : i=1:ic=0:rem begin loop -------
8222 :
8230 : ifmid$(a$,i,1)=""ormid$(a$,i+1,1)=""then8530
8231 : im=asc(mid$(a$,i,1))
8232 : in=asc(mid$(a$,i+1,1))
8240 : ifim<>29andim<>32andim<>160then8500
8250 : ifin=29orin=32orin=160then8500
8260 : a$=left$(a$,i)+" "+right$(a$,len(a$)-i)
8261 : ifmid$(a$,i,1)=" "ormid$(a$,i,1)="{rght}"theni=i+1:goto8261
8265 : lu=lu-1:iflu=0then8510
8266 : ic=ic+1
8270 : i=i+1
8400 :
8500 i=i+1
8505 ifi<len(a$)then8230
8510 ifi=len(a$)andlu>0andic<>0then8220
8520 gosub9000:gosub9200
8530 nextsa
8540 goto50
8600 rem in die mitte -----------------
8610 print"{down}bitte warten!"
8620 forsa=antoen
8630 :gosub9000:gosub9100
8635 ifright$(a$,1)=chr$(29)thena$=left$(a$,len(a$)-1):goto8635
8636 ifright$(a$,1)=chr$(32)thena$=left$(a$,len(a$)-1):goto8636
8637 ifright$(a$,1)=chr$(160)thena$=left$(a$,len(a$)-1):goto8637
8640 :iflen(a$)=slthen8680
8650 :as=sl-len(a$): rem anzahl spaces
8660 :a$=left$(i$,as/2)+a$
8670 :gosub9000:gosub9200
8680 nextsa
8690 goto50
9000 rem positionieren ****************
9010 hb%=sa/256:lb%=sa-hb%*256
9020 print#15,"p";chr$(3);chr$(lb%);chr$(hb%);chr$(1)
9030 input#15,a
9040 return
9100 rem satz einlesen ****************
9110 ifa=50thenreturn
9120 input#1,a$
9140 return
9200 rem satz schreiben ***************
9210 ifa=50thenreturn
9211 ifa$=""thena$=":":rem zur sicherheit !!!
9220 print#1,a$
9240 return
9900 rem beenden **********************
9910 close 1
9920 close 15
9930 print "++ ende ++"
9940 end
10000 rem sort-alogarithmus ----------------------------------------------------
10010 print"{clr}{rvon}datei sortieren{rvof}"
10100 print"bitte geben sie anfang und ende des zu sortierenden bereichs an !
10110 input"anfang";an
10120 input"ende"; en
10122 sa=an:gosub9000:ifa=50thenprint"anfangssatz existiert nicht!":goto50
10124 sa=en:gosub9000:ifa=50thenprint"endsatz zu hoch!":goto50
10130 ifen-an>m%thenprint"bereich zu gross!":goto50
10135 print"{down}bitte markieren sie den bereich inner-
10136 print"halb der strings nach dem die sortierung";
10137 print"erfolgen soll!"
10138 sa=an:gosub9000:gosub9100:ifa=50thenprint"satz existiert nicht!":goto50
10139 ig=-1:gosub60000:ig=0
10140 ic=0:forsa=antoen:ic=ic+1
10145 :gosub9000:gosub9100:ifa=50thenprint"satznummer zu hoch!":sa=en+1:goto1015
10146 :m$(ic)=a$
10150 nextsa
10180 z=0:lg(1)=1:rg(1)=en-an+1
10190 gosub10200:goto10400
10200 rem quicksort -----------------
10210 z=z+1:iflg(z)>=rg(z)then10350
10220 x=lg(z):y=rg(z)
10230 vg$=mid$(m$(int((x+y)/2)),ma(1),mb(1)-ma(1)+1)
10240 if x>y then10330
10250 if mid$(m$(x),ma(1),mb(1)-ma(1)+1)<vg$thenx=x+1:goto10250
10260 if mid$(m$(y),ma(1),mb(1)-ma(1)+1)>vg$theny=y-1:goto10260
10270 if x>ythen10330
10280 s$=m$(x):m$(x)=m$(y):m$(y)=s$
10290 x=x+1:y=y-1:goto10240
10330 rg(z+1)=y:lg(z+1)=lg(z):gosub10210
10340 lg(z+1)=x:rg(z+1)=rg(z):gosub10210
10350 z=z-1:return
10400 rem abspeichern
10410 ic=0:forsa=antoen:ic=ic+1
10420 :gosub9000:a$=m$(ic):gosub9200
10430 nextsa
10500 goto50
20000 rem korrektur-routine ----------------------------------------------------
20001 fori=0tolen(be$):printchr$(20);:nexti:be$=""
20010 sa=sa+sc : rem textzeiger
20015 ifsa<0thensa=0 : rem sperre
20020 gosub 9000 : rem positionieren
20025 ifa=50thensa=sa-sc:goto20020
20040 input#1,a$
20060 return
30000 rem directory einlesen ---------------------------------------------------
30010 print"{clr}{rvon}inhaltsverzeichnis:{down}{rvof}"
30110 print#15,"io":open2,8,2,"#"
30120 t=18:s=1
30130 print#15,"u1";2;0;t;s
30140 print#15,"b-p";2;0
30150 get#2,x$:ifx$=""thenx$=chr$(0)
30160 t=asc(x$)
30170 get#2,x$:ifx$=""thenx$=chr$(0)
30180 s=asc(x$)
30190 forx=0to7
30192 print#15,"b-p";2;x*32+2
30194 get#2,x$:ifx$=""thenx$=chr$(0)
30196 ifasc(x$)=0then30290
30200 print#15,"b-p";2;x*32+5
30210 ff$=""
30220 fory=0to15
30230 get#2,x$:ifx$=""thenx$=chr$(0)
30240 ifasc(x$)=160then30270
30250 ff$=ff$+x$
30260 nexty
30270 ifa=0thena=1:printff$;:goto30290
30280 a=0:printtab(19);"{CBM-M} ";ff$
30290 nextx
30300 ift<>0then30130
30310 close2:print
30330 goto50
50000 rem texteingabe ----------------------------------------------------------
50002 ifar=1thenar=0:a$="{rvon}satznummer zu hoch!{rvof}":goto50007
50006 gosub9000:gosub9100
50007 print"{home} ";:print"{home}{rvon}satz";sa;"{rvof}"
50008 print" {up}";a$
50009 print"{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}{CBM-T}";
50010 print"{down}"
50020 be$="":poke198,0:poke211,0:poke214,22:sys58732:print"{yel}{CBM-@}";
50030 getx$:ifpeek(203)=1then50120
50035 if peek(203)=7andpeek(653)=0 then sc=1 : gosub 20000 : goto 50000
50037 if peek(203)=7andpeek(653)=1 then sc=-1 : gosub 20000 : goto 50000
50040 ifx$=""then50030
50050 iflen(be$)=0andasc(x$)=20then50030
50060 i=asc(x$):ifi<32ori>133andi<159thenifi<>20then50030
50070 iflen(be$)=slandi<>20then50030
50075 ifx$=" "thenx$="{rght}"
50080 be$=be$+x$
50090 printchr$(20);x$;"{CBM-@}";
50100 ifi=20thenbe$=left$(be$,len(be$)-2):goto50030
50110 goto50030
50120 print"{left} ":gosub9000
50130 ifbe$=""thenbe$=":"
50140 print"{home}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}":return
60000 rem markieren wie datiert wird -------------------------------------------
60010 i$=a$:fori=1tolen(i$):m$(i)="0":next
60020 im=1:print
60025 printi$:fori=1tolen(i$):printm$(i);:next:print
60030 print"{down}edit >";mid$(i$,im,1);"< {rvon}"im"{rvof}"
60035 i=peek(203):ifi=64then60035
60040 ifi=2andpeek(653)=0andim<>len(i$)thenim=im+1:goto60070
60042 ifi=2andpeek(653)=1andim<>1thenim=im-1:goto60070
60045 ifi=56thenm$(im)="1":goto60070
60055 ifi=35thenm$(im)="0":goto60070
60056 ifi=1then60100
60060 goto60035
60070 print"{up} {left}{left}{left}{left}{left}{left}{left}{left}{left}{left}{left}{left}{left}{left}{left}{left}{left}{left}{left}{left}{left}{left}{left}{left}{left}{up}{up}{up}";:goto60025
60100 rem umrechnen
60102 fori=1to3:ma(i)=0:mb(i)=0:nexti
60105 forii=1to3
60110 :fori=1tolen(i$):ifma(ii)=0andm$(i)=right$(str$(ii),1)thenma(ii)=i
60113 nexti
60115 :ifma(ii)=0then60150
60120 : mb(ii)=ma(ii)
60125 : mb(ii)=mb(ii)+1:remprint,mb(ii)
60130 : ifm$(mb(ii))<>right$(str$(ii),1)thenmb(ii)=mb(ii)-1:goto60150
60135 : ifmb(ii)=len(i$)then60150
60140 : goto60125
60150 nextii
60170 print"die sortierung erfolgt nach folgendem stringbereich:"
60180 print"{rvon}"mid$(i$,ma(1),mb(1)-ma(1)+1)"{rvof}"
60190 return
61000 rem floppy-fehlerkanal lesen ---------------------------------------------
61005 print"{clr}{rvon}floppy-fehlerkanalabfrage{rvof}"
61010 input#15,a,b$,c,d
61020 printa,b$,c,d
61040 goto50
0 REM ********************************* 1 REM * TEXTEDITOR V.III.4 * 2 REM * <C> 1985 * 3 REM * VON MICHAEL NICKLES * 4 REM ********************************* 8 IF IM=1 THENPOKE 648,204:POKE 53272,56:POKE 56576,0:PRINT"{CLR}":GOTO 20 9 POKE 53280,0:POKE 53281,0:PRINT"{CLR,WHITE}"; 10 M%=300:DIM M$(M%),M(M%):REM PUFFER 15 DIM LG(100),RG(100) :REM Q.SORT 19 REM ANFANGSMENUE ------------------------------------------------------------ 20 PRINT"{CLR,RVSON,8SPACE}TEXTEDITOR - VERSION 3{10SPACE,RVOFF}"; 21 PRINT"{RVSON,4SPACE}<C> 1985 VON MICHAEL NICKLES{8SPACE,RVOFF}"; 22 PRINT"{2DOWN,5SPACE}ALTE DATEI EDITIEREN{2SPACE}-{2SPACE}'A' 23 PRINT"{5SPACE}NEUE DATEI EROEFFNEN{2SPACE}-{2SPACE}'N' 24 PRINT"{DOWN,5SPACE}D.Z.SATZ LADEN{8SPACE}-{2SPACE}'D' 25 GET I$:IF PEEK (203)=1 THENI$="A" 26 IF I$<>"A"AND I$<>"N"AND I$<>"D" THEN25 27 IF I$="D" THENIM=1:LOAD "D.Z.SATZ",8,1 28 POKE 198,0:INPUT"{2DOWN}DATEINAME{2SPACE}*{3LEFT}";D$:POKE 198,0 29 INPUT"SATZLAENGE{2SPACE}40{4LEFT}";SL:IF SL<1OR SL>254 THENPRINT"GEHT NICHT !":GOTO 30 30 IF I$="A" THEN40 31 INPUT"SATZANZAHL ";SN:HB=INT (SN/255):LB=SN-HB*255 32 PRINT"BITTE LEERE DISK EINLEGEN <RETURN>":POKE 198,0:WAIT 198,1 33 OPEN 15,8,15 35 PRINT#15,"I0":OPEN 1,8,3,D$+",L,"+CHR$ (SL) 36 PRINT#15,"P";CHR$ (3);CHR$ (LB);CHR$ (HB);CHR$ (1) 37 PRINT#1,CHR$ (255) 38 CLOSE 1:CLOSE 15 40 OPEN 15,8,15 41 PRINT#15,"I0":OPEN 1,8,3,D$:PRINT 50 REM ZWISCHENMENUE ----------------------------------------------------------- 51 PRINT"{RVSON}S / L / V / O / D / I / Z / F / ENDE{SPACE,3SPACE,RVOFF}"; 52 GET I$:IF I$="" THEN52 53 IF I$="E" THENPRINT"{CLR}";:GOTO 9900 54 IF I$="V" THENPRINT"{CLR}";:GOTO 6000 55 IF I$="O" THENPRINT"{CLR}";:GOTO 10000 56 IF I$="D" THENPRINT"{CLR}";:GOTO 7000 57 IF I$="I" THENPRINT"{CLR}";:GOTO 30000 58 IF I$="Z" THENPRINT"{CLR}";:GOTO 8000 59 IF I$="F" THENPRINT"{CLR}";:GOTO 61000 60 IF I$<>"S"AND I$<>"L" THEN52 61 INPUT"SATZNR";SA:IF SA<0 OR SA>63000 THEN60 64 IF I$="L" THENPRINT"{CLR}";:GOTO 2000 66 IF I$="S" THENPRINT"{CLR}";:MO=1:GOTO 3000 70 GOTO 50 2000 REM LESEN ----------------------------------------------------------------- 2001 :PRINT"{CLR,RVSON}";SA;"{LEFT}-";STR$ (SA+20);"{SPACE,RVOFF}" 2005 IA=SA 2007 FOR SA=IA TO IA+20 2010 : GOSUB 9000:GOSUB 9100 2025 : IF A=50 THENSA=IA+21:GOTO 2045 2030 : IF LEN (A$)<40 THENPRINTA$ 2035 : IF LEN (A$)>=40 THENPRINTA$; 2045 NEXT :PRINT 2046 IF A=50 THENPRINT"SATZNUMMER ZU HOCH!" 2050 GOTO 50 3000 REM SCHREIBEN ------------------------------------------------------------- 3005 PRINT"EINGABESATZ{2SPACE}//=KEINE EINGABE" 3010 GOSUB 9000 :REM POSITIONIEREN 3020 IF A=50 THENPRINT"SATZNUMMER ZU HOCH!":GOTO 50 :REM FEHLERKANALABFR. 3030 GOSUB 50000:A$=BE$ :REM INPUT-ROUT. 3040 IF A$="//" THENMO=0:GOTO 50:REM ENDE 3050 GOSUB 9200 :REM A$ SCHREIBEN 3060 SA=SA+1 :REM NAECHSTER SATZ 3070 GOTO 3010 :REM NEXT (LOOP) 6000 REM VERSETZEN ------------------------------------------------------------- 6002 PRINT"{CLR,RVSON}VERSETZEN{RVOFF}" 6010 PRINT"{DOWN}BITTE GEBEN SIE ANFANG UND ENDE DES BE- REICHES AN, DEN SIE "; 6015 PRINT"VERSETZEN WOLLEN !" 6020 POKE 198,0:INPUT"{DOWN}ANFANG";AN 6021 POKE 198,0:INPUT"ENDE";EN:IF AN>EN THENPRINT"DAS IST UNSINN !":GOTO 6020 6023 SA=AN:GOSUB 9000:IF A=50 THENPRINT"ANFANGSSATZ EXISTIERT NICHT!":GOTO 50 6024 SA=EN:GOSUB 9000:IF A=50 THENPRINT"ENDSATZ ZU HOCH!":GOTO 50 6026 PRINT"{DOWN}QUELLDATEI = ";D$ 6028 POKE 198,0:INPUT"ZIELDATEI ";Z$:IF Z$="" THEN Z$=D$:PRINT"{UP,13RIGHT}";D$ 6029 PRINT"{DOWN}BITTE ANFANG DES NEUEN BEREICHS ANGEBEN!";:POKE 198,0:INPUTNB:PRINT 6030 SA=NB:GOSUB 9000:IF A=50 THENPRINT"NEUER ANFANGSSATZ ZU HOCH!":GOTO 50 6031 SA=NB+EN-AN:GOSUB 9000:IF A=50 THENPRINT"BEREICH ZU GROSS!":GOTO 50 6032 PRINT"BITTE WARTEN!" 6036 SA=AN:IM=0:IF AN=EN THENPRINT"*** FERTIG ***":POKE 198,0:WAIT 198,1:GOTO 50 6038 GOSUB 9000:GOSUB 9100:IF A=50 THEN6100 6040 M$(SA-AN)=A$ 6045 IF SA=EN THEN6100 6050 IF SA-AN=M% THENIM=1:GOTO 6100:REM * 6060 SA=SA+1:GOTO 6038 6100 REM TRANSPORT 6110 PRINT"BITTE DISK MIT ZIELDATEI EINLEGEN !":POKE 198,0:WAIT 198,1 6120 CLOSE 1:PRINT#15,"I0":OPEN 1,8,3,Z$ 6200 SA=NB:GOSUB 9000:IF A=50 THEN50 6205 II=EN-AN:IF IM=1 THENII=M%:REM * 6215 FOR I=0 TOII 6220 A$=M$(I):IF A$="" THENA$=":" 6225 GOSUB 9200:REM SCHREIBEN 6240 SA=SA+1:NEXT I:IF IM<>0 THEN6260 6250 PRINT"BITTE DISK MIT QUELLDATEI EINLEGEN !":POKE 198,0:WAIT 198,1:GOTO 50 6260 AN=AN+M%:NB=NB+M%:PRINT"BITTE DISK MIT QUELLDATEI EINLEGEN !" 6270 POKE 198,0:WAIT 198,1:CLOSE 1:PRINT#15,"I0":OPEN 1,8,3,D$:GOTO 6036:REM * 7000 REM PRINTER-ROUTINE ------------------------------------------------------- 7001 PRINT"{CLR,RVSON}PRINTER-ROUTINE MPS 801{RVOFF} 7002 INPUT"{DOWN}ANFANG";AN:INPUT"ENDE";EN:IF AN>EN THEN7000 7005 SA=AN:GOSUB 9000:IF A=50 THENPRINT"ANFANGSSATZ EXISTIERT NICHT!":GOTO 50 7006 SA=EN:GOSUB 9000:IF A=50 THENPRINT"ENDSATZ ZU HOCH!":GOTO 50 7009 AN=AN-1:PRINT"{DOWN}BITTE WAEHLEN SIE:{DOWN} 7010 PRINT,"NORMALE AUSGABE ..1 7011 PRINT,"D.Z.S. AUSGABE ...2 7012 PRINT,"EIGENE ROUTINE ...3 7013 GET I$:IF VAL (I$)<1OR VAL (I$)>3 THEN7013 7014 IF I$="2" THEN7100 7015 IF I$="3" THEN7500 7020 REM NORMALE AUSGABE -------------- 7021 OPEN 3,4,7:FOR SA=AN TOEN:GOSUB 9000:GOSUB 9100 7025 IF A=50 THENPRINT"SATZNUMMER ZU HOCH!":SA=EN+1:GOTO 7045 7035 PRINT#3,RIGHT$ ("{3SPACE}"+STR$ (SA),3);"{4SPACE}"; 7036 FOR I=1 TOLEN (A$) 7037 :IF MID$ (A$,I,1)="{RIGHT}" THENPRINT#3," ";:GOTO 7040 7038 :PRINT#3,MID$ (A$,I,1); 7040 NEXT I:PRINT#3 7045 NEXT SA:CLOSE 3:PRINT:GOTO 50 7100 REM D.Z.SATZ AUSGABE ------------- 7101 OPEN 3,4,7:FOR SA=AN TOEN:GOSUB 9000:GOSUB 9100 7102 IF A=50 THENSA=EN+1:PRINT"SATZNUMMER ZU HOCH!":GOTO 7170 7110 PRINT#3,RIGHT$ ("{4SPACE}"+STR$ (SA),4);"{4SPACE}"; 7120 FOR I=1 TOLEN (A$):IF A$="" THEN7160 7125 IM=ASC (MID$ (A$,I,1)) 7126 IF IM>64AND IM<91OR IM>96AND IM<123 THEN7160 7130 IF IM=64 THENPRINT#3,CHR$ (34);:GOTO 7170 7135 IF IM=173 THENPRINT#3,"AE";:GOTO 7170 7140 IF IM=174 THENPRINT#3,"SS";:GOTO 7170 7145 IF IM=176 THENPRINT#3,"AE";:GOTO 7170 7150 IF IM=181 THENPRINT#3,"UE";:GOTO 7170 7152 IF IM=182 THENPRINT#3,"OE";:GOTO 7170 7154 IF IM=184 THENPRINT#3,"UE";:GOTO 7170 7156 IF IM=185 THENPRINT#3,"OE";:GOTO 7170 7158 IF IM=94 THENPRINT#3,":"; :GOTO 7170 7159 IF IM=95 THENPRINT#3,","; :GOTO 7170 7160 PRINT#3,CHR$ (IM); 7170 NEXT I:PRINT#3,CHR$ (13); 7175 NEXT SA:CLOSE 3:PRINT:GOTO 50 7500 REM EIGENE ROUTINE --------------- 7510 PRINT"{DOWN}SIE HABEN NOCH KEINE EIGENE PRINTER-{SPACE,3SPACE}ROUTINE DEFFINIERT!":GOTO 50 8000 REM TEXT ZENTRIEREN ------------------------------------------------------- 8001 I$="":FOR I=1 TO10:I$=I$+"{20RIGHT}":NEXT I 8002 REM SL=27 8005 REM DIMM$(300):GOTO8100 8010 PRINT"{RVSON}ZENTRIEREN{RVOFF,DOWN}" 8015 PRINT"BITTE GEBEN SIE ANFANG UND ENDE DES{5SPACE}BEREICHES AN, DER ZENTRIERT"; 8017 PRINT" WERDEN SOLL!" 8020 INPUT"ANFANG";AN:INPUT"ENDE";EN:IF AN>EN THEN50 8025 SA=AN:GOSUB 9000:IF A=50 THENPRINT"ANFANGSSATZ EXISTIERT NICHT!":GOTO 50 8030 SA=EN:GOSUB 9000:IF A=50 THENPRINT"ENDSATZ ZU HOCH!":GOTO 50 8040 PRINT"{DOWN}LINKS-RECHTS BUENDIG ZENTRIEREN{SPACE,SPACE}- '1' 8045 PRINT"{DOWN}IN DIE MITTE ZENTRIEREN{SPACE,9SPACE}- '2' 8050 GET II$:IF II$<>"1"AND II$<>"2" THEN8050 8060 IF II$="2" THEN8600 8099 REM LINKS-RECHTS BUENDIG --------- 8100 PRINT"{DOWN}BITTE WARTEN!" 8105 FOR SA=AN TOEN 8110 : GOSUB 9000:GOSUB 9100:PRINTA$:PRINTLEN (A$) 8120 : IF RIGHT$ (A$,1)=CHR$ (29) THENA$=LEFT$ (A$,LEN (A$)-1):GOTO 8120 8122 : IF RIGHT$ (A$,1)=CHR$ (32) THENA$=LEFT$ (A$,LEN (A$)-1):GOTO 8122 8123 : IF RIGHT$ (A$,1)=CHR$ (160) THENA$=LEFT$ (A$,LEN (A$)-1):GOTO 8123 8130 : IF LEN (A$)=SL THEN8520 8200 : LU=SL-LEN (A$):PRINT"LU=";LU 8220 : I=1:IC=0:REM BEGIN LOOP ------- 8222 : 8230 : IF MID$ (A$,I,1)=""OR MID$ (A$,I+1,1)="" THEN8530 8231 : IM=ASC (MID$ (A$,I,1)) 8232 : IN=ASC (MID$ (A$,I+1,1)) 8240 : IF IM<>29AND IM<>32AND IM<>160 THEN8500 8250 : IF IN=29OR IN=32OR IN=160 THEN8500 8260 : A$=LEFT$ (A$,I)+" "+RIGHT$ (A$,LEN (A$)-I) 8261 : IF MID$ (A$,I,1)=" "OR MID$ (A$,I,1)="{RIGHT}" THENI=I+1:GOTO 8261 8265 : LU=LU-1:IF LU=0 THEN8510 8266 : IC=IC+1 8270 : I=I+1 8400 : 8500 I=I+1 8505 IF I<LEN (A$) THEN8230 8510 IF I=LEN (A$)AND LU>0AND IC<>0 THEN8220 8520 GOSUB 9000:GOSUB 9200 8530 NEXT SA 8540 GOTO 50 8600 REM IN DIE MITTE ----------------- 8610 PRINT"{DOWN}BITTE WARTEN!" 8620 FOR SA=AN TOEN 8630 :GOSUB 9000:GOSUB 9100 8635 IF RIGHT$ (A$,1)=CHR$ (29) THENA$=LEFT$ (A$,LEN (A$)-1):GOTO 8635 8636 IF RIGHT$ (A$,1)=CHR$ (32) THENA$=LEFT$ (A$,LEN (A$)-1):GOTO 8636 8637 IF RIGHT$ (A$,1)=CHR$ (160) THENA$=LEFT$ (A$,LEN (A$)-1):GOTO 8637 8640 :IF LEN (A$)=SL THEN8680 8650 :AS=SL-LEN (A$): REM ANZAHL SPACES 8660 :A$=LEFT$ (I$,AS/2)+A$ 8670 :GOSUB 9000:GOSUB 9200 8680 NEXT SA 8690 GOTO 50 9000 REM POSITIONIEREN **************** 9010 HB%=SA/256:LB%=SA-HB%*256 9020 PRINT#15,"P";CHR$ (3);CHR$ (LB%);CHR$ (HB%);CHR$ (1) 9030 INPUT#15,A 9040 RETURN 9100 REM SATZ EINLESEN **************** 9110 IF A=50 THENRETURN 9120 INPUT#1,A$ 9140 RETURN 9200 REM SATZ SCHREIBEN *************** 9210 IF A=50 THENRETURN 9211 IF A$="" THENA$=":":REM ZUR SICHERHEIT !!! 9220 PRINT#1,A$ 9240 RETURN 9900 REM BEENDEN ********************** 9910 CLOSE 1 9920 CLOSE 15 9930 PRINT "++ ENDE ++" 9940 END 10000 REM SORT-ALOGARITHMUS ---------------------------------------------------- 10010 PRINT"{CLR,RVSON}DATEI SORTIEREN{RVOFF}" 10100 PRINT"BITTE GEBEN SIE ANFANG UND ENDE DES ZU{2SPACE}SORTIERENDEN BEREICHS AN ! 10110 INPUT"ANFANG";AN 10120 INPUT"ENDE"; EN 10122 SA=AN:GOSUB 9000:IF A=50 THENPRINT"ANFANGSSATZ EXISTIERT NICHT!":GOTO 50 10124 SA=EN:GOSUB 9000:IF A=50 THENPRINT"ENDSATZ ZU HOCH!":GOTO 50 10130 IF EN-AN>M% THENPRINT"BEREICH ZU GROSS!":GOTO 50 10135 PRINT"{DOWN}BITTE MARKIEREN SIE DEN BEREICH INNER- 10136 PRINT"HALB DER STRINGS NACH DEM DIE SORTIERUNG"; 10137 PRINT"ERFOLGEN SOLL!" 10138 SA=AN:GOSUB 9000:GOSUB 9100:IF A=50 THENPRINT"SATZ EXISTIERT NICHT!":GOTO 50 10139 IG=-1:GOSUB 60000:IG=0 10140 IC=0:FOR SA=AN TOEN:IC=IC+1 10145 :GOSUB 9000:GOSUB 9100:IF A=50 THENPRINT"SATZNUMMER ZU HOCH!":SA=EN+1:GOTO 1015 10146 :M$(IC)=A$ 10150 NEXT SA 10180 Z=0:LG(1)=1:RG(1)=EN-AN+1 10190 GOSUB 10200:GOTO 10400 10200 REM QUICKSORT ----------------- 10210 Z=Z+1:IF LG(Z)>=RG(Z) THEN10350 10220 X=LG(Z):Y=RG(Z) 10230 VG$=MID$ (M$(INT ((X+Y)/2)),MA(1),MB(1)-MA(1)+1) 10240 IF X>Y THEN10330 10250 IF MID$ (M$(X),MA(1),MB(1)-MA(1)+1)<VG$ THENX=X+1:GOTO 10250 10260 IF MID$ (M$(Y),MA(1),MB(1)-MA(1)+1)>VG$ THENY=Y-1:GOTO 10260 10270 IF X>Y THEN10330 10280 S$=M$(X):M$(X)=M$(Y):M$(Y)=S$ 10290 X=X+1:Y=Y-1:GOTO 10240 10330 RG(Z+1)=Y:LG(Z+1)=LG(Z):GOSUB 10210 10340 LG(Z+1)=X:RG(Z+1)=RG(Z):GOSUB 10210 10350 Z=Z-1:RETURN 10400 REM ABSPEICHERN 10410 IC=0:FOR SA=AN TOEN:IC=IC+1 10420 :GOSUB 9000:A$=M$(IC):GOSUB 9200 10430 NEXT SA 10500 GOTO 50 20000 REM KORREKTUR-ROUTINE ---------------------------------------------------- 20001 FOR I=0 TOLEN (BE$):PRINTCHR$ (20);:NEXT I:BE$="" 20010 SA=SA+SC : REM TEXTZEIGER 20015 IF SA<0 THENSA=0 : REM SPERRE 20020 GOSUB 9000 : REM POSITIONIEREN 20025 IF A=50 THENSA=SA-SC:GOTO 20020 20040 INPUT#1,A$ 20060 RETURN 30000 REM DIRECTORY EINLESEN --------------------------------------------------- 30010 PRINT"{CLR,RVSON}INHALTSVERZEICHNIS:{DOWN,RVOFF}" 30110 PRINT#15,"IO":OPEN 2,8,2,"#" 30120 T=18:S=1 30130 PRINT#15,"U1";2;0;T;S 30140 PRINT#15,"B-P";2;0 30150 GET #2,X$:IF X$="" THENX$=CHR$ (0) 30160 T=ASC (X$) 30170 GET #2,X$:IF X$="" THENX$=CHR$ (0) 30180 S=ASC (X$) 30190 FOR X=0 TO7 30192 PRINT#15,"B-P";2;X*32+2 30194 GET #2,X$:IF X$="" THENX$=CHR$ (0) 30196 IF ASC (X$)=0 THEN30290 30200 PRINT#15,"B-P";2;X*32+5 30210 FF$="" 30220 FOR Y=0 TO15 30230 GET #2,X$:IF X$="" THENX$=CHR$ (0) 30240 IF ASC (X$)=160 THEN30270 30250 FF$=FF$+X$ 30260 NEXT Y 30270 IF A=0 THENA=1:PRINTFF$;:GOTO 30290 30280 A=0:PRINTTAB( 19);"M ";FF$ 30290 NEXT X 30300 IF T<>0 THEN30130 30310 CLOSE 2:PRINT 30330 GOTO 50 50000 REM TEXTEINGABE ---------------------------------------------------------- 50002 IF AR=1 THENAR=0:A$="{RVSON}SATZNUMMER ZU HOCH!{RVOFF}":GOTO 50007 50006 GOSUB 9000:GOSUB 9100 50007 PRINT"{HOME,40SPACE}";:PRINT"{HOME,RVSON}SATZ";SA;"{RVOFF}" 50008 PRINT"{40SPACE,UP}";A$ 50009 PRINT"TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT"; 50010 PRINT"{DOWN}" 50020 BE$="":POKE 198,0:POKE 211,0:POKE 214,22:SYS 58732:PRINT"{YELLOW}@"; 50030 GET X$:IF PEEK (203)=1 THEN50120 50035 IF PEEK (203)=7AND PEEK (653)=0 THEN SC=1 : GOSUB 20000 : GOTO 50000 50037 IF PEEK (203)=7AND PEEK (653)=1 THEN SC=-1 : GOSUB 20000 : GOTO 50000 50040 IF X$="" THEN50030 50050 IF LEN (BE$)=0AND ASC (X$)=20 THEN50030 50060 I=ASC (X$):IF I<32OR I>133AND I<159 THENIF I<>20 THEN50030 50070 IF LEN (BE$)=SLAND I<>20 THEN50030 50075 IF X$=" " THENX$="{RIGHT}" 50080 BE$=BE$+X$ 50090 PRINTCHR$ (20);X$;"@"; 50100 IF I=20 THENBE$=LEFT$ (BE$,LEN (BE$)-2):GOTO 50030 50110 GOTO 50030 50120 PRINT"{LEFT,SPACE}":GOSUB 9000 50130 IF BE$="" THENBE$=":" 50140 PRINT"{HOME,24DOWN}":RETURN 60000 REM MARKIEREN WIE DATIERT WIRD ------------------------------------------- 60010 I$=A$:FOR I=1 TOLEN (I$):M$(I)="0":NEXT 60020 IM=1:PRINT 60025 PRINTI$:FOR I=1 TOLEN (I$):PRINTM$(I);:NEXT :PRINT 60030 PRINT"{DOWN}EDIT{SPACE,2SPACE}>";MID$ (I$,IM,1);"<{4SPACE,RVSON}"IM"{RVOFF}" 60035 I=PEEK (203):IF I=64 THEN60035 60040 IF I=2AND PEEK (653)=0AND IM<>LEN (I$) THENIM=IM+1:GOTO 60070 60042 IF I=2AND PEEK (653)=1AND IM<>1 THENIM=IM-1:GOTO 60070 60045 IF I=56 THENM$(IM)="1":GOTO 60070 60055 IF I=35 THENM$(IM)="0":GOTO 60070 60056 IF I=1 THEN60100 60060 GOTO 60035 60070 PRINT"{UP,25SPACE,25LEFT,3UP}";:GOTO 60025 60100 REM UMRECHNEN 60102 FOR I=1 TO3:MA(I)=0:MB(I)=0:NEXT I 60105 FOR II=1 TO3 60110 :FOR I=1 TOLEN (I$):IF MA(II)=0AND M$(I)=RIGHT$ (STR$ (II),1) THENMA(II)=I 60113 NEXT I 60115 :IF MA(II)=0 THEN60150 60120 : MB(II)=MA(II) 60125 : MB(II)=MB(II)+1:REM PRINT,MB(II) 60130 : IF M$(MB(II))<>RIGHT$ (STR$ (II),1) THENMB(II)=MB(II)-1:GOTO 60150 60135 : IF MB(II)=LEN (I$) THEN60150 60140 : GOTO 60125 60150 NEXT II 60170 PRINT"DIE SORTIERUNG ERFOLGT NACH FOLGENDEM{3SPACE}STRINGBEREICH:" 60180 PRINT"{RVSON}"MID$ (I$,MA(1),MB(1)-MA(1)+1)"{RVOFF}" 60190 RETURN 61000 REM FLOPPY-FEHLERKANAL LESEN --------------------------------------------- 61005 PRINT"{CLR,RVSON}FLOPPY-FEHLERKANALABFRAGE{RVOFF}" 61010 INPUT#15,A,B$,C,D 61020 PRINTA,B$,C,D 61040 GOTO 50
Dokumentation zum Listing 23
Wichtigste Variablen:
| M% | Größe des Stringpuffers |
| M$(1)-M$(M%), M(1)-M(M$) | Stringpuffer |
| LG, RG | werden für Quicksort-Unterprogramme benötigt |
| A$ | von relativer Datei eingelesener String beziehungsweise String, der auf relative Datei geschrieben wird. |
| SA | Satz, auf den der Zeiger der relativen Datei positioniert ist beziehungsweise werden soll. |
| D$ | Filename der Datei |
| LB, HB | Lowbyte, Highbyte |
| AN, EN | Anfang beziehungsweise Ende eines Bereiches der relativen Datei, der sortiert beziehungsweise ausgedruckt werden soll. |
Variablen beziehungsweise Stringnamen, die mit I beginnen, sind Hilfsvariablen beziehungsweise Strings, die sich während dem Programmablauf ständig ändern – zum Beispiel Schleifen, Prüfvariablen etc.
| 0 - 15 | Vorbereitungen, Felder dimensionieren etc. |
| 19 - 30 | Anfangsmenü |
| 31 - 41 | Anlegen einer neuen Datei. |
| 50 - 70 | Zwischenmenü beziehungsweise Menüzeile. Dies ist der Hauptteil des Programmes. Von hier aus werden alle möglichen Routinen angewählt. |
| 2000 - 2050 | Routine zum Lesen von Sätzen einer relativen Datei. |
| 3000 - 3070 | Routine zum Schreiben von Sätzen in eine relative Datei. |
| 6000 - 6270 | Routine zum Versetzen bestimmter Bereiche einer relativen Datei innerhalb derselben oder auf andere relative Dateien. Die Daten werden jeweils zuerst von der Quelldatei in den Puffer M% gelesen. Dann wird die Quelldatei geschlossen und die Zieldatei geöffnet. Sodann wird der Pufferinhalt in die Zieldatei übertragen etc. |
| 7000 - 7510 | Printer-Routine zum Ausdrucken von relativen Dateien. |
| 7500 - 7999 | In diesem Bereich können Sie eine eigene Printer-Routine unterbringen. So kann zum Beispiel der deutsche Zeichensatz Ihrem Drucker angepaßt werden. |
| 8000 - 8690 | Verschiedene Routinen zur formatierten Textausgabe einer relativen Datei. |
| 9000 - 9140 | Die bereits vorgestellten Unterprogramme zum Lesen, Schreiben, Positionieren und Schließen einer Datei. |
| 10000 - 10500 | Routine zum Sortieren eines bestimmten Bereiches innerhalb der Datei. Der gewünschte Bereich, der aus maximal M% Sätzen bestehen darf, wird in den Stringpuffer eingelesen. Nun wird der Stringpuffer mittels einem Quicksort-Algorithmus (10200 - 10350) sortiert und anschließend wieder in die Datei auf Disk zurückgeschrieben. |
| 20000 - 20060 | Korrekturroutine – »Blättern« in einer relativen Datei mittels den Cursor-Tasten. Diese Routine wird von der Texteingaberoutine ab 50000 aufgerufen. |
| 30000 - 30330 | Unterprogramm zum Einlesen des Disk-Directories ohne Programmverlust. |
| 50000 - 50140 | Modifizierte INPUT-Routine. |
| 60000 - 60190 | Unterprogramm zum Markieren des Stringbereiches, der beim Sortieren als Kriterium gelten soll. |
| 61000 - 61040 | Unterprogramm zur Abfrage des Fehlerkanals des Floppy-Laufwerks. |
Die Dokumentation ist sehr oberflächlich; ich glaube nicht, daß am Editor noch viel verbessert werden kann. Mit dem Editor bekommen Sie relative Dateien in den Griff. Mit Hilfe der drei Unterprogramme zum Lesen, Schreiben und Positionieren können Sie sich leicht die unterschiedlichsten Dateiverwaltungen mittels kurzer Basic-Programme zusammenbasteln. Dazu müssen Sie jedoch wissen, wie mit dem Editor umzugehen ist:
Bedienungsanleitung zum Texteditor
Nach dem Programmstart mittels RUN erscheint das Grundmenü. Zum einen kann von hier aus ein alternativer Zeichensatz geladen werden, der mit dem Zeichensatz-Editor erstellt werden beziehungsweise ab hexadezimal E000 liegen muß. Der Zeichensatz muß den Namen »D.Z.SATZ« haben (beziehungsweise Name im Listing Zeile 27 umändern). Es bietet sich also an, mit dem Zeichensatz-Editor einen deutschen Zeichensatz automatisch generieren zu lassen und ihn dann als D.Z.SATZ auf die Disk, auf der sich auch der Editor befindet, zu speichern. Zum Laden des Alternativ-Zeichensatzes muß einfach die Taste »D« gedrückt werden. Wenn das Disketten-Laufwerk wieder zum Stillstand kommt, befindet sich der neue Zeichensatz im Computer. Natürlich hätte ich nach dem Filenamen des Zeichensatzes fragen können, nachdem »D« gedrückt wurde.
Ich habe mich jedoch für diese Lösung entschieden, da sich dadurch viel schneller mit dem Texteditor arbeiten läßt: Man muß so nicht erst mehrere Parameter eingeben, bevor man mit dem Arbeiten beginnen kann.
Mittels »N« kann eine neue Datei angelegt werden. Sie müssen lediglich den Dateinamen (Filenamen), maximal erlaubte Satzlänge und die Satzanzahl der Datei angeben. Dann eine Disk mit genügend freiem Platz einlegen und RETURN drücken. Je nach Größe der Datei kann es einige Zeit in Anspruch nehmen, bis der Computer hiermit fertig ist. Das Anlegen einer relativen Datei wird so zum Kinderspiel. Mittels »A« wird eine alte Datei zum weiteren Bearbeiten geöffnet. Es ist hierzu erforderlich, daß sich die Disk, auf der sich die Datei befindet, im Laufwerk befindet (logisch). Ganz Eilige können auch einfach mehrmals die RETURN-Taste drücken. Dann wird einfach das erste Programm der Disk, die sich im Laufwerk befindet, geladen und die maximale Zeichenanzahl auf 40 festgelegt.
Achtung: Beim Anlegen einer relativen Datei ist immer folgendes zu beachten: Soll die Satzlänge einer Datei 40 Zeichen betragen, so muß man beim Anlegen der Datei als maximale Satzlänge 41 angeben – also immer eins mehr als die Satzlänge später tatsächlich sein soll. Dieser eine Platz beziehungsweise Zeichen wird nämlich für das CHR$(13) (Carriage Return) benötigt, das am Satzende jeweils angehängt wird! Es ist in bedingten Fällen auch möglich, auf das CHR$(13) zu verzichten. Man muß dann jedoch »ganz cool« über die zahlreich auftretenden FILE DATA beziehungsweise OVERFLOW IN RECORD ERRORs hinwegsehen!
Ich rate Ihnen jedenfalls, nicht an diesem einen Zeichen zu sparen!
Nach Anwahl des ersten oder zweiten Grundmenü-Punktes (alte Datei eröffnen beziehungsweise neue anlegen) gelangt man ins Hauptmenü, das aus einer einzigen Zeile besteht:
s/l/v/o/d/i/z/f/ende
Die einzelnen Buchstaben beziehungsweise Tasten stehen für folgende Funktionen:
| s | – schreiben |
| l | – lesen |
| v | – versetzen |
| o | – ordnen |
| d | |
| i | – Inhaltsverzeichnis |
| z | – zentrieren |
| f | – Fehlerkanal abfragen |
| ende | – na, was wohl? |
Die Menüpunkte im einzelnen
Schreiben
Zuerst muß die Satznummer eingegeben werden, ab der man schreiben will. Danach erscheint das Satzeingabe-Bild. In der obersten Bildschirmzeile sind Satznummer sowie momentaner Inhalt des Satzes zu sehen. Der Cursor steht in der untersten Zeile des Bildschirms. Dort kann ein neuer Satzinhalt eingegeben werden. Die Cursor-Tasten sind hierbei in ihrer Funktion zum Bewegen des eigentlichen Cursors außer Gefecht gesetzt. Auch alle anderen Steuertasten (zum Beispiel Farbwahl und CLR), außer der DEL-Funktion, funktionieren nicht mehr. Wie ich anfangs bereits sagte, kann bei einem Texteditor für relative Dateien keineswegs an die Vorzüge eines Textverarbeitungssystems gedacht werden. Sie können lediglich gesamte Sätze schreiben und diese mit RETURN absenden, wobei Sie automatisch in die nächste Zeile gelangen. Mit den Tasten CURSOR UP und CURSOR DOWN kann der Text nach oben beziehungsweise unten gescrollt werden.
Durch Eingabe von // wird die Texteingabe beendet – das Hauptmenü erscheint wieder.
Einen gewaltigen Vorteil hat ein Relative-Dateien-Editor allerdings gegenüber einem Textverarbeitungssystem: Bei einem Stromausfall gehen nur die Daten verloren, die sich gerade im Puffer des Floppy-Laufwerks befinden (meist nur 2 - 3 Sätze bei 40 Zeichen Satzlänge)!
Lesen
Auch hier muß nur die Satznummer des Satzes eingegeben werden, ab der gelesen werden soll. Auf dem Bildschirm erscheinen sodann die 20 Sätze ab dem eingegebenen Wert. Wenn Sie nach der Satznummer gefragt werden und einfach die RETURN-Taste drücken, ohne eine Zahl einzugeben, so wird ab dort weitergelesen beziehungsweise geschrieben, wo zuletzt aufgehört worden ist – also, wo der Zeiger gerade steht.
Versetzen
Diese Funktion erlaubt es, Teilbereiche (auch ganze relative Dateien) innerhalb einer Datei zu versetzen beziehungsweise auf eine andere Datei zu übertragen. Zunächst müssen Anfang und Ende des Bereiches angegeben werden, der versetzt werden soll. Danach muß die Zieldatei angegeben werden. Soll innerhalb der relativen Datei versetzt werden, so muß als Zieldatei einfach der Name der Quelldatei (= Datei, mit der gerade gearbeitet wird) angegeben werden.
Letztendlich muß nur noch der Anfang des neuen Bereiches angegeben werden. Beachten Sie bitte, daß die Satzlänge der Zieldatei entweder größer oder mindestens genauso groß wie die der Quelldatei sein muß!
Ordnen
Heißt nichts anderes als Sortieren einer Datei. Zunächst wird der Bereich angegeben, der sortiert werden soll (darf nicht größer als 300 Sätze sein!). Dann muß markiert werden, welcher Teilbereich beziehungsweise welcher Schlüssel der Datei als Sortierkriterium gelten soll. Mittels den Cursortasten LEFT-RIGHT wird der Bereich markiert. Als Kriterium gilt der Bereich, unter dem Einser stehen. Mit den Tasten 1 und 0 können solche Einser gesetzt beziehungsweise gelöscht werden. Beispiel:
001BETRACHTEN 56
00111111111111100
Nun gilt zum Beispiel bei einer Verben-Tabelle, wie wir sie schon bald kennenlernen werden, das Verb als Kriterium.
Zur Sortierroutine, die im Editor integriert ist, ist zu bemerken, daß sie nur für kleine Bereiche geeignet ist, deren Satzlänge kleiner als 40 Zeichen ist. In einem anderen Kapitel dieses Kurses finden Sie noch ein Sortierprogramm für riesige Dateien mit Satzlängen bis zu 255 Zeichen!
Durch Aufruf dieser Routine können relative Dateien beziehungsweise Teilbereiche einer Datei mit einem Drucker ausgegeben werden. Das Druckermenü bietet dann folgende Auswahlmöglichkeiten:
Normale Ausgabe: Der Text wird so ausgegeben, wie er mit dem normalen Zeichensatz des C 64 aussieht.
D.z.s.-Ausgabe: Ein Text, der mit Hilfe des deutschen Zeichensatzes erstellt wurde, wird umgerechnet ausgegeben. Das heißt, anstelle von ß wird ss, anstelle von ä ein ae ausgedruckt etc. Dies ist nötig, da die Druckerroutine für einen MPS-801 »Billigdrucker« geschrieben wurde.
Eigene Routine: Wenn Sie einen besseren Drucker haben (Neid!!!), können Sie sich eine eigene Druckroutine schreiben (siehe Listingsdokumentation). Wer will, kann mittels Bit-Gefummel den deutschen Zeichensatz auch auf einem MPS-801 darstellen.
Inhaltsverzeichnis
Hier wird das Directory ohne Programmverlust ausgegeben.
Zentrieren
Durch diese Routine können Dateibereiche zentriert werden. Es ist so möglich, den Text formatiert auszugeben. Es gibt zwei Formatierungsmöglichkeiten: Links-rechtsbündig zentriert und in die Mitte zentrieren. Bei ersterem werden einfach soviele SPACEs eingefügt, bis der Text am linken und rechten Rand anstößt. Beim in die Mitte zentrieren wird jeder Satz so dargestellt, daß links und rechts gleich viele SPACEs sind.
Fehlerkanalabfrage:
Hiermit kann der Fehlerkanal des Floppy-Laufwerks abgefragt werden.
Ende
Muß unbedingt betätigt werden, wenn man die Arbeit an einem Text beenden will. Nur dann wird gewährleistet, daß der Puffer des Diskettenlaufwerks auf Disk gespeichert wird und so keine Daten verloren gehen können.
Sie können den Editor auch jederzeit mittels RUN/STOP-Taste verlassen. Mittels GOTO 50 im Direktmodus kommen Sie dann wieder ins Hauptmenü.
Dies ist auch schon alles, was Sie zum Umgang mit dem Editor wissen müssen. Wir werden im Rahmen des Kurses den Editor noch oft benötigen. Ich rate Ihnen deshalb, sich schon jetzt mit seinen einzelnen Funktionen gründlich vertraut zu machen. Im nächsten Kapitel finden Sie ein weiteres Utility, das ebenfalls (wie der Zeichensatz-Editor) direkt auf den Text-Editor für relative Dateien anwendbar ist: Ein Sortierprogramm für relative Dateien.
Das Sortieren von riesigen Datenfeldern (relativen Dateien)
Bisher haben wir immer nur kleine Tabellen sortiert. Also Dateien, die immer komplett im Computer standen. Unser Texteditor für relative Dateien ist zwar in der Lage, riesige Dateien in den Griff zu bekommen, was jedoch das Sortieren von Datenfeldern anbelangt, so bedient er sich eines kleinen Kunstgriffes. Der Dateiabschnitt, der sortiert werden soll, wird komplett in ein Puffer-Stringfeld innerhalb des Basic-RAMs geladen. Dieser Puffer wird sodann mit Hilfe eines Quicksort-Unterprogramms sortiert. Anschließend wird der Pufferinhalt wieder auf die relative Datei auf Disk übertragen. Die »Sortierkapazität« unseres Texteditors ist also auf die Größe des Puffers und somit auf den Speicher des C 64 begrenzt. Dieser Puffer kann nicht als besonders groß bezeichnet werden. Sie werden mit Sicherheit sehr bald auf seine Grenzen stoßen. Spätestens dann wird es kritisch, wenn man den Wortschatz zu einem Adventure sortieren will. Wir haben schließlich vor, Wortschätze mit über 1000 Worten zu verwalten – und die wollen erst einmal sortiert sein.
Wir brauchen also ein Sortierprogramm, das in der Lage ist, Dateien von Disk zu sortieren, die nicht auf einmal in den Speicher unseres C 64 geladen werden können.
Das Programmieren von solchen Datei-Sortier-Programmen ist eine äußerst haarige Sache. Auch die besten Programmierer müssen sich vor dem Programmieren lange und genaue Überlegungen und Skizzen auf Papier zeichnen, um genauen Durchblick zu behalten. Die Schwierigkeit liegt weniger bei der komplizierten Programmiertechnik, die erforderlich ist, sondern vielmehr bei den Grundüberlegungen, die gemacht werden müssen. Ähnlich wie bei den einzelnen Sortieralgorithmen (Vgl. Kurs über effektives Programmieren in der Zeitschrift 64'er) gibt es auch hier zahlreiche Möglichkeiten zum Lösen des Problems. Die Grundroutinen sind zwar immer die gleichen, es gibt jedoch unendlich viele Möglichkeiten, sie zu kombinieren. Auch die besten Sortiermethoden für Dateien, die komplett auf einmal im Speicher sind, setzen sich aus mehreren Sortieralgorithmen zusammen (zum Beispiel Quicksort und Bubblesort kombiniert), um eine möglichst schnelle Ausführungszeit zu erreichen. Bei diskorientierten Sortierprogrammen spielt neben dem Zeitaufwand, der zum Sortieren gebraucht wird, auch noch ein anderer Faktor eine dominierende Rolle: Die Zahl der Zugriffe auf Disk. Selbstverständlich ist es möglich, unser Quicksort-Unterprogramm (Listing 8) mit wenig Aufwand so zu modifizieren, daß es in der Lage ist, eine riesige Datei auf Disk zu sortieren. Dabei wären bei großen Dateien zehntausende, ja bis zu über 100000 Schreib-Lese-Operationen mit dem Diskettenlaufwerk notwendig. Sie werden mir sicherlich zustimmen, wenn ich nun behaupte, daß eine solche Sortiermethode eine unglaublich große Strapaze für unser Floppy-Laufwerk (ganz zu schweigen von der Diskette, die dieser Tortur ausgesetzt wird) wäre.
Eine andere Methode wäre es zum Beispiel, mehrere Disketten zu verwenden. Zum Beispiel die Quelldisk, auf der sich die Tabelle befindet und eine weitere Disk, auf der das Sortierprogramm zwischenspeichern kann. Dies wäre zumindest schonend für die Diskette.
Beide Methoden sind jedoch äußerst zeitaufwendig. Eine Methode, möglichst schnell zu sortieren, ist es, im Speicher des C 64 nur die Satznummern zu puffern und diese zu sortieren, um dann gemäß dieser sortierten Satznummern die ganze Datei umzuordnen. Während man mit der allereinfachsten Methode (gesamte Datei auf einmal im Speicher) etwa 1000 Sätze (mittlerer Länge) sortieren kann, erlaubt die zuletzt vorgestellte Methode schon ein Vielfaches dieser Kapazität.
Die Methode hat jedoch wieder eine Einschränkung: Man kann eine bestimmte Satzanzahl (Größe des Satznummern-Puffers im Speicher) nicht überschreiten.
Wir wollen keine halben Sachen machen und ein absolut endgültiges Sortierprogramm schreiben, das folgende Eigenschaften haben soll:
— Es kann jede Datei, die sich auf einer Diskettenseite in Form einer relativen Datei unterbringen läßt, sortiert werden.
— Die Sortierung muß möglichst Disketten- und Floppy-Laufwerk-schonend ablaufen. Auf schnelle Ausführungszeit wird deshalb weniger Wert gelegt.
Auf den folgenden Seiten werde ich Ihnen anhand von Ablaufplänen und Schemen ausführlich erläutern, wie man eine solche Sortierung programmiert. Diese Grundlagen sind übrigens in keinster Weise auf den C 64 beschränkt – sie gelten auch für jedes andere Computersystem, mit dem relative Dateien verwaltet werden können.
Lehnen Sie sich also gemütlich in Ihren Sessel, besorgen Sie sich was zu knabbern und/oder trinken und studieren Sie ausführlich die folgenden Seiten!
Anhand der folgenden Skizzen wollen wir uns zunächst einmal grundlegende Gedanken über das Sortieren von riesigen Dateien machen. Das Verstehen der Programmiertechnik wird Ihnen dann viel leichter fallen. Zunächst benötigen wir wieder eine Ausgangsdatei. Um es möglichst einfach zu halten, beschränke ich mich hier auf eine Datei mit nur einem Schlüssel: Eine Verben-Datei, wie sie uns im Laufe des Kurses noch oft in ähnlicher Form begegnen wird. Diese Beispielsdatei sehen Sie in Bild 6a. Die »Ausgangsdatei« besteht aus 15 Sätzen – Verben in total ungeordneter Reihenfolge. Wir wollen einmal annehmen, daß der Speicher unseres C 64 viel zu klein ist, um diese Datei auf einmal fassen zu können – beziehungsweise die Datei zu groß ist, um komplett in den Computer geladen werden zu können. Wir benötigen also einen Puffer – ein Stringfeld, in das wir Teile der riesigen Datei laden können, um sie dort zu verändern. Das folgende Beispiel macht besonders leicht deutlich, wie so ein »Puffer« arbeitet.

Bild 6b. Datenpuffer der Kapazität M%
Bild 6c. Die M%-niedrigsten Elemente der unsortierten Ausgangsdatei (Zeiger auf 1. Satz) werden in den Puffer gelesen und dabei gleichzeitig alphabetisch geordnet (im Puffer!). Die Daten, die in den Puffer gelesen werden (bzw. die Sätze), werden gleichzeitig in der Ausgangsdatei gelöscht.
Herr Helm hat zwei große Schwimmbecken – eines leer und eines voll mit Wasser. Er möchte das leere Schwimmbecken nun mit dem Wasser des vollen Schwimmbeckens füllen. Da er das volle Becken jedoch nicht einfach hochheben, zum anderen Becken hintragen und hineinleeren kann, braucht er ein Hilfsmittel. Zum Beispiel einen Eimer, der maximal so groß sein darf, daß Herr Helm ihn im gefüllten Zustand noch gut tragen kann. Nun kann er den Eimer im vollen Becken mit Wasser vollschöpfen, zum leeren Becken gehen und den Eimer dort hineinleeren. Herr Helm muß also mehrmals zwischen den Becken hin und her laufen, bis das volle Becken leer und das vorher leere Becken voll ist. Je kleiner der Eimer ist, umso öfter muß Herr Helm zwischen den Becken hin und her gehen...
Der Eimer ist als »Puffer« beziehungsweise »Datenpuffer« zu sehen. Den »Eimer«, den wir als Puffer zum Sortieren benutzen werden, sehen Sie in Bild 6b. Er faßt bis zu 6 Sätze. In Wirklichkeit ist der Speicher des C 64 natürlich groß genug, um mehrere hundert Sätze puffern zu können. Der Einfachheithalber wollen wir uns jedoch auf einen kleinen Puffer beschränken. Unser Puffer besteht aus zwei Teilen. Im einen (M$(1)-M$(%)) werden die Sätze gespeichert. M% ist die Größe des Puffers – also 6. In der anderen Hälfte des Puffers (M$(1)-M$(M%)) wird gespeichert, an welcher Stelle die Sätze in der relativen Datei stehen – also die Nummern der Sätze.
Als Ziel des gesamten Sortiervorgangs soll die Verben-Datei alphabetisch geordnet vorliegen. Wie beginnen wir nun den Sortiervorgang? Das einzige Feld, das wir sortieren können, ist der Puffer. Wir müssen also zuerst Sätze von der Verben-Datei in den Puffer lesen, um sie dort verarbeiten zu können. Unsinnig wäre es, einfach die ersten 6 Sätze der Datei in den Puffer zu lesen und zu sortieren – das bringt nichts.
Wir durchlaufen zunächst die ganze Verben-Datei einmal von Anfang bis Ende und suchen die niedrigsten 6 Sätze heraus. Diese Sätze schreiben wir in den Puffer (Schema Bild 6c).
Das Suchschema läuft so ab, daß die 6 Sätze am Ende bereits alphabetisch im Puffer abgelegt sind. Gleichzeitig werden alle Sätze, die in den Puffer geschrieben werden, auf der Ausgangsdatei gelöscht. Nachdem der Puffer voll ist, sieht die relative Datei auf Disk wie in Bild 6d aus:

Bild 6e. Unser nächstes Ziel: Platz schaffen, um den Puffer wieder in die Ausgangsdatei zurückschreiben zu können.
Bild 6f. Hier sehen Sie, wie nach dem Aussortieren der ersten Sätze Platz zum Zurückschreiben geschaffen wird.
Betrachten wir nun einmal die ersten M%-Sätze der jetzigen Datei. An genau dieser Stelle müssen am Ende des Sortiervorgangs die 6 niedrigsten Sätze stehen. Diese Sätze befinden sich in unserem Puffer. Allerdings können wir den Puffer jetzt nicht gleich dorthin übertragen, da sich in ihm noch volle Sätze befinden, die beim Zurückschreiben des Puffers überschrieben werden würden – also verlorengingen.
Zählen wir doch einmal, wie viele volle Sätze noch im oberen Bereich stehen: genau 4 (Satz 1, 2, 3 und 5). Wir müssen also 4 Sätze »in Sicherheit« bringen, bevor der Pufferinhalt in den oberen Bereich geschrieben werden darf.
Aber wohin denn nun mit den 4 Sätzen? Sicherlich werden Sie schon festgestellt haben, daß sich im unbetroffenen Teil der Datei, der beim Übertragen des Puffers nicht überschrieben wird, genau 4 leere Stellen befinden. 4 Stellen, an denen vorher Sätze standen, die sich jetzt gerade im Puffer befinden und die später in den oberen Bereich geschrieben werden. Es bietet sich also an, die 4 Sätze im oberen Bereich in die 4 Leersätze des unteren Bereiches zu schreiben (s. Bild 6e).
Wenn Sie nicht ganz mitgekommen sind, lesen Sie bitte den Abschnitt noch mal durch. Es gibt nun verschiedene Methoden, den oberen Bereich zu räumen. Die einfachste ist, die gesamte Datei mit Schleifen zu durchlaufen. Dies erfordert jedoch wieder zahlreiche Lesezugriffe per Disk und wir haben uns vorgenommen, ein möglichst Disketten- und Laufwerk-schonendes zu entwickeln. Wir entscheiden uns deshalb für einen komplizierteren Weg, der jedoch nur sehr wenige Zugriffe mit dem Floppy-Laufwerk erfordert.
Wie ich bereits sagte, ist der Puffer in zwei Hälften unterteilt – eine für die Sätze selbst und eine für die Satznummern. Werfen Sie bitte einmal einen Blick auf Bild 6f. Hier wird gezeigt, wie die Pufferhälfte, in der die Satznummern stehen, mit der kleinsten Satzzahl beginnend sortiert wird. Hierfür verwenden wir unser Quicksort-Unterprogramm, das aus Geschwindigkeitsgründen jedoch auf Zahlen-Sortieren modifiziert wird.
Nachdem die Nummern-Pufferhälfte sortiert ist, wird ein Pufferzeiger gesetzt – der genau auf den ersten leeren Satz zeigt, der sich nicht mehr innerhalb des oberen Bereiches befindet (in unserem Beispiel SATZ 8). Dies ist nötig, damit versehentlich nicht Daten, die aus dem oberen Bereich »gerettet« werden sollen, in einen leeren Satz innerhalb des oberen Bereiches geschrieben werden.
Bild 6g zeigt schließlich, wie die 4 Sätze aus dem oberen Bereich auf die 4 leeren Stellen des unteren Bereiches verteilt werden. Nun kann der Pufferinhalt wieder in die Verben-Datei zurückgeschrieben werden (Bild 6h). Die ersten M%-Sätze der Datei sind damit alphabetisch geordnet.


Nun wird der Zeiger der Verben-Datei, der bisher an erster Stelle stand (weil wir die ganze Datei und nicht nur einen bestimmten Bereich sortieren wollen) um den Wert M% erhöht. Er zeigt sodann auf die Stelle, an der der noch unsortierte Bereich der Datei beginnt (Bild 6i). Der nächste Sortierabschnitt kann nun beginnen.

Sind alle Sortierabschnitte beendet – also jeweils Puffer mit den M% niedrigsten Elementen vom unsortierten Bereich füllen, Sätze aus dem Pufferbereich in Sicherheit bringen, Puffer zurückschreiben etc. – liegt die komplette Verben-Datei sortiert vor (Bild 6j).

Eine Frage ist noch offen ...
Woher das BIER in SATZ 16 kommt? Nun, das haben Sie sich nach diesem Brocken Theorie mit Sicherheit verdient (und ich auch!)...
Anschließend finden Sie das Listing 24 zum Sortierprogramm. Bitte studieren Sie auch die Dokumentation ausführlich, damit Sie später in der Lage sind, das Programm eigenen Vorstellungen entsprechend zu ergänzen beziehungsweise abzuändern.
0 rem ********************************
1 rem * sortierprogramm fuer *
2 rem * relative dateien *
3 rem * <c> 1985 bei *
4 rem * michael nickles *
5 rem ********************************
100 m%=300 :rem groesse des puffers
105 n%=m% :rem wert von m% speichern
110 dimm$(m%) :rem puffer fuer saetze
120 dimms(m%) :rem puffer fuer zahlen
130 dimlg(100),rg(100):z=0:lg(1)=1:rg(1)=m% :rem fuer quicksort
200 rem anfangsmenue -----------------------------------------------------------
201 :
205 poke53280,0:poke53281,0:print"{clr}{wht}";
210 print"{rvon} rel.datei sortierprogramm {rvof}";
215 print"{rvon} von michael nickles {rvof}"
220 input"{down}{down}dateiname ";n$
230 open15,8,15:rem floppykanal auf
235 print#15,"i0":rem initialisieren
240 open1,8,3,n$ :rem rel.datei oeffnen
250 input#15,a,b$:rem fehlerabfrage
254 ifa<>0thenprint"{rvon}error{rvof} ";b$:end
270 print"{down}{down} {rvon}g{rvof}anze datei sortieren
275 print"{down} {rvon}t{rvof}eilbereich sortieren
277 print"{down} {rvon}l{rvof}esen
280 geti$:ifi$<>"g"andi$<>"t"andi$<>"l"then280
281 ifi$="l"then10000
285 ifi$="g"thenan=1:goto290
286 print"{down}bitte geben sie anfang und ende des bereiches an, der sortiert";
287 print" werden soll!"
288 input"anfang ";an
289 input"ende ";en:ifan>enthenprint"unsinn!":goto295
290 print"{down}bitte geben sie an, nach welchem string-bereich die sortierung
291 print"erfolgem soll!"
292 input"{down}von zeichen ";zl
293 input"bis zeichen ";zr
295 zr=zr-zl+1
300 rem sortiervorgang anfang --------------------------------------------------
301 :
305 ti$="000000" :rem zeit auf 0
310 zd=an :rem zeiger setzen
320 zp=1 :rem pufferzeiger
325 sa=zd:gosub9000:gosub9100 :rem 1.satz lesen
330 vg$=a$ :rem vergl.satz
400 rem die m% kleinesten elemente von satz zd bis letzten satz suchen----------
401 :
402 print"{down}{rvon}";zp;"{left}. durchlauf {rvof}{down}"
410 sa=zd+1:rem ----- begin loop -----
411 printsa,a$ :rem supermike
430 :gosub9000 :rem positionieren
431 ifa=50thenm%=sa-zd:en=sa:goto500 :rem end loop
432 ifsa=en+1then500 :rem end loop
435 :gosub9100 :rem a$ lesen
440 :ifmid$(a$,zl,zr)>mid$(vg$,zl,zr)ora$="@"then460
445 : vg$=a$ :rem neuer vg$
450 : mm=sa :rem satznr. merken
460 sa=sa+1 :rem next satz
465 goto411:rem ----- cont loop -----
470 :
500 rem kleinstes element im bereich zd bis zd+m$ = vg$ wurde gefunden ---------
501 :
510 sa=mm:gosub9000:a$="@":gosub9200 :rem satz mm loeschen
520 m$(zp)=vg$:ms(zp)=mm :rem puffer fuellen
530 zp=zp+1 :rem puffer-zeiger+1
540 ifzp>m%then600 :rem puffer voll
550 rem neuen vg$ suchen
555 sa=zd
560 gosub9000:gosub9100:ifa$="@"thensa=sa+1:goto560
565 vg$=a$:mm=sa
570 goto400
600 rem puffer ist jetzt voll --------------------------------------------------
601 :
610 print"{down}{rvon}pufferinhalt{rvof}{down}":fori=1tom%:printm$(i),ms(i):nexti:rem supermike
620 rem ms(1)-ms(m%) wird jetzt mit quicksort sortiert -------------------------
621 :
625 gosub30000:rem aufruf quicks.up
630 print"{down}{rvon}sortierter nummerpuffer{rvof}{down}":fori=1tom%:printms(i):next:rem supermike
650 rem neuen pufferzeiger setzen ----------------------------------------------
651 :
655 zp=1
660 ifms(zp)<zd+m%thenzp=zp+1:goto660
670 print"zp=";zp :rem supermike
700 rem platz fuer puffer schaffen ---------------------------------------------
701 :
710 fori=zdtozd+m%-1
720 :sa=i:gosub9000:gosub9100 :rem a$ lesen
729 printa$ :rem supermike
730 :ifa$="@"then760
740 : sa=ms(zp):gosub9000:gosub9200 :rem luecke fuellen
745 : zp=zp+1 :rem p.zeiger +1
760 nexti
800 rem puffer zurueckschreiben ------------------------------------------------
801 :
810 sa=zd
820 : fori=1tom%
830 : a$=m$(i)
831 : printa$ :rem supermike
840 : gosub9000:gosub9200
850 : sa=sa+1
860 : nexti
900 rem naechsten sortiervorgang vorbereiten------------------------------------
901 :
910 ifm%<>n%oro%thenprint"sortierzeit: ";ti$:print"{down}{down}*** fertig ***":close15:end
920 zd=zd+m%:rem dateizeiger erhoehen
924 zp=1 :rem pufferzeiger auf 1
925 sa=zd
926 gosub9000:gosub9100:ifa$="@"thensa=sa+1:goto560
927 vg$=a$:mm=sa
930 ifm%>en-zdthenm%=en-zd
935 ifm%=en-zdtheno%=1
950 goto400 :rem naechste sortierrunde
9000 rem positionieren ***************
9001 :
9010 : hb%=sa/256:lb%=sa-hb%*256
9020 : print#15,"p";chr$(3);chr$(lb%);chr$(hb%);chr$(1)
9030 : input#15,a:rem fehlerabfrage
9040 return
9100 rem satz lesen ***************
9101 :
9110 : ifa=50thenreturn: rem fehler
9120 : input#1,a$
9130 return
9200 rem satz schreiben **************
9201 :
9210 : print#1,a$
9230 return
10000 rem datei lesen ----------------------------------------------------------
10001 :
10010 print"{rvon}lesen / ende{rvof}"
10015 geti$:ifi$<>"l"andi$<>"e"then10015
10020 ifi$="e"thenprint"{clr}":goto270
10030 input"satznummer ";ia
10034 print"{rvon}satz";ia;"{left}-";ia+20;"{rvof}"
10035 forsa=iatoia+20
10040 : gosub9000:gosub9100
10050 : iflen(a$)<40thenprinta$
10060 : iflen(a$)>=40thenprinta$;
10070 nextsa
10080 goto10010
30000 rem quicksort -----------------------------------------------------------
30001 rg(1)=m%
30010 z=z+1:iflg(z)>=rg(z)then30120
30020 x=lg(z):y=rg(z)
30030 vg=ms(int((x+y)/2))
30040 if x>y then30100
30050 if ms(x)<vgthenx=x+1:goto30050
30060 if ms(y)>vgtheny=y-1:goto30060
30070 if x>ythen30100
30080 s=ms(x):ms(x)=ms(y):ms(y)=s
30090 x=x+1:y=y-1:goto30040
30100 rg(z+1)=y:lg(z+1)=lg(z):gosub30010
30110 lg(z+1)=x:rg(z+1)=rg(z):gosub30010
30120 z=z-1:return
0 REM ******************************** 1 REM * SORTIERPROGRAMM FUER * 2 REM * RELATIVE DATEIEN * 3 REM * <C> 1985 BEI * 4 REM * MICHAEL NICKLES * 5 REM ******************************** 100 M%=300 :REM GROESSE DES PUFFERS 105 N%=M% :REM WERT VON M% SPEICHERN 110 DIM M$(M%) :REM PUFFER FUER SAETZE 120 DIM MS(M%) :REM PUFFER FUER ZAHLEN 130 DIM LG(100),RG(100):Z=0:LG(1)=1:RG(1)=M% :REM FUER QUICKSORT 200 REM ANFANGSMENUE ----------------------------------------------------------- 201 : 205 POKE 53280,0:POKE 53281,0:PRINT"{CLR,WHITE}"; 210 PRINT"{RVSON,6SPACE}REL.DATEI SORTIERPROGRAMM{9SPACE,RVOFF}"; 215 PRINT"{RVSON,9SPACE}VON MICHAEL NICKLES{12SPACE,RVOFF}" 220 INPUT"{2DOWN}DATEINAME ";N$ 230 OPEN 15,8,15:REM FLOPPYKANAL AUF 235 PRINT#15,"I0":REM INITIALISIEREN 240 OPEN 1,8,3,N$ :REM REL.DATEI OEFFNEN 250 INPUT#15,A,B$:REM FEHLERABFRAGE 254 IF A<>0 THENPRINT"{RVSON}ERROR{RVOFF,2SPACE}";B$:END 270 PRINT"{2DOWN,8SPACE,RVSON}G{RVOFF}ANZE DATEI SORTIEREN 275 PRINT"{DOWN,8SPACE,RVSON}T{RVOFF}EILBEREICH SORTIEREN 277 PRINT"{DOWN,8SPACE,RVSON}L{RVOFF}ESEN 280 GET I$:IF I$<>"G"AND I$<>"T"AND I$<>"L" THEN280 281 IF I$="L" THEN10000 285 IF I$="G" THENAN=1:GOTO 290 286 PRINT"{DOWN}BITTE GEBEN SIE ANFANG UND ENDE DES{SPACE,4SPACE}BEREICHES AN, DER SORTIERT"; 287 PRINT" WERDEN SOLL!" 288 INPUT"ANFANG ";AN 289 INPUT"ENDE{3SPACE}";EN:IF AN>EN THENPRINT"UNSINN!":GOTO 295 290 PRINT"{DOWN}BITTE GEBEN SIE AN, NACH WELCHEM STRING-BEREICH DIE SORTIERUNG 291 PRINT"ERFOLGEM SOLL!" 292 INPUT"{DOWN}VON ZEICHEN ";ZL 293 INPUT"BIS ZEICHEN ";ZR 295 ZR=ZR-ZL+1 300 REM SORTIERVORGANG ANFANG -------------------------------------------------- 301 : 305 TI$="000000" :REM ZEIT AUF 0 310 ZD=AN :REM ZEIGER SETZEN 320 ZP=1 :REM PUFFERZEIGER 325 SA=ZD:GOSUB 9000:GOSUB 9100 :REM 1.SATZ LESEN 330 VG$=A$ :REM VERGL.SATZ 400 REM DIE M% KLEINESTEN ELEMENTE VON SATZ ZD BIS LETZTEN SATZ SUCHEN---------- 401 : 402 PRINT"{DOWN,RVSON}";ZP;"{LEFT}. DURCHLAUF{SPACE,3SPACE,RVOFF,DOWN}" 410 SA=ZD+1:REM ----- BEGIN LOOP ----- 411 PRINTSA,A$ :REM SUPERMIKE 430 :GOSUB 9000 :REM POSITIONIEREN 431 IF A=50 THENM%=SA-ZD:EN=SA:GOTO 500 :REM END LOOP 432 IF SA=EN+1 THEN500 :REM END LOOP 435 :GOSUB 9100 :REM A$ LESEN 440 :IF MID$ (A$,ZL,ZR)>MID$ (VG$,ZL,ZR)OR A$="@" THEN460 445 : VG$=A$ :REM NEUER VG$ 450 : MM=SA :REM SATZNR. MERKEN 460 SA=SA+1 :REM NEXT SATZ 465 GOTO 411:REM ----- CONT LOOP ----- 470 : 500 REM KLEINSTES ELEMENT IM BEREICH ZD BIS ZD+M$ = VG$ WURDE GEFUNDEN --------- 501 : 510 SA=MM:GOSUB 9000:A$="@":GOSUB 9200 :REM SATZ MM LOESCHEN 520 M$(ZP)=VG$:MS(ZP)=MM :REM PUFFER FUELLEN 530 ZP=ZP+1 :REM PUFFER-ZEIGER+1 540 IF ZP>M% THEN600 :REM PUFFER VOLL 550 REM NEUEN VG$ SUCHEN 555 SA=ZD 560 GOSUB 9000:GOSUB 9100:IF A$="@" THENSA=SA+1:GOTO 560 565 VG$=A$:MM=SA 570 GOTO 400 600 REM PUFFER IST JETZT VOLL -------------------------------------------------- 601 : 610 PRINT"{DOWN,RVSON}PUFFERINHALT{RVOFF,DOWN}":FOR I=1 TOM%:PRINTM$(I),MS(I):NEXT I:REM SUPERMIKE 620 REM MS(1)-MS(M%) WIRD JETZT MIT QUICKSORT SORTIERT ------------------------- 621 : 625 GOSUB 30000:REM AUFRUF QUICKS.UP 630 PRINT"{DOWN,RVSON}SORTIERTER NUMMERPUFFER{RVOFF,DOWN}":FOR I=1 TOM%:PRINTMS(I):NEXT :REM SUPERMIKE 650 REM NEUEN PUFFERZEIGER SETZEN ---------------------------------------------- 651 : 655 ZP=1 660 IF MS(ZP)<ZD+M% THENZP=ZP+1:GOTO 660 670 PRINT"ZP=";ZP :REM SUPERMIKE 700 REM PLATZ FUER PUFFER SCHAFFEN --------------------------------------------- 701 : 710 FOR I=ZD TOZD+M%-1 720 :SA=I:GOSUB 9000:GOSUB 9100 :REM A$ LESEN 729 PRINTA$ :REM SUPERMIKE 730 :IF A$="@" THEN760 740 : SA=MS(ZP):GOSUB 9000:GOSUB 9200 :REM LUECKE FUELLEN 745 : ZP=ZP+1 :REM P.ZEIGER +1 760 NEXT I 800 REM PUFFER ZURUECKSCHREIBEN ------------------------------------------------ 801 : 810 SA=ZD 820 : FOR I=1 TOM% 830 : A$=M$(I) 831 : PRINTA$ :REM SUPERMIKE 840 : GOSUB 9000:GOSUB 9200 850 : SA=SA+1 860 : NEXT I 900 REM NAECHSTEN SORTIERVORGANG VORBEREITEN------------------------------------ 901 : 910 IF M%<>N%OR O% THENPRINT"SORTIERZEIT: ";TI$:PRINT"{2DOWN}*** FERTIG ***":CLOSE 15:END 920 ZD=ZD+M%:REM DATEIZEIGER ERHOEHEN 924 ZP=1 :REM PUFFERZEIGER AUF 1 925 SA=ZD 926 GOSUB 9000:GOSUB 9100:IF A$="@" THENSA=SA+1:GOTO 560 927 VG$=A$:MM=SA 930 IF M%>EN-ZD THENM%=EN-ZD 935 IF M%=EN-ZD THENO%=1 950 GOTO 400 :REM NAECHSTE SORTIERRUNDE 9000 REM POSITIONIEREN *************** 9001 : 9010 : HB%=SA/256:LB%=SA-HB%*256 9020 : PRINT#15,"P";CHR$ (3);CHR$ (LB%);CHR$ (HB%);CHR$ (1) 9030 : INPUT#15,A:REM FEHLERABFRAGE 9040 RETURN 9100 REM SATZ LESEN *************** 9101 : 9110 : IF A=50 THENRETURN : REM FEHLER 9120 : INPUT#1,A$ 9130 RETURN 9200 REM SATZ SCHREIBEN ************** 9201 : 9210 : PRINT#1,A$ 9230 RETURN 10000 REM DATEI LESEN ---------------------------------------------------------- 10001 : 10010 PRINT"{RVSON}LESEN / ENDE{RVOFF}" 10015 GET I$:IF I$<>"L"AND I$<>"E" THEN10015 10020 IF I$="E" THENPRINT"{CLR}":GOTO 270 10030 INPUT"SATZNUMMER ";IA 10034 PRINT"{RVSON}SATZ";IA;"{LEFT}-";IA+20;"{RVOFF}" 10035 FOR SA=IA TOIA+20 10040 : GOSUB 9000:GOSUB 9100 10050 : IF LEN (A$)<40 THENPRINTA$ 10060 : IF LEN (A$)>=40 THENPRINTA$; 10070 NEXT SA 10080 GOTO 10010 30000 REM QUICKSORT ----------------------------------------------------------- 30001 RG(1)=M% 30010 Z=Z+1:IF LG(Z)>=RG(Z) THEN30120 30020 X=LG(Z):Y=RG(Z) 30030 VG=MS(INT ((X+Y)/2)) 30040 IF X>Y THEN30100 30050 IF MS(X)<VG THENX=X+1:GOTO 30050 30060 IF MS(Y)>VG THENY=Y-1:GOTO 30060 30070 IF X>Y THEN30100 30080 S=MS(X):MS(X)=MS(Y):MS(Y)=S 30090 X=X+1:Y=Y-1:GOTO 30040 30100 RG(Z+1)=Y:LG(Z+1)=LG(Z):GOSUB 30010 30110 LG(Z+1)=X:RG(Z+1)=RG(Z):GOSUB 30010 30120 Z=Z-1:RETURN
Dokumentation zum Super-Sort-Listing
| 100 - 130 | Hier werden die Felder für die beiden Pufferteile und den Quicksort definiert. Durch Ändern der Variable M% kann die Größe des Puffers geändert werden (momentaner Wert 300). |
| 200 - 295 | Anfangsmenü und Eingabe aller Parameter. AN - EN, Bereich innerhalb der Datei NS, der sortiert werden soll. ZL, ZR begrenzen den Bereich innerhalb der Sätze, nach dem sortiert werden soll. Beispiel: MID$(A$,ZL,ZR) |
| 300 - 330 | Vorbereitung für den ersten Sortiervorgang (Sortieren der M% niedrigsten Sätze, beginnend ab dem Zeiger auf die Ausgangsdatei). ZD, Zeiger in Datei. Um die M% niedrigsten Sätze zu finden, wird die gesamte Datei von Satz ZD bis zum letzten Satz durchlaufen. VG$ wird als Vergleichsargument bestimmt. |
| 400 - 470 | Hier wird mittels einer Schleife die Datei ab ZD bis Ende nach den M% niedrigsten Sätzen durchsucht. Dabei wird jeweils der Vergleichsstring VG$ mit einem Satz des Suchbereiches verglichen. Wird ein Satz gefunden, der kleiner als VG$ ist, so nimmt VG$ dessen »Wert« an. In der Variablen MM wird gespeichert, an welcher Stelle der Datei der momentane »Wert2«, der in VG$ gespeichert ist, steht. Ist die Datei einmal durchlaufen, so steht in VG$ der kleinste Satz des durchsuchten Bereiches. |
| 500 - 570 | Der Satz MM, also die zugehörige Satznummer zum im Durchlauf gefundenen, niedrigsten Wert VG$, wird in der Datei gelöscht. Löschen ist eigentlich eine falsche Bezeichnung. In den Satz MM wird vielmehr ein »%« geschrieben. Dies ist notwendig, da ein leerer Satz, also A$="" schließlich auch ein Satz ist. Leere Sätze sind kleiner als alles andere. Hat eine Datei, die sortiert wird, leere Sätze, so stehen diese nach dem Sortiervorgang immer an den ersten Stellen in der geordneten Datei. In der Datei, die sortiert werden soll, darf somit nie in einem Satz ein einzelnes »%« stehen, da es dann zu Schwierigkeiten beim Lückenfüllen kommen kann. Wer auf »%« in seiner Datei nicht verzichten kann, kann im Listing das »%« auch durch ein anderes Zeichen ersetzen, das dann nicht verwendet werden kann (in der Datei). Nun wird geprüft, ob der Puffer schon voll ist (Zeile 540). ZP ist der Pufferzeiger. Sein Wert wird immer dann um 1 erhöht, wenn ein Satz in den Puffer geschrieben worden ist. Ist noch Platz im Puffer, geht es so weiter: Ein neues Vergleichselement VG$ muß bestimmt werden. Dabei muß aufgepaßt werden, daß das neue VG$ niemals " " sein darf. Hierfür sorgt Zeile 560. Nun folgt ein Rücksprung zu 400 – es wird wieder das kleinste Element gesucht. |
| 600 - 670 | Der Puffer ist jetzt voll. Die Pufferhälfte MS(1)-MS(M%), in der die Satznummern gespeichert sind, wird nun mittels dem Quicksort-Unterprogramm sortiert (vergleiche Bild 6). Anschließend wird der Pufferzeiger ZP auf den ersten Satz gesetzt (Leersatz beziehungsweise Satz mit »%«), der im unteren Bereich liegt (Zeile 650 - 670). |
| 700 - 760 | Hier werden die vollen Sätze, die noch immer im oberen Bereich stehen, evakuiert – also in die leeren Sätze des unteren Bereiches geschrieben. Hierzu wird der obere Bereich mit einer Schleife durchlaufen. Wird ein voller Satz gefunden, so wird er in den Leersatz geschrieben, auf den der Pufferzeiger gerade deutet. Der Pufferzeiger wird sodann um den Wert 1 erhöht. |
| 800 - 860 | Der Puffer wird mittels Schleife in den oberen Bereich ab ZD (Zeiger auf Datei) geschrieben. |
| 900 - 950 | Nun wird der nächste Sortierabschnitt vorbereitet. Der Dateizeiger ZD wird um den Wert M% erhöht – also auf den Satz gestellt, bei dem der noch unsortierte Bereich der Datei beginnt. Ein neues Vergleichs-Element wird bestimmt. Der Pufferzeiger wird wieder auf den Wert 1 zurückgesetzt. Sodann wird überprüft, ob der Puffer größer ist als der Bereich, der noch sortiert werden muß. Ist dies der Fall, so wird der Puffer so verkleinert (M% in Zeile 930), daß der Bereich, der noch sortiert werden muß, genau in ihn hineinpaßt. In Zeile 910 wird solch eine Puffergrößenveränderung festgestellt – die Sortierung ist beendet. Die Variable erfüllt den gleichen Zweck dann, wenn alles glatt aufgeht, und M% nicht erniedrigt wird. |
| 9000 - 9230 | Unterprogramme zum Positionieren, Lesen und Schreiben eines Satzes auf eine relative Datei. |
| 10000 - 10080 | Kleine Lese-Routine zum Orientieren vor dem Sortieren. |
| 30000 - 30120 | Quicksort-Unterprogramm |
Ich möchte an dieser Stelle nochmals darauf hinweisen, daß das hier vorgestellte Sortierprogramm keineswegs das bestmögliche ist. Verbesserungen sind insbesondere in punkto Sätze aus dem oberen Bereich in die Leersätze übertragen, möglich. Bevor Sie anhand eigener Ideen Verbesserungen vornehmen, sollten Sie jedoch stets bedenken, ob diese Verbesserungen Disketten und Floppy-Laufwerk schonend genug behandeln – also möglichst wenige Schreib-Lese-Operationen ausführen.
Arbeiten mit dem Super-Sort-Programm
Nach dem Programmstart müssen Sie den Namen der Datei angeben, die Sie sortieren möchten (Disk einlegen!).
Danach haben Sie die Auswahlmöglichkeit unter den drei Menüpunkten »Ganze Datei sortieren«, »Teilbereich sortieren« und »Lesen«. »Lesen« funktioniert wie beim relativen Dateien-Editor, und dient hier lediglich zur Orientierung für Sie. Wählen Sie »Ganze Datei sortieren«, so wird die gesamte Datei sortiert. Das Satzende wird automatisch durch Abfragen des Floppykanals nach Fehler 50 ermittelt.
»Teilbereich ermitteln« erlaubt es, einen bestimmten Bereich der Datei zu ordnen. Vor dem Sortieren müssen Sie noch angeben, nach welchem Stringbereich die Sortierung erfolgen soll – also welcher Stringbereich verglichen wird. Diese Funktion ist besonders wichtig beim Sortieren von Dateien mit mehr als einem Schlüssel.
Alle Zeilen im Programm, die mit REM SUPERMIKE gekennzeichnet sind, können weggelassen werden (zur Geschwindigkeitserhöhung). Sie ermöglichen es, den ganzen Sortiervorgang auf dem Monitor mitzuverfolgen.
Bedenken Sie, daß das Sortieren von Dateien mit mehreren 1000 Sätzen sehr viel Zeit in Anspruch nimmt. Es lohnt sich dann kaum, beim Computer zu warten.
Geheimtip: Sortiervorgang vor dem Schlafengehen starten – am nächsten Morgen ist alles vorbei!
Wer Angst vor einer zu hohen Stromrechnung hat, kann ein Schalter-Interface an den User-Port anschließen und das Programm so modifizieren, daß sich der Computer samt Peripherie am Ende des Sortiervorganges selbst abschaltet!
Eine bessere Lösung
Unser Super-Port funktioniert einwandfrei. Wer nur ab und zu sortieren will, ist mit ihm gut bedient. Die folgende Sortiermöglichkeit ist eine wesentliche Verbesserung gegenüber unserer ersten. Wer die Handhabung von Puffern und Zeigern verstanden hat, wird auch mit der folgenden Theorie keine Probleme haben.
Unser Super-Sort 1 benötigt noch ziemlich viele Disk-Zugriffe, um den Puffer zu füllen. Es gibt jedoch eine wesentlich schnellere und schonendere Lösung:
Theorie zum Super-Sort 2
Auch hier wird wieder ein Puffer der Größe M% verwendet. Zu Beginn des Sortiervorgangs wird der Puffer einfach mit den ersten M%-Sätzen der Datei gefüllt. Wir suchen also nicht wie bei Super-Sort 1 die M% niedrigsten Sätze heraus.
Nun wird der gesamte Pufferinhalt mit dem Quicksort sortiert. Der nächste Schritt besteht darin, die gesamte Datei vom Zeiger beginnend bis zum Ende zu durchlaufen. Der Zeiger steht beim ersten Sortierabschnitt bei Satz M%, beim zweiten Durchlauf auf 2mal M% etc. Bei jedem eingelesenen Satz wird geprüft, ob dieser kleiner ist als der größte Satz im Puffer (steht beim sortierten Puffer an letzter Stelle). Ist dies nicht der Fall, wird der nächste Satz gelesen. Tritt aber nun der Fall ein, daß der Satz kleiner als der größte Satz des Puffers ist, passiert folgendes:
Wir nennen den eingelesenen Satz nun einmal A$. A$ muß nun irgendwie in den Puffer geschrieben werden. Anhand der binären Suchmethode (an anderer Stelle des Kurses beschrieben) ermitteln wir, wo im Puffer A$ eingefügt werden muß. Der letzte Satz im Puffer wird dadurch aus dem Puffer hinausgeworfen (wir müssen schließlich Platz für A$ schaffen). Damit er jedoch nicht verlorengeht, wird er wieder in die Datei zurückgeschrieben – und zwar an die Stelle, an der A$, das sich nun im Puffer befindet, gestanden hat.
Diese Methode hat den Vorteil, daß pro Pufferfüllung die Datei nur ein einziges Mal durchlaufen werden muß. Auch das Problem, Platz für den Puffer-Zurückschreibevorgang zu schaffen, entfällt. Bei Methode 2 wird zwar wesentlich mehr innerhalb des Rechners sortiert, es wird jedoch soviel Zeit bei Diskoperationen gespart, daß Methode 2 wesentlich schneller und Disk-schonender ist.
Wie gesagt, für diesen Kurs reicht Super-Sort I vollkommen aus. Wer viel sortieren will, sollte sich jedoch ein Sortierprogramm der oben beschriebenen Methode schreiben.
Trick am Rande: Diskettenseite abfragen
An dieser Stelle möchte ich einmal ein Problem abhandeln, das immer dann auftaucht, wenn man Programme schreiben will, die sich über zwei oder mehrere Disk-Seiten erstrecken. Bei Adventure-Spielen und KI-Programmen, die sich selbst verwalten, ist dies besonders häufig der Fall. Im Programm kommt dann früher oder später die Anweisung BITTE DISK-SEITE X EINLEGEN. Wie kann man nun innerhalb eines Programmes feststellen, ob auch die richtige Disk-Seite eingelegt worden ist? Dem Anfänger bereitet dies oft Schwierigkeiten, da Probleme wie diese nicht im Bedienungshandbuch des Floppy-Laufwerks behandelt werden. Die Lösung liegt einfach darin, den ID der eingelegten Disk einzulesen. Dies setzt natürlich voraus, daß der ID der ersten Diskette 01, der der zweiten 02 etc. beträgt. Im folgenden finden Sie deshalb zunächst ein kleines Hilfsprogramm, mit dem es möglich ist, Diskname und ID einer Disk nachträglich zu verändern ohne, daß die Disk formatiert werden muß beziehungsweise ein sich auf ihr befindliches Programm verloren geht (Listing 25).
10 rem ********************************
15 rem * programm zum aendern von *
20 rem * diskname und id *
25 rem ********************************
30 print"{clr}{rvon}disk-utility 1 {rvof}";
35 print"{down}{down} disk-name aendern - '1'
40 print" disk-id aendern - '2'
42 print" ende - '3'
45 getx$:ifx$<>"1"andx$<>"2"andx$<>"3"then45
46 ifx$="3"thenprint"{down}{down}*** fertig ***":end
50 open15,8,15:rem floppykanal oeffnen
55 print#15,"i0":open1,8,3,"#":print#15,"u1:3"0",18,0":ifx$="1"then90
60 print#15,"b-p:3,162":get#1,a$,b$,c$,d$,e$:print"{down}alter disk-id ist {rvon} ";a$;
65 printb$;c$;d$;e$:input"{down}neuer disk-id ";f$
70 iflen(f$)<>4thenf$=left$(f$+" ",5)
75 print#15,"b-p:3,162":print#1,f$;:print#15,"u2:3"dv",18,0
80 i$="i"+mid$(str$(dv),2):print#15,i$
85 run
90 print#15,"b-p:3,144":r$="":forl=1to16:get#1,a$:r$=r$+a$:nextl
95 print"{down}alter disk-name ist ";"{rvon}"r$"{rvof}"
100 input"{down}neuer disk-name ";ne$
105 ne$=left$(ne$+" ",16)
110 print#15,"b-p:3,144":print#1,ne$;:print#15,"u2:3"dv",18,0":goto80
10 REM ******************************** 15 REM * PROGRAMM ZUM AENDERN VON * 20 REM * DISKNAME UND ID * 25 REM ******************************** 30 PRINT"{CLR,RVSON}DISK-UTILITY 1{26SPACE,RVOFF}"; 35 PRINT"{2DOWN,5SPACE}DISK-NAME AENDERN{3SPACE}-{2SPACE}'1' 40 PRINT"{5SPACE}DISK-ID{3SPACE}AENDERN{3SPACE}-{2SPACE}'2' 42 PRINT"{5SPACE}ENDE{16SPACE}-{2SPACE}'3' 45 GET X$:IF X$<>"1"AND X$<>"2"AND X$<>"3" THEN45 46 IF X$="3" THENPRINT"{2DOWN}*** FERTIG ***":END 50 OPEN 15,8,15:REM FLOPPYKANAL OEFFNEN 55 PRINT#15,"I0":OPEN 1,8,3,"#":PRINT#15,"U1:3"0",18,0":IF X$="1" THEN90 60 PRINT#15,"B-P:3,162":GET #1,A$,B$,C$,D$,E$:PRINT"{DOWN}ALTER DISK-ID IST{SPACE,RVSON,SPACE}";A$; 65 PRINTB$;C$;D$;E$:INPUT"{DOWN}NEUER DISK-ID ";F$ 70 IF LEN (F$)<>4 THENF$=LEFT$ (F$+"{6SPACE}",5) 75 PRINT#15,"B-P:3,162":PRINT#1,F$;:PRINT#15,"U2:3"DV",18,0 80 I$="I"+MID$ (STR$ (DV),2):PRINT#15,I$ 85 RUN 90 PRINT#15,"B-P:3,144":R$="":FOR L=1 TO16:GET #1,A$:R$=R$+A$:NEXT L 95 PRINT"{DOWN}ALTER DISK-NAME IST ";"{RVSON}"R$"{RVOFF}" 100 INPUT"{DOWN}NEUER DISK-NAME ";NE$ 105 NE$=LEFT$ (NE$+"{14SPACE}",16) 110 PRINT#15,"B-P:3,144":PRINT#1,NE$;:PRINT#15,"U2:3"DV",18,0":GOTO 80
Beachten Sie bitte, daß der Diskname nicht mehr als 17 und der Disk ID nicht mehr als 5 Zeichen Länge haben darf. Nun brauchen wir nur noch ein kleines Basic-Unterprogramm, mit dem der Disk-ID gelesen werden kann (Listing 26).
51200 rem up - disk einlegen ----------
51210 print"{rvon}Bitte Disk-Seite";side;"{left} einlegen !{rvof}":poke198,0:wait198,1
51240 open 2,8,2,"#":print#15,"u1 2 0 18 0":print#15,"b-p";2;162:get#2,i$
51250 close2:ifside<>val(i$)then51200
51260 return
51200 REM UP - DISK EINLEGEN ---------- 51210 PRINT"{RVSON}BITTE DISK-SEITE";SIDE;"{LEFT,SPACE}EINLEGEN !{RVOFF}":POKE 198,0:WAIT 198,1 51240 OPEN 2,8,2,"#":PRINT#15,"U1 2 0 18 0":PRINT#15,"B-P";2;162:GET #2,I$ 51250 CLOSE 2:IF SIDE<>VAL (I$) THEN51200 51260 RETURN
Bevor das Programm mittels GOSUB 51200 aufgerufen werden kann, muß der Floppykanal 15 geöffnet sein. Anschließend muß noch durch die Variable SIDE (beziehungsweise SI) bestimmt werden, welche Seite eingelegt werden soll. Das Unterprogramm führt erst dann ein RETURN aus, wenn die gewünschte Seite eingelegt ist. Innerhalb eines Basic-Programmes sieht dies dann so aus:
10 OPEN15,8,15
20 SIDE=2
30 GOSUB51200
40 END
Es bietet sich an, den ID der ersten Seite als 1SIDE, den der zweiten Seite als 2SIDE etc. zu benennen, da eine einzige Ziffer beim ID nicht ausreicht!
Vergessen Sie auch nicht aus Sicherheitsgründen, nach einem Disk-Wechsel immer neu zu initialisieren.
Sie können dieses Unterprogramm leicht in eigenen Basic-Programme einbauen. Das Disk- und ID-Änder-Programm speichern Sie am besten wieder auf Ihrer Utility-Disk, auf der sich auch die Editoren befinden.
(M. Nickles/bs)