Algorithmen und Datenstrukturen

| Algorithmen & Datenstrukturen Programmieren 1 von Prof. Jürgen Plate

# Programmierübungen in "C"

Zur Vorbereitung der Aufgabe gehören:

- sofern nicht in der Angabe vorgeschrieben, geeignete Datenstrukturen (mit Begründung),

- eine vernünftige Unterteilung in Teilprojekte (mit Beschreibung),

- sauber gezeichnete Struktogramme (sofern der Algorithmus sehr einfach ist, genügt eine verbale Beschreibung),

- ein Terminplan für die Fertigstellung der einzelnen Teilprojekte und

- abschließend vollständige C-Quellprogramme (deren Lauffähigkeit vorzuführen ist).

Je nach Komplexität der Aufgabe können einzelne Punkte stark gekürzt werden.

## Erste Schritte

### Potenzierung

Ein schnelles Potenzierungsverfahren für ganze Zahlen bietet der folgende Algorithmus (WertExponente):

    Potenz = 1
    solange Exponent ungleich 0 ist wiederhole:
       Wenn Eponent gerade dann:  Exponent = Exponent/2, Wert = Wert * Wert
                           sonst: Exponent = Exponent-1, Potenz = Potenz * Wert

Setzen Sie das Verfahren in ein C-Programm um und testen Sie es mit verschiedenen Werten.

### Zahlentabelle

Schreiben Sie ein Programm, das in Tabellenform Zahlenwerte in dezimal, oktal und hexadezimal (= sedezimal) darstellt. Der Wertebereich der Zahlen soll im Dialog mit dem Benutzer eingegeben werden (untere Grenze, Obere Grenze). Denken Sie an eine Überschriftszeile und die rechtsbündige Ausrichtung der Zahlen. Anmerkung: Sie sollen nicht per Programm in hexadezimal oder oktal umrechnen, sondern diese Arbeit der Funktion `printf` anvertrauen.

### Ausgabe und Eingabe von Zeichen auf dem Bildschirm

1. Schreiben Sie ein Programm, das ein Zeichen (z.B. '*') in einer Zeile "laufen läßt". Benutzen Sie dazu nur einfache Zeichenausgabe-Funktionen. Um den Vorgang am Bildschirm verfolgen zu können, benutzen Sie als Verzögerungsfunktion den Aufruf der Systemfunktion `sleep(sec)`, die `sec` Sekunden wartet (Headerdatei: `unistd.h`). Wenn Ihnen `sleep()` zu langsam ist, können Sie auch `usleep()` verwenden. Das "u" steht für "Mikro". Hier wird die Anzahl der Mikrosekunden als Parameter angegeben (max. 1000'000 Mikrosekunden = 1 Sekunde). Das Programm soll abbrechen, wenn das Zeichen am rechten Rand angekommen ist (Bildschirm ist ca. 80 Zeichen breit).

   Hinweis: Wie bei den meisten Betriebssystemen wird auch bei dem im Praktikum verwendeten UNIX die Ausgabe gepuffert. Das heißt, es wird erst etwas ausgegeben, wenn ein Newline kommt oder gar erst, wenn das Programm zuende ist. Sie müssen also dafür sorgen, daß nach jedem Zeichen wirklich auch eine Bildschirmausgabe erfolgt. Sie erreichen dies durch den Funktionsaufruf `fflush(stdout);` nach jeder Ausgabe.

   Erweitern Sie das Programm außerdem um die Fähigkeit, das Zeichen auch links zu schieben, nämlich dann, wenn es am rechten Rand angekommen ist. Programmende bei Rückkehr des Zeichens zum Zeilenanfang.

2. Schreiben Sie nun das Programm so um, dass für das Schieben Funktionen verwendet werden:
   1. Verwenden Sie zwei Funktionen: `void movright(void)` und `void movleft(void)`, die beim Aufruf das Zeichen jeweils um eine Position nach rechts oder links bewegen. Stellen Sie Ihr Programm (genauer: die Funktion `main()`) so um, daß nun anstelle der Ausgabeanweisungen die FUnktionsaufrufe verwendet werden.
   2. Verwenden Sie danach eine universelle Funktion `void movrl(int rl)`, die abhängig vom Parameter nach rechts oder links schiebt (z.B.: rl == 0 schiebt rechts, rl != 0 schiebt links).
   3. Erweitern Sie das Programm um die Fähigkeit, im Dialog mit dem Benutzer die Schieberichtung zu steuern. So soll ein eingegebenes Leerzeichen ein Rechtsschieben um eine Position bewirken, ein 'b' das Linksschieben. Andere Zeichen sollen direkt wieder ausgegeben werden und ein bestimmtes (z.B. 'e') soll das Programm beenden.

   Hinweis: Auch die Eingabe wird wie die Ausgabe gepuffert. Das heißt, es wird beim Aufruf von `getchar()` gewartet, bis nach dem Zeichen die Enter-Taste gedrückt wird. Um das zu vermeiden, muß das System in den ungepufferten Betrieb geschaltet werden. Verwenden Sie statt der Funktion `getchar()` die Funktion `getch()`, die in der Headerdatei `adprakt.h` definiert ist. Mit `getch()` wird das eingegebene Zeichen auch nicht mehr automatisch auf dem Bildschirm geechot, sondern erst durch die Ausgabe in Ihrem Programm.
   Das Programmskelett sieht im Prinzip so aus:

       #include <stdio.h>
       #include <stdlib.h>
       #include <adprakt.h>

       int main()
         {
         char c = ' ';
         while (c != 'e')
          {
          c = getch();
          switch (c)
            {
            case ' ': moveright();  break; /* rechtsschieben */
            case 'b': moveleft();   break; /* linksschieben */
            case 'e': printf("\a"); break; /* fertig, Beep! */
            default  : putchar(c);   break; /* anderes Zeichen */
            }
          fflush(stdout);
          }
         printf("\n"); /* Newline */
         return 0;
         }

## Relais-Steuerung

Verwenden Sie die Unterprogramme zur Relais-Steuerung (siehe auch Praktikumsanleitung), um eine Verkehrsampel zu programmieren. Beachten Sie dabei die Lichterfolge (rot, rot-gelb, grün, gelb, rot, ...) und die Schaltzeiten (kurze Zeit: 2s, lange Zeit: 10s). Welche Schaltzeiten sind lang, welche kurz? Hinweis: Zur Zeitverzögerung dient die Funktion `sleep()`, deren Parameter die Wartezeit in Sekunden angibt. Die Zuordnung der Lampen ist R1: rot, R2: gelb, R3: grün.

Die Headerdatei `adprakt.h` stellt zwei Funktionen für das Praktikum zur Verfügung:

`int relais(int R1, int R2, int R3, int R4, int R5, int R6, int R7, int R8, char Result[200])`

schaltet Relais in der Relaisbox des Rechners "blackhole", der sich im Labor ganz hinten rechts befindet. Die Relaisbox wird für das Praktikum mit dem schwarzen Hauptschalter eingeschaltet (die roten Schalter bitte auf Stellung "ein" belassen). Nach dem Praktikum abschalten nicht vergessen (die Box, nicht den Rechner).

Durch den Wert 1 werden die Relais R1 - R8 eingeschaltet, durch den Wert 0 wieder ausgeschaltet. Als Rückgabewert erhält man den Return-Value der Steuersoftware oder bei einem Fehler (z. B. Box nicht eingeschaltet) -1. Im String "Result" wird die Klartextmeldung der Steuersoftware zurückgegeben.

`int relais_off(char Result[200])`

Mit diesem Befehl werden alle Relais wieder ausgeschaltet. Als Rückgabewert erhält man den Return-Value der Steuersoftware (Link siehe unten) oder bei einem Fehler -1. Im String "Result" wird die Klartextmeldung der Steuersoftware zurückgegeben.

Das folgende Programm schaltet die rote Lampe ein und aus.

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <adprakt.h>

    int main()
      {
      char Result[200];
      int erg;
      erg = relais(1,0,0,0,0,0,0,0,Result);
      printf("Erg.: %d Res.: %s\n", erg,Result);
      sleep(1);
      erg = relais(0,0,0,0,0,0,0,0,Result);
      printf("Erg.: %d Res.: %s\n", erg,Result);
      return(0);
      }

Beachten Sie, daß für die Funktion `sleep()` die Headerdatei `unistd.h` zusätzlich benötigt wird.

Hinweis: Da nicht alle gleichzeitig auf die Relais-Fernsteuerung zugreifen können, werden die beiden o. a. Funktionen für die Dauer der Programmentwicklung simuliert, indem man sie zunächst im Programm selbst definiert, z.B. als Ausgabefunktion mit `printf` für die einzelnen Relais-Werte. Der Rückgabewert kann dann immer 0 sein.

Wenn das Programm dann lauffähig ist, kommentiert man die selbst geschriebenen Funktionen aus und läßt; es auf die realen Relais zugreifen.

## Stringausgabe

Entwickeln Sie ein C-Programm, das auf die Eingabe von Abkürzungsbezeichnungen für die Wochentage (in deutsch) mit der Ausgabe des vollen Namens der Wochentage (in englisch) reagiert.

- Die Abkürzungsbezeichnungen für die Wochentage sollen aus drei Zeichen bestehen und folgendermaßen lauten:

  `MON, DIE, MIT, DON, FRE, SAM, SON.`

  Zwischen Groß- und Kleinbuchstaben soll dabei nicht unterschieden werden, d. h. statt "MON" kann z.B. auch "mon" oder "Mon" oder "moN" usw verwendet werden.

- Auf die Eingabe einer falschen - auch zu kurzen Abkürzungsbezeichnung soll das Programm mit der Ausgabe von "No-day" reagieren.
- Das Programm soll in einer Schleife in jeweils einer neuen Zeile die Eingabe einer neuen Abkürzungsbezeichnung anfordern und die entsprechende Ausgabe in der nächsten Zeile erzeugen. Abschluß jeder Eingabe mit <RETURN>.
- Werden mehr als drei Zeichen eingegeben, so sind nur die ersten drei Zeichen zu berücksichtigen. Der Rest der Eingabezeile ist zu ignorieren.
- Das Programm soll enden, wenn nach einer Eingabeaufforderung als erstes Zeichen ein Dateiende-Zeichen (<CTRL-D>) eingegeben wird.

### Beispiel für einen Programmdialog:

    Geben Sie die Abkürzung eines Wochentages ein
    ? mon
    --> Monday
    ? mo
    --> No_day
    ? Dienstag
    -->Tuesday
    ? ^D

### Hinweise zur Lösung:

- Gliedern Sie das Programm möglichst sinnvoll.
- Zur eigentlichen Ermittlung des vollen englischen Namens aus der deutschen Abkürzung können Sie geeignete zweidimensionale char-Arrays zur Speicherung der Abkürzungsbezeichnungen und der vollen Wochentags-Namen oder eine `if - else if`-Kaskade verwenden.
- Die Einlesefunktion `scanf()` liefert als Rückgabewert entweder die Anzahl der eingelesenen Elemente oder EOF (= -1), wenn <>CTRL-D> gedrückt wurde.

## Buchstabenhäufigkeit

Schreiben Sie ein Programm, das einen Text einliest und die Häufigkeiten der eingegebenen Zeichen ermittelt (also die einzelnen ASCII-Zeichen zählt). Ist der Text zuende, sollen die Häufigkeiten der Buchstaben 'A' bis 'Z', 'a' bis 'z' und der Ziffern von '0' bis '9' absolut und prozentual ausgegeben werden.

Verwenden Sie zur Speicherung der Häufigkeiten ein Array mit 256 Elementen. Das jeweils aktuelle Eingabezeichen wird als Feldindex verwendet. Zählen Sie gleichzeitig alle eingegebenen Zeichen - dieser Wert wird für die Prozentangabe benötigt.

## Temperaturen umrechnen

Entwickeln Sie zwei Funktionen `ToFahrenheit` und `ToCelsius`, die eine Temperatur von Fahrenheit nach Celsius und umgekehrt konvertieren. Die Umrechnung erfolgt nach folgenden Formeln:

Celsius nach Fahrenheit: tF = 9 / 5 * tC + 32

Fahrenheit nach Celsius: tC = (tF - 32) * 5 / 9

Welchen Parametertyp und Rückgabewert haben die Funktionen?

Testen Sie die neuen Funktionen, indem Sie in einem Hauptprogramm eine Temperatur zuerst von Celsius nach Fahrenheit und wieder zurück nach Celsius konvertieren und das Ergebnis ausgeben.

Schreiben Sie anschliessend ein Programm, das eine Umrechnungstabelle Fahrenheit nach Celsius für das Intervall von +32 bis +212 Fahrenheit sowie eine Umrechnungstabelle Celsius nach Fahrenheit für das Intervall von -30 bis +50 Celsius ausgibt.

## Widerstandswerte

Gemäß DIN 41429 wird der Wert eines elektrischen Widerstands durch einen Farbcode dargestellt: Zwei Ziffern, gefolgt von 0 bis 6 Nullen. Die beiden Ziffern und die Anzahl der Nullen wird durch folgende Farben dargestellt:

0: schwarz
1: braun
2: rot
3: orange
4: gelb
5: grün
6: blau
7: violett
8: grau
9: weiß

z. B. 1200 Ohm = braun-rot-rot. Schreiben Sie ein Programm, das für eine eingegeben Zahlenwert den Farbcode ausgibt.

Es sollen nur Widerstandswerte zwischen 1 Ohm und 5 Megohm berücksichtigt werden.

Tip: Ermitteln Sie die Anzahl der Nullen durch fortlaufende Division durch 10 und zerlegen Sie die verbleibende zweistellige Zahl in Zehner- und Einerstelle.

## Maße geometrischer Körper

Schreiben Sie ein Programm, das die Oberfläche und das Volumen der geometrischen Körper Quader, Kugel und Zylinder berechnet. Die Eingabe des Programms besteht aus einer Zeile, welche alle benötigten Angaben enthält:

- Quader: Buchstabe "Q", Länge, Breite, Höhe
- Kugel: Buchstabe "K", Radius
- Zylinder: Buchstabe "Z", Radius, Höhe

Die Formeln für die Berechnung lauten (Hinweis: Die Konstante PI heißt in C "`M_PI`" und ist in `math.h` mit dem Zahlenwert definiert.):

- Quader:
  Oberfläche = 2 * (L*B + L*H + B*H)
  Volumen = L * B * H
- Kugel:
  Oberfläche = 4 * R2 * PI
  Volumen = 4/3 * R3 * PI
- Zylinder:
  Oberfläche = 2 * PI * R *(R + H)
  Volumen = H * R2 * PI

## Kaufmännische Zinsrechnung

Ein Kapital K werde T Tage lang mit P% pro Jahr verzinst. Die Zinsen Z werden nach der sogenannten kaufmännischen Zinsformel berechnet:

                      P       T
            Z = K * ----- * -----
                     100     360

Es ist ein Programm zu schreiben, das aus je drei der vier Größen die vierte Größe berechnet. Das Programm fragt dazu per Menü ab, welcher Wert gesucht wird (Eingabe durch einen Buchstaben: K, P, Z oder T). Danach werden die anderen drei Größen abgefragt und der gewünschte Wert berechnet und ausgegeben. Die Eingaben sollen auf Plausibilität geprüft und gegebenenfalls neu abgefordert werden.

## Zahlenraten

### Variante 1:

Der erste Mitspieler gibt eine ganze Zahl im Bereich von 0..999 vor. Aufgabe des zweiten Mitspielers ist es diese Zahl mit möglichst wenigen Versuchen zu erraten. Entsprechend der Qualität des Rateversuchs ist ein geeigneter Text mit den Hinweisen "zu klein", "zu groß" oder "stimmt" auszugeben.

Algorithmus: Zerlegung in Einzelprobleme, z. B.:

1. Ausgabe eines Textes als Aufforderung zur Eingabe der Geheimzahl,
2. Einlesen des Zahlenwertes durch Funktionsaufruf,
3. Löschen des Bildschirminhaltes,
4. Aufforderung einen Rateversuch auszuführen,
5. Vergleich der Geheimzahl und der geratenen Zahl,
6. Ausgabe des Textes mit der Qualität des Ratens,
7. Beenden falls die Zahl richtig war,
8. Fortsetzen bei Punkt 4, falls die Zahl falsch war.

Bibliotheks-Funktionen: scanf(), printf(), clrscr()

### Variante 2:

Die Geheimzahl wird durch den Rechner ermittelt, der eine Zufallszahl im Bereich 0 ... 999 ermittelt. Die Anzahl der Rateversuche wird auf 9 begrenzt.

Algorithmus: (ersetzt den vorhergehenden teilweise)

- Initialisierung des Zufallsgenerators durch Ihren Geburtstag. Sie multiplizieren Tageszahl mit dem Geburtsmonat.
- Endekriterium ist erfüllt falls richtig geraten wurde oder falls die maximale Anzahl von Rateversuchen erreicht wurden.

Bibliotheks-Funktionen: rand(), srand().

### Variante 3:

Abhängig von der Nähe des Rateversuchs zur gesuchten Zahl werden Texte ausgegeben (kalt, heiß, wärmer...), die auch von dem vorhergehenden Rateversuch abhängen können.

Präzisieren der Aufgabenstellung für den ersten Rateversuch:

- < 10 daneben --> "heiß"
- < 100 daneben --> "warm"
- > 100 daneben --> "kalt"

Für Folgeversuche, die im gleichen Intervall liegen:

- liegt der Versuch besser: "heißer", "wärmer", "nicht mehr so kalt"
- liegt der Versuch schlechter: "weniger heiß", "weniger warm", "kälter"

Algorithmus:
setzt bei (5) ein, der Vergleich muß bezüglich weiterer Schwellen durchgeführt werden, basierend auf der Differenz zwischen Geheimzahl und geratener Zahl;

1. der Vergleich führt zu einem in einer Variablen festzuhaltenden Ergebnis -> 0 richtig, 1 heiß zu groß, 2 heiß zu klein ....;
2. der Vergleich mit der vorhergehenden Differenz führt zu weiteren Zuständen der Ergebnisvariablen
3. für die Textausgabe kann nun die Mehrfachverzweigung basierend auf der Ergebnisvariablen angewandt werden
4. oder diese als Index für eine Texttabelle (Array von Strings) verwendet werden.

## Münzautomat

Erstellen Sie ein ANSI-C-Programm, das einen Münzautomaten simuliert. Dazu geben Sie einen beliebigen Betrag zwischen 1 Euro und 10 DM vor (entweder durch Eingabe oder als Zufallszahl). Der Betrag kann mit verschiedenen Münzen "abgezahlt" werden:

            2 Euro
            1 Euro
            50 Cent
            10 Cent
             5 Cent

Die Münzen werden in beliebiger Reihenfolge "eingeworfen" (durch Eingabe im Programm) und der Restbetrag jeweis angezeigt. Überzahlen soll nicht möglich sein. Die Anzeige soll in großen Ziffern erfolgen und wie im folgenden Beispiel aussehen:

          #####   ##### #####
          #   #       # #
          #####      #  ####
          #   #     #       #
          ##### *   #   ####

Tipp: Rechnen Sie mit Cent (1 Euro wird dann als 100 und 2 Euro als 200 eingegeben).

Die Bildschirmausgabe soll als Funktion formuliert werden, die eine Integerzahl als Eingabeparameter besitzt und diese in der oben angegebenen Form auf dem Bildschirm ausgibt. Der Dezimalpunkt, dargestellt durch ein Sternchen, ist fester Bestandteil der Ausgabe und nicht Kennzeichen einer float-Variablen.

### Hinweise zur Lösung:

Die Vereinbarung der Ziffern ist unter `/home/praktikum/ziff.h` zu finden. Sie lautet:

    char * ziff[10][5] =
        { {" ### ",
           "#   #",
           "#   #",
           "#   #",
           " ### " },

          {"   ##",
           "    #",
           "    #",
           "    #",
           "    #" },

          {"#### ",
           "    #",
           " ### ",
           "#    ",
           "#####" },

          {"#### ",
           "    #",
           " ### ",
           "    #",
           "#### " },

          {"#  # ",
           "#  # ",
           "#####",
           "   # ",
           "   # " },

          {"#####",
           "#    ",
           "#### ",
           "    #",
           "#### " },

          {" ####",
           "#    ",
           "#####",
           "#   #",
           "#####" },

          {"#####",
           "    #",
           "   # ",
           "  #  ",
           "  #  " },

          {"#####",
           "#   #",
           "#####",
           "#   #",
           "#####" },

          {"#####",
           "#   #",
           "#####",
           "    #",
           "#### " }};

(Sie können die Definition auch per "Cut-and-paste" von der Webseite übernehmen). Die Ziffer j (j = 0...9) ist durch folgendes Programmstück zum Testen darstellbar:

        for (i=0; i<5; i++)
          printf("%s ",ziff[j][i]);

## Arbeiten mit Binärdateien

Zum Speichern von Daten für das Telefonverzeichnis des Fachbereichs wird die folgende Struktur definiert:

    struct Adresse
      {
      char Name[30];
      char Vorname[30];
      int Raumnummer;
      int Telefon;
      };

Gegeben ist eine Binärdatei namens "teldat", die Datensätze vom Struktur-Typ "Adresse" enthält.

1. Schreiben Sie eine Funktion `tabelle`, welche die Binärdatei zum Lesen öffnet, und alle Datensätze nacheinander als formatierte Tabelle ausdruckt. Die Tabelle soll folgendes Aussehen besitzen:

    +-----------------------------------------+------+------+
    | Name                                    | Raum | Tel. |
    +-----------------------------------------+------+------+
    | Dr. Martin Bechteler                    | 4038 | 3418 |
    | Dr. Gerd Becker                         | 4036 | 3416 |
    | Dr. Maximilian Bleicher                 | 2051 | 3467 |
      ...

Die Funktion hat folgenden Prototyp:

    int tabelle(char dateiname[]);

Sie gibt 0 zurück, wenn das öffen der Datei fehlschlägt, sonst 1.

2. Schreiben Sie eine Funktion `kopiere`, welche die Binärdatei mit den Adressen (also die Quelldatei) zum Lesen öffnet und gleichzeitig eine weitere Binärdatei, die Zieldatei, zum Schreiben öffnet. Die zweite Binärdatei soll Datensätze enthalten, die folgender Struktur genügen:

    struct NeueAdresse
      {
      char Name[30];
      int Telefon;
      };

Aufgabe der Funktion ist es, jeweils einen Datensatz aus der Quelldatei zu lesen, die Inhalte in einen Datensatz der Zieldatei zu übertragen und diesen dann in die Zieldatei zu schreiben. Die Funktion hat folgenden Prototyp:

    int kopiere(char quelldateiname[], char zieldateiname[]);

Sie gibt 0 zurück, wenn das öffen der Dateien fehlschlägt, sonst 1.

Entwerfen Sie ein geeignezes Hauptprogramm zum Testen der FUnktionen.

## Fahrstuhl

In einem Hochhaus mit 20 Stockwerken fährt ein Aufzug. Im Aufzug gibt es 20 Knöpfe zur Wahl des Fahrtzieles, und auf jedem Stock gibt es einen Knopf zur Anforderung des Fahrstuhles.
Ohne auf die faire Behandlung der Aufzugsbenutzer zu achten, entwickeln Sie einen einfachen Algorithmus für einen Fahrstuhl, der, solange es geht, seine Fahrtrichtung beibehält, bis in seine augenblickliche Fahrtrichtung keine Anforderungen mehr existieren, woraufhin er die Fahrtrichtung wechselt, oder aber, falls gerade keine Anforderungen anliegen, einfach auf dem augenblicklichen Stockwerk bleibt und auf die nächste Anforderung wartet. Es wird angenommen, die Stockwerke seien von 1 bis 20 durchnummeriert.
Nehmen Sie folgende Grundaktionen an:

- `fahre_oben:` fahre nächsthöheres Stockwerk an
- `fahre_unten:` fahre nächsttieferes Stockwerk an
- `lade:` lade oder entlade auf dem aktuellen Stockwerk Fahrgäste Stockwerk_ist_Fahrziel: ist aktuelles Stockwerk angefordert worden (j/n)?
- `aktuelles_Stockwerk:` liefert Nummer des aktuellen Stockwerkes
- `max_Stockwerk:` liefert das höchste Stockwerk (Stockwerknummer) das augenblicklich angefordert ist. Falls keins angefordert wurde, wird 0 geliefert.
- `min_Stockwerk:` liefert das tiefste Stockwerk (Stockwerknummer) das augenblicklich angefordert ist. Falls keins angefordert wurde, wird 0 geliefert.
- `warte_Anforderung:` bleibt (geschlossen) auf dem aktuellen Stockwerk bis eine neue Anforderung eintritt

Simulieren Sie den Aufzug mit Hilfe von Bildschirm und Tastatur.

## Iteration, Abfrage und Funktion

Sie sollen eine mathematische Funktion double mysin (double x); entwerfen und programmieren, die mittels der Taylorreihenentwicklung für ein hinreichend großes n:

für ein gegebenen Wert x den Sinus berechnet.

Die Genauigkeit des über die Reihenentwicklung gewonnen Sinuswertes hängt von der Anzahl `m` der beteiligten Summanden `[..]` ab. Diese Anzahl `m` ist abhängig vom Wert x und im voraus nur schwer abzuschätzen.

Wir geben einen Wunschfehler vor und berechnen die Summe, durch Addition weiterer Summanden `[..],` solange bis die gewünschte Genauigkeit erreicht ist. D. h.: Ist der jeweils hinzuzufügende Betrag des Summanden kleiner oder gleich dem Wunschfehler, so ist die gewünschte Genauigkeit erreicht.

Wunschfehler: 10 -10

#### Auffrischung:

Der Sinus ist eine periodische Funktion. Seine Funktionswerte kehren alle x = x + 2*PI wieder. D. h. der relevante Definitionsbereich von sin x liegt zwischen 0 und 2*PI. Ist der x-Wert größer als 2*PI, so kann vor der Reihenberechnung, das überzählige Vielfache von 2*PI von x abgezogen werden, bis der Wert wieder im o. g. relevanten Definitionsbereich ist.

#### Häusliche Vorbereitung

- Fertigen Sie ein Struktogramm an, daß den Algorithmus zur Berechnung des Sinuswertes durch die Funktion `mysin()` darstellt.
  Damit transparent wird, ab welchem Summanden die gewünschte Genauigkeit erreicht wird, gibt die Funktion `mysin()` am Ende mittels `printf()` die Zahl `m` der gebildeten Summanden aus. Dieser Testausdruck wird dann später auskommentiert.

  Hinweis: Die Konstante PI heißt in C `M_PI` und ist in `math.h` mit dem Zahlenwert definiert.

- Um die Funktion `mysin()` entsprechend testen zu können, wird ein Hauptprogramm benötigt. Dieses Hauptprogramm soll eine Liste von 30 berechneten Sinuswerten `mysin()` für 0 <= x <= 2*PI ausgeben. Außerdem sollen die Werte anhand der C-Bibliotheks-Funktion `double sin(double x)` verglichen und die Differenz zum `mysin()`-Wert ausgegeben werden. Eine Ausgabezeile enthät nacheinander die Werte von x, `mysin(x)`, `sin(x)` sowie die Differenz zwischen `mysin(x)` und `sin(x)`.

  Fertigen Sie ein entsprechendes Struktogramm an.

- Folgen Sie bei Ihrem Algorithmus der Reihenentwicklung (Keine Fuktionen für xn oder fakultät verwenden!).

Hinweis: `#include <math.h>` am Anfang des Hauptprogramms ist notwendig, um auf die C-Bibliotheks-Funktion `double sin(double x)` zugreifen zu können. Außerdem muß im Menü Projekt die Auswahl Optionen angewählt werden. Im Reiter Linkeroptionen muß im Bereich Bibliotheken die Bibliothek math ausgewählt werden.

Bringen Sie Ihr Programm zum Laufen. Die Differenzen zur C-Bibliotheksfunktion sollten sich maximal in der Größenordnung 10-12 bewegen.

## Magische Quadrate

Ein magisches Quadrat ist eine quadratische Anordnung mit n Zeilen und n Spalten von Zahlen derart, dass die Summe der Zahlen in einer beliebigen Zeile, Spalte, oder in der Hauptdiagonale alle gleich sind.

4 | 9 | 2
3 | 5 | 7
8 | 1 | 6

Wenn n ungerade ist, kann man ein magisches Quadrat folgendermaßen erstellen. Im Feld Q[i][j] (i=1..n, j=1..n) wird eingetragen:

1. Setzen Sie k = j - i + (n -1)/2 und m = 2*j - i.
2. Wird k >= n, ersetzen Sie k durch k - n und fahren Sie fort bei Schritt 4.
3. Wird k < 0, ersetzen Sie k durch k + n.
4. Wird m > n, so ersetzen Sie m durch m - n und fahren Sie fort bei Schritt 6.
5. Wird m <= 0, ersetzen Sie m durch m + n.
6. Ergebnis: Q[i][j] = K*n + m.

Erstellen Sie ein Programm, das mit diesen Regeln bei Benutzereingabe von ungeradem n ein magisches Quadrat erzeugt, das in einem Array gespeichert und dann am Bildschirm schön formatiert ausgegeben wird.

## Textanalyse

Ein über die Standardeingabe einzugebender Text soll zeichenweise gelesen und analysiert werden.

Erstellen Sie ein ANSI-C-Programm, mit dem

- die Anzahl der Zeichen,
- die Anzahl der Worte,
- die Anzahl der Zeilen,
- die Anzahl der Zeichen der längsten Zeile,
- die Länge des längsten Wortes

des Eingabetextes ermittelt werden kann. Unter einem Wort sei dabei jede Zeichenfolge, die mit einem Buchstaben oder Underscore ('_') beginnt, nur aus Buchstaben, Ziffern und den Underscore ('_') besteht und von beliebigen Steuer- oder Sonder-Zeichen (nicht aber Ziffern) begrenzt wird, verstanden. Beachten Sie, daß nach dieser Definition z. B. die Zeichenfolge `;2abc%` kein Wort enthält.

### Hinweise zur Lösung:

- Der Text ist mit der Funktion getchar() einzulesen und darf nicht in Arrays abgelegt werden. Achtung: Die Eingabe erfolgt gepuffert, d. h. wenn Sie etwas eintippen, wird die Eingabe erst an das Programm übergeben, wenn Sie die Enter-Taste gedrückt habe. Sie geben also immer zeilenweise ein. Das Programm bearbeitet Ihre Eingabe aber trotzdem Zeichen für Zeichen.
- Aus Vereinfachungs- (u. Portabilitäts-) Gründen kann angenommen werden, daß die deutsche Umlaute nicht im Text vorkommen.
- Berücksichtigen Sie, daß die letzte Zeile nicht unbedingt mit einem Zeilenendezeichen abgeschlossen sein muß.
- Andererseits stellt ein Dateiendezeichen unmittelbar nach einem Zeilenende keine neue Zeile dar.
- Zur Vereinfachung der Wortermittlung sind die folgenden symbolischen Konstanten zu definieren und sinnvoll zu verwenden:
  - symbolische Konstante (`--> #define`) zur Kennzeichnung der jeweiligen Zeichenklasse
    - BUCH (Groß- u. Kleinbuchstaben, Underscore)
    - ZIFF (Ziffern)
    - SPACE (Leerzeichen (' '), Tabulator ('\t') und Newline ('\n') )
    - SONST (restliche Zeichen, d.h. Steuerzeichen und sonstige Sonderzeichen)
  - symbolische Konstante zur Kennzeichnung des jeweils aktuellen Textzustands
    - PREWORT (nächstes Zeichen kann Anfang eines Wortes sein)
    - INWORT (innerhalb eines Wortes)
    - NOWORT (innerhalb einer Zeichenfolge, die kein Wort ist, nächstes Zeichen kann nicht Anfang eines Wortes sein)
- Überlegen Sie sich den durch ein Zeichen in Abhängigkeit von seiner Klasse und dem alten Textzustand jeweils ausgelösten Zustandsübergang. Betrachten Sie dazu das Zustandsübergangsdiagramm:

  Ein solches Diagramm ist recht einfach aufgebaut:

  - Alle Zustände werden als (beschrifteter) Kreis dargestellt.
  - Der Übergang zu einem anderen Zustand wird durch einen Pfeil dargestellt. Der Pfeil wird mit der Übergangsbedingung beschriftet.

- Beschreiben Sie den durch das Programm zu realisierenden Algorithmus vor der Formulierung in C durch ein Struktogramm.

- Beginnen Sie mit der Programmschleife, die zeichenweise von der Standardeingabe liest. Die Kommentare markieren später zu ergänzende Programmteile. In der mit `/* TEST */` gekennzeichneten Zeile steht eine Ausgabeanweisung, mit der Sie die Funktion ihres Programmes testen können. Wenn das Programm zufriedenstellend läuft, wird diese Anweisung gelöscht:

      #include <stdio.h>
      #include <ctype.h>

      /* Textzustaende definieren */

      /* Zeichenklassen definieren */

      int main(void)
        {
        int c;                 /* Eingelesenes Zeichen */
        int zustand;           /* Textzustand */
        int zklasse;           /* Zeichenklasse von c */

        /* Anfangswerte setzen */

        while( (c=getchar())!=EOF )
          {
          /* ermittle Zeichenklasse */

          /* fuehre Zustandswechsel durch */

          /* TEST */ printf("%c %d %d\n",c,zklasse,zustand);
          }

        /* Ausgabe der Ergebnisse */

        return(0);
        }

- Schreiben Sie nun Ihr C-Programm in drei Etappen:
  1. Schreiben Sie den Programmteil zum Ermitteln der Zeichenklassse und testen Sie, ob bei den Eingaben die Zeichenklasse richtig ermittelt wird.
  2. Schreiben Sie nun den "Automaten", der die Zustandsübergänge ermittelt und testen Sie auch die Funktion dieses Programmteils. Erst wenn das funktioniert:
  3. Fügen Sie nun die Programmteile hinzu, die Zeichen, Worte und Zeilen zählen.

- Wenn das Programm einwandfrei funktioniert, er weitern Sie es so, daß von der Tastatur ein Dateiname eingelesen wird und die Textanalyse mit dieser Datei durchgeführt wird.
- Änder Sie das Programm abschließend so, daß anstelle der mit `#define` definierten Konstanten die folgenden beiden Aufzählungstypen verwendet werden:

      enum Zeichenklasse { BUCH, ZIFF, SPACE, SONST } zklasse;
      enum Textzustand { PREWORT, INWORT, NOWORT } zustand;

  ## Hotelzimmer-Verwaltung

  Sie werden vom Besitzer des Gasthofs "Zum wilden Mann" damit beauftragt, ein C-Programm zur Verwaltung der 20 Zimmer des Gasthofs zu schreiben.

  Das Programm soll in einem Dialog mit dem Benutzer folgende Optionen anbieten:

  1. Zimmer Nummer N belegen für Gast mit Namen G
  2. Zimmer Nummer N wieder freigeben
  3. Alle freien/belegten Zimmer anzeigen
  4. Die Namen aller Gäste auflisten
  5. Herausfinden, welcher Gast sich in Zimmer N befindet
  6. Herausfinden, in welchem Zimmern sich Gast G befindet
  7. Hilfe (Auflisten aller Menüpunkte)
  8. Programm beenden
  Als Datenstruktur wird ein Feld von Zeichenketten verwendet, dessen Index mit der Zimmernummer übereinstimmt udn das den Namen des Gastes aufnimmt.

  Vergessen Sie nicht für eine gute Fehlerbehandlung zu sorgen, z.B. wenn Zimmer versehentlich mehrfach belegt werden sollen!

  ## Telefonliste

  Entwickeln Sie ein Programm zur Verwaltung von Telefondaten. An das Programm werden folgende Forderungen gestellt:
  - Die Teilnehmer werden in einem Array gespeichert.
  - Jedes Element des Arrays besteht aus einem String, der die folgende Informationen speichert:
    - Name und Vorname des Teilnehmers (Zeichenkette, jeweils max. 30 Zeichen)
    - Telefonnummer (Zeichenkette, max. 15 Zeichen)
  - Das Programm soll über ein Menü gesteuert werden und folgende Funktionen enthalten:
    - Eintragen eines neuen Teilnehmers mit Namen und Telefonnummer in alphabetisch richtiger Reihenfolge (d.h. der Eintrag wird an der passenden Stelle im Array eingefügt und nachfolgende Einträge ggf. nach hinten verschoben)
    - Suchen eines Teilnehmers (nach Name oder Vorname)
    - Löschen eines Teilnehmers
    - Auflisten aller Teilnehmer
    - Speichern der Telefonliste in einer Textdatei
    - Laden der Telefonliste aus einer Textdatei
    - Beenden des Programms
    - Hilfe (Auflisten aller Menüpunkte)
    Wählen Sie möglichst sinnvolle und leicht zu merkende Befehlsbuchstaben für die einzelnem Menüpunkte, z. B. 'h' oder '?' für "Hilfe".

  ## Histogramm

  Erstellen Sie ein ANSI-C-Programm, mit dem ein Histogramm von Prüfungsnoten ermittelt und in folgender Form auf dem Bildschirm ausgegeben werden kann:

                        Histogramm der Noten
        |
        |
        |
        |
        |
        |
        |
        |
        |
        |
        |
        |
        |
        |                   *
        |                *  *  *
        |             *  *  *  *  *
        |       *     *  *  *  *  *
        | *  *  *     *  *  *  *  *        *
        | *  *  *  *  *  *  *  *  *  *     *  *
        | *  *  *  *  *  *  *  *  *  *  *  *  *
        --+--+--+--+--+--+--+--+--+--+--+--+--+
          0  1  2  3  4  5  6  7  8  9 10 11 12

  Die Noten werden bei der Eingabe in Kategorien (0 bis 12) eingeteilt und zwar nach folgenden Schema:

      Noten:      1.0 1.3 1.7 2.0 2.3 2.7 3.0 3.3 3.7 4.0 4.3 4.7 5.0
      Kategorie:   0   1   2   3   4   5   6   7   8   9  10  11  12

  Damit die gesamte Bildschirmausgabe sichtbar ist, ist bei Noten, deren Auftritte die Anzahl 20 überschreitet die Ausgabe auf 19 Sternchen zu begrenzen und an der Position des 20. (obersten) Sternchens die tatsächliche Anzahl der Auftritte auszugeben.

  Die Noten stehen in einer Textdatei namens `noten.dat`, jeweils eine Note pro Zeile. Das Programm öffnet die Datei und liest alle Noten (bis EOF). Danach wird die Datei geschlossen und das Histogramm ausgegeben.

  ### Hinweise zur Lösung :

  - Die Eingabe besteht aus drei Zeichen. Prüfen Sie die Eingabe auf korrekte Noten gemäß obiger Skala und geben Sie eine Fehlermeldung bei Falscheingaben aus (der Eingabewert wird dann ignoriert).
  - Legen Sie zur Aufnahme der Auftritte der einzelnen Noten ein int-Array an, dessen Komponenten zweckmäßigerweise über die Noten-Kategorie indiziert werden.
  - Die Bildschirmausgabe darf nicht über eine absolute Cursor-Positionierung erfolgen.
  - Zum Test erstellen Sie die Datei `noten.dat` im Projektverzeichnis (dort, wo sich auch `main.c` befindet.

  ## Ringpuffer mit Arrays

  Ein Ringpuffer ist ein nach dem FIFO-Prinzip (first-in-first-out) organisierter Speicher zur Pufferung von Daten, die z.B. von einem Meßdatenerfassungsprogramm "angeliefert" werden, und zeitversetzt von einem Auswertungsprogramm weiterverarbeitet werden.

  Der Puffer muß groß genug sein, damit er auch bei maximalem Zeitversatz nicht "überläuft".

  Der Versuch, in einen vollen Puffer zu schreiben muss verhindert werden, stattdessen soll eine Meldung "Puffer voll" ausgegeben werden.

  Ebenso muss dafür gesorgt werden, dass umgekehrt aus einem leeren Puffer nicht mehr gelesen wird -> Meldung "Puffer leer".

  Die Abbildung des Rings in einem linear adressierten Speicher geschieht dadurch, dass ein am Pufferende angekommener Zeiger wieder an den Anfang gesetzt wird.

  1. Beginnen Sie bei der Datenstruktur, skizzieren Sie den Puffer in verschiedenen Betriebszuständen und tragen Sie die jeweiligen Schreib- bzw. Lese-Indizes sowie die jeweils belegten Speicherzellen ein:
     - ganz am Anfang
     - nach einigen Schreibvorgängen
     - nach einigen Leseoperationen
     - wenn der Puffer voll wird
     - wenn ein Zugriff auf das Ende des linearen Pufferbereichs erfolgt ist
     - wenn der Puffer "leergelesen" wird.

  2. Überlegen Sie,
     - welche Variablen benötigt werden,
     - wie die Pufferzustände "leer" und "voll" erkannt werden können,
     - welche elementaren Funktionen gebraucht werden,
     - welche der Variablen ggf. global (außerhalb aller Funktionen) vereinbart werden müssen, damit sie auf einfache Weise von den verschiedenen Funktionen benutzt werden können (möglichst wenige!),
     - auf welche Weise Funktionsresultate an die aufrufende Funktion übergeben werden sollen,
     - welche Funktion welche Überprüfungen durchführen soll (z.B. Puffer leer bzw. voll ?).

  3. Zeichnen Sie die Struktogramme der benötigten Funktionen zum Initialisieren, Schreiben und Lesen des Puffers.

  4. Für Testzwecke eignet sich am besten ein Ringpuffer für ca. 20 char-Elementen. Zeichnen Sie jetzt das Struktogramm für die Testfunktion (main), mit der per Tastatureingabe das Füllen und Auslesen des Puffers - dargestellt als Zeile am Bildschirm - verfolgt werden kann:
     - jedes eingegebene Zeichen soll im Puffer an seiner Schreib-Position erscheinen;
     - wird nur "return" (new line) eingegeben, so soll ein Zeichen aus dem Puffer gelesen (entfernt) werden;
     - beides soll auch in der Bildschirmdarstellung deutlich werden! Am einfachsten ist es, wenn jede Veränderung im Puffer zu einer neuen Darstellung des kompletten Pufferinhalts in einer neuen Zeile führt.

  5. Erst jetzt wird der C-Quellcode geschrieben und alles getestet.

  ## Lineare Listen

  Zeichenketten (strings) haben Sie in der Vorlesung als Arrays von Zeichen kennengelernt. Man kann sie auch als Listen realisieren. Dabei machen wir das so, dass jedes Listenelement jeweils nur ein einziges Zeichen speichert. Diese Zeichen werden in Listenform miteinander verkettet.

  Beispiele: Folgende Graphik zeigt zwei Zeichenketten, string1 und string2, und eine leere Zeichenkette als Listen realisiert:

  Eine Operation, die häufig für Zeichenketten benötigt wird, ist das Zusammenhängen zweier Zeichenketten. Diese Operation nimmt als Eingabeparameter zwei Zeichenketten - string1 und string2. Sie hängt string2 an string1 an. Dabei wird in string1 das Ergebnis zurückgegeben (der Pointer string1 wird also verändert).

  Die folgende Graphik zeigt das Ergebnis von concat für die beiden anfangs gegebenen Zeichenketten

  1. Definieren Sie den graphisch dargestellten Zeichenkettendatentyp (wir nennen ihn in der Folge ZK) in C als lineare Liste.
  2. Realisieren Sie die Funktion`struct ZK *genstring(char * s),` die aus der Zeichenkette s (in Standard-Arraydarstellung) eine Zeichenkettenliste generiert (nicht vergessen: es muß auch Speicherpaltz reserviert werden). Aufrufbeispiel:
     `string1 = genstring("Hallo Welt");`
  3. Realisieren Sie die Funktion `struct ZK *Concat (struct ZK *string1, struct ZK *string2)`, die string2 an string1 anhängt und einen Zeiger auf string1 zurückliefert. Aufrufbeispiel:
     `string1 = Concat(string1, string2);`

  1. Zeichnen Sie die Struktogramme der benötigten Funktionen zum Initialisieren, Schreiben, Lesen und Löschen der Puffereinträge.

  2. Erst jetzt wird der C-Quellcode geschrieben und alles getestet.

  ## Auswerten einer Webserver-Statistik

  Jeder Zugriff auf einen Webserver, d. h. der Abruf einer Webseite, wird protokolliert. Die Protokolldatei ist eine Textdatei mit folgendem Format:

      160.98.6.5 - - [27/Apr/2004:08:49:54 +0200] "GET /skripten/netze/b1-15.gif HTTP/1.1" 404 322
      160.98.6.5 - - [27/Apr/2004:08:49:54 +0200] "GET /skripten/netze/b1-16.gif HTTP/1.1" 404 322
      160.98.6.5 - - [27/Apr/2004:08:49:54 +0200] "GET /skripten/netze/netz1.gif HTTP/1.1" 404 322
      82.174.14.6 - - [27/Apr/2004:09:24:30 +0200] "GET /mm-cd/sound/jet1.wav HTTP/1.1" 200 7148
      82.174.14.6 - - [27/Apr/2004:09:24:31 +0200] "GET /mm-cd/sound/jet2.wav HTTP/1.1" 200 7148

  Die einzelnem Felder sind jeweils durch Leerzeichen getrennt. Ihre Bedeutung:

  Nr. | Bedeutung
  --- | ---
  1 | IP-Adresse des abrufenden Rechners
  2 | -
  3 | -
  4 | Datum und Uhrzeit des Abrufs (in eckige Klammern eingeschlossen)
  5 | Befehl zum Abruf der Seite (in "..." eingeschlossen)
  6 | Resultatcode
  7 | Anzahl übertragene Bytes

  Uns interessieren für die folgende Aufgabe nur Zeilen, bei denen das Feld 5 den GET-Befehl enthält und bei denen die Resultatcode zwischen 100 und 399 liegt.

  ### Aufgabe

  Es ist ein Programm zu entwickeln, das die oben beschriebene Webserver-Protokolldatei auswertet und für jede Datei (siehe Feld 5) feststellt, wieviele Zugriffe darauf erfolgten. Dazu wird die Protokolldatei (Musterdatei unter `/home/praktikum/access.log`) zeilenweise gelesen und aus jeder Zeile werden die Felder 5 und 6 extrahiert.
  - Sofern Feld 6 nicht im erlaubten Bereich (100 bis 399) liegt, wird die Zeile ignoriert (Hinweis: Es genügt das Untersuchen der ersten Ziffer).

    Andernfalls wird aus Feld 5 (Achtung: Dieses Feld kann ebenfalls Leerzeichen enthalten.) der Dateiname mit Pfad extrahiert. Aus dem Feldinhalt `"... GET /mm-cd/sound/jet1.wav HTTP/1.1 ..."` ergibt sich beispielsweise `/mm-cd/sound/jet1.wav`. Formulieren Sie für diesen Aufgabenteil eine Funktion, der eine komplette Zeile aus der Eingabedatei übergeben wird und die entweder den Dateinamen oder eine leere Zeichenkette zurückliefert. Der Dateiname soll auf maximal 30 Zeichen begrenzt werden. Der Funktionsprototyp lautet:

        char * extract(char * zeile);

    Testen Sie diese Funktion mit einem einfachen Hauptprogramm.

  - Der extrahierte Dateiname wird zusammen mit der Anzahl seiner Aufrufe in einer linearen Liste gespeichert. Dazu verwenden Sie die unten angegebene Datenstruktur. Für jeden Dateinamen wird der Name in der Liste gesucht.
    - Ist der Name noch nicht in der Liste, wird ein neues Listenelement erzeugt und dieses in die Liste (nach Namen) einsortiert sowie die Anzahl der Aufrufe auf 1 gesetzt.
    - Ist der Name dagegen schon vorhanden, wird die Anzahl der Aufrufe bein entsprechenden Listenelement um 1 erhöht.
    - Anmerkung: Berücksichtigen Sie auch den Sonderfall, dass die Liste noch leer ist.
    Datenstruktur:

        struct liste
          {
          char name[31];
          int anzahl;
          struct liste * next;
          };

    Formulieren Sie für diesen Aufgabenteil eine Funktion, die das Enfügen übernimmt:

        struct liste * insert(struct liste * root, char * name);

    Die Funktion hat zwei Parameter, einen Zeiger auf den Listenanfang und das einzufügende Wort. Sie liefert einen Zeiger auf den Listenanfang zurück. Denken Sie auch an die Initialisierung der Zeiger im Hauptprogramm. Testen Sie diese Funktion zusammen mit der folgenden.

  - Ist die Eingabedatei vollständig bearbeitet, wird eine Aufrufstatistik erzeugt. Für einen ersten Test geben Sie in einer Tabelle den Dateinamen und die Anzahl der Abrufe aus.
    Erweitern Sie dann die Ausgabe folgendermassen:
    - Bestimmen Sie die Summe aller Aufrufe.
    - Berechnen Sie für jedes Listenelement den prozentualen Wert der Aufrufstatistik [Prozent = (Anzahl * 100)/Summe].
    - Geben Sie in der Liste den Dateiname und den Prozentanteil aus, indem nach dem Namen für je 2% ein '#' ausgedruckt wird (d.h. 100% = 50 mal '#'). Dabei wird für den Namen immer eine Länge von 30 Zeichen genommen. Ganz hinten wird dann noch die Anzahl dezimal ausgegeben.
    Es ergibt sich dann in etwa folgendes Druckbild:

        /mm-cd/sound/jet1.wav         |##### 10
        /mm-cd/sound/jet2.wav         |## 4
        /ad/uebungen.html             |############# 26
        /ad/variablenvereinbarung.gif |#### 8
        ...

    (Anmerkung: Linksbündige Ausgabe von Strings bekommt man mit `'... %-30s ...'` in der Formatdefinition von `printf`).
    Die Funktion hat nur einen Parameter, den Zeiger auf den Listenanfang, und gibt nichts zurück:

        void print(struct liste * root);

    Berücksichtigen Sie auch den Fall, dass die Liste noch leer ist.

  ## Projekt: Crossreferenz-Tabelle

  Es soll ein Programm entwickelt werden, das eine Textdatei einliest und daraus die Worte extrahiert. Die Worte werden in einem Array sortiert gespeichert und zu jedem Wort auch die Zeilen seines Auftretens erfasst. Die Zeilennummern werden separat für jedes Wort in einer linearen Liste gespeichert.Auf diese Weise erhält man für jedes Wort eine Liste der Zeilen, in denen das betreffende Wort auftaucht.

  Ist die Datei vollständig gelesen, wird die Liste in eine Datei ausgegeben. Dazu werden Formatierungszeichen der Web-Sprache HTML in die Datei geschrieben, die dann mit einem handelsüblichen Browser betrachtet werden kann. Im Prinzip sieht das Ergebnis dann folgendermassen aus:

  Adam | 2 4 12 13 14 54 123 234
  Birne | 7 12 33 43 44 45 46 78 321 433 434 542
  .... usw.

  Hinweise zur Programmierung:

  Das Programm zerfällt in mehrere Teilaufgaben:

  1. Dateinamen erfragen und Text-Datei öffnen. Dann alle Worte aus der Datei extrahieren. Betrachten Sie dazu die Aufgabe "Textanalyse". Dort wie hier wird ein Wort folgendermassen definiert: Es beginnt mit einem Buchstaben und besteht aus Buchstaben und Ziffern. Im Gegensatz zur Aufgabe "Textanalyse" soll hier aber nicht die Länge eines Wortes definiert, sondern das Wort selbst in einer String-Variablen gespeichert werden. Schreiben Sie also zuerst diesen Programmteil und geben Sie zur Kontrolle jedes gefundene Wort und die Zeilennummer seines Auftretens am Bildschirm aus.

  2. Erweitern Sie nun das Programm dahingehend, dass die gefundenen Worte in einem Array gespeichert werden. Da später noch die Zeilennummern als lineare Liste gesprechert werden müssen, reicht ein String-Array nicht aus. Vielmehr sind die Arraykomponenten Elemente der folgenden Struktur:

         struct eintrag
           {
           char wort[100];
           struct liste * zeilen;
           };

     `struct liste * znum` verweist dabei auf die (später zu ergänzende) Liste der Zeilennummern. Damit der Compiler nicht meckert, muss diese Datenstruktur vor `eintrag` definiert werden:

         struct liste
           {
           int zeile;
           struct liste * next;
           };

     Das Array wird dann definiert als

         struct eintrag xref[1000];

     so dass sich folgendes Bild ergibt:

          Array           Listen
         +-----+  +-----+  +-----+  +-----+
         |.....|--|     |--|     |--|     |
         |     |  +-----+  +-----+  +-----+
         +-----+  +-----+
         |.....|--|     |
         |     |  +-----+
         +-----+  +-----+  +-----+
         |.....|--|     |--|     |
         |     |  +-----+  +-----+
         +-----+  +-----+  +-----+  +-----+
         |.....|--|     |--|     |--|     |
         |     |  +-----+  +-----+  +-----+
         +-----+
           ...

     Die gefundenen Worte sollen ins Array `xref` einsortiert werden, so dass das Array immer eine aufsteigend sortierte Wortfolge enthält. Ist ein Wort schon im Array, wird es nicht nochmals gespeichert. Lassen Sie sich für diesen Aufgabenteil vom Skript anregen: "Ausgewählte Algoritmen" --> "Sortieren durch Einfügen".

     Geben Sie zur Kontrolle die Wortliste auf dem Bildschirm aus. Die Wörter müssen aufsteigend sortiert sein und jedes Wort darf nur einmal vorkommen.

  3. Nun erfolgt die dritte Erweiterung, die Speicherung der Zeilennummern.
     - Tritt ein Wort das erste Mal auf, wird eine neue lineare Liste erzeugt und das erste Element dieser Liste nimmt die Zeilennummer auf. Die Komponente `zeilen` verweist auf den Anfang dieser Liste.
     - Tritt ein Wort auf, das bereits in `xref` gespeichert ist, wird für die Zeilennummer ein weiteres Element an die lineare Liste angehängt.
     Geben Sie zur Kontrolle die Wortliste mit Zeilennummern auf dem Bildschirm aus.

  4. Der letzte Teil beinhaltet die Ausgabe in eine HTML-Datei. Dazu wird die bereits existierende Kontrollausgabe erweitert und modifiziert.
     - Der Name der HTML-Datei ergibt sich durch den Namen der Eingabedatei, an den die Zeichenkette ".html" angehängt wird. Diese Datei wird zum Schreiben eröffnet.
     - Danach wird der folgende HTML-Vorspann in die Datei geschrieben:

             <HTML>
             <HEAD>
             <TITLE>Cross-Referenzliste</TITLE>
             </HEAD>
             <BODY>
             <H2>Cross-Referenz von xxxxxxxxx</H2>
             <TABLE BORDER=1 CELLPADDING=3 CELLSPACING=0>

       Für "xxxxxxxxx" soll der Name der Eingabendatei eingesetzt werden.
     - Die Ausgabe für ein Wort bildet eine Zeile (Row) der HTML-Tabelle. Eine Row wird durch die HTML-Sprachelemente `<TR> ... </TR>` begrenzt. Innerhalb der Row gibt es zwei Spalten (columns), die durch die HTML-Sprachelemente `<TD> ... </TD>` begrenzt werden. In der ersten Spalte steht das Wort, in der zweiten Spalte alle Zeilennummern, getrennt durch Leerzeichen. Eine komplette Tabelle-Zeile sieht dann z. B. so aus:

             <TR><TD>Adam</TD><TD>2 4 12 13 14 54 123 234</TD></TR>

     - Nach Ausgabe der Wortliste wird die Datei mit den folgenden HTML-Sprachelemneten abgeschlossen:

             </TABLE>
             </BODY>
             </HTML>

       und dann endgültig geschlossen.

     Tipp: Als Eingabedatei kann man recht gut `main.c` verwenden.

     ## Projekt: Terminkalender

     Schreiben Sie ein Programm, das einen Kalender für das aktuelle Jahr produziert. Damit steht auch der erste Wochentag (z.B. 1.1.2003 ist ein Mittwoch) fest. Der Kalendar soll folgendes Aussehen haben:

              Januar
         ---------------------
         Mo      6  13  20  27        02  09:00 Frühstück bei Tiffany
         Di      7  14  21  28        03  Wichtiger Termin
         Mi  1   8 [15] 22  29        06* Epiphanias
         Do  2   9  16  23  30        10  Alfred Geb. (23)
         Fr  3  10  17  24  31        25  Opa Geb. (91)
         Sa  4  11  18  25
         So  5  12  19  26
         ---------------------

     Der aktuelle Tag ist durch eckige Klammern hervorgehoben (im Beispiel der 15.). Die Einträge, die im Kalendar rechts erscheinen sollen, stehen in einer Datei mit Namen "kalender.dat". Pro Tag sind beliebig viele Einträge erlaubt. Die Ausgabe soll nach Datum (gegebenfalls bei gleichem Datum nach Uhrzeit) sortiert erfolgen. Jede Zeile dieser Datei hat folgenden Aufbau:

         TT.MM[.JJJJ][-hh:mm] Eintrag

     wobei TT für Tag, MM für Monat, JJJJ für Jahr, hh für Stunde und mm für Minute steht. Optionale Teile sind in der Syntaxspezifikation durch [] geklammert (sie dürfen also auch fehlen). Ist die Jahreszahl angegeben, so bedeutet dies ein Geburtsdatum und im Kalender soll automatisch in Klammern hinter dem Eintrag das Alter ausgegeben werden.

     Die Beispieldatei für das obige Blatt lautet:

         10.01.1980 Alfred Geb.
         02.01-09:00 Frühstück bei Tiffany
         03.01 Wichtiger Termin
         25.01.1912 Opa Geb.

     Feiertage werden berechnet (siehe unten) und gegebenenfalls in die rechte Liste mit aufgenommen. Beachten Sie, dass die Terminliste länger als der Monatskalender links werden kann. Achten Sie auch darauf, dass der Text gegebenenfalls gekürzt werden muss (max. Zeilenbreite 76 Zeichen).

     Die Termineinträge eines jeden Monats werden in einer linearen Liste gespeichert (Dynamische Datenstruktur). Diese Listen werden wiederum mit einem 12-elementigen Pointerarray verknüpft, so dass sich folgendes Bild ergibt:

         Pointer-
         Array           Listen
         +-----+  +-----+  +-----+  +-----+
         |     |__|     |__|     |__|     |
         |     |  +-----+  +-----+  +-----+
         +-----+  +-----+
         |     |__|     |
         |     |  +-----+
         +-----+  +-----+  +-----+
         |     |__|     |__|     |
         |     |  +-----+  +-----+
         +-----+  +-----+  +-----+  +-----+
         |     |__|     |__|     |__|     |
         |     |  +-----+  +-----+  +-----+
         +-----+
           ...

     Schreiben Sie ein Programm in C, das einen Kalender für das eingegebene Jahr 2003 erzeugt. Das Programm liest - sofern angegeben - die Jahreszahl von der Kommandozeile ein (ansonsten wird das aktuelle Jahr genommen) und dann die Datei "kalender.dat". Es zeigt beim Start das Kalenderblatt für Januar. Sehen Sie folgende Möglichkeiten der Steuerung vor:
     - Monatsweise vor- und zurückblättern (Begrenzung auf den Zeitraum 1900 bis 2100).
     - Löschen und Hinzufügen von Terminen (gleichzeitig soll die Datei "kalender.dat" aktualisiert werden
     - Ausdruck der angezeigten Kalenderseite auf dem Drucker

     Hinweise zur Programmierung:

     - Zuerst werden die Feiertage in dem entsprechenden Jahr berechnet. Es gibt in Deutschland drei Typen von Feiertagen, die getrennt berechnet werden müssen:
       1. Feste Feiertage: Diese sind immer am gleichen Tag im Jahr, aber an unterschiedlichen Wochentagen. Dazu zählen Neujahr (1.1.), Epiphanias (6.1.), Tag der Arbeit (1.5.), Mariä Himmelfahrt (15.8.), Tag der deutschen Einheit (3.10.), Reformationsfest (31.10.), Allerheiligen (1.11.), Weihnachten (25./26.12.).
       2. Osterabhängige Feiertage: Ostern wird immer am ersten Sonntag nach dem ersten Frühlingsvollmond gefeiert. Die Feiertage dieser Gruppe haben stets den gleichen Abstand in Tagen zum Ostersonntag. Diese Feiertage liegen stets auf dem gleichen Wochentag, haben aber jeweils ein anderes Datum, das um ca. 1 Monat schwanken kann. Zu dieser Gruppe zählen Fastnacht (47 Tage vor Ostern), Karfreitag, Ostern, Christi Himmelfahrt (Ostern+39), Pfingsten (Ostern+49/50), Fronleichnam (Ostern+60).
       3. Wenigschwankende Feiertage: Das sind Feiertage, die zwar auf einem festen Wochentag liegen, aber nur innerhalb einer Woche schwanken koennen. Diese sind: Buß- und Bettag (16.-22.11.), die Advente, Erntedankfest (30.9.-6.10.), Muttertag (8.-14.5.).

     - Das Osterdatum lässt sich mit einem Algorithmus von Gauß berechnen (siehe Skript). Hat man dies einmal ermittelt, kann man die beweglichen Feiertage leicht ausrechnen.

     - Aus dem Wochentag, auf den der 1. Januar fällt ergibt sich die Wochentagsverteilung für das ganze Jahr und ebenso die wenigschwankenden Feiertage.

     Um dieses Problem zu lösen, soll eine Zerlegung in Teilprobleme erfolgen. Diese Teilprobleme werden dann durch Struktogramme beschrieben und danach als folgende Funktionsaufrufe in C kodiert:

     1. `int istSchaltjahr(long jahr)` soll den Wert 1 zurückliefern, wenn `jahr` ein Schaltjahr ist, und sonst 0 (Null). Diese Funktion gibt also an, um wieviele Tage `jahr` länger ist, als ein normales Jahr mit 365 Tagen.

     2. `int monatsLaenge(int monat, long jahr)` liefert als Rückgabewert die Länge eines gegebenen Monats in einem gegebenen Jahr, z.B. hat der Monat 2 (Februar) im Schaltjahr 1996 eine Länge von 29 Tagen. Prinzipiell ist die Lösung durch folgende Tabelle gegeben:

        Jan | Feb | Mar | Apr | Mai | Jun | Jul | Aug | Sep | Okt | Nov | Dez
        31 | 28/29 | 31 | 30 | 31 | 30 | 31 | 31 | 30 | 31 | 30 | 31

     3. `int wochenTag (int tag, int monat, long jahr)` liefert für ein konkretes Datum, z.B. den 24.12.1994, die Nummer (Montag = 0, ..., Sonntag = 6) des zugehörigen Wochentages zurückliefert, in diesem Fall eine 5 (Samstag).

     4. `void printMonat(int monat, long jahr)` erzielt für einen gegebenen Monat in einem gegebenen Jahr die oben dargestellte Auflistung eines kompletten Kalendermonats.
     Schaltjahre sind alle Jahre, die eine der zwei folgenden Bedingungen erfüllen:
     - Teilbar durch 4 und nicht teilbar durch 100.
     - Teilbar durch 400.
     Schreiben Sie zunächst das Programm für das Kalenderblatt (linke Seite) und fügen Sie erst dann die Feiertagsfunktionen hinzu, wenn die Ausgabe des Kalenderblatts einwandfrei funktioniert. Zum Schluß werden die Funktionen für die Anzeige der Termine aus der Datei und die Terminänderungsfunktion programmiert.

     Zum Inhaltsverzeichnis

     ###### Copyright © FH München, FB 04, Prof. Jürgen Plate

     ###### Letzte Aktualisierung: 15. Jun 2006