PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : C: mehrspaltige Datei einlesen



Mase
28-08-2007, 18:53
Hallo,

ich bin noch recht neu in der Welt von C, würde aber gerne Daten aus einer bestimmten Spalte einer mehrspaltigen .txt einlesen.

Der Inhalt der Datei sieht in etwa so aus:

0; 107,6; 32,8; 148,9; 2,6; 179,2; 8,7; 158,7; -41,6;
1; 109,5; 29,5; 151,5; 0,6; 181,2; 6,8; 159,0; -42,8;
2; 110,8; 27,4; 153,1; -0,8; 182,2; 5,8; 158,3; -41,4;
3; 112,6; 25,3; 155,0; -2,3; 183,8; 4,5; 158,1; -40,0;

..... das ganze geht noch einige 1000 Zeilen so weiter.

Interessant sind für mich die Werte aus der 8. Spalte, deswegen würde ich diese gerne in ein Array einlesen. Bisher hab ich es (etwas umständlich wahrscheinlich) geschafft, die Werte aus der 8. Spalte in eine neue Datei zu schreiben.

Mein Problem liegt jetzt darin, die chars, die momentan in der neuen Datei stehen, als Zahlen in ein Array einzulesen, damit ich sie später weiterverarbeiten kann.

fscanf hab ich schon probiert, nur tritt dabei eine Zugriffsverletzung auf...

Hier ist mein bisheriger Code:

(1) - um in die neue Datei zu schreiben
-----------------------


# include <stdio.h>

int main()
{
FILE* fp_TEST;
FILE* fp_TEST2;
char INHALT;
long count, POS, VAR;
fp_TEST = fopen("c:\\temp\\01_.txt", "r");
fp_TEST2 = fopen("c:\\temp\\02.txt", "r+");
if (fp_TEST2 == NULL)
fp_TEST2 = fopen("c:\\temp\\02.txt", "a");

POS = 0;
VAR = 0;
while (POS != 1250)
{
count = VAR;
POS++;
while (count != 7)
{
if (getc(fp_TEST) == ';')
count++;
}
fseek(fp_TEST, 1, SEEK_CUR);
while ((INHALT = getc(fp_TEST)) != ';')
{
putchar(INHALT);
putc(INHALT, fp_TEST2);
}
putc('\n', fp_TEST2);
printf("\n");
VAR = -1;
}

fclose(fp_TEST);
fclose(fp_TEST2);
getchar();
}

--------------------------
(2) - eine Probe um zu sehen, ob fscanf funktioniert
--------------------------


# include <stdio.h>

int main()
{
FILE* fp_TEST2;
float ZAHL;
if((fp_TEST2 = fopen("c:\\temp\\01__.txt", "r")) == NULL)
fprintf(stderr, "FEHLER 001");
else
fprintf(stdout, "ERFOLG");

fscanf(fp_TEST2, "%f", ZAHL);
printf("%f", ZAHL);

fclose(fp_TEST2);
getchar();
}
--------------------------

Natürlich würde ich mich freuen, wenn Ihr mir bei dem Problem helfen könntet. Toll wäre auch noch, wenn von Euch Hinweise kommen, wie das Ganze evtl. viel einfacher gehen könnte ;).

Danke und Gruß,

MaSe

jeebee
28-08-2007, 19:09
man atof Das Trennzeichen zwischen Vor/Nachkommastellen muss ein . (Dezimalpunkt) sein, damit atof funktioniert. Um atof zu verwenden musst du stdlib.h einbinden.

locus vivendi
28-08-2007, 20:52
Hallo,

ich bin noch recht neu in der Welt von C, würde aber gerne Daten aus einer bestimmten Spalte einer mehrspaltigen .txt einlesen.
Kannst du das noch weiter präzisieren? Insbesondere ob es immer die gleiche Anzahl von Spalten ist, und ob dein Parser die Datei nur dann akzeptieren soll, wenn die Zahlen ein genaues Format einhalten (also z.B. zwingend mit Komma und genau einer Nachkommastelle).

Außerdem, muss es denn C sein? Oder würde auch C++ gehen? (Perl, Awk, Python usw. eignen sich dafür sicherlich auch hervorragend, nur könnte ich damit nicht helfen)


Toll wäre auch noch, wenn von Euch Hinweise kommen, wie das Ganze evtl. viel einfacher gehen könnte
Wenn du die Datei gleich validieren möchtest würde ich vorschlagen, zeilenweise zu lesen und die gewünschte Spalte mit einem Regulärem Ausdruck zu extrahieren. Das geht im Vergleich zu C mit C++ bequemer, dank weniger umständlich zu bedienender Libraries.

Wenn es nicht validierend sein soll, dann könntest du beim zeilenweise Lesen bleiben, aber die Werte z.B. mit "sscanf" lesen. Das geht dann auch in "Plain C" noch recht bequem.

jan61
28-08-2007, 21:39
...
float ZAHL;
...
fscanf(fp_TEST2, "%f", ZAHL);
...

Die *scanf-Funktionen erwarten als Argument zum Hineinschreiben immer einen Pointer - logisch, sonst würde die Rückgabe des Werts auch nicht klappen. Richtig wäre also
fscanf(fp_TEST2, "%f", &ZAHL);Jan

Mase
29-08-2007, 11:48
Die Dateien haben immer das gleiche Format, genauso wie die Zahlen. Also immer 9 Spalten, und alle Zahlen mit Komma und einer Nachkommastelle. Zusätzlich können noch negative Zahlen auftreten.

Es sollte schon C sein, denn mit C++ kenne ich mich noch gar nicht aus.

Ansonsten danke für die Tips, die helfen schonmal!

locus vivendi
29-08-2007, 13:49
Es sollte schon C sein, denn mit C++ kenne ich mich noch gar nicht aus.
Darf ich unterstellen dass du dich mit C auch noch in der Lernphase befindest? In diesem Fall wäre es vielleicht eine günstige Gelegenheit mal ein Auge auf C++ zuwerfen.
Schon allein deshalb weil C++ dynamische Datenstrukturen anbietet in die du die eingelesenen Werte speichern kannst ohne dich um die Verwaltung des Speichers kümmern zu müssen. Oder haben alle Dateien mit denen du arbeitest immer die gleiche Größe?

Ansonsten, wie schon geschrieben, die Scanf-Funktionen (fscanf, sscanf) genauer anschauen (sowohl in C als auch C++ nützlich). Da gibt es z.B. einen Schalter für den Format-String mit dem du die Zuweisung unterdrücken kannst. Nützlich um das Format der Daten zu prüfen ohne Dummy-Variablen bereithalten zu müssen. Und ich würde mir auch anschauen wie du die Eingabefeldbreite begrenzen kannst.

Nur als Idee am Rande noch folgendes: Statt mit Locales herumzuhantieren um zwischen Punkt und Komma als Dezimalseperator für Fließkommazahlen zu wechseln, kannst du natürlich auch ein Integer lesen, dann den Punkt, dann wieder ein Integer.

peschmae
29-08-2007, 18:56
man atof Das Trennzeichen zwischen Vor/Nachkommastellen muss ein . (Dezimalpunkt) sein, damit atof funktioniert. Um atof zu verwenden musst du stdlib.h einbinden.




... except that atof() does not detect errors.


Naja, Fehlererkennung gehört imo dann doch zum wichtigsten bei I/O aus Dateien/von Benutzern, aber das ist dann schamloses Ausnützen der Vorteile proprietärer Software ;)

Benutze zwar selber atoi() auch mal in kleinen Progrämmchen, die sonst keiner sieht :D

MfG Peschmä

jan61
29-08-2007, 19:31
...
Nur als Idee am Rande noch folgendes: Statt mit Locales herumzuhantieren um zwischen Punkt und Komma als Dezimalseperator für Fließkommazahlen zu wechseln, kannst du natürlich auch ein Integer lesen, dann den Punkt, dann wieder ein Integer.

Das ist aber extrem aufwändig. Du musst dann nämlich beim "Nachkomma-Integer" zählen, wieviele Stellen er hat, um dann den float mittels VorkommaInteger + NachkommaInteger / pow(10, AnzahlNKStellen) zu berechnen oder den NK-Integer linksshiften, bis er eine festzulegende Stellenanzahl hat. Dann vielleicht doch lieber erst per fgets() einlesen, evtl. Kommata gegen Punkte auswechseln und dann per sscanf() einlesen :-)

Jan

locus vivendi
29-08-2007, 20:56
Zitat von locus vivendi Beitrag anzeigen
...
Nur als Idee am Rande noch folgendes: Statt mit Locales herumzuhantieren um zwischen Punkt und Komma als Dezimalseperator für Fließkommazahlen zu wechseln, kannst du natürlich auch ein Integer lesen, dann den Punkt, dann wieder ein Integer.Das ist aber extrem aufwändig. Du musst dann nämlich beim "Nachkomma-Integer" zählen, wieviele Stellen er hat, um dann den float mittels VorkommaInteger + NachkommaInteger / pow(10, AnzahlNKStellen) zu berechnen oder den NK-Integer linksshiften, bis er eine festzulegende Stellenanzahl hat. Dann vielleicht doch lieber erst per fgets() einlesen, evtl. Kommata gegen Punkte auswechseln und dann per sscanf() einlesen :-)
Mase hat geschrieben das es immer genau (*) eine Nachkommastelle gibt . Deshalb kann man im Scanf-Formatstring die Feldbreite auf 1 begrenzen. Wenn man direkt daran anschließend dann das Semikolon im Formatstring angibt, hat man auf diese Weise gleich eine Fehlererkennung, sollte doch mal eine mißgebildete Datei auftauchen.
Wenn man aus den beiden Integern dann eine Fließkommazahl machen will reicht es aus die zweite durch 10 zu teilen und je nach Vorzeichen der ersten zu addieren oder zu subtrahieren.

*: Streng genommen hat er nicht "genau eine Nachkommastelle" gesagt. Aus dem Kontext heraus verstehe ich das aber so, weil meine Frage dies zum Inhalt hatte.

jan61
29-08-2007, 22:17
Mase hat geschrieben das es immer genau (*) eine Nachkommastelle gibt . Deshalb kann man im Scanf-Formatstring die Feldbreite auf 1 begrenzen...

OK, wenn man 100% voraussetzen kann, dass die Eingangsdaten immer korrekt sind, dann wird es - wie Du beschrieben hast - einfacher.


Wenn man direkt daran anschließend dann das Semikolon im Formatstring angibt, hat man auf diese Weise gleich eine Fehlererkennung, sollte doch mal eine mißgebildete Datei auftauchen...

Das hat mich jetzt mal interessiert. Ich habe ein kleines Programm gebaut, mit dem ich Eingaben von Zahlen auf diverse Arten mit fscanf abhole:
jan@jack:~/tmp> cat lese_float1.c
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
float x = 0.0;
int y = 0, z = 0;
char buf[20];

/* als String lesen */
fgets(buf, 19, stdin);
printf("Eingabe: %s", buf);

/* Vor- und Nachkomma-Int mit , als Dezimalstelle */
fscanf(stdin, "%d,%d", &y, &z);
x = y + (float)z / 10.0;
printf("aus Int y=%d z=%d unbegrenzt=%f abgeschnitten=%.1f\n", y, z, x, x);

/* Vor- und Nachkomma-Int mit , als Dezimalstelle, auf 1 Stelle begrenzt */
fscanf(stdin, "%d,%1d", &y, &z);
x = y + (float)z / 10.0;
printf("aus Int y=%d z=%d mit 1 NK-Stelle=%f abgeschnitten=%.1f\n", y, z, x, x);

/* Vor- und Nachkomma-Int mit , als Dezimalstelle, auf 1 Stelle begrenzt , ; am Ende */
fscanf(stdin, "%d,%1d;", &y, &z);
x = y + (float)z / 10.0;
printf("aus Int y=%d z=%d mit ; und 1 NK-Stelle=%f abgeschnitten=%.1f\n", y, z, x, x);

/* float unbegrenzt */
fscanf(stdin, "%f", &x);
printf("Float unbegrenzt=%f\n", x);

/* float mit 1 NK-Stelle */
fscanf(stdin, "%.1f", &x);
printf("Float 1 NK-Stelle=%f\n", x);

/* float mit 1 NK-Stelle und mit ; am Ende */
fscanf(stdin, "%.1f;", &x);
printf("Float mit ; und 1 NK-Stelle=%f\n", x);

return 0;
}
Übersetzen geht ganz einfach:
jan@jack:~/tmp> make lese_float1
cc lese_float1.c -o lese_float1
Dann habe ich mir eine Textdatei mit allen möglichen Kombinationen von Zahlen gebaut:
jan@jack:~/tmp> cat float.txt
1234.123
1234.099
1234,123
1234,099
-1234.123
-1234.099
-1234,123
-1234,099
1234.123;
1234.099;
1234,123;
1234,099;
-1234.123;
-1234.099;
-1234,123;
-1234,099;
1234.1
1234.0
1234,1
1234,0
-1234.1
-1234.0
-1234,1
-1234,0
1234
-1234
1234.1 1;
1234.0 1;
1234,1 1;
1234,0 1;
-1234.1 1;
-1234.0 1;
-1234,1 1;
-1234,0 1;
und dann so einen Textlauf gestartet:
jan@jack:~/tmp> while read zahl; do
echo -e "$zahl\n$zahl\n$zahl\n$zahl\n$zahl\n$zahl\n$zahl" | ./lese_float1
done <float.txt >float_erg.txtDas Ergebnis hier zu posten spare ich mir mal (ist ziemlich lang), guckt es Euch selber an. Mein Fazit: Mit fscanf allein kann man definitiv keine Eingaben auf Plausibilität prüfen. Wenn man nicht absolut sicher ist, dass die Eingabedaten immer 100% korrekt sind, dann muss man auf andere Funktionen zurückgreifen. Mein Favorit wäre in diesem Fall PCRE (Perl Compatible Regular Expressions).

Jan

Wen es interessiert - PCRE gibt es hier: http://www.pcre.org/

BLUESCREEN3D
30-08-2007, 13:25
fscanf(stdin, "%.1f", &x);
fscanf(stdin, "%.1f;", &x);
Hättest du mit -Wall kompiliert, dann wüsstest du, dass bei scanf() im Gegensatz zu printf() kein %. geht :D
Außerdem sollte man schon den Rückgabewert beachten, wenn man die Eingabe validieren will und hinter dem Komma sollte kein signed int stehen.

Leider garantiert scanf() nicht, dass die Eingabe exakt stimmt: Wenn am Ende des Format-Strings ein Semikolon steht und dies bei der Eingabe fehlt, dann kann man das nicht erkennen.

Aber man könnte es so versuchen:

int y;
unsigned int z;
char semicolon[2]; /* zweites Byte für \0 */
if (scanf("%d,%1u%1[;]", &y, &z, semicolon) != 3)
{
/* Fehler */
}

locus vivendi
30-08-2007, 14:26
Das hat mich jetzt mal interessiert. Ich habe ein kleines Programm gebaut, mit dem ich Eingaben von Zahlen auf diverse Arten mit fscanf abhole:[...]
In dem Programm vermisse ich die Auswertung der Rückgabewerte der fscanf Aufrufe.


Mein Fazit: Mit fscanf allein kann man definitiv keine Eingaben auf Plausibilität prüfen.
Wenn man es so macht wie ich geschrieben habe, also Feldlänge auf Eins begrenzen und direkt danach das Semikolon im Formatstring, dann bricht fscanf schon ab. Allerdings bemerkt man den Fehler nicht notwendigerweise bei der fscanf-Operation die vorzeitig abbricht, sondern erst bei der Folgenden (die dann bis zum Ende der Zeile nicht mehr genügend Felder vorfindet). Allerdings kann man den Aufruf auch so gestalten, dass man es gleich merkt, und zwar indem man als Letztes im Formatstring noch ein einzelnes Zeichen konvertiert und prüft ob es ein "Newline" ist. Wenn ein Aufruf dann nicht alle erwarteten Felder konvertiert weiß man, dass der Fehler in der soeben eingelesenen Zeile stecken muss.

Mal Pseudocode um das zu verdeutlichen:

FILE* fp = fopen(...);
...
int units, decimal;
char expected_newline;
int n_items = fscanf(fp, "%*i.%*1i; %*i.%*1i; %i.%1i; %*i.%*1i;%c", &units, &decimal, &expected_newline);
if(n_items == EOF && !ferror(fp) )
{ // Ganz normal Ende erreicht
}
else if(n_items == EOF)
{
std::cout << "\nFailed to read from stream!\n";
// Noch mehr Fehlerbehandlung...
}
else if(n_items != 3)
{
std::cout << "\nFailed to convert a value!\n";
// Noch mehr Fehlerbehandlung...
}
if(expected_newline != '\n')
{
std::cout << "\nLine not terminated by newline!\n";
// Noch mehr Fehlerbehandlung
}
double d = units + (units > 0 ? 0.1 * decimal : -0.1 * decimal);
std::cout << "\nValue = " << d << '\n';

Dieser Code kommt dem von Mase beschriebenen Format recht nahe. Eine Zeile hat hier immer vier Spalten, und die Werte der dritten werden gespeichert.
Für einen tatsächlichen Einsatz würde ich allerdings zumindest auch noch die Feldlänge der Vorkomma-Komponente begrenzen.

Ich sehe auch gerade das Bluescreen3d ebenfalls etwas gepostet hat was in die selbe Richtung wie mein Geschreibsel geht. Seine Lösung mit dem Semikolon als einzigem Zeichen im "Scanset", oder wie immer C das nennt, ist auch ein Tick eleganter als meine mit dem nachträglichen "if". Ich schicke mein Post trotzdem ab...

jan61
30-08-2007, 20:07
Hättest du mit -Wall kompiliert, dann wüsstest du, dass bei scanf() im Gegensatz zu printf() kein %. geht :D
Außerdem sollte man schon den Rückgabewert beachten, wenn man die Eingabe validieren will und hinter dem Komma sollte kein signed int stehen.

Stimmt, man sollte doch ab und zu die man-Pages genauer lesen :(


...Aber man könnte es so versuchen:

int y;
unsigned int z;
char semicolon[2]; /* zweites Byte für \0 */
if (scanf("%d,%1u%1[;]", &y, &z, semicolon) != 3)
{
/* Fehler */
}

Das habe ich auch gleich ausprobiert - interessante und elegante Variante. Wieder was gelernt :)

Jan