Einlesen von daten file
-
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.
-
Also bei mir besteht das Problem weiterhin und beim Ausführen stürzt es ab
-
mm? bei mir auch. jetzt.
liegt wohl daran, dass ich hier bei mir zum testen eine etwas andere version hatte und nicht aufgepasst, was ich wo noch geändert hatte...hab jetzt den code etwas aufgeräumt und umstrukturiert:
using record_t = vector <float>; using label_t = int; struct dataStruct { vector <record_t> dsgnMat; vector<label_t> labels; }; std::pair<record_t, label_t> record_from(string const& line) //hier jetzt ohne den ursprünglichen stream zu übergeben. //zuviele streams machen mir den kopf ganz wirr. { istringstream ss(line); //ignoriere den ersten wert (bis zum ersten ';') ss.ignore(std::numeric_limits<std::streamsize>::max(), ';'); record_t recordLine; float f{ 0.f }; char tmp; while (ss >> f >> tmp) { recordLine.push_back(f); //ignoriert den letzten wert } 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::invalid_argument{"wrong line format given to record_from" }; } istream& operator >> (istream& ins, dataStruct& data) { data.dsgnMat.clear(); data.labels.clear(); ins.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); //so, besser einfach als kompliziert: string line; while (getline(ins, line)) { record_t recordLine; label_t label; std::tie(recordLine, label) = record_from(line); data.dsgnMat.push_back(std::move(recordLine)); data.labels.push_back(label); } 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"); try { //jetzt mit ausnahmebehandlung. infile >> data; } catch (...) { for (int i = 0; i < 100; ++i) cout << "ich hätte exceptions nicht ansprechen sollen."; return -1; } // Complain if something went wrong. if (!infile) //nicht eof! - eof steht für "end of file" { cout << "something went wrong!\n"; return 1; } //zum testen: for (auto const& row: data.dsgnMat) { for (auto&& value: row) { cout << value << "; "; } cout << endl; } //das close am ende ist unnötig, die arbeit übernimmt der destruktor von infile }
-
Hallo Sewing,
du solltest aber dann schnellstens lernen mit dem Debugger zu arbeiten, um solche Fehler zu analysieren und dann beheben zu können. Welche IDE benutzt du denn?
-
Visual Studio Community. Ja das stimmt, ich habe da Nachholbedarf und werde mir das schnellstmöglich aneignen. Leider funktioniert der obige Code immer noch nicht

-
Unter Debugging with Visual Studio 2005/2008, Part 1: Debugging Concepts ff. (besonders Part 3) kriegst du einen guten Überblick über die Möglichkeiten.
Kurzhinweis:
Breakpoint setzen (Links-Klick auf linken Zeilenrand oder F9)
Programm im Debugmodus starten
Warten bis Breakpoint erreicht wird
Im Einzelschritt (F10) die Zeilen ausführen (dann kannst du den Ablauf verfolgen und siehst im Watch-Fenster die aktuellen Variableninhalte)