Einlesen von daten file



  • Hallo Community,

    ich möchte gerne ein .txt file einlesen, welcher aus 30000 Zeilen mit jeweils 16 Gleitkommazahlen besteht, die jeweils mit ; getrennt sind. Dazu habe ich das untere Programm.

    Allerdings möchte ich erst ab Zeile 2 einlesen, da in der ersten Zeile die Bezeichnungen der Spalten stehen. Zusätzlich möchte ich erst ab den jeweils zweiten Einträgen einer jeden Zeile mit dem einlesen starten und ebenfalls die letzte Zahl in jeder Zeile ignorieren. Jetzt stehe ich etwas auf dem Schlauch und hoffe, dass ihr mir da weiter helfen könnt. Danke schon einmal für Eure Hilfe

    #include <fstream>
    #include <iostream>
    #include <sstream>
    #include <string>
    #include <vector>
    #include <algorithm>
    #include <iterator>
    using namespace std;
    
    using record_t = vector <double>;
    using data_t = vector <record_t> ;
    
    //-----------------------------------------------------------------------------
    // Let's overload the stream input operator to read a list of CSV fields (which a CSV record).
    // Remember, a record is a list of doubles separated by commas ','.
    istream& operator >> (istream& ins, record_t& record)
    {
    	// make sure that the returned record contains only the stuff we read now
    	record.clear();
    
    	// read the entire line into a string (a CSV record is terminated by a newline)
    	string line;
    	getline(ins, line);
    
    	// now we'll use a stringstream to separate the fields out of the line
    	stringstream ss(line);
    	string field;
    	while (getline(ss, field, ';'))
    	{
    		// for each field we wish to convert it to a double
    		// (since we require that the CSV contains nothing but floating-point values)
    		stringstream fs(field);
    		double f = 0.0;  // (default value is 0.0)
    		fs >> f;
    
    		// add the newly-converted field to the end of the record
    		record.push_back(f);
    	}
    
    	// Now we have read a single line, converted into a list of fields, converted the fields
    	// from strings to doubles, and stored the results in the argument record, so
    	// we just return the argument stream as required for this kind of input overload function.
    	return ins;
    }
    
    //-----------------------------------------------------------------------------
    // Let's likewise overload the stream input operator to read a list of CSV records.
    // This time it is a little easier, just because we only need to worry about reading
    // records, and not fields.
    istream& operator >> (istream& ins, data_t& data)
    {
    	// make sure that the returned data only contains the CSV data we read here
    	data.clear();
    
    	// For every record we can read from the file, append it to our resulting data
    	record_t record;
    	while (ins >> record)
    	{
    		data.push_back(record);
    	}
    
    	// Again, return the argument stream as required for this kind of input stream overload.
    	return ins;
    }
    
    //-----------------------------------------------------------------------------
    // Now to put it all to use.
    int main()
    {
    	// Here is the data we want.
    	data_t data;
    
    	// Here is the file containing the data. Read it into data.
    	ifstream infile("test.txt");
    	infile >> data;
    
    	// Complain if something went wrong.
    	if (!infile.eof())
    	{
    		cout << "Fooey!\n";
    		return 1;
    	}
    
    	infile.close();
    
    	// Otherwise, list some basic information about the file.
    	cout << "Your CSV file contains " << data.size() << " records.\n";
    
    	unsigned max_record_size = 0;
    	for (unsigned n = 0; n < data.size(); n++)
    		if (max_record_size < data[n].size())
    			max_record_size = data[n].size();
    	cout << "The largest record has " << max_record_size << " fields.\n";
    
    	cout << "The second field in the fourth record contains the value " << data[0][0] << ".\n";
    	return 0;
    }
    


  • hallo,
    was du suchst ist basic_istream::ignore(len, delim) - das ignoriert dir maximal "len" zeichen, aber höchstens bis zum zeichen "delim".

    für das ignorieren des letzten werts: einfach nicht in den vector einfügen.

    istream& operator >> (istream& ins, data_t& data)
    {
        data.clear();
    
        //ignoriere die nächsten zeichen bis zum '\n'    
        ins.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        record_t record;
        while (ins >> record)
        {
            data.push_back(record);
        }
        return ins;
    }
    
    istream& operator >> (istream& ins, record_t& record)
    {
        record.clear();
    
        string line;
        getline(ins, line);
    
        istringstream ss(line); //istringstream reicht
        //ignoriere den ersten wert (bis zum ersten ';')
        ss.ignore(std::numeric_limits<std::streamsize>::max(), ';');
    
        while (ss) {
            double f = 0.0; char tmp;
            ss >> f >> tmp;
            if (ss) record.push_back(f); //ignoriert den letzten wert
        }
        return ins;
    }
    

    eventuell kannst du dir das aber alles ersparen und findest einen fertigen, konfigurierbaren und performanten csv-reader?



  • statt basic_istream::ignore ist es auch durchaus üblich (aber imo nicht so schön explizit), getline zu verwenden und den wert einfach zu verwerfen.



  • Dankeschön für deine Hilfe 🙂

    Mit verwerfen habe ich mir auch schon überlegt, beispielsweise mit .erase oder?

    wo finde ich denn so einen schlanken und schnellen csv reader? Im netz wird man ja mit Optionen diesbzüglich erschlagen.

    Noch eine Frage: ist vector eine gute Option zum Speichern solcher Datenfiles?
    Insbesondere, wenn ich am Anfang und am Ende jedes Untervektors Elemente entfernen bzw. modifizieren möchte?



  • Hallo,

    mit verwerfen (einer zeile) meinte ich

    string ignore;
    getline(istream, ignore);
    

    zum löschen einzelner elementen aus einem vector gibt es zwar pop_back, allerdings kein pop_front (weil das sehr ineffizient wäre; pop_back ist kein performance-problem)

    vector ist im prinzip nichts anderes als ein verwaltetes und einfach zu bedienendes dynamisches array. wenn du nicht viele elemente hinten einfügst oder wie wild elemente aus der mitte/vom beginn des vector löschst und du erst zur laufzeit weißt, für wieviele elemente du platz brauchst, ist vector schon die richtige wahl. wenn du allerdings genau weißt, wieviele elemente du (jemals) einlesen wirst, dann ist ein array mit fixer größe am stack sicherlich schneller. wenn du viele insert/delete operationen hast, dann vmtl. std::list oder auch std::deque. je nachdem, was du brauchst... dafür ist vector sehr schnell im random access (im vgl. zur list). am besten ist es, die werte, die du nicht brauchst, erst gar nicht in den vector zu kopieren.

    fertige CSV-libraries kenne ich nicht/habe ich (in C++) noch nie benötigt, aber ich würde da wohl zuerstmal schauen, ob boost so etwas in der art hat.



  • Vielen Dank dove für deine nützliche und ausführliche Hilfe!

    Die Daten die ich hier einlese, muss ich im weiteren Verlauf nur auslesen bzw. in Algorithmen verwenden. An den Werten im Container ändere ich also nichts.

    Soll ich dann also lieber std::array<double> verwenden?

    Da muss ich ja bei der Deklaration schon die Größe festlegen, die aber ja erst beim Einlesen des Files bekannt wird, oder?

    Wie funktioniert denn genau

    while (ss) { 
            double f = 0.0; char tmp; 
            ss >> f >> tmp; 
            if (ss) record.push_back(f); //ignoriert den letzten wert 
        }
    

    der stream ss beinhaltet eine Zeile mit simikolons und die while wird solange ausgeführt, bis der stream am Ende der Zeile angekommen ist. Dann wird jeweils die Zahl; in float geschoben und das rechtsstehende ; weiter in tmp, richtig?

    aber wann gibt dann die if(ss) true aus? und wo und wie wird die letzte Ziffer der Zeilen fallengelassen?

    Kannst du mir das bitte erklären?

    Danke schonmal



  • klar.

    das funktioniert so: ein istream (egal, ob ifstream, istringstream, ...) hat intern ein paar flags, die seinen aktuellen zustand wiedergeben: eof, fail, bad, und good - die kann man mit den gleichnamigen funktionen auch explizit abfragen. gleichzeitig überlädt der istream operator! und operator bool - so dass man ihn einfach in abfragen verwenden kann (wie du es ja selbst auch machst)

    operator bool() liefert einfach zurück: !istream::fail()

    while (in >> var)
    //heißt soviel wie
    while (operator>>(in, var).operator bool())
    //heißt soviel wie
    while (!operator>>(in, var).fail())
    

    fail() liefert dann true, wenn das interne failbit oder das interne badbit (die unterscheidung muss dich jetzt nicht unbedingt interessieren) gesetzt sind; das passiert u.a. dann, wenn ein einleseversuch nicht funktioniert hat, etwa weil das ende des streams erreicht wurde. in diesem fall wird zusätzlich noch das eofbit gesetzt.

    while (!operator>>(in, var).fail())
    //heißt so viel wie
    while (true) {
      string var;
      in >> var; //kann funktionieren, muss aber nicht - diverse gründe
      if (in.bad() || in.fail()) break;
      //...
    }
    //jetzt kann (sollte) man noch testen:
    if (in.fail() && in.eof()) //alles ok: einlesen in var hat nicht funktioniert, weil der stream ans ende gelangt ist.
    

    der code von oben:

    while (!ss.fail()) { 
            double f = 0.0; char tmp; 
            ss >> f >> tmp; //das funktioniert nur, wenn *nach* dem double noch ein char (das ';') kommt. beim letzten wert ist in einer csv-datei *nicht* mehr der fall.
            //der stream kann also den wert nicht einlesen und setzt das failbit - und das eofbit, weil es das ende der zeile war.
    
            if (!ss.fail()) record.push_back(f); //ignoriert den letzten wert,
            //nur, wenn ss>>f>>tmp funktioniert hat, wird dem vector der wert eingefügt. 
            //ss>>f>>tmp *kann* für den letzten wert nicht funktionieren, da kein char (kein tmp) mehr da ist.
    
           //alternativ:
           if (!ss.eof()) record.push_back(f);
           //das macht das vielleicht expliziter, dass der letzte wert ignoriert wird.
        }
    

    (falls es immer noch nicht klar ist, einfach weiter fragen)

    nummer 2:
    da du die größe des arrays erst beim einlesen kennst, musst du ein dynamisches array verwenden. ein std::array könntest du nur dann verwenden, wenn du schon vor dem einlesen - beim kompilieren - weißt, wie groß die datei (immer) sein wird.

    wenn die werte im container immer so bleiben, ist ein vector wahrscheinlich genau das richtige. einmal erzeugt sollte er allerdings nicht mehr weiter verändert werden - achte also einfach darauf, dass du deine vektoren nicht unnötig kopierst [nebenbei: C++ wird irgendwann in zukunft eine klasse namens std::array_view dafür anbieten und die C++ Core Guideline Support Library kennt dafür (und mehr) die klasse span<>] - der code sieht ansonsten recht gut aus. (bisschen viele kommentare für meinen geschmack).

    im übrigen kann ich all diese definitionen auch nicht auswendig, sondern weiß, wo ich nachsehen muss: http://en.cppreference.com/w/cpp/io/basic_ios/operator_bool



  • Ich bin dir sehr dankbar, was für eine ausführliche und hilfreiche Erklärung.

    Tausend Dank!



  • Ich darf nochmal kurz nachfragen:

    Durch dein spezifisches Stream Layout

    ss >> f >> tmp;
    

    legst du also fest, das ein Feld aus einer Gleitkomma Zahl gefolgt von einem Char zu bestehen hat. Bei der letzten Zahl ist das nicht mehr der Fall, weshalb auch die Zahl an sich verworfen wird?

    Weiterhin wird durch die Codezeile festgelegt, dass immer nur ein Feld pro while-Schleifen Aufruf bearbeitet wird, oder?

    Wozu dient denn die if(ss) Abfrage dann noch?



  • s >> f >> tmp;
    

    ist exakt gleichbedeutend mit:

    s >> f;
    s >> tmp;
    

    das heißt, die letzte zahl wird jedenfalls auch in f eingelesen und nicht automatisch verworfen. erst das s>>tmp schlägt fehl. daher die überprüfung. ohne die überprüfung if (ss) würde also das zuletzt eingelesene f auch noch in den vector eingefügt werden.

    kürzer wäre vielleicht

    if (s >> f >> tmp) vector.push_back(f);
    


  • Hab vielen Dank dove



  • Wenn ich jetzt aber diese jeweils letzte Ziffer jeder Zeile in einen eigenen Vector der Länge 30000 abspeichern wolle, muss ich das auch in den operator Überladungs Funktionen machen geschickterweise oder?



  • geschickter weise willst du die datei bzw. jede einzelnen zeile nur einmal einlesen/verarbeiten, ja.

    die beste lösung dafür, wäre wohl statt

    using record_t = vector<double>;
    

    eine eigene klasse zu verwenden:

    struct record_t {
      vector<double> values;
      double first; //willst du vielleicht auch
      double last;
    };
    

    aber wie genau dieser selbstdefinierte typ aussehen sollte, hängt natürlich am meisten davon ab, was genau du damit weiter tun willst.



  • Also die erste Zahl in jeder Zeile ist tatsächlich nur nen Zeilenindex und da später die Zeilen sowieso randomisiert werden, kann ich die verfallen lassen.
    Alle weiteren Zahlen in den 30k Zeilen will ich wie bereits geschehen in nem 2 dimensionalen vector abspeichern. Nur zusätzlich möchte ich jetzt noch die letzten Zahlen einer jeden Zeile in einer Art Spaltenvektor abspeichern



  • Hab es jetzt folgendermaßen gelöst, gibt es von Eurer Seite aus Verbesserungsvorschläge?

    #include <fstream>
    #include <iostream>
    #include <sstream>
    #include <string>
    #include <vector>
    #include <algorithm>
    using namespace std;
    
    using record = vector <float>;
    
    struct recordStruct {
    	record rec;
    	int lbl;
    };
    
    struct dataStruct {
    	vector <record> dsgnMat;
    	vector<int> labels;
    };
    //-----------------------------------------------------------------------------
    // Let's overload the stream input operator to read a list of CSV fields (which a CSV record).
    // Remember, a record is a list of doubles separated by commas ';'.
    istream& operator >> (istream& ins, recordStruct& recordLine)
    {
    	recordLine.rec.clear();
    	string line;
    	getline(ins, line);
    	istringstream ss(line);
    
    	//ignoriere den ersten wert (bis zum ersten ';') 
    	ss.ignore(std::numeric_limits<std::streamsize>::max(), ';');
    
    	while (ss) {
    		float f{ 0.f };
    		char tmp;
    		if (ss >> f >> tmp) 
    			recordLine.rec.push_back(f); //ignoriert den letzten wert 
    		else 
    			if (f == 1) 
    				recordLine.lbl = 1;
    			else if (f == 2)
    				recordLine.lbl = -1;
    	}
    	return ins;
    }
    
    //-----------------------------------------------------------------------------
    // Let's likewise overload the stream input operator to read a list of CSV records.
    // This time it is a little easier, just because we only need to worry about reading
    // records, and not fields.
    istream& operator >> (istream& ins, dataStruct& data)
    {
    	// make sure that the returned data only contains the CSV data we read here
    	// clear once
    	data.dsgnMat.clear();
    	data.labels.clear();
    	ins.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    	// For every record we can read from the file, append it to our resulting data
    	recordStruct recordLine;
    	while (ins >> recordLine)
    	{
    		data.dsgnMat.push_back(recordLine.rec);
    		data.labels.push_back(recordLine.lbl);
    	}
    
    	// Again, return the argument stream as required for this kind of input stream overload.
    	return ins;
    }
    
    //-----------------------------------------------------------------------------
    // Now to put it all to use.
    int main()
    {
    	// Here is the data we want.
    	dataStruct data;
    
    	// Here is the file containing the data. Read it into data.
    	ifstream infile("RadarDataset.txt");
    	infile >> data;
    
    	// Complain if something went wrong.
    	if (!infile.eof())
    	{
    		cout << "Could not find data file!\n";
    		return 1;
    	}
    
    	infile.close();
    
    }
    


  • hallo,

    ja, ich würde den operator>> für die einzelne zeile unter diesen umständen als eigene funktion implementieren. die ist außerdem ja eh nur dazu da, eine einzelne zeile einzulesen und sollte am ende nicht öffentlich sichtbar sein.

    du verschwendest ja ein bisschen speicherplatz in dem du zwischen record und recordStruct unterscheidest. das ist aber gar nicht nötig.

    using record_t = vector <float>; 
    using label_t = int; //der symmetrie wegen
    
    struct dataStruct { 
        vector <record_t> dsgnMat; 
        vector<label_t> labels; 
    }; 
    
    std::pair<record_t, label_t> read_record(istream& ins) //statt operator>>
    //std::pair, weil die funktion zwei rückgabewerte hat - einen record und ein label
    { 
        string line; 
        getline(ins, line); 
        istringstream ss(line); 
    
        //ignoriere den ersten wert (bis zum ersten ';') 
        ss.ignore(std::numeric_limits<std::streamsize>::max(), ';'); 
    
        record_t recordLine;
    
        while (ss) { 
            float f{ 0.f }; 
            char tmp; 
            if (ss >> f >> tmp) 
                recordLine.push_back(f); //ignoriert den letzten wert 
            else {
                if (f == 1) 
                    return std::make_pair(std::move(recordLine), 1);
                else if (f == 2) 
                    return std::make_pair(std::move(recordLine), -1);
            }
        } 
    
        throw std::runtime_error{"wrong format"}; //evtl auftretende fehler
       //könntest du noch berücksichtigen. mit rückabwicklung und so, ist eine 
       //gute denkaufgabe.
    } 
    
    istream& operator >> (istream& ins, dataStruct& data) 
    { 
        // make sure that the returned data only contains the CSV data we read here 
        // clear once 
        data.dsgnMat.clear(); 
        data.labels.clear(); 
    
        ins.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); 
        // For every record we can read from the file, append it to our resulting data 
        record_t recordLine; 
        label_t label;
    
        for (std::tie(recordLine,label) = read_record(ins); ins;
             std::tie(recordLine, label) = read_record(ins))
        { 
            data.dsgnMat.push_back(std::move(recordLine)); 
            data.labels.push_back(label); 
        } 
    
        // Again, return the argument stream as required for this kind of input stream overload. 
        return ins; 
    }
    


  • Danke vielmals. Wozu dienen move und tie an der Stelle?



  • wenn eine funktion mit mehreren rückgabewerten (std::pair, std::tuple) arbeitet, dann kann man mit std::tie die einzelnen rückgabewerte in einzelne variablen packen.

    ohne tie:

    pair<int, int> some_function () {
      return make_pair(1, 2);
    }
    
    //...
    
    pair<int, int> result = some_function();
    cout << result.first << ", " << result.second; //nicht schön.
    

    mit tie:

    pair<int, int> some_function () {
      return make_pair(1, 2);
    }
    
    int result, status;
    tie(result, status) = some_function();
    cout << result << ", " << status;
    

    (mit C++17 wird das noch üblicher werden)

    move dient dazu, zu verhindern, dass eine kopie angelegt wird. mit move sage ich dem compiler: "mach keine kopie, sondern stiehl dem vector den inhalt, ich verspreche dir, ich rühre den original-vector nicht mehr an" - das was nach einem move von der variable übrig bleibt, ist sozusagen nur mehr eine "leere hülle".

    bei push_back ebenso, weil dort auch sonst eine kopie des originaldatensatzes erstellt würde, obwohl du ja gar keine kopie brauchst.



  • Danke, ich habe durch diesen Thread schon unwahrscheinlich viel gelernt 🙂

    Mh habe deinen Code statt meinem jetzt in mein Projekt eingebunden und jetzt crasht es beim ausführen "Abort() has been called"

    #include <fstream>
    #include <iostream>
    #include <sstream>
    #include <string>
    #include <vector>
    #include <algorithm>
    #include <tuple> //std::pair
    #include <utility> //std::tuple
    using namespace std;
    
    using record_t = vector <float>;
    using label_t = int; //der symmetrie wegen 
    
    struct dataStruct {
    	vector <record_t> dsgnMat;
    	vector<label_t> labels;
    };
    
    std::pair<record_t, label_t> read_record(istream& ins) //statt operator>> 
    													   //std::pair, weil die funktion zwei rückgabewerte hat - einen record und ein label 
    {
    	string line;
    	getline(ins, line);
    	istringstream ss(line);
    
    	//ignoriere den ersten wert (bis zum ersten ';') 
    	ss.ignore(std::numeric_limits<std::streamsize>::max(), ';');
    
    	record_t recordLine;
    
    	while (ss) {
    		float f{ 0.f };
    		char tmp;
    		if (ss >> f >> tmp)
    			recordLine.push_back(f); //ignoriert den letzten wert 
    		else {
    			if (f == 1)
    				return std::make_pair(std::move(recordLine), 1);
    			else if (f == 2)
    				return std::make_pair(std::move(recordLine), -1);
    		}
    	}
    
    	throw std::runtime_error{ "wrong format" }; //evtl auftretende fehler 
    												//könntest du noch berücksichtigen. mit rückabwicklung und so, ist eine 
    												//gute denkaufgabe. 
    }
    
    istream& operator >> (istream& ins, dataStruct& data)
    {
    	// make sure that the returned data only contains the CSV data we read here 
    	// clear once 
    	data.dsgnMat.clear();
    	data.labels.clear();
    
    	ins.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    	// For every record we can read from the file, append it to our resulting data 
    	record_t recordLine;
    	label_t label;
    
    	for (std::tie(recordLine, label) = read_record(ins); ins;
    		std::tie(recordLine, label) = read_record(ins))
    	{
    		data.dsgnMat.push_back(std::move(recordLine));
    		data.labels.push_back(label);
    	}
    
    	// Again, return the argument stream as required for this kind of input stream overload. 
    	return ins;
    }
    
    //-----------------------------------------------------------------------------
    // Now to put it all to use.
    int main()
    {
    	// Here is the data we want.
    	dataStruct data;
    
    	// Here is the file containing the data. Read it into data.
    	ifstream infile("test.txt");
    	infile >> data;
    
    	// Complain if something went wrong.
    	if (!infile.eof())
    	{
    		cout << "Could not find data file!\n";
    		return 1;
    	}
    
    	infile.close();
    
    }
    


  • sorry, war mein fehler - die funktion wirft die exception (die ich selbst ohne nachzudenken eingefügt hab), weil eine zeile zu viel eingelesen wird.

    das liegt am ende daran, dass das eofbit zwar gesetzt ist, aber das keine auswirkung auf "fail()" hat und damit auf die bedingung in der schleife. musste ich jetzt selbst erst testen. die lösung besteht darin, die schleife so zu formulieren:

    do {
      std::tie(recordLine, label) = read_record(ins); 
      data.dsgnMat.push_back(std::move(recordLine)); 
      data.labels.push_back(label); 
    } while (!ins.eof());
    

    immer mit der dokumentation der funktionen arbeiten; bei solchen fehlern den debugger oder eigene debug-funktionen schreiben. dann kommt man schnell drauf, was los ist.


Anmelden zum Antworten