Institut für Computational Science Prof. Dr. H. Hinterberger Praxismodul 6 Dateien verarbeiten Dateien verarbeiten 2 Aufbau A. Einführung B. Tutorial C. Selbständige Aufgabe D. Abgabebedingungen Inhalt 1. Der Datentyp Textfile ....................................................................................................................................................... 3 2. Fehlerbehandlung...............................................................................................................................................................6 3. Programmentwurf............................................................................................................................................................. 7 4. Hilfsmittel für den Programmentwurf......................................................................................................................9 5. Angewandter Programmentwurf...............................................................................................................................12 Phase 1 – Analyse.....................................................................................................................................................................15 Phase 2 – Modellierung ....................................................................................................................................................... 18 Phase 3 – Entwurf ................................................................................................................................................................. 20 Phase 4 – Implementation.................................................................................................................................................24 Phase 5 – Verifikation.......................................................................................................................................................... 30 6. Neue Sprachelemente .....................................................................................................................................................31 Dateien verarbeiten 3 A. Einführung Damit die von einem Programm erzeugten oder verarbeiteten Daten auch noch zur Verfügung stehen nachdem das Programm terminiert, müssen diese in Dateien auf einem permanenten Speichermedium gesichert werden. 1. Der Datentyp Textfile Für Daten, die in Dateien auf Speichermedien wie Festplatten, Disketten, Magnetbänder, CD-ROM, usw. permanent gespeichert sind, hat sich der Begriff "file" eingebürgert. Pascal unterscheidet drei File-Typen: typisierte, untypisierte Files und Textfiles. Typisierte und untypisierte Files werden im Praxismodul 7 behandelt. Dieses Praxismodul bietet einen Einstieg in die Verarbeitung von Dateien auf der Basis von Textfiles. Files deklarieren Mit dem von Pascal vorgesehenen Standard-Filetyp Textfile werden Files deklariert, welche Textzeichen enthalten, die zeilenweise angeordnet sind. Jede Zeile schliesst dabei mit einem Zeilenende-Zeichen ab. Um mit einem File arbeiten zu können, muss dieses in der Variablendeklaration mit einem Namen bezeichnet werden: Var Textzeichen: Textfile; Diese Filevariable (Textzeichen) ist vom Typ Textfile, d.h. alle für Textfiles erlaubten Operationen können auf sie angewandt werden. Die Daten einer Dateivariablen sind im Hauptspeicher abgelegt, sie sind also nicht permanent. Damit diese Daten auf einem anderen Speichermedium permanent gespeichert werden können, muss die Filevariable einer externen Datei zugeordnet werden. Dies geschieht mit Hilfe eines Aufrufs der Prozedur AssignFile. Wenn wir beispielsweise eine Textdatei erzeugen oder mit einer bestehenden Datei arbeiten möchten, schreiben wir: Filevariable Dateiname AssignFile(Textzeichen, 'C:\Meintext.txt') oder AssignFile(Textzeichen, OpenDialog1.FileName) falls wir einen Dateinamen mit der Dialogkomponente von Delphi, welche das WindowsStandarddialogfeld "Öffnen" erzeugt, auswählen. Dateien verarbeiten 4 Files öffnen Nach der Zuordnung der Filevariablen zur externen Datei muss die Filevariable "geöffnet", das heisst für die Eingabe oder Ausgabe vorbereitet werden. Bereits bestehende Files werden mit der Prozedur Reset geöffnet: Reset(Textzeichen); Eine Textdatei ist nach dem Öffnen mit Reset schreibgeschützt. Eine bereits geöffnete Datei wird mit Reset geschlossen und wieder geöffnet. Jede, auf Reset folgende Operation, "sieht" den Anfang des geöffneten Files. Neue Files können mit der Prozedur Rewrite erzeugt und gleichzeitig geöffnet werden: Rewrite(Textzeichen) Falls bereits eine gleichnamige Datei vorhanden ist, wird der Inhalt dieser Datei gelöscht. Jede, auf Rewrite folgende Operation, "sieht" den Anfang des geöffneten Files. Im Gegensatz zu Rewrite öffnet die Prozedur Append ein existierendes Textfile so, dass am Ende des Files neuer Text hinzugefügt werden kann: Append(Textzeichen) Die Prozedur Append kann nur auf Textfiles angewandt werden. Files schliessen Wenn ein Programm die Bearbeitung eines Files beendet hat, muss dieses mit Hilfe der Standardprozedur CloseFile geschlossen werden: CloseFile(Textzeichen); Hierbei wird die zugehörige Datei aktualisiert, d.h. eventuell noch ausstehende Datentransfers werden ausgeführt. Die Filevariable kann anschliessend einer anderen Datei zugeordnet oder die gerade geschlossene Datei kann durch ein anderes Programm verwendet werden. Textfiles lesen Angenommen, unsere im Verzeichnis C:\ gespeicherte Datei Meintext.txt enthält Text, mit Zeilen, die nicht mehr als 127 Zeichen enthalten. Das folgende Codesegment liest die erste Zeile dieses Textes in die Stringvariable Zeile: Var Textzeichen: Textfile; Zeile: String; Zeichen: Char; begin AssignFile(Textzeichen, 'C:\Meintext.txt'); Reset(Textzeichen); Read(Textzeichen, Zeile); Read(Textzeichen); Dateien verarbeiten 5 Das erste Read liest die erste Zeile der Datei, das zweite Read das Zeilenende-Zeichen (speichert dieses aber nicht in einer Variablen des Programms). Damit das Zeilenende-Zeichen nicht separat gelesen werden muss, stellt Pascal die Standardprozedur Readln (Abk. für Read line) zur Verfügung. Diese erlaubt es, die letzten zwei Programmzeilen in obigem Beispiel wie folgt zusammen zu fassen: Readln(Textzeichen, Zeile); Die Prozedur Readln liest also eine Zeile Text bis ans Ende und bewirkt, dass der nächste Aufruf von Read oder Readln die nächste Zeile lesen wird. Falls keine nächste Zeile vorhanden ist, führt ein solches Read zu einem E/A-Fehler. Beim Einsatz von Readln muss darauf geachtet werden, dass nicht über Text gesprungen wird, den man eigentlich hätte lesen wollen, denn die Prozedur liest soviel wie in der Variablen, in die gelesen wird, Platz vorhanden ist und ignoriert den Rest bis zum Zeilenende. Textfiles haben die nützliche Eigenschaften, dass wir mit der Funktion Eoln (Abk. für "End of line") Zeilenumbrüche erkennen können. Sie erlauben auch das Lesen und Schreiben von Zeichen, die nicht vom Typ Char sind. Die folgende Anweisung liest alle Zeichen einer Zeile einzeln bis ans Ende der Zeile: while not Eoln(Textzeichen) do Read(Textzeichen, Zeichen); Wenn ein Programm versucht über das Ende eines Files hinaus zu lesen, entsteht ein E/A-Fehler, der das Programm zum Abbruch bringt. Mit der Funktion EOF (End Of File) können wir prüfen, ob wir am Ende eines Files angekommen sind, denn EOF liefert den Wahrheitswert "TRUE" sobald dies der Fall ist. Die vorsichtige Programmiererin prüft deshalb auf "End Of File" vor jeder ReadAnweisung: if not EOF(Textzeichen) then Read(Textzeichen, Zeichen); EOF kann natürlich auch als Bedingung eingesetzt werden, wenn Files innerhalb einer Schleife bearbeitet werden: while not EOF do . . . Textfiles schreiben Um ein Textfile fürs Schreiben vorzubereiten gibt es zwei Möglichkeiten: Die Prozedur Rewrite und die Prozedur Append. Um eine zusätzliche Zeile mit dem Text "ETH Zuerich" ans Ende unserer Datei Meintext.txt anzufügen, schreiben wir: Zeile:= 'ETH Zuerich'; Append(Textzeichen); Writeln(Textzeichen, Zeile); Hätten wir Write(Textzeichen, Zeile); Dateien verarbeiten 6 geschrieben, wäre zwar der Text "ETH Zuerich" ans Ende der Datei geschrieben worden, aber ohne Zeilenende-Zeichen. Mit Reset geöffnete Textfiles sind schreibgeschützt (das heisst, sie können nur gelesen werden) während in mit Rewrite und Append geöffnete Textfiles nur geschrieben werden kann. Typisierte und untypisierte Files (das Thema von Praxismodul7) lassen immer Lesen und Schreiben zu, wobei es keine Rolle spielt, ob sie mit Reset oder Rewrite geöffnet wurden. Automatische Typenkonvertierung Textfile ist wohl der am meisten verwendete File-Typ. Deshalb stellt Pascal verschiedene spezielle Routinen zur Verfügung damit Programmiererinnen Textfiles leichter verarbeiten können. Read und Write beispielsweise erlauben es, dass Werte gelesen und geschrieben werden können, die nicht vom Typ Char sind. Zwei Beispiele illustrieren diese Typenkonvertierung: Var Textzeichen: Textfile; Zahl: Integer; Wahr: Boolean; Der Prozeduraufruf Read(Textzeichen, Zahl) liest eine Ziffernfolge im File Textzeichen, interpretiert diese als ganze Zahl vom Typ Integer und speichert ihren Wert in der Variablen Zahl. Mit der Anweisungsfolge Wahr:= true; Write(Textzeichen, Wahr) wird am Ende des Files Textzeichen der String 'TRUE' angefügt. 2. Fehlerbehandlung Pascal überprüft standardmässig ob Aufrufe von Eingabe-/Ausgabe-Prozeduren und -Funktionen zu Problemen geführt haben (E/A-Fehler), die es verhindern, dass die Operation erfolgreich ausgeführt werden kann. Beim Auftreten eines Fehlers wird eine so genannte Exception (Fehlerbedingung, Ablaufunterbrechung) ausgelöst oder – falls die Exception-Behandlung deaktiviert ist – das Programm abgebrochen. Die automatische Fehlerüberprüfung kann mit der Compiler-Direktive {$I+} aktiviert und mit {$I-} deaktiviert werden. Eine Compiler-Direktive ist ein Kommentar mit einer speziellen Syntax. In Delphi können Compiler-Direktiven überall dort eingesetzt werden, wo auch Kommentare erlaubt sind. Eine CompilerDirektive beginnt mit einem $ als erstem Zeichen nach dem Kommentarzeichen. Darauf folgt der Dateien verarbeiten 7 Name der Direktive (bestehend aus einem oder mehren Buchstaben). Auf den Namen und die erforderlichen Parameter können Kommentare folgen. Kommentar Compiler-Direktive Name Parameter { $ I + } Wenn eine Prozedur oder Funktion im {$I-} Modus kompiliert wurde, löst ein Eingabe/AusgabeFehler keine Exception aus. Man kann aber das Ergebnis einer Eingabe/Ausgabe-Operation durch einen Aufruf der Standardfunktion IOResult selber ermitteln: AssignFile(Textzeichen, 'C:\Meintext.txt') {$I-} Reset(Textzeichen); {$I+} if IOResult = 0 {File wurde fehlerfrei geöffnet} then {File verarbeiten} Rufen Sie die Funktion IOResult auch dann auf, wenn Sie der Fehler nicht interessiert, denn die Fehleranzeige wird erst durch den Aufruf von IOResult gelöscht. Wenn Sie das nicht tun und im Modus {$I+} arbeiten, dann schlägt der nächste Aufruf einer Eingabe/Ausgabe-Prozedur aufgrund des nicht gelöschten IOResult-Fehlerstatus fehl. Weil Pascal-Files eine Abstraktion von tatsächlichen Dateien sind, enthält ein Programm keine Information über die physische Eigenart einer Datei. Deshalb müssen wir nicht angeben, ja nicht einmal wissen, ob die Daten von einem Stapel Lochkarten, einem Plattenspeicher, oder einer Tastatur kommen. Es ist die Aufgabe des Betriebssystems, konkrete Dateien ihrem Programm während seiner Laufzeit zuzuordnen. Das Betriebssystem erreicht dies über Instruktionen, welche nicht zu unserem Programm gehören. 3. Programmentwurf Mit den im Laufe der ersten fünf E.Tutorials erarbeiteten Grundlagen sind Sie nun in der Lage umfangreichere Programme zu schreiben und können damit auch anspruchsvollere Probleme lösen. Problemlösen und Programmentwurf sind gewissermassen zwei Seiten der gleichen Münze. Problemlösungsmethoden Für die Aufgabe, eine Lösung für ein Problem zu entwerfen, gibt es verschiedene Methoden, von denen jede in bestimmten aber nicht in allen Situationen erfolgreich angewandt werden kann. Eine davon ist, dass man versucht, das Problem rückwärts zu lösen. Wenn das Problem beispielsweise darin besteht, für eine gegebene Eingabe eine bestimmte Ausgabe zu erzeugen, könnte Dateien verarbeiten 8 man mit der Ausgabe beginnen und versuchen, auf die gegebene Eingabe zurück zu schliessen. Diese Vorgehensweise ist typisch für jemanden, der z.B. den Algorithmus für das Schlaufen eines komplizierten Knotens herausfinden möchte. Man beginnt mit dem vollständigen Knoten und versucht, währenddem man ihn löst, herauszufinden wie er erzeugt wurde. Ein anderer, allgemeiner Problemlösungsansatz ist, ein verwandtes Problem zu suchen, das entweder einfacher zu lösen ist oder das bereits gelöst wurde, um anschliessend die Lösung auf das aktuelle Problem anzuwenden. Diese Technik ist besonders nützlich für die Entwicklung von Programmen, weil hier eine der grössten Schwierigkeiten nicht darin besteht, einen Einzelfall zu lösen, sondern einen allgemeinen Algorithmus zu finden, der alle Fälle eines Problems löst. Genauer gesagt, falls wir die Aufgabe hätten, ein Programm zu entwickeln, das eine Liste mit Namen sortiert, dann ist unsere Aufgabe nicht eine spezielle Liste zu sortieren, sondern einen Algorithmus zu finden, mit dem irgendeine Namensliste sortiert werden kann. Schrittweise Verfeinerung Ein weiteres Verfahren besteht im Wesentlichen darin, dass man nicht versucht eine Aufgabe als Ganzes auf einmal zu lösen sondern durch eine schrittweise Verfeinerung mit der ein gegebenes Problem als aus verschiedenen Teilproblemen bestehend betrachtet wird. Die Idee ist, dass man durch Zerlegen eines Problems in Teilprobleme in die Lage kommt, die gesamte Lösung schrittweise anzugehen, wobei jeder einzelne Schritt einfacher zu bewältigen ist als das ursprüngliche Problem. Die schrittweise Verfeinerung schlägt nun vor, jeden dieser Schritte wiederum in kleinere Schritte zu zerlegen und diese wiederum in noch kleinere Einheiten, bis das ganze Problem auf eine Sammlung einfacher Teilprobleme reduziert ist. So gesehen ist die schrittweise Verfeinerung eine so genannte Top-Down-Methode indem sie vom Allgemeinen zum Spezifischen schreitet. Im Gegensatz dazu bewegt man sich mit Bottom-UpMethoden vom Spezifischen zum Allgemeinen. In der Praxis werden beide Vorgehensweisen mit Vorteil so eingesetzt, dass sie sich ergänzen. Eine nützliche Eigenschaft von Lösungen, die durch schrittweise Verfeinerung erzeugt werden ist, dass sie eine natürliche modulare Struktur aufweisen, und darin liegt einer der wichtigsten Gründe für die Beliebtheit dieser Methode beim Entwurf von Algorithmen. Wenn ein Algorithmus eine natürliche modulare Struktur aufweist, dann werden die entsprechenden Programme überschaubarer und leichter zu handhaben. Zudem sind die Teilprobleme, die durch eine schrittweise Verfeinerung erzeugt werden, mit der Programmierung in Teams gut vereinbar. Denn eine Software, die in Teilaufgaben (oder mögliche Module) gegliedert ist, kann auf die Mitglieder eines Teams aufgeteilt werden, damit diese sie unabhängig voneinander bearbeiten können. Die Methode der schrittweisen Verfeinerung wurde zu einer der wichtigsten Entwurfsmethodiken in der Datenverarbeitung und ist deshalb eine Technik, mit der Programmierer vertraut sein müssen. Aber sie bleibt nur eine von vielen Entwurfsmethoden, die für Informatiker von Interesse sind. Dateien verarbeiten 9 Problemlösungsphasen Dass Problemlösen mehr eine Kunst als eine Wissenschaft ist hat schon in den 40er Jahren der ungarische Mathematiker Georg Polya gezeigt als er diesen Vorgang in folgende, lose definierte Phasen gegliedert hatte: Phase 1: Das Problem verstehen (Erkennen, was gefragt wird) Phase 2: Einen Plan ausdenken (Eingehen auf das, was gefragt wird) Phase 3: Den Plan ausführen (Das Resultat erarbeiten) Phase 4: Das Resultat evaluieren (Wie genau ist es, kann es für die Lösung anderer Probleme verwendet werden?) Auf das Entwerfen von Programmen angewandt kann dieses Vorgehen zu folgender Liste führen: Phase 1: Das Problem verstehen (Analyse) Phase 2: Formulierung der Aufgabenstellung als Problem der Datenverarbeitung (Modellierung) Phase 3: Einen detaillierten Problemlösungsablauf erarbeiten (Entwurf eines Algorithmus) Phase 4: Formulieren dieser Lösung als Programm (Implementation) Phase 5: Ausführen, Testen des Programms (Verifikation) Die Erfahrung zeigt, dass diese Phasen nicht unbedingt in der aufgeführten Reihenfolge ausgeführt werden. Erfolgreiche Problemlöser beginnen oft mit dem Formulieren von Problemlösungsstrategien (Phase 3) bevor das Problem ganz verstanden ist (Phasen 1 und 2). Wenn diese Strategien sich als nicht erfolgreich erweisen (während den Phasen 4 und 5), führt dies meistens zu einem besseren Verständnis der Eigenheiten des Problems und man sollte dadurch in der Lage sein andere, erfolgreichere Strategien zu formulieren. 4. Hilfsmittel für den Programmentwurf Zwei oft angewandte Methoden um die Arbeit während den ersten drei Phasen des Programmentwurfs zu unterstützen sind Formulierungen in Pseudocode und Flussdiagramme (Programmablaufplan) Pseudocode Pseudocode (deutsch: Scheincode) bezeichnet eine Darstellung eines Algorithmus in einer Schreibweise in der Art einer Programmiersprache, die aber in der Regel näher an der menschlichen Sprache ist, als ausformulierter Programmcode. Pseudocode wird dann eingesetzt wenn die Funktionsweise eines Algorithmus im Vordergrund und die sprachspezifische Implementation im Hintergrund steht. Dies führt zu grösserer Übersichtlichkeit, weil triviale Codestücke je nach belieben stark abgekürzt werden können und Formalitäten nicht "die Sicht versperren". Der Algorithmus zur Umwandlung von Grad Fahrenheit in Grad Celsius könnte in Pseudocode etwa so formuliert werden: read(F) Dateien verarbeiten 10 subtrahiere 32 multipliziere mit 5/9 write(C) Flussdiagramme Ein Flussdiagramm (engl. flowchart) ist ein Ablaufdiagramm für ein Computerprogramm, das auch als Programmablaufplan bezeichnet wird. Flussdiagramme werden oft unabhängig von Computerprogrammen zur Darstellung von Prozessen und Tätigkeiten eingesetzt. Hauptsächlich werden die folgenden drei Elemente verwendet: Mit diesen Symbolen lassen sich die fundamentalen Konstruktionselemente für Algorithmen, nämlich Sequenz, Fallunterscheidung und Wiederholung, grafisch darstellen. Als Beispiel einer Sequenz sehen Sie rechts das Flussdiagramm für die Umwandlung von Grad Fahrenheit in Grad Celsius. Zur Illustration der Fallunterscheidung betrachten wir den Algorithmus für die Umwandlung von Grad Fahrenheit in Grad Celsius mit der Ergänzung, dass die Umrechnung nur ausgeführt werden soll, wenn die Temperatur nicht unter den Gefrierpunkt fällt. Dateien verarbeiten 11 Dieses Flussdiagramm zeigt den Programmablauf, wenn eine Tabelle erzeugt werden soll, die für jedes Grad Fahrenheit zwischen 0 und 100 den entsprechenden Wert in Celsius angibt. Ein typischer Fall einer einfachen Wiederholung. Dateien verarbeiten 12 B. Tutorial 5. Angewandter Programmentwurf Im Zentrum dieses Tutorials steht der Programmentwurf, auf Englisch Software Design. Dabei handelt es sich nicht nur um das eigentliche Schreiben des Programmcodes, sondern es geht um die gesamte Software-Entwicklung, welche sich von der Analyse des Problems in der realen Welt über die Modellierung und das Programmieren bis zum Testen des fertigen Programms erstreckt. Die nötigen Programmier-Grundlagen und -Konzepte für das Implementieren eines funktionierenden Programms (das Handwerk) haben Sie sich in den bisherigen 5 Modulen erarbeitet. Wir werden uns darum in diesem Tutorial hauptsächlich auf die Analyse-, die Modellierungs- und die Entwurfs-Phasen konzentrieren. Wir haben uns entschieden dieses Tutorial auf Papier abzugeben. Die Schritte, die wir behandeln, werden auch im professionellen Progammierumfeld oft mit Papier, Bleistift und Radiergummi angegangen, für kreative Prozesse sind das einfach sehr praktische Hilfsmittel. Sie haben im Einführungstext mit der Gliederung des Programmentwurfs in 5 Phasen eine mögliche Vorgehensweise bei der Entwicklung von komplexeren Programmen kennen gelernt. In diesem praktischen Teil wollen wir nun diesen Ablauf anhand eines konkreten Problems umsetzen. Bevor wir mit der Phase 1 beginnen können, muss Ihnen die Problemstellung bekannt sein. Lesen Sie dazu den folgenden Artikel über das Benford-Gesetz, welcher im Januar 2006 in einem NZZ Folio zum Thema „Statistik“ erschienen ist. Das Rätsel der abgegriffenen Seiten Vor über hundert Jahren stiess ein Astronom auf ein merkwürdiges statistisches Gesetz, das heute Steuerbetrüger entlarven soll. Es ist das seltsamste Gesetz, das die Statistik zu bieten hat. Nehmen Sie Ihre Steuererklärung und zählen Sie, wie oft eine Eins am Anfang einer Zahl steht, dann zählen Sie die Zwei, die Drei, die Vier, die Fünf, bis Sie bei der Neun sind. Anders als erwartet, werden Sie die Ziffern Eins bis Neun nicht gleich häufig an der ersten Stelle finden, sondern oft die Eins häufiger als die Zwei, die Zwei häufiger als die Drei und die wiederum häufiger als die Vier, die Fünf, die Sechs und so weiter. Nicht nur Steuererklärungen bergen dieses Muster, sondern auch Börsenkurse, Bevölkerungszahlen, Dateigrössen. Selbst wer alle Zahlen einer Ausgabe der «Schweizer Illustrierten» oder die Gewichte von hundert zufällig im Garten gesammelten Steinen untersucht, wird auf die geheimnisvolle Ziffernverteilung stossen. Dem amerikanischen Astronomen Simon Newcomb fiel bereits im 19. Jahrhundert auf, Dateien verarbeiten 13 dass die ersten Seiten von Logarithmentafeln abgegriffener waren als die letzten: offenbar wurde der Logarithmus einer Zahl, die mit Eins begann, am häufigsten nachgeschlagen. Warum das so war, blieb Newcomb verborgen, aber er fand eine Formel, mit der sich die genauen Anteile der Ziffern berechnen liessen. In einer grossen Menge Zahlen beginnen 30 Prozent mit einer Eins, 18 Prozent mit einer Zwei, 13 Prozent mit einer Drei und so weiter bis zur Neun, die in 5 Prozent an erster Stelle steht. Newcomb publizierte seine Beobachtung 1881 im «American Journal of Mathematics». Dann ging sie vergessen, bis 1938 der Physiker George Benford auf dasselbe Phänomen stiess. Benford zählte 20 229 Anfangsziffern in Sterbetafeln, Baseballstatistiken und Zeitungen aus und fand in vielen seiner Beispiele Newcombs Muster. Allerdings nicht in allen: Die Zahlen, die dem Benford-Gesetz, wie es bald genannt wurde, folgten, durften weder ganz zufällig sein wie Lottozahlen noch starr vorgegeben wie physikalische Konstanten oder Jahrzahlen. Am besten erfüllten die Zahlen die Regel, wenn sie den Bereich zwischen diesen Extremen belegten. Benford nannte sie «unregelmässige Zahlen». Das klingt diffus und war es auch. Eine genauere Definition konnte Benford nicht geben, und auch den Grund für die eigentümlichen Häufigkeiten fand er nicht. Den suchten Mathematiker noch über 50 Jahre vergeblich. Dabei machte ihnen besonders der merkwürdige Umstand zu schaffen, dass sich das Muster am deutlichsten zeigte, wenn die Zahlen aus ganz verschiedenen Kategorien stammten. Die beste Übereinstimmung mit der Theorie stellte Benford bei Zahlen aus Zeitungsartikeln fest: Körpergrössen, Temperaturen, Verluste, Erdbebenstärken. Aber was haben 1 Meter 83, 22 Grad und 3 Millionen Dollar gemeinsam? Erst 1996 fand der Mathematiker Theodore Hill heraus, dass gerade in der Verschiedenheit des Zahlenmaterials der tiefere Grund für das Benford-Gesetz liegt: Alles, was sich messen lässt, ist letztlich das Resultat natürlicher Prozesse. Zahlen, die solche Prozesse beschreiben, folgen mathematisch definierten Verteilungen. Hill konnte zeigen: Wenn man zufällig Zahlen aus unterschiedlichen Verteilungen auswählt, werden ihre Anfangsziffern dem BenfordGesetz gehorchen, auch wenn die einzelnen Verteilungen keine Ähnlichkeit damit haben. Das Benford-Gesetz ist die Mutter aller Verteilungen. Das ist selbst für Mathematiker nicht leicht zu verstehen, aber es ist so. Die wundersame Ziffernverteilung blieb eine statistische Kuriosität, bis Steuerbeamte und Controller merkten, dass auch die Ziffern in Bilanzen dem Gesetz unterliegen. Und Experimente zeigten, dass Menschen spontan nicht in der Lage sind, Zahlen zu erfinden, die dem Benford-Gesetz gehorchen. Mit dem Zählen von Ziffern sollte es also möglich sein, Betrügern auf die Spur zu kommen. «Als ich zum ersten Mal vom Benford-Gesetz hörte, dachte ich, das ist ja wunderbar», sagt der Mathematiker Jean-Luc Wichoud und fügt dann bedauernd an, «in der Praxis sieht es leider etwas anders aus.» Wichoud arbeitet bei der Eidgenössischen Steuerverwaltung und setzt sich mit der Anwendung des Benford-Gesetzes bei der Mehrwertsteuer auseinander. Dabei zeigt sich: Die Resultate aus den Millionen von geprüften Zahlen stimmen nicht exakt genug mit der Benford-Verteilung überein. Dafür gibt es zwei mögliche Gründe: Entweder es wird grossflächig betrogen, oder es gibt bei der Mehrwertsteuer systematische Abweichungen vom Benford-Gesetz. Es kann etwa vorkommen, dass je nach Branche und Berechnungsart die Häufigkeit der Anfangsziffern ausschert. In diesem Fall müssen die Daten vor der Analyse entsprechend bearbeitet werden. Wie genau, versucht Wichoud herauszufinden. Erst wenn er es weiss, können einzelne Abrechnungen geprüft werden. Sein Vorgesetzter Bernhard Stebler warnt vor übertriebenen Hoffungen. Zwar blieb ein Dateien verarbeiten 14 vor drei Jahren auf andere Weise aufgedeckter Betrugsfall auch beim Test nach Benford hängen. Aber eben erst im Nachhinein und nachdem das «Sieb richtig eingestellt war», wie Stebler sagt, die Daten also richtig bearbeitet worden waren. Das Benford-Gesetz ist eine von vielen Möglichkeiten, mit denen sich vielleicht dereinst ein Betrug aufspüren liesse - oder auch nicht. Im Internet kursiert bereits ein Programm zur Erzeugung von Anfangsziffern in den richtigen Häufigkeiten. (Reto U. Schneider, NZZ Folio 1/06) Dateien verarbeiten 15 Phase 1 – Analyse Beschreiben und Analysieren des Problems Sie haben sich nun das nötige Hintergrundwissen über das Benford-Gesetz angeeignet. Können Sie sich erklären, wieso das Benford-Gesetz nicht für alle Zahlen resp. Zahlenquellen gilt? Welche Ursachen können für eine Abweichung verantwortlich sein? Ziele dieser Phase In der Analyse-Phase wollen wir das Problem in der Realität verstehen und beschäftigen uns mit der Frage wie ein Mensch die Aufgabe lösen würde. In der Software-Entwicklung beinhaltet diese Phase meist auch Gespräche mit den zukünftigen Benutzerinnen, um deren Wünsche an das System kennen zu lernen. Daraus lassen sich die Ziele und Anforderungen an das Programm ableiten. Damit Sie sich eine Vorstellung machen können, wie man das Benford-Gesetz anwendet, finden Sie untenstehend einen kurzen Text. Überprüfen Sie, ob die Zahlen in diesem Kochrezept Benfordverteilt sind. Suchen Sie im Rezept alle Anfangsziffern und notieren Sie sich laufend im Kasten rechts die Anzahl der gefundenen Ziffern. Glarner Zoggle Zutaten für Teig: 80 g Schabzieger 270 g Weissmehl 200 g Knöpflimehl 2 TL Salz 1 dl Milch 1 dl Wasser 4 Eier Zwiebel-PetersilieMischung: 1 Zwiebel 1 Bund Petersilie 30 g Butter Salz, Pfeffer, Muskatnuss Für den Teig Weiss-, Knöpflimehl und Salz in einer Schüssel mischen, eine Mulde formen. Milch, Wasser und Eier verquirlen, hineingiessen. Mit einer Kelle zu einem Teig verrühren. Teig mit der Kelle klopfen, bis er glatt ist und Blasen wirft. Schabzieger an der Bircherraffel reiben und unter den Teig rühren. Zugedeckt 30 Minuten ruhen lassen. Teig portionenweise durch das Knöpflisieb in knapp siedendes Salzwasser streichen. Zoggle ziehen lassen, bis sie an die Oberfläche steigen. Mit einer Schaumkelle herausnehmen, abtropfen lassen. In einer vorgewärmten Schüssel zugedeckt warm stellen. Zwiebeln schälen, hacken und in Butter bei kleiner Hitze 5 Minuten andämpfen. Gehackte Petersilie beifügen und kurz dämpfen, würzen. Zwiebel-Petersilie-Mischung über die Zoggle geben. Nach Belieben mit Schabzieger bestreuen. Platz für Ihre Auswertung: Dateien verarbeiten 16 Insgesamt sollten Sie im Rezept 12 Anfangsziffern gefunden haben und deren Verteilung zeigt, dass diese offenbar ebenfalls dem Benford-Gesetz gehorchen. Während der Bestimmung der Häufigkeitsverteilung der Anfangsziffern haben Sie soeben ganz intuitiv einen Lösungsalgorithmus für das Benford-Gesetz angewandt. Da Computer allerdings weder intelligent sind, noch intuitiv handeln können, müssen Sie diese komplexen Abläufe in einzelne Zwischenschritte aufschlüsseln. In dieser Phase der Softwareentwicklung beschränken wir uns auf von Menschen ausführbare Algorithmen. In der nächsten Phase 2 "Modellbildung" werden wir dann diese Algorithmen in ein Modell umwandeln um schlussendlich durch Maschinen ausführbare Algorithmen zu erhalten. Schreiben Sie Ihren Lösungsalgorithmus möglichst detailliert auf. Menschlicher Lösungs-Algorithmus für das Benford-Gesetz: - Bleistift zur Hand nehmen - Am Anfang des Rezepts mit dem Lesen beginnen - 1. Zahl suchen - … Ziele und Anforderungen Die Ziele unseres Programms ergeben sich aus der Aufgabenstellung und sollten zu Beginn des Software-Design Prozesses festgehalten werden. Die weiteren Aufgaben können anschliessend durch schrittweise Verfeinerung modelliert und implementiert werden. Dateien verarbeiten 17 Wir setzen uns für diese Aufgabe folgende Ziele: Wir möchten ein Programm entwickeln, welches einen Text (beispielsweise den Geschäftsbericht 2005 der Migros) einliest und dessen Benford-Verteilung untersucht. Die Resultate sollen in eine Datei geschrieben werden, so dass in Excel ein Diagramm erstellt werden kann. Als nächstes definieren wir, welche Anforderungen wir an das Programm haben, wir formulieren damit gewissermassen die Leitplanken unseres zu entwickelnden Systems: - Dem Programm können beliebige (Text-)Dokumente zur Verarbeitung übergeben werden - Das Programm soll alle Anfangsziffern des kompletten Dokuments heraussuchen. Textdateien sind daran zu erkennen, dass sie zeilenweise angeordnet sind und Buchstaben sowie Ziffern und Leerzeichen enthalten. Zudem besitzen sie am Ende jeder Zeile ein Zeilenendzeichen (Eoln) sowie am Textende ein Eof-Zeichen. - Die Ausgabe aller gefundenen Ziffern soll in eine Datei und in einem Format erfolgen, welches die Weiterverarbeitung in einer Tabellenkalkulation (Excel) erlaubt. Dazu werden wir zwischen jedem Wert ein ; einfügen und nach jeweils Ziffern auf eine neue Zeile wechseln - Ein Formular (GUI) soll die Benutzerin die Angabe der zu verarbeitenden Datei erleichtern und sie zusammenfassend über die Resultate der Berechnung informieren Unser bisheriger Lösungsansatz setzt den Menschen als Prozessor ein. Da wir aber ein Programm schreiben wollen, in dem der Computer diese Arbeit übernehmen soll, müssen wir in unserem Algorithmus den Menschen durch eine Maschine ersetzen. Dieser Schritt passiert in der Modellierung, welche das Thema der nächsten Phase ist. Dateien verarbeiten 18 Phase 2 – Modellierung Formulieren der Aufgabenstellung als Problem der Datenverarbeitung Mit der Modellbildung beginnt die Konstruktion der Lösung. Ziel dieser Phase ist es ein Modell unseres Programms zu erstellen, welches den Anforderungen aus Phase 1 gerecht wird. Die mit der Modellbildung einhergehende Abstraktion ist einer der wichtigsten und auch schwierigsten Schritte beim Problemlösen. Das Modell hilft uns, heikle und kritische Programmpunkte zu identifizieren und in der nächsten Phase 3 detailliert auszuarbeiten. Für die Modellbildung verwenden die meisten Programmierer so genannte Flussdiagramme. Sie haben im Einleitungstext das Prinzip der Flussdiagramme und dessen Symbole kennen gelernt. Sehr hilfreich beim Entwerfen eines Flussdiagramms ist das vorgängige Definieren des Inputs und des Outputs des Programms (I/O Schnittstelle): Welche Eingaben stehen meinem Programm zur Verfügung und welche Daten soll es liefern? In der folgenden Grafik sehen Sie ein mögliches Modell unseres Benford-Programms. Studieren Sie das Diagramm und versuchen Sie den Programmablauf nachzuvollziehen. Können Sie einen kritischen Programmpunkt erkennen? Dateien verarbeiten 19 Input Text Start Migros Geschäftsbericht Datei auswählen Nein Datei vorhanden? Ja Zeichen einlesen End of file? Ja Nein Anfangsziffer suchen Zähler für Ziffer und Total um 1 erhöhen Output Prozentuelle Anteile ausrechnen Anfangsziffern und Häufigkeit ausschreiben Excel-Datei Stop Ein kritischer Punkt versteckt sich hinter der Bezeichnung "Anfangsziffer suchen". Der Mensch kann die erste Ziffer einer Zahl in einem grossen Text problemlos erkennen und benennen. Ein Computer kann den Text aber nicht als Ganzes erfassen, sondern liest ihn zeichenweise ein. Zu einem bestimmten Zeitpunkt während dem Programmablauf kennt der Computer also nur genau ein Zeichen des ganzen Textes. Wie müssen wir vorgehen, damit der Computer trotzdem alle Anfangziffern eines Textes findet? Die detaillierte Ausarbeitung dieses Programmpunktes und das Erstellen eines lesbaren Algorithmus ist Ziel der nächsten Phase. Computer- Dateien verarbeiten 20 Phase 3 – Entwurf Erarbeiten eines detaillierten Problemlösungsablaufs Mit Hilfe unseres Modells haben wir in der vorangegangenen Phase den Programmpunkt "Anfangsziffer suchen" als kritisches Element identifiziert. Nun fokussieren wir uns nur noch auf diesen Programmabschnitt und setzen diesen schrittweise in Pseudocode um. Wie Sie in der Einführung gesehen haben, versteht man unter Pseudocode eine formlose, individuelle Notation, in der ein Algorithmus beschrieben wird. Dazu verwenden wir eine Mischung von Deutsch und Pascal. In der nächsten Phase übertragen Sie dann diesen Pseudocode Zeile für Zeile in Programmcode. Kritische Programmabschnitte aufschlüsseln und ausarbeiten Zuerst gilt es wiederum den Input und Output (I/O) dieses kritischen Programmabschnitts zu bestimmen. Wie wir aus unserem Flussdiagramm entnehmen, können wir folgende I/O-Daten festhalten: Input: Ein Zeichen aus der Datei (z.B. "h" aus der Rezept-Zeile "...mehl 1 Zwiebel…") Output: Falls eine Anfangsziffer gefunden wurde, soll diese Ziffer an den nächsten Programmschritt weitergegeben werden. (z.B. "1") • Überlegen Sie sich, wie Sie folgende Anfangsziffern finden könnten, obwohl Sie durch eine Schablone nur ein Zeichen pro Mal erkennen könnten? (Sie dürfen natürlich die Schablone über die Datei bewegen). • Wie muss ein Computer bei der Erkennung vorgehen? Welche Zeichen muss er dabei unterscheiden? (Dazu ist es hilfreich, wenn Sie alle Leerschläge mit Leuchtstift einfärben, sowie alle EolnZeichen eintragen). Dateien verarbeiten 21 Es gibt mehrere Möglichkeiten, wie man diese Anfangsziffern finden kann. Wir schlagen folgenden Weg vor, wobei Sie natürlich frei sind, Ihre eigene Idee auszuprobieren: Da wir immer nur die Anfangsziffer einer Zahl suchen, interessiert uns vor allem das Zeichen vor dieser Ziffer. Dieses Zeichen ist nämlich entweder ein Leerzeichen (vgl. " 1 Zwiebel") oder aber ein Eoln-Zeichen (vgl. "80 g Schabziger"). Der Programmabschnitt "Anfangszeichen suchen" braucht demnach zwei Informationen um die Anfangsziffer zu identifizieren: Wie lautet das aktuelle Zeichen (Ziffer?) und wie lautete das Zeichen vor dieser Zahl (Leerzeichen oder Eoln?). Konkret heisst dies, dass der Programmabschnitt so lange ein Zeichen aus der Datei verlangt, bis er eine Ziffer gefunden hat. Um überprüfen zu können, ob es sich bei dieser Ziffer um die Anfangsziffer handelt, muss ihm das vorgängige Zeichen bekannt sein. Deshalb muss vor dem Einlesen eines neuen Zeichens das jeweils alte Zeichen in einer temporären Variablen abgelegt werden. Zudem muss überprüft werden, ob es sich beim neuen Zeichen um ein Eoln-Zeichen handelt. Diese Information muss auch in einer temporären Variablen abgelegt werden. Falls die Funktion eine Anfangsziffer gefunden hat, wird diese dem nächsten Programmschritt übergeben. Transformieren Sie nun diese Anleitung in Pseudocode: Pseudocode des Programmschritts "Anfangsziffer suchen": var zeichen, vorgaengiges_zeichen: Char var vorgaengiger_zeilenumbruch: Boolean wiederhole so lange bis das Ende der Datei (Eof) erreicht: { zeichen := zeichen_aus_datei() …. FALLS (zeichen = eine Zahl zwischen 1-9) DANN { vorgaengiges_zeichen = " " ODER vorgaengiger_zeilenumbruch = true } ….. Dateien verarbeiten 22 Externe und interne Datenstrukturen festlegen Neben der Ausarbeitung der kritischen Programmschritte in Pseudocode, umfasst die EntwurfsPhase aber auch noch das Festlegen der Datenstruktur. Die Datenstruktur repräsentiert dabei die Gegenstände der realen Welt, in unserem Fall also die Eingabe- sowie die Ausgabedatei. Zusätzlich muss aber auch die Programm-interne Datenstruktur festgelegt werden (z.B. lokale, globale Variablen und Arrays). Das Definieren der Datenstrukturen ist wichtig, da diese uns während dem gesamten Programmablauf zur Verfügung stehen und wir mit unseren Prozeduren und Funktionen auf diese zugreifen. Geschickt ausgewählte Datenstrukturen sind entscheidend für einen optimalen Programmablauf und eine etwaige Fehlersuche! Die Eingabedateien liegen uns in Form von Textdateien vor. Deren Struktur können wir nicht beeinflussen. Sie werden immer von oben links nach unten rechts Zeichen für Zeichen eingelesen. Als Programm-interne zentrale Datenstruktur verwenden wir zwei eindimensionalen Arrays[1-9]. Darin speichern wir, wie oft eine Zahl gefunden wurde und die prozentuale Verteilung der Zahlen. Damit sie uns in allen Prozeduren und Funktionen zur Verfügung stehen, deklarieren wir sie als globale Arrays. Die Informationen in diesen Arrays werden wir sowohl im GUI als auch in eine Datei ausgeben. Wie immer wählen wir zwei aussagekräftige Arraynamen: ZiffernAbsolut[] und ZiffernProzent[] Die Ausgabedatei (CSV-Datei -> comma seperated values) müssen wir an den Excel-Importfilter anpassen. Damit Sie in Excel erfolgreich weiterverarbeitet werden kann, empfehlen wir folgende Struktur (beachten Sie, dass die einzelnen Zahlen durch ein Semikolon ";" voneinander getrennt sind). CSV-Datei: Import in Excel: Ziffer;Absolut;Prozent 1;4;0.333333333333333 2;3;0.25 3;2;0.166666666666667 4;1;0.0833333333333333 5;1;0.0833333333333333 6;0;0 7;0;0 8;1;0.0833333333333333 9;0;0 GUI skizzieren Durch das Skizzieren des GUIs schliessen wir die Entwurfsphase ab. Wie Ihnen nun bestimmt auffällt, sind wir bei den vorangegangenen E.Tutorials jeweils an diesem Punkt eingestiegen. Dies mag vielleicht bei kleinen Projekten noch funktionieren, bei grösseren und komplexeren Programmier-Projekten ist jedoch eine ausführliche Planung unerlässlich und spart schlussendlich viel Zeit und vor allem Nerven. Dateien verarbeiten 23 Studieren Sie die nachfolgende Skizze und überlegen Sie sich, mit welchen Delphi Objekten man diese Ausgabe erreichen könnte. Für die Resultate-Tabelle werden wir ein neues Delphi-Objekt verwenden (StringGrid), welches eine einfache tabellarische Darstellung von Werten ermöglicht. Die restlichen Objekte kommen Ihnen bestimmt bekannt vor, da wir sie ja bereits in anderen Aufgaben verwendet haben. An diesem Punkt schliessen wir nun die Planungsphase ab und wechseln das Arbeits-Medium. In der nächsten Phase werden Sie nämlich die auf Papier entwickelten Flussdiagramme und Pseudocodes in Delphi umsetzen und am Computer ausprogrammieren. Während dieser Implementierungsphase kann es übrigens durchaus vorkommen, dass Sie auftretende Probleme kurz skizzieren und mit Hilfe von Flussdiagrammen oder Pseudocode lösen. Dateien verarbeiten 24 Phase 4 – Implementation Formulieren der Lösung als Programm Diesen Schritt kennen Sie aus den vorhergehenden Lektionen bereits bestens. In den bisherigen Modulen haben wir uns hauptsächlich auf diese Phase konzentriert. Wir werden Ihnen daher bei diesem Programm mehr Freiheiten lassen, damit Sie Ihre persönliche Vorgehensweise entwickeln können. Trotzdem werden wir ein grobes Ablaufsschema vorgeben und neue Elemente und Konzepte kurz vorstellen. Auf jeden Fall empfiehlt sich bei Fragen die kontext-sensitive Hilfe von Delphi - die immer nur einen F1-Tastendruck entfernt ist - zu konsultieren. Falls Sie vor dem Drücken auf F1 den Ausdruck oder das Objekt zu dem Sie Hilfe wünschen im Unit- oder im Form-Fenster markieren, erscheint automatisch die entsprechende Hilfe. Wünschen Sie allgemeine Hilfe, können Sie die ganze Hilfedatei über Help-Topics nach Stichworten oder nach Themen geordnet durchsuchen. Weitere Informationen zu den Operationen mit externen Files erhalten Sie im theoretischen Teil und im Begleittext zur Vorlesung. Erstellen Sie ein neues Projekt (BenfordProjekt) mit einer Unit-Datei (Benford) GUI zeichnen Erstellen Sie die grafische Benuterzoberfläche. In der Phase 3 haben Sie bereits eine Skizze gesehen, wie das GUI ungefähr aussehen könnte. Die meisten zu verwendenden Objekte kennen Sie bereits, für die 4 Aktionen (Einlesen, Kopieren, Berechnen, Ausschreiben) werden wir TButtons verwenden, und TEdit-Felder um die Namen der Einund Ausgabedatei anzuzeigen. TStringGrid Das einzig neue Element ist das Gitter-Objekt (TStringGrid, ) in welchem wir die Resultate unserer Berechnung auf dem Bildschirm anzeigen werden. Sie finden es im Register „Zusätzlich“. Im Objektinspektor können Sie die folgenden Eigenschaften des TStringGrid-Objekts anpassen: - ColCount/RowCount: Anzahl Spalten/Reihen Dateien verarbeiten 25 - DefaultColWidth/DefaultRowHeight: Höhe der Spalten/Reihen - FixedCols/FixedRows: Festlegen von Überschriftspalten und -reihen - ScrollBars: Verwenden von Scrollbalken Erstellen Sie das GUI in Anlehnung an die Zeichnung von Phase 3. Geben Sie den Objekten eigene Namen, damit Sie diese später einfacher aufrufen können Datei einlesen und anzeigen In diesem Schritt werden Sie eine Prozedur programmieren, welche beim Klicken auf den ‚Datei auswählen’-Knopf ein Fenster öffnet, in welchem Sie eine Datei für die Analyse auswählen können. Nach der Auswahl soll der Name in eine Variable geschrieben und in einem Textfeld angezeigt werden. Dies ist Voraussetzung, damit wir in den nächsten Schritten auf die Datei zugreifen können. OpenDialog Im Delphi gibt es für das Auswählen einer Datei eine vordefinierte Objektvorlage OpenDialog ( ), welches Sie im Register „Dialoge“ finden. Dieses Objekt wird für die Benutzerin unsichtbar bleiben, es stellt unserem Programm aber die Funktionalität für die Dateiauswahl zur Verfügung. Das Aufrufen des OpenDialogs geschieht mit dem Befehl OpenDialog1.Execute Dieser Befehl hat einen Rückgabewert, wie Sie es bereits von den Funktionen aus der letzten Übung kennen. Wurde eine Datei angegeben, meldet der Befehl ein True zurück, hat die Benutzerin jedoch auf Abbrechen geklickt, gibt’s ein False zurück. Diese Rückgabewerte werden wir für eine Eingabe-Validierung(Ist eine Datei vorhanden?) benutzen. Der Befehl zum Auslesen des Namens der ausgewählten Datei lautet OpenDialog1.Files[0] Hinweis: Die Eigenschaft Files ist ein Array, auf dessen erste Komponente Sie mit [0] zugreifen können. Fügen Sie dem Formular ein OpenDialog-Objekt hinzu. Verschieben Sie dieses an einen Ort im Formular, wo es Ihnen nicht in den Weg kommt Dateien verarbeiten 26 Generieren Sie für den „Datei auswählen“-Knopf einen OnClick-Event, welcher das OpenDialogObjekt aufruft Überprüfen Sie anhand des Rückgabewerts ob eine Datei angegeben wurde, falls ja, schreiben Sie den Namen der Datei in eine globale String-Variable und schreiben Sie den Dateinamen in das entsprechende Textfeld Datei kopieren und ausgeben Bevor wir uns mit der Berechnung nach Benford beschäftigen, wollen wir uns absichern, dass das Einlesen und Ausschreiben der Datei korrekt funktioniert. Dazu werden wir eine Testprozedur schreiben, welche den Inhalt des ausgewählten Files in eine zweite Datei kopiert, also sozusagen eine Backup-Datei erstellt. Sie haben in der Vorlesung und der Einführung bereits einiges über die Arbeit mit externen Files gehört und gelesen. Wichtig ist zu wissen, dass Sie nie direkt auf das File zugreifen können, sondern dass Sie die Datei immer einer Filevariable vom Typ Textfile zuweisen müssen. Genau so wichtig ist, dass Sie die Datei am Schluss wieder schliessen, denn sonst kann kein anderes Programm darauf zugreifen. Dateizugriff Der Ablauf bei einem Lesezugriff auf ein File folgt demnach diesem Schema: VAR InputFile: Textfile; Zeichen: Char; BEGIN AssignFile(InputFile, 'E:\rezept.txt'); Reset(InputFile); Read(InputFile, Zeichen); CloseFile(InputFile); END. Entsprechend wird beim Schreibzugriff auf eine Datei vorgegangen, mit dem Unterschied, dass Rewrite statt Reset und Write/Writeln statt Read/Readln verwendet wird. Eoln/Eof Das Spezielle an Textfiles ist, dass jede Zeile mit einem Zeilenende-Zeichen (Eoln, End-of-line) und das ganze File mit einem Fileende (Eof, End-of-file) abgeschlossen ist. Dateien verarbeiten 27 ChangeFileExt Der Datei, die durch das Kopieren entsteht, müssen Sie einen neuen Namen zuordnen, sonst weiss das Programm nicht, wohin es den Output schreiben soll. Mit der Funktion VAR DateiNameOriginal, DateiNameKopie: String; DateiNameKopie := ChangeFileExt(DateiNameOriginal, '.bac') können Sie die Datei-Endung ändern, aus rezept.txt wird dadurch rezept.bac (.bac für Backup). Datei-Inhalt kopieren Das eigentliche Kopieren können Sie mit zwei While-Schleifen (WHILE NOT Eof, WHILE NOT Eoln) bewerkstelligen. Diese verschachtelte Schleifen-Konstruktion haben Sie bereits beim WohnblockProgramm im Tutorial 2 kennen gelernt. Schreiben Sie eine OnClick-Prozedur für den ‚Datei kopieren’-Knopf, welcher überprüft, ob ein Eingabefile vorhanden ist. Ist das der Fall soll die Prozedur eine identische Kopie der Datei machen mit einer anderen Datei-Endung. Zum Testen können Sie das „Glarner Zoggel“-Rezept kopieren, welches Sie im E.Tutorial herunterladen können. Öffnen Sie die kopierte Datei im Notepad und überprüfen Sie ob die beiden Dateien identisch sind. Benford berechnen Hier können Sie nun kreativ sein. Programmieren Sie den von Ihnen in der vorherigen Phase entwickelten Algorithmus zum Ausfindigmachen von Benford-Ziffern aus. Zeigen Sie die Resultate in der Tabelle des Formulars an. Resultate in Arrays abzulegen Wir empfehlen, dass Sie zum Speichern der Häufigkeit der gefundenen Ziffern einen Array (ZiffernAbsolut: Array[1..9] of Integer) verwenden. In einem zweiten Array (ZiffernProzent: Array[1..9] of Real) können Sie - nachdem das ganze File abgearbeitet wurde - die prozentuellen Anteil aller Ziffern berechnen. Dazu bietet es sich an in einer separaten Variablen das Total der gefundenen Ziffern abzuspeichern. Dateien verarbeiten 28 TStringGrid aktualisieren Ausserdem möchten wir der Benutzerin unser Resultat im Formular anzeigen, dafür haben wir ja bereits eine Tabelle mithilfe eines TStringGrid-Objekts bereitgestellt. Wie können Sie nun die Daten aus den Arrays in die Gitterstruktur rübertransformieren? Hier kommt uns entgegen, dass die Zellen eines TStringGrids ebenfalls wie Elemente eines Arrays behandelt werden können. Um in der ersten Reihe der Tabelle eine Überschrift mit alle Ziffern zu erzeugen, können wir demnach folgenden Code verwenden: FOR i := 1 TO 9 DO StringGrid1.Cells[i-1,0] := IntToStr(i); Nach dem gleichen Prinzip können Sie so die Resultate aus den Häufigkeits- und Prozent-Arrays auslesen und im Gitter anzeigen lassen. Zeichen in Ziffern umwandeln mit ord() Wie kann man überprüfen, ob ein eingelesenes Zeichen eine Ziffer zwischen 1 und 9 ist? Verwenden Sie dazu die ord(Zeichen)-Funktion, welche den Ordinalwert des Zeichens zurückgibt. Eine entsprechende Bedingung wäre also IF (ord(Zeichen) >= ord('1')) AND (ord(Zeichen) <= ord('9') THEN Typenumwandlung Denken Sie daran, dass Sie Char-Zeichen mit StrToInt in Integer-Ziffern umwandeln können. Andere evt. benötigte Umwandlungsfunktionen sind IntToStr, FloatToStr. Beispiel: Ziffer := StrToInt(Zeichen); Programmieren Sie eine FOR-Schleife, in welcher Sie alle Elemente der beiden Arrays mit 0 (Null) initialisieren Schreiben Sie in einer zweiten FOR-Schleife den Inhalt der beiden Arrays (Im Moment o) in die Felder der TStringGrid-Tabelle aus. In derselben Schleife können Sie gleich die Überschriften ausgeben lassen Fügen Sie Ihren selber entwickelten Benford-Algorithmus zwischen diese beiden FOR-Schleifen ein Resultate in Datei ausschreiben Sie haben nun die Resultate programmintern in Arrays gespeichert. Diese Information geht aber verloren, sobald Sie das Programm beenden. Wir wollen diese Information jedoch permanent speichern um sie danach ins Excel importieren zu können. Dateien verarbeiten 29 Erstellen Sie eine Prozedur für den „Datei ausschreiben“-Knopf, welcher die absoluten und prozentualen Zahlen in eine Datei mit der Endung .csv ausgibt (Siehe Bild unten). Damit Excel die Elemente richtig anzeigt, muss zwischen jeden Wert ein ; eingefügt werden. So sollte das .csv-Datei - in einem Editor geöffnet – aussehen: Und die entsprechende Datei im Excel: Dateien verarbeiten 30 Phase 5 – Verifikation Ausführen und Testen des Programms Sie haben nun ein Programm von A bis Z selber entwickelt und programmiert. Als letzter Schritt wollen wir nun überprüfen, ob ihre Software auch korrekt funktioniert. Lesen Sie dazu die TestDateien aus dem E.Tutorial ein und lassen Sie die Benford-Verteilung berechnen und ausgeben. Vergessen Sie nicht am Schluss des E.Tutorials das Feedback-Formular auszufüllen. Besten Dank. Dateien verarbeiten 31 C. Selbständige Arbeit Sie haben in diesem Modul bereits genug selbständige Arbeit verrichtet, weshalb wir hier auf eine zusätzliche Aufgabe verzichten. D. Abgabebedingungen Ein lauffähiges Programm für die Berechnung der Benford-Verteilung. Drucken Sie das kommentierte Programm für die Abgabe aus. Sie sollten die Begriffe und Sprachelemente mit einfachen Worten erklären können. Sie sollten auch die von Ihnen entworfenen Flussdiagramme und den Pseudocode vorzeigen und erläutern können. Sie haben das Phasen-Modell, welches beim Software-Design häufig zur Anwendung kommt, verstanden und können die Ziele der einzelnen Phasen in eigenen Worten zusammenfassen. 6. Neue Sprachelemente Variablen sind kursiv geschrieben Sprachelement Bedeutung Beispiel Textfile Datentyp zur Verwaltung von Textdateien. Wird zur Deklaration von Filevariablen verwendet Var AssignFile Bringt eine Filevariable in Verbindung zum Namen, inkl. Verzeichnispfad, einer Datei AssignFile(Dokument, 'C:\Meintext.txt'); Rewrite Bereitet ein File vor, Daten in eine Datei zu transferieren. Der Inhalt bestehender Dateien wird überschrieben Rewrite(Dokument); Append Bereitet ein Textfile vor, Daten ans Ende einer Textdatei anzufügen Rewrite(Dokument); Reset Öffnet ein File fürs Lesen Reset(Dokument); CloseFile Schliesst ein File und stellt sicher, dass alle geschriebenen Daten in die Datei transferiert werden CloseFile(Dokument); EOF Prüft, ob bereits die letzte Filekomponente verarbeitet wurde EOF(Dokument); EOLN Prüft, ob die nächste Filekomponente ein Zeilenende-Zeichen ist EOLN(Dokument); Dokument: Textfile;