String bzw. Char 'splitten'



  • Hallo @ all,

    ich habe leider das "kleine" Problem das ich es einfach nicht schaffe einen Text zu splitten und anschließend in die Klassen zu transportieren.

    Meine Frage wäre mal vorne weg, ist es einfacher bzw. besser einen char zu splitten oder einen string?

    void TravelAgency::readFile() {
        // SAMPLE: F|70|1499.0|20150104|20150109|FRA|ORD|United Airlines
        fstream datei;
        char zeile[256];
        string stringzeile;
    
        datei.open("bookings.txt", ios::in);
    
        string trennung_("|");
        while (!datei.eof())
        {
            datei.getline(zeile, sizeof(zeile));
            stringzeile = charToString(zeile);     // die Attribute in den Klassen sind entweder ein String oder Integer..
            cout << stringzeile << endl;
        }
        cout << "Textdatei erfolgreich eingelesen" << endl;
        datei.close();
    }
    
    string TravelAgency::charToString(char toString[]){
        stringstream ss;
        string isString;
        ss << toString;
        ss >> isString;
        return isString;
    }
    

    Wenn ich es nur hinkriegen würde anhand des Zeichens ('|') den String bzw. Charkette zu splitten wäre der Rest nun ein Kinderspiel.

    Falls möglich nicht explizit die Lösung verraten sondern ich würde es gerne selber versuchen, damit es auch hängen bleibt 🙂

    Danke im Vorrauuuuuus 🕶



  • Naja, du brauchst halt eine split-Hilfsfunktion, welche die Trennzeichen sucht und dann die einzelnen Strings extrahiert und in einen vector o.ä. stopft. Auf einzelne Zeichen greifst du mit operator[] zu (kannst auch find() benutzen), einen Teilstring erstellst du mit substr().



  • Darüber hinaus sieht das Einlesen doch ein wenig merkwürdig aus. Die Abbruchbedingung ist jedenfalls nicht richtig (getline kann fehlschlagen) und das mit 256 recht willkürlich dimensionierte Array sieht auch suspekt aus. Soweit mir bekannt macht man zeilenweises Einlesen in C++ üblicherweise so:

    ifstream datei("bookings.txt");
    string zeile;
    while (getline(datei,zeile))
    {
        cout << zeile << endl;
    }
    cout << "Textdatei erfolgreich eingelesen" << endl;
    


  • Mit getline kann man auch splitten:

    fstream datei("bookings.txt");
    string data;
    while (getline(datei, data, '|'))
    {
        cout << data << '|';
    }
    cout << "Textdatei erfolgreich eingelesen" << endl;
    

    Und am besten mal ein paar Beiträge von "Werner Salomon" hier im Forum anschauen, z.B. CSS ähnliche Text-Datei parsen oder [Hilfe] .csv zu .xml.



  • Ich habe es mal etwas verbessert nur komme ich in eine Endlosschleife.

    void TravelAgency::readFile() {
        ifstream data("bookings.txt");
        string line;
        while (getline(data, line)) {
    
            TravelAgency::splitter(line);
        }
    }
    
    void TravelAgency::splitter(string line_){
        string symbol ("|");
        size_t pos;
        string substring;
        int length_;
    
        while(line_ != "\n"){                    // Solange line_ nicht gleich "Enter" ist
            length_ = line_.length();            // Länge des Strings
            pos = line_.find(symbol);            // Finde Position vom Symbol
            substring = line_.substr(0,pos);     // Anfang bis Position = Substring
            line_ = line_.substr(pos+1, length_);// line_ ist ab nun ab der Position bis zum Ende der "Zeile"
            cout << substring << endl;
        }
    }
    

    Textdokument:

    F|70|1499.0|20150104|20150109|FRA|ORD|United Airlines
    R|71|466.0|20150104|20150109|Chicago OHare Intl Airport|Chicago OHare Intl Airport|Avis
    H|72|501.03|20150104|20150109|Mark Twain Hotel|Peoria
    F|73|1358.0|20150109|20150110|ORD|FRA|United Airlines
    F|74|330.0|20150608|20150608|STR|CDG|Air France
    

    Meine Ausgabe:

    United Airlines
    United Airlines
    ...
    

    Mein Ziel war es immer bis zum Symbol einen substring zu "erstellen" und den Rest - natürlich ohne das Symbol am Anfang - als neue line_ zu haben, damit er den so lange zerlegt bis ich nur noch das "Enter" habe.

    Ich hoffe das mein Ansatz überhaupt richtig ist 😃

    Danke bisher an alle die mir geholfen haben 🙂



  • Deine Abbruchbedingung stimmt nicht. Der Fall das in line_ nur noch ein Newline enthält kommt nie vor. Das Newline hat getline schon entfernt. Du müsstest mal einen Test einbauen ob dein find(symbol) überhaupt noch was findet, bevor du den Anfang des Strings löschen willst. Wenn find() nichts findet gibt es einen sehr großen Wert (string::npos) zurück. Dieser Wert +1 ist zu groß für den Datentyp, es gibt einen Overflow und wir sind wieder bei 0.



  • Hallo depream,

    depream schrieb:

    Meine Frage wäre mal vorne weg, ist es einfacher bzw. besser einen char zu splitten oder einen string?

    am besten weder noch!

    Was vielen Leuten nicht bewusst ist; es gibt in C++ einen std::istream in Form z.B. von cin oder ifstream. Und die Aufgabe dieses Streams ist es NICHT(nur) Zeichen aus einer Datei in den Speicher zu schaufeln - das könnte ein std::streambuf auch ganz allein, sondern aus einer Folge von Zeichen ein Objekt zu machen. Ein Objekt kann ein Zeichen, ein Integer, ein Datum oder eine TravelAgency sein. Die Folge von Zeichen kann in einer Datei oder im Memory (als String) stehen. Es bleibt eine Folge von Zeichen und das Lesen einer kompletten Zeile aus der Datei in einen String verschiebt das Problem nur und löst praktisch nichts (ok - nicht viel, aber dazu vielleicht später).

    Th69 hat das schon vorgemacht. Wobei ich noch einen Schritte weiter gehe und nicht nur Strings lesen, sondern gleich die Objekte.

    Um z.B. ein Zeichen oder ein Integer zu lesen gibt es Funktionen in C++.

    ifstream datei(...);
        char c; // ein Zeichen
        datei >> c; // liest das Zeichen
        if( datei )
        {   // alles ok; usw.
    

    In 'c' kann jetzt ein 'F' ein 'R' oder ein 'H' oder was anderes stehen.
    Für das lesen (und prüfen) des Trennzeichens hält C++ auch etwas bereit. Zunächst wie oben:

    char pipe_zeichen;
        datei >> pipe_zeichen;
        if( datei && pipe_zeichen == '|' )
        {   // erst jetzt ist alles i.O.
    

    Ein std::istream kann auch eine Funktion 'aufnehmen', die eine gewisse Manipulation vornimmt. Man nennt diese Funktion daher auch Manipulator. Sie muss folgende Signatur haben:

    std::istream& 'name'( std::istream& );
    

    'name' ist irgendein Name natürlich ohne ''. Angenommen es gibt einen Manipulator pipe - also:

    std::istream& pipe( std::istream& );
    

    dann kann man folgendes aufrufen:

    datei >> pipe;
    

    Hier wird diese Funktion dann mit 'datei' als Parameter aufgerufen und die Implementierung von pipe muss eine Referenz auf 'datei' wieder zurückgeben.

    std::istream& pipe( std::istream& ) {
            char pipe_zeichen;
            in >> pipe_zeichen;
            if( in && pipe_zeichen == '|' )
                return in; // alles gut
            in.setstate( std::ios_base::failbit ); // setzt das Fehlerbit im istream
            return in;
        }
    

    eine fortgeschrittene Variante und weitere Tipps findest Du als Char<> hier.

    Um die ersten beiden Objekte (Zeichen und Nummer) zu lesen reicht dann:

    char c;
        int nr; 
        datei >> c >> pipe >> nr;
        if( datei )
        {   // Erfolg
    

    depream schrieb:

    Falls möglich nicht explizit die Lösung verraten sondern ich würde es gerne selber versuchen, damit es auch hängen bleibt

    dann versuche es mal selber weiter.

    Noch eine Frage: In der 3. Zeile der Textdatei ist ein '|' weniger als in den anderen 4 Zeilen. Ist das ein Fehler oder können hinter dem zweiten Datum 'beliebig' viele Felder und damit '|'-Zeichen pro Datensatz kommen?

    Gruß
    Werner


Log in to reply