Datei einlesen und anhand schlüsselbegriffe verarbeiten



  • Hallo zusammen,

    ich bin neu hier und möchter erstmal alle begrüßen!
    Neu für mich ist auch C++, wo ich mich jetzt mal "halbwegs" eingelesen habe.
    Programmiererfahrung habe ich jedoch mit C und Fortran, sodass nicht alles neu für mich ist.

    Als Übung wollte ich jetzt eine Funktion schreiben, die ich auch später benötige.

    Zu meinem Problem:

    Ich habe eine Input-file die aus mehreren blöcken besteht. Ein Beispiel mal hier:

    RoBi * * * TestInput * *
    ,,1,3,3,8

    MATErial,1
    USER,33
    136000 78000 68000 163000 40000
    260.0 260.0 260.0, 10.0
    6000.0 6000.0 6000.0 0.0 0.0 0.0
    -1.0 0.0 0.0
    0.0 0.0 1.0

    CONStants
    t=1
    v=-1
    m=10
    n=10

    COORdinates
    1 0 0.0 0.0
    2 0 10.0 0.0
    3 0 10.0 10.0
    4 0 0.0 0.0

    Dabei hat jeder block seine Bedeutung, die durch das Schlüsselwort darüber gekennzeichnet wird. Außerdem habe ich sowohl "," als auch "space" als delimiter Zeichen (eigentlich auch den Zeilenumbruch)

    Meine bisherige herangehensweise ist nicht so toll und es gibt bestimmt bessere Lösungen, deshalb meine Frage ob jemand eine elegantere Methode kennt.

    Zu meiner Idee/bisherigen Umsetzung:
    Ich lade die Datei zunächst mit ifstream in ein ifstream Objekt (ifs). Danach speichere ich den gesamten text in einem string.
    nun verarbeite ich den stream in einer while schleife und lese dabei jedes Zeichen mit get(c) (ifs.get(c)), wenn ich dabei auf ein space, komma oder zeilenende stoße lese ich zeichen seit dem letzten gefundenen delimiter-zeichen in einen string (aus meinem gesamten string).
    Dann prüfe ich noch über einen Vergleich ob eines der Schlüsselwörter im gefundenen String steckt.
    Je nach schlüsselwort sollen die daten in gesonderte vectoren gespeichert werden.
    Außerdem ist das mit den Zählern, die ich verwende um die Position der gefundenen Zeichen zu bekommen unsauber, sodass es zu Fehlern kommt.

    Das ganze sieht als Code ziemlich hässlich aus und ich denke, dass das ganze sehr ineffizient ist, da die vectoren immer wieder vergrößert werden.
    Gerade im Coordinaten Block können zum Teil viele Koordinaten stehn!

    getline habe ich auch schon versucht, jedoch kommt dann das Problem, dass ich ja unterschiedliche delimiter verwende und in getline kann ich immer nur einen angeben.

    Für Hilfe wäre ich sehr dankbar!

    Vg biro



  • Hast Du eine formale Spezifikation des Formats? Von der würde ich starten.
    Dein Beispiel ist ja nur eine Instanz so einer Datei - da kann zumindest ich jetzt keinen tollen Vorschlag aus dem Hut zaubern um das einzulesen. Dazu habe ich schlicht zu wenig Informationen.

    Dein Bauchgefühl, dass das besser geht als alles in einen String zu lesen und diesen dann zu zerhacken ist aber richtig... 🙂

    PS: Wenn Dir erstmal eine Fingerübung reicht, um mit C++ I/O Erfahrungen zu sammeln, versuch doch nur Ein- und Ausgabe eines der Blöcke zu schreiben.

    class material{
      // ...
    };
    
    int main() {
      istream in; // enthaelt nur MATErial Block.
      ostream out;
      //...
      material m1, m2;
      in>>m1;
      out<<m1;
      in>>m2; 
      assert(m1==m2);
    }
    


  • danke für deine Antwort.

    Also die formale spezifikation/format muss ich eigentlich selbst ausarbeiten.
    Bei Koordinaten bieten sich halt gerade die drei Raumrichtungen an, weshalb das auch so gemacht ist. Bei Material könnte man dann auch maximal drei Werte pro Zeile zulassen.

    Ich steh ja selbst noch am Anfang, die input die ich gezeigt habe war nur so ein erster Wurf 😉



  • Hallo biro,

    lies dir mal den Artikel Ein- und Ausgabe in C++ - IO-Streams dazu durch (ins. "2.6 Spezialbehandlung bestimmter Typen" - "eigene Typen")

    Mit Hilfe des Stream Eingabeoperators (>>) kannst du beliebige Typen einlesen:

    std::vector<int> values;
    int value;
    
    istream in("inputfile");
    
    while(in >> value)
    {
      values.push_back(value);
    }
    

    So kannst du bspw. eine Liste von Zahlen (durch Whitespaces getrennt) einlesen.
    Und bei Fließkommazahlen entsprechend 'int' durch 'double' (oder 'float') ersetzen.

    Diesen Code kannst du dann in die von "Furble Wurble" erwähnte Material-Klasse einbauen:

    class material
    {
      friend std::istream& operator >> (std::istream& is, material &mat);
    };
    
    std::istream& operator >> (std::istream& is, material &mat)
    {
      // read material from istream
      // ...
    }
    

    PS: Du kannst natürlich auch verschiedene Typen mischen:

    int x;
    float y;
    string z;
    
    is >> x >> y >> z;
    

    ...

    PPS: Für fortschrittlichere Ein- und Ausgabe kansnt du auch mal nach den Beiträgen von "Werner Salomon" hier im Forum suchen z.B. csv datei durchsuchen

    Edit: << durch >> ersetzt - danke sebi707 😉



  • Th69 schrieb:

    Mit Hilfe des Stream Eingabeoperators (<<) [...]

    Du meinst wohl >> . Der links Shift ist für Ausgabe.



  • Hey Th69,

    danke für deine antwort.
    Ich schau mir alles mal an und versuche es mit deinen Hinweisen. Auf den ersten Blick muss ich mir da jedoch noch einiges klar machen. Ich melde mich dann nochmal wenn ich was hinbekommen hab.

    für weitere Tips bin ich natürlich auch froh!

    Edit:

    Th69 schrieb:

    PS: Du kannst natürlich auch verschiedene Typen mischen:

    int x;
    float y;
    string z;
    
    is >> x >> y >> z;
    

    diese Anweisung versteh ich nicht ganz. Werden dabei die selben Zeichen in eine int variable, eine float variable und eine string variable geschrieben ?



  • biro schrieb:

    Werden dabei die selben Zeichen in eine int variable, eine float variable und eine string variable geschrieben ?

    Nein es werden 3 unterschiedliche Sachen, getrennt durch Whitespace, eingelesen. Damit könntest du Beispielsweise eine Zeile einlesen die wie folgt aussieht:
    7 12.5 bla



  • nein! Voraussetzung für diese Anweisung ist, dass in dem Stream ein int, ein float und ein String nacheinander kommen ...

    Edit:
    Oh ... viiiieeel zu spät 😃



  • Wenn du das Format selbst bestimmen darfst dann nimm irgendein strukturiertes Format wie JSON oder XML. Der Overhead beim Lesen/Schreiben mag zwar etwas größer sein, aber du ersparst dir Mordsarbeit, wenn später Erweiterungen notwendig werden.



  • Mit JSON kenne ich mich nicht aus, aber XML ist eigentlich eine gute Idee! Nur muss ich mir da dann wieder ganz andere Gedanken fürs einlesen machen!
    Oder gibt es zufällig Bibliotheken zum einlesen von XML ?



  • biro schrieb:

    Oder gibt es zufällig Bibliotheken zum einlesen von XML ?

    Klar die gibts wie Sand am Meer. Es ist fast schon schwierig sich für eine davon zu entscheiden. Ich habe selbst schon tinyxml und pugixml benutzt. Ich weiß noch, dass ich pugixml aus einem Grund deutlich besser fand ich weiß nicht mehr warum. Liegt schon einige Zeit zurück. Wenn du wirklich auf XML umsteigen möchtest schau dir einfach ein paar Libraries an. Jede gute Library sollte irgendein Getting Started Tutorial haben, wo etwas Code gezeigt wird. Da kann man sich schonmal einen Eindruck machen.



  • meine fortschritte sind leider sehr ernüchternd!

    Ich habe jetzt das einlesen der "unformatierten" input verworfen und versuche mich jetzt an xml mit pugixml. Um mich ran zu tasten habe ich eine ganz einfache xml erstellt:

    <?xml version="1.0"?>
    <Coordinates>
    	1.0 2.0
    <\Coordinates>
    

    und versuche diese einzuladen:

    #include<iostream>
    #include"pugixml.hpp"
    
    int main(){
    
    	pugi::xml_document doc;
    
    	pugi::xml_parse_result result = doc.load_file("input.xml");
    
    	return 0;
    }
    

    Leider scheitert es dabei schon, der compiler liefert mir einen Fehler:

    /tmp/xmlLesen-932e9d.o: In Funktion `main':
    xmlLesen.cpp:(.text+0x24): Nicht definierter Verweis auf `pugi::xml_document::xml_document()'
    xmlLesen.cpp:(.text+0x46): Nicht definierter Verweis auf `pugi::xml_document::load_file(char const*, unsigned int, pugi::xml_encoding)'
    xmlLesen.cpp:(.text+0x68): Nicht definierter Verweis auf `pugi::xml_document::~xml_document()'
    xmlLesen.cpp:(.text+0x8f): Nicht definierter Verweis auf `pugi::xml_document::~xml_document()'
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    

    Die Bibliotheken für pugixml habe ich im gleichen verzeichnis.
    Kann mir da vielleicht jemand weiterhelfen ?



  • biro schrieb:

    der compiler liefert mir einen Fehler:
    ...
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    ...

    Nicht der Compiler, der Linker! Da mussst Du wohl mal schauen, wie man die Bibliothek richtig dazu linkt ...



  • biro schrieb:

    Die Bibliotheken für pugixml habe ich im gleichen verzeichnis.
    Kann mir da vielleicht jemand weiterhelfen ?

    Im selben Verzeichnis reicht nicht. Man muss dem Compiler (eigentlich dem Linker) schon explizit sagen, dass die Bibliotheken eingebunden werden ( -l ) sollen und wo diese zu finden sind ( -L ). Beim Aufruf des Compilers sieht das dann etwa so aus:

    clang++ -L/hier/liegen/die/libs -lpugixml ...

    Wobei der Dateiname Bibliothek wahrscheinlich libpugixml.a lautet. Bei der -l -Option werden dann allerdings das lib-Präfix und die Dateinamenerweiterung weggelassen.
    Da du aber wohl mit einer IDE arbeitest, solltest du mal schauen, wo du das dort bei denen Projekteigenschaften einstellen kannst. Das heisst dann "zusätzliche Bibliotheken/Pfade" oder so ähnlich. Das sollte eigentlich jede IDE irgendwo haben.

    Finnegan



  • Bei pugixml ist das gar nicht mal so kompliziert. In dem Archiv was es auf der Homepage zum Download gibt, existiert ein Ordner src mit 3 Dateien. Eine .cpp Datei und zwei .hpp Dateien. Die 3 Dateien einfach zum Projekt hinzufügen und schon sollte alles funktionieren. Eine Library kann man sich immer noch daraus bauen wenn man das unbedingt möchte, aber bis man das einem Anfänger erklärt hat...



  • sebi707 schrieb:

    Bei pugixml ist das gar nicht mal so kompliziert. In dem Archiv was es auf der Homepage zum Download gibt, existiert ein Ordner src mit 3 Dateien. Eine .cpp Datei und zwei .hpp Dateien. Die 3 Dateien einfach zum Projekt hinzufügen und schon sollte alles funktionieren. Eine Library kann man sich immer noch daraus bauen wenn man das unbedingt möchte, aber bis man das einem Anfänger erklärt hat...

    genau so habe ich es gemacht, war ja auch in der Anleitung so beschrieben.
    Wie man am code sieht habe ich die header datei per include auch eingebunden. Deswegen verstehe ich das Problem nicht?!

    @Belli:
    du hast recht, der Linker 😉



  • Hast du die .cpp Datei auch in das Projekt eingebunden (wie auch immer das in deiner IDE geht)? Einfach in den Ordner kopieren geht bei headern aber die .cpp Dateien müssen im Projekt auftauchen.



  • ich hab vi verwendet und die .cpp ist nur über die header-file eingebunden. Normalerweise verwende ich code:blocks, werde ich später für das problem dann auch mal verwenden!



  • biro schrieb:

    die .cpp ist nur über die header-file eingebunden.

    Offensichtlich fehlen Dir wichtíge Grundlagenkenntnisse ...
    Eine cpp - Datei wird nicht über ein Header-File eingebunden, sondern kompiliert und (dazu)gelinkt!

    Siehe auch:

    Belli schrieb:

    biro schrieb:

    der compiler liefert mir einen Fehler:
    ...
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    ...

    Nicht der Compiler, der Linker! Da mussst Du wohl mal schauen, wie man die Bibliothek richtig dazu linkt ...



  • danke für den Hinweis, habe den entsprechenden Abschnitt in c++ von Breymann nochmal nachgelesen. Hab das beim ersten lesen wohl nicht ganz richtig aufgenommen!
    Aber ich bin noch am Anfang und lernfähig 😉


Anmelden zum Antworten