Formatiertes Einlesen mit zwei Syntaxregeln

neuesich

Mitglied
Hallo,

Ich bin es mal wieder^^
Ich möchte eine formatierte Datei zeilenweise einlesen, dabei aber gleichzeitig zwei Formen berücksichtigen
1. [Zahl]:[Großbuchstabe];
2. [Zahl]->[Zahl];

Meine erste Idee war die folgende:
Code:
#include <stdio.h>

int main() {
int error=0, i=-1,o1=-1,o2=-1;
char c='0';
FILE *datei;

datei = fopen ("file.dat", "r");

if(datei){
    while(!error){
        if ((fscanf(datei, "%i:%c;", &i, &c) != EOF) && (c>=65) && (c<=90) && (i>0) && (i<10000))
            printf("Nummer %i mit Label %c wurde definiert\n", i, c);
        else if((fscanf(datei, "%i->%i;", &o1, &o2) != EOF) && (o1>0) && (o1<10000) && (o2>0) && (o2<10000))
            printf("%i zeigt auf %i\n", o1, o2);
        else
            error=1;

        }
    fclose (datei);
}
Allerdings kann es natürlich nicht funktionieren, da fscanf ja zweimal ausgeführt wird.
Ich bräuchte Anregungen für eine Umsetzung.

Danke im Voraus!

P.S. file.dat:
Code:
1:A;
2:B;
3:C;
4:E;
5:F;
6:D;
7:E;
8:F;
9:D;

1->2;
1->3;
1->4;
1->5;
2->6;
2->7;
3->8;
3->9;
 
Hi

eine Möglichkeit:
a) Mach ein char-Array, in das du dann in der Schleife
mit fgets die komplette Zeile einliest.
b) dann kannst mit sscanf aus dem char-Array lesen.
sscanf ist ziemlich gleich wie fscanf, nur liest es nicht aus einer Datei, sondern aus char-Arrays.
(f=file, s=string)
Im Array ist ja bei beiden Aufrufen das Gleiche drin, nicht schon die nächste Zeile.


Noch ein Hinweis zum (f/s)scanf: Du prüfst, ob der Returnwert EOF ist.
Es können aber noch andere Fehler auftreten (zB. das, was %i sein sollte, ist keine Zahl)
Wenn alles gut gelaufen ist, geben die scanf die Anzahl der eingelesenen Variablen zurück.
Du könntest also prüfen, ob der Returnwert 2 war (dann hat alles gepasst).
 
a) Mach ein char-Array, in das du dann in der Schleife
mit fgets die komplette Zeile einliest.
Aber bei fgets muss man einen maximalen Buffer festlegen, oder? Aber später sollen mehrere Operationen (also eine der beiden Syntaxregeln) in einer Zeile stehen. Dann müsste ich eher zeilenweise mit fscanf("%s",s) arbeiten oder?

Der Tipp mit sscanf ist super, genau das habe ich gesucht!

Noch ein Hinweis zum (f/s)scanf: Du prüfst, ob der Returnwert EOF ist.
Es können aber noch andere Fehler auftreten (zB. das, was %i sein sollte, ist keine Zahl)
Wenn alles gut gelaufen ist, geben die scanf die Anzahl der eingelesenen Variablen zurück.
Du könntest also prüfen, ob der Returnwert 2 war (dann hat alles gepasst).
Super, danke für den Hinweis. War mir öfters unsicher ob scanf einen booleschen Wert zurückgibt, oder nicht. Man lernt immer dazu :)
 
Ja, fgets braucht eine Maximallänge.
Aber nicht wegen fgets, sondern weil das char-Array nur eine bestimmte Länge hat.
Da ändert fscanf(%s) auch nichts dran.
Im Gegenteil ist fgets besser, weil fscanf bei %s überhaupt keine Beschränkung hat.
Würde da was zu lang kommen bekommt man Programmabstürze oder andere "lustige" Effekte...

Wenn die Zeile zu lang war, bekommst du den Rest (oder das nächste Stück) beim nächsten fgets
Es kann aber passieren, dass so ein Teil "x:y;" (oder eben mit ->) in der Mitte abgeschnitten wird.

Vor einem Lösungsvorschlag noch eine Frage:
Sollen da unbegrenzt viele pro Zeile möglich sein, oder... wie ist das geplant?
Die scanf brauchen für mehrere auch noch eine Anpassung...
 
Es sollen gerne unbegrenzt viele möglich sein. Ja das stimmt, das fehlt noch in der bisherigen Überlegung. Ich dachte daran dafür strtok zu nutzen und beim ; den String zu teilen.

Dann wähle ich einfach den Buffer so groß, dass immer eine der beiden Operationen rein passen würde. Berücksichtigt fgets Leerzeichen?

//edit: Ist es geschickter eine Datei erst einzulesen und dann den eingelesenen String zu verarbeiten oder direkt beim Einlesen? Ich tendiere derzeit für die erste Variante, da ansonsten ja noch Zugriffe o.ä. einen Fehler erzeugen könnten und wenn man die gesamte Datei schon eingelesen hat, ist man wenigstens auf der sicheren Seite
 
Zuletzt bearbeitet:
Zum edit:
Zuerst die ganze Datei einlesen macht einiges einfacher, aber:
Was ist, wenn die Datei zu groß für den Speicher ist?
20GB passen auf die Platte, aber eher nicht in übliche RAMs.

fgets schneidet Leerzeichen nicht ab, falls du das meinst.

Zum strtok: Eigentlich alles gut,
aber das Problem mit dem abgeschnittenen Rest bei fgets bleibt noch.
Und wenn ein Teil zwischen zwei ; eventuell länger ist als der Buffer und abgeschnitten...
das würde eine grausige Bastelei.

Hmm...vllt. am einfachsten, beim Einlesen gleich alles rauszufiltern.

Frage: Ist es akzeptabel, dass das Programm bei Riesendateien sagt "Ist mir zu groß"?
(Funktionieren wird es unabhängig von der Antwort,
nur der Programmieraufwand unterscheidet sich)
 
Frage: Ist es akzeptabel, dass das Programm bei Riesendateien sagt "Ist mir zu groß"?
(Funktionieren wird es unabhängig von der Antwort,
nur der Programmieraufwand unterscheidet sich)
Auf jeden Fall, da es sich um eine eigen spezifiziertes Dateiformat handelt. Frage ist nur, wo die Grenze zu setzen ist. Im Prinzip sollten ein paar Megabyte bereits als Maximum ausreichen...

Zum strtok: Eigentlich alles gut,
aber das Problem mit dem abgeschnittenen Rest bei fgets bleibt noch.
Und wenn ein Teil zwischen zwei ; eventuell länger ist als der Buffer und abgeschnitten...
das würde eine grausige Bastelei.
Das habe ich mir auch schon gedacht, dass es ein ziemliches Hin- und Hergeschiebe ist^^
 
Dann im Endeffekt jetzt doch die Datei ganz im Speicher :D

Also...

Zuerst mal eine Konstante, wieviel MB maximal gebraucht werden dürfen (außerhalb von main):
C++:
#define MAX_MB 8
Name kann natürlich angepasst werden (auch bei allen anderen Variablen).

Dann die Datei öffnen und prüfen, ob sie nicht zu lang ist:
C++:
FILE *datei;
int bytes;

datei = fopen("file.dat", "rb");
//prüfen ob offen

fseek(datei, 0, SEEK_END);
bytes = ftell(datei);
fseek(datei, 0, SEEK_SET);

if(bytes >= (MAX_MB<<20))
{
    printf("Zu lange Datei.\n");
    fclose(datei);
    return -1;
}

...

fclose(datei);
Was fseek/ftell machen:
Bei einer Datei, die zum Lesen geöffnet wird, beginnt man ja normalerweise am Anfang.
Mit dem ersten fseek springt man zum Ende (genauer: 0 Byte vor dem Ende, deshalb die 0).
Jetzt kann man mit ftell herausfinden, das wievielte Byte das dort ist.
Ganz am Ende: damit hat man dann gleich die Länge der Datei.
Das zweite fseek springt wieder zu Anfang zurück
(SEEK_BEGIN wäre vllt. passender, aber es heißt eben SEEK_SET).

Das if danach sollte klar sein.
<<20 multipliziert MAX_MB mit 2^20, also praktisch *1024*1024
Bytes pro MB.

Warum "rb" beim fopen: Windows hat die seltsame Angewohnheit, ein Byte Zeilenwechsel im Programm zu zwei Byte in der Datei zu machen (und umgekehrt), wenn nur "r" verwendet wird.Bei den Bytezählereien hier würde das stören. "rb" verhindert es.


Wenn das erledigt ist: char-Array anlegen und gesamte Datei rein
C++:
FILE *datei;
int bytes;

char *daten; //neu
char c;
int i;

datei = fopen("file.dat", "rb");
//prüfen ob offen

fseek(datei, 0, SEEK_END);
bytes = ftell(datei);
fseek(datei, 0, SEEK_SET);

if(bytes >= (MAX_MB<<20))
{
    printf("Zu lange Datei.\n");
    fclose(datei);
    return -1;
}

daten = (char *)malloc(bytes); //
if(daten == NULL) //
{
    printf("Zu wenig Speicher\n"); //
    fclose(datei); //
    return -1; //
}

i = 0; //
for(--bytes; bytes >= 0; bytes--) //
{
    c = fgetc(datei); //
    if(c != ' ' && c != '\n' && c != '\r' && c != '\t' && c != -1) //
        daten[i++] = c; //
}

fclose(datei);

...

free(daten); //
In der Schleife Byte für Byte einlesen und Leerzeichen,
Zeilenwechsel, Tabulatoren gleich rausfiltern.
Dann hat man keine Probleme mehr damit.

Die Schleife schaut vllt. etwas seltsam aus:
Es muss bytes mal ausgeführt werden.
Ob ich von 0 bis bytes oder von bytes bis 0 zähle ist egal.
Hier wird von bytes Richtung 0 gezählt, so spart man sich nämlich eine weitere Variable (die man zum Zählen von 0 bis zum nicht veränderbaren bytes brauchen würde).
Das --bytes am Anfang, weil bei zB. 4 Bytes erst bei 3 angefangen wird. 3 2 1 0.

So.
Bei den ... kann man jetzt ganz einfach mit strtok/sscanf wie schon oben alles auslesen.
Kein Ärger mit abgeschnittenen Sachen, Zeilenwechsel oder Leerzeichen.

Gruß
 
Wow danke.
Code:
Bei den ... kann man jetzt ganz einfach mit strtok/sscanf wie schon oben alles auslesen.
Kein Ärger mit abgeschnittenen Sachen, Zeilenwechsel oder Leerzeichen.
Ich poste dann meine Ideen (oder eher Probleme :D) zum Auslesen der Syntax demnächst...

Noch eine Sache zur if-Bedingung in Zeile15:
Mein Compiler meckert "error: expected ')' before ';' token" und darf man eine Änderungsoperation auf ein define anwenden? Instinktiv hätte ich es jetzt anders herum gemacht
 
Diese Zeile 15?
C++:
if(bytes >= (MAX_MB<<20))

Welche Änderungsoperation auf define?
Wenn du MAX_MB<<20 meinst, da wird MAX_MB nicht geändert.
Wenn man schreibt
C++:
int i = 8;
int j = i<<20;
wird 8 zwar mit dem <<20 verrechnet, aber in i bleibt trotzdem 8
 
Zurück