CSV Datei einlesen und verarbeiten



  • Statt stringstream würde ich istringstream verwenden. Ausserdem noch eine kleine Korrektur.
    Statt:

    zeilenparser >> wert;
        ergebnis.push_back(wert);
    

    besser:

    if (zeilenparser >> wert)
          ergebnis.push_back(wert);
    

    Wenn das einlesen nämlich fehl schlägt, dann wird ein nicht initialisierter Wert übernommen.



  • SeppJ schrieb:

    Eine recht einfache Version für die Werner mich bestimmt haut (und Pi zufrieden grinst) wäre (ungetestet, sondern eben schnell hier m Forum programmiert):

    😃

    Hallo Javun, hallo SeppJ,

    was haltet Ihr denn hier von?

    #include <iostream>
    #include <fstream>
    #include <vector>
    #include <limits>
    
    // -- ein Manipulator, der 'columns' Spalten, die mit 'D' getrennt sind, überspringt
    template< char D >
    struct skipcolumns
    {
        explicit skipcolumns( int columns ) : m_columns( columns ) {}
        friend std::istream& operator>>( std::istream& in, skipcolumns sc )
        {  
            for( int c = 0; c < sc.m_columns; ++c ) 
                in.ignore( std::numeric_limits< std::streamsize >::max(), D );
            return in;
        }
    private:
        int m_columns;
    };
    // --   überliest den Rest einer Zeile
    std::istream& skipline( std::istream& in )
    {
        return in.ignore( std::numeric_limits< std::streamsize >::max(), '\n' );
    }
    
    std::vector< double > readValues( const char* filename, int wspalte )
    {
        using namespace std;
        vector< double > values;
        ifstream f(filename);
        // Bem.: Indizierung der Spalten beginnt mit 0
        for( double value; f >> skipcolumns<';'>( wspalte-1 ) >> value >> skipline; )
            values.push_back( value );
        return values;
    }
    
    int main()
    {
        using namespace std;
        vector< double > values = readValues( "input.txt", 3 );
        return 0;
    }
    

    Gruß
    Werner



  • Hallo Werner,

    wie sähe denn die elegante Lösung aus, wenn der Typ von values beliebig sein soll?

    template<typename T>
    std::vector< T > readValues( const char* filename, int wspalte )
    {
        using namespace std;
        vector< T > values;
        ifstream f(filename);
        // Bem.: Indizierung der Spalten beginnt mit 0
        for( T value; f >> skipcolumns<';'>( wspalte-1 ) >> value >> skipline; )
            values.push_back( value );
        return values;
    }
    
    int main()
    {
        using namespace std;
        vector< string > values = readValues< string >( "input.txt", 3 );
        return 0;
    }
    

    Bei std::string hat man nun das Problem, dass der Rest einer Zeile nicht mehr überlesen wird.



  • Gugelmoser schrieb:

    wie sähe denn die elegante Lösung aus, wenn der Typ von values beliebig sein soll?
    ...
    Bei std::string hat man nun das Problem, dass der Rest einer Zeile nicht mehr überlesen wird.

    Hallo Gugelmoser,

    Verwendet man bei obigen Konstrukt std::string ohne sonstige Änderungen wird das Wort gelesen, was in dieser Spalte beginnt. Das endet spätestens mit dem Zeilenende oder vorher, wenn dort noch irgendwo ein Space in der Zeile auftaucht. Selbst in ersterem Fall würde 'skipline' korrekt das verbleibende EOL ('\n') überlesen.

    Das Problem ist doch eher, dass evt. zu viel oder zu wenig in 'value' steht.
    Wenn die Zeile lautet:

    Spalte1;Spalte2;dritte-Spalte;egal;noch;was
    

    so wird der Text "dritte-Spalte;egal;noch;was" nach value eingelesen, obwohl man vielleicht nur "dritte-Spalte" wollte.
    Steht in der Zeile

    Spalte1;Spalte2;dritte Spalte;egal;noch;was
    

    landet nur "dritte" in 'value' - also auch falsch.

    Die Lösung wäre ein Manipulator, wie z.B. line aus diesem Beitrag. Man muss dann nur noch zwischen dem Typ des Manipulators und dem Typ unterscheiden, der gelesen wird. Zunächst selektiere ich so weit möglich den Vorgang des Lesens des Wertes in eine eigenen Funktion - vielleicht so:

    namespace detail
    {
        template< typename T >
        bool read( std::istream& in, T& value )
        {
            return !(in >> value).fail();
        }
    }
    template<typename T>
    std::vector< T > readValues( const char* filename, int wspalte )
    {
        using namespace std;
        vector< T > values;
        ifstream f(filename);
        // Bem.: Indizierung der Spalten beginnt mit 0
        for( T value; f >> skipcolumns<';'>( wspalte-1 ); f >> skipline )
            if( detail::read< T >( f, value ) )
                values.push_back( value );
        return values;
    }
    

    bis hier ist noch alles wie vorher. Dann führe ich eine Eigenschaft is_manipulator ein und spezialisiere dann das Einlesen.

    template< typename T >
    struct is_manipulator : std::integral_constant< bool, false > // erfordert #include <type_traits>
    {};
    

    Jetzt biete ich im namespace detail eine zweite Funktion an, die das Einlesen per Manipulator realisiert. Gleichzeitig brauche ich noch eine Meta-Funktion, die aus dem Typ 'T' den einzulesenden Typ 'V' bestimmt. Wenn T kein Manipulator ist, sind T und V natürlich identisch.

    namespace detail
    {
        template< typename T, typename V >
        typename std::enable_if< !is_manipulator< T >::value, bool >::type read( std::istream& in, V& value )
        {
            return !(in >> value).fail();
        }
        template< typename T, typename V >
        typename std::enable_if< is_manipulator< T >::value, bool >::type read( std::istream& in, V& value )
        {
            return !(in >> T( value )).fail();
        }
    
        // --  Meta-Funktion value_to_read< T >::type bestimmt den einzulesenden Typ
        template< typename T, typename Enabler = void >
        struct value_to_read;
        template< typename T >
        struct value_to_read< T, typename std::enable_if< !is_manipulator< T >::value >::type >
        {
            typedef T type;
        };
        template< typename T >
        struct value_to_read< T, typename std::enable_if< is_manipulator< T >::value >::type >
        {
            typedef typename T::value_type type;
        };
    }
    

    Jetzt kann man einem konkreten Manipulator die Eigenschaft 'is_manipulator' geben und den value_type natürlich. Hier am Beispiel eines column_readers für std::string

    template< char D >
    struct column_reader
    {
        typedef std::string value_type;
        explicit column_reader( std::string& txt ) : m_txt( txt ) {}
        friend std::istream& operator>>( std::istream& in, column_reader r )
        {
            return getline( in, r.m_txt, D );
        }
        std::string& m_txt;
    };
    template< char D >
    struct is_manipulator< column_reader< D > > : std::integral_constant< bool, true >
    {};
    

    Die Template-Funktion read_values und ihr Aufruf ist natürlich noch anzupassen.

    template<typename T>
    std::vector< typename detail::value_to_read< T >::type > readValues( const char* filename, int wspalte )
    {
        using namespace std;
        typedef typename detail::value_to_read< T >::type value_type;
        vector< value_type > values;
        ifstream f(filename);
        // Bem.: Indizierung der Spalten beginnt mit 0
        for( value_type value; f >> skipcolumns<';'>( wspalte-1 ); f >> skipline )
            if( detail::read< T >( f, value ) )
                values.push_back( value );
        return values;
    }
    
    int main()
    {
        using namespace std;
        vector< string > values = readValues< column_reader<';'> >( "input.txt", 3 );
        return 0;
    }
    

    Das sieht vielleicht in diesem Beispiel völlig overengineered aus. Wir benutzen eine ähnlich Konstruktion aber tatsächlich in produktivem Code, wobei aber 'read_values<>' eine komplette Lib zum Lesen von Typen aller Art aus Konfigurationsdateien ist.

    Gruß
    Werner



  • Danke dir, Werner. 🙂



  • SeppJ schrieb:

    Spätestens in Zeile 17 greifst du auf nicht existierende Indizes zu. Außerdem sind noch ein paar andere Fehler drin, die ich unten in meinem Code stillschweigend berichtige.

    Eine recht einfache Version für die Werner mich bestimmt haut (und Pi zufrieden grinst) wäre (ungetestet, sondern eben schnell hier m Forum programmiert):

    vector<double> readValues(const char* filename,int wspalte)
    {
     ifstream in(filename);
     
     vector<double> ergebnis;
     for(string zeile; getline(in, zeile);)
      {
        stringstream zeilenparser(zeile);
        for (int i = 0; i < wspalte; ++i)  // Die erste Spalte hat in dieser Zählweise den Index 0
         zeilenparser.ignore(zeile.length(), ';');
        double wert;
        zeilenparser >> wert;
        ergebnis.push_back(wert);
      }
     return ergebnis;
    }
    

    Wenn du mehrere Spalten nacheinander liest, solltest du auch unbedingt einen Stream übergeben, anstatt immer wieder die Datei neu zu öffnen und schließen.

    Das kann doch gar nicht funktionieren, es wird nachdem ";" einfach alles in wert geschrieben und nicht "nur" die Spalte...Gibt es noch eine andere Möglichkeit, oder muss ich halt nach einem ; suchen und alles dahinter löschen, damit in Wert nur noch die Spalte steht.

    @Werner: Vielen Dank für das ausführlichen Code, jedoch verstehe ich leider gar nicht was Du da machst. Auch ist mir nicht ganz klar wie ich das "D" nun durch ein Semikolon wie bei mir der Fall ersetzen kann.



  • Javun schrieb:

    Das kann doch gar nicht funktionieren, es wird nachdem ";" einfach alles in wert geschrieben und nicht "nur" die Spalte...

    In einem double kann kein ';' vorkommen. Deshalb wird auch nicht zu viel in wert eingelesen, weil das Einlesen stoppt, sobald ein Zeichen kommt, das nicht zu einer double-Repräsentation passt.



  • Hmm...Leider funktioniert die Funktion aber nicht.

    Wo liegt der Fehler:

    vector<double> readCSV(const char* filename,int wspalte)
    {
        ifstream in(filename);
        vector<double> ergebnis;
        string zeile;
        while (getline(in, zeile)){
                   stringstream zeilenparser(zeile);
                   for (int i = 0; i < wspalte; ++i){
                       zeilenparser.ignore(zeile.length(), ';');
                       }
                   double wert = 0;
                   if (zeilenparser >> wert){
                      ergebnis.push_back(wert);
                      }
                   }
        return ergebnis;
    }
    


  • Javun schrieb:

    Hmm...Leider funktioniert die Funktion aber nicht.

    Hallo Javun,

    'funktioniert nicht' ist nun mal keine ausreichende Fehlerbeschreibung. Der Code oben lässt sich übersetzen und enthält keinen offensichtlichen Fehler.
    Also muss der "Fehler" an Deinen Erwartungen an diesen Code liegen - die kennt aber außer Dir niemand.

    - wie sieht die CSV-Datei aus ? - bitte poste mal 2-3 Zeilen davon
    - welche Spalte möchtest Du lesen?
    - welche Werte sollen anschließend in dem vector stehen?

    .. versuche mal, so richtig konkret zu antworten 😉

    Gruß
    Werner

    PS.: haben die Zahlen in der CSV-Datei als Dezimaltrenner vielleicht ein Komma? 😃



  • Werner Salomon schrieb:

    Javun schrieb:

    Hmm...Leider funktioniert die Funktion aber nicht.

    Hallo Javun,

    'funktioniert nicht' ist nun mal keine ausreichende Fehlerbeschreibung. Der Code oben lässt sich übersetzen und enthält keinen offensichtlichen Fehler.
    Also muss der "Fehler" an Deinen Erwartungen an diesen Code liegen - die kennt aber außer Dir niemand.

    - wie sieht die CSV-Datei aus ? - bitte poste mal 2-3 Zeilen davon
    - welche Spalte möchtest Du lesen?
    - welche Werte sollen anschließend in dem vector stehen?

    .. versuche mal, so richtig konkret zu antworten 😉

    Gruß
    Werner

    PS.: haben die Zahlen in der CSV-Datei als Dezimaltrenner vielleicht ein Komma? 😃

    Hier mir als Beispiel einige Zeilen aus der .csv-Datei:

    1;0,1;0,0000001;0,00069141;0,00000001;0,00000009;170;219827982,5;1,52633E-08;5,32351E-10
    2;0,2;0,0000001;0,000719846;0,00000002;0,00000008;170;422288361,7;2,11549E-08;2,77123E-10
    3;0,3;0,0000001;0,000736571;0,00000003;0,00000007;170;619049561,4;2,56135E-08;1,89041E-10
    4;0,4;0,0000001;0,000749848;0,00000004;0,00000006;170;810784289,5;2,93129E-08;1,44336E-10
    5;0,5;0,0000001;0,000765191;0,00000005;0,00000005;170;993158843,2;3,24426E-08;1,17832E-10
    6;0,6;0,0000001;0,000786943;0,00000006;0,00000004;170;1158849086;3,50445E-08;1,00984E-10
    7;0,7;0,0000001;0,000818453;0,00000007;0,00000003;170;1299939550;3,71166E-08;9,0024E-11
    8;0,8;0,0000001;0,000861895;0,00000008;0,00000002;170;1410763624;3,86664E-08;8,2952E-11
    9;0,9;0,0000001;0,0009183;0,00000009;0,00000001;170;1489624629;3,97324E-08;7,85605E-11
    

    Ich erwarte z.B. bei wzeile = 10 den Wert 170 zurück. Warum genau das nicht funktioniert konnte ich bisher leider nicht herausfinden.

    Edit: Jetzt bin ich auch drauf gekommen nach deinem Tipp 🙂 Scheint wirklich am Komma zu liegen...



  • Javun schrieb:

    Ich erwarte z.B. bei wzeile = 10 den Wert 170 zurück.

    Meinst du wspalte = 10? Aber es gibt doch gar nicht 11 Spalten. "170" steht in Spalte 6, wenn man von 0 aus durchzählt.


Anmelden zum Antworten