Problem: Werte in eine map einlesen
-
LadyM schrieb:
Jetzt muss ich für die map allerdings <string, string> verwenden, da sonst das substr nicht funktioniert.
Du musst ja den string auch noch in einen double umwandeln

// Dies ist eine Zeile aus deinem Listing data[zeile.substr(0, found-1)] = zeile.substr(begin,length); // Die kannst du umwandeln in data[zeile.substr(0, found-1)] = makeDouble( zeile.substr(begin,length) ); // Und du musst du dir dann halt noch eine entsprechende Funktion basteln (irgendwo drüber): double makeDouble( const string& value ) { //... }LadyM schrieb:
Jedoch sollte die map je nachdem als <string, int> oder <string, double> oder <string, string> eingesetzt werden, also mit einem Template.
Wie muss ich denn das einbauen? Irgendwie komm ich da gerade nicht weiter...
Sollst du oder willst du?
Ich würde nämlich glatt sagen, das macht keinen Sinn, aber wenn du es machen sollst, bleibt dir ja nix anderes übrig..
- Die Funktion "read" zur Templatefunktion machen
- An entsprechenden Stellen "string" durch den Template-Parameter austauschen
- Die "makeDouble"-Funktion irgendwie umbenennen und auch als Template-Funktion machen, mit stringstream lassen sich die Werte dann wunderbar in ein eigenes Format umwandeln.
-
Richtig, ich will nicht, ich soll

Brauch ich da unbedingt zusätzlich diese makeDouble-Funktion oder kann ich das nicht irgendwie alles in die read-Funktion schreiben?
Ich hab das mal so eingebaut, nur weiß ich nicht, wie ich das Template da jetzt genau einbauen muss...?
bool read() { map<string, T> data; // ??? string::size_type found = string::npos; ifstream IN ("test.txt"); if (IN.is_open()) { string zeile; while (getline(IN, zeile)) { found = zeile.find_first_of("|"); size_t begin = zeile.find_first_not_of(' ', found + 1); size_t end = zeile.find_first_of('|', found + 1); size_t length = end - begin; string result = zeile.substr(begin,length); stringstream sstr; T value; // ??? sstr << result; sstr >> value; data[zeile.substr(0, found-1)] = value; } IN.close(); return true; } else { cout<<"ERROR\n"; return false; } }
-
Perfekt

Jetzt noch ein "template<class T>" vor die Funktionsdeklaration "bool read()" und mit "read<double>()" oder "read<int>()" aufrufen
-
OK, danke!
Jetzt aber noch ne letzte Frage... (hoff ich zumindest)
Diese read-Funktion steht ja in einer extra-Datei.
Wenn ich jetzt in der main-Funktion folgenden Aufruf habe, erkennt er natürlich "data" nicht:int main() { Test test(); test.read<double>(); double abc = data["ABC"]; return 0; }Bevor ich das Template eingefügt hatte, hab ich die map halt in die Klasse geschrieben und dann mit test.data["ABC"] aufgerufen. Aber das funktioniert jetzt nicht mehr...
-
Die Funktion könnte die map zurückgeben, oder du übergibst eine Referenz auf eine (leere) map als Parameter.
-
Es ist doch relativ sinnlos einen string aus einer Datei zu lesen, ihn anschließend wieder in einen stringstream zu schieben um dann einen anderen Typ zu lesen.
Besser gleich den Typ aus der Datei zu lesen.Eine read-Funktion, die eine map< string, T > aus einem beliebigen Input-Stream liest, könnte so aussehen:
template< typename T > std::istream& read( std::istream& in, std::map< std::string, T >& m ) { using namespace std; char pipe; for( std::pair< string, T > x ; (in >> x.first >> pipe >> x.second).ignore( numeric_limits< streamsize >::max(), '\n' ); ) { if( pipe != '|' ) { in.setstate( ios_base::failbit ); return in; } if( !m.insert( x ).second ) cerr << "Warnung: doppelter Eintrag - " << x.first << endl; } }Das Format pro Eintrag ist "Key-Wort | T Kommentar <Zeilenende>"
Key-Wort darf keine Space-Zeichen enthalten und zwischen dem Wort und dem Pipe-Zeichen muss immer mindestens ein Space stehen.Deine vollständige read-Template-funktion oder Methode (?) wäre dann
template< typename T > bool read( std::map< std::string, T >& m ) { ifstream IN ("test.txt"); if (IN.is_open()) { read( IN, m ); return true; } cout<<"ERROR\n"; return false; }d.h. beim Aufruf übergist Du einfach die map Deiner Wahl, dann erkennt der Compiler auch, was er zu tun hat.
LadyM schrieb:
int main() { Test test(); test.read<double>(); double abc = data["ABC"]; return 0; }Ich meine es muss
Test test;heißen, sonst hält der Compiler dies für einen Funktions-Prototypen.
Gruß
Werner
-
Hallo,
erstmal danke für Eure Hilfe.
Ich hab das inzwischen mit dem Code von Werner probiert und es funktioniert eigentlich auch, es sei denn, die Datei sieht so aus:ABC | 1.23 | Kommentar 1 DEF | Hier_steht_ein_Text | Kommentar 2 GHI | 4.56 | Kommentar 3Jetzt kann ich zwar auf die 1.23 noch zugreifen, aber für GHI wird mir 0 und nicht 4.56 ausgegeben.
Könnt Ihr mir vielleicht sagen, wie ich das noch lösen kann?
-
LadyM schrieb:
... es funktioniert eigentlich auch, es sei denn, die Datei sieht so aus:
ABC | 1.23 | Kommentar 1 DEF | Hier_steht_ein_Text | Kommentar 2 GHI | 4.56 | Kommentar 3Jetzt kann ich zwar auf die 1.23 noch zugreifen, aber für GHI wird mir 0 und nicht 4.56 ausgegeben.
Ich unterstelle mal, dass dies genau dann passiert, wenn die map test::data (?) den Typ std::map< std::string, double > hat. Dann läuft die Funktion ab der Stelle wo statt einer Zahl 'Hier_steht_ein_Text' steht auf einen Fehler und danach wird gar nichts mehr eingelesen.
Wenn man jetzt die map mit dem operator[] nach "GHI" fragt ..double was_ist = test::data["GHI"];.. dann existierte vorher kein Eintrag für "GHI", und daher wird Dir hier der Default-Wert für ein double - und das ist die 0. - zurückgeliefert.
Ich rate mal, dass Du die Datei nicht verändern willst/kannst und dass Du letztlich nur die Einträge benötigst, welche auch wirklich Zahlen enthalten. - mal unter uns, das ist aber ein komisches Format!
Ok - eine Lösung wäre ein Manipulator, der die Unterscheidung zwischen double und kein double während des Einlesens erledigt. Einzufügen in Zeile 7 in meinem ersten Code-Posting (Funktion read( .., map )) - beim Einlesen von x.second. Details kann ich Dir frühestens morgen sagen - ich habe jetzt keine Zeit.
Kannst Du mir derweil noch sagen, was das für double sind - also was bedeuten sie ? .. und was bedeutet dann das 'Hier_steht_ein_Text' in diesem Zusammenhang?
Gruß
Werner
-
Werner Salomon schrieb:
Ich unterstelle mal, dass dies genau dann passiert, wenn die map test::data (?) den Typ std::map< std::string, double > hat. Dann läuft die Funktion ab der Stelle wo statt einer Zahl 'Hier_steht_ein_Text' steht auf einen Fehler und danach wird gar nichts mehr eingelesen.
Wenn man jetzt die map mit dem operator[] nach "GHI" fragt ..double was_ist = test::data["GHI"];.. dann existierte vorher kein Eintrag für "GHI", und daher wird Dir hier der Default-Wert für ein double - und das ist die 0. - zurückgeliefert.
Genauso ist es!
Werner Salomon schrieb:
Ich rate mal, dass Du die Datei nicht verändern willst/kannst und dass Du letztlich nur die Einträge benötigst, welche auch wirklich Zahlen enthalten. - mal unter uns, das ist aber ein komisches Format!
Richtig, die Datei darf nicht verändert werden und im Normalfall benötigt man nur die Zahlen, höchstens zu Informationszwecken werden irgendwann die Strings eingelesen. Ja, das Format ist etwas gewöhnungsbedürftig, aber es gibt zwei Arten von Dateien und die zweite ist sogar noch schlimmer, mit der komm ich überhaupt nicht klar.

Werner Salomon schrieb:
Ok - eine Lösung wäre ein Manipulator, der die Unterscheidung zwischen double und kein double während des Einlesens erledigt. Einzufügen in Zeile 7 in meinem ersten Code-Posting (Funktion read( .., map )) - beim Einlesen von x.second. Details kann ich Dir frühestens morgen sagen - ich habe jetzt keine Zeit.
Das wär echt super, wenn Du mir da noch ein bißchen weiterhelfen könntest...
Werner Salomon schrieb:
Kannst Du mir derweil noch sagen, was das für double sind - also was bedeuten sie ? .. und was bedeutet dann das 'Hier_steht_ein_Text' in diesem Zusammenhang?
Die Datei war so nur ein Ausschnitt. Komplett sieht die Datei immer folgendermaßen aus (inklusive der Leerzeile):
ABC | PROJEKT_NAME | Kommentar DEF | PROJEKT_TYP | Kommentar GHI | ZUSATZTEXT | JKL | 123.4000000e+00 | Kommentar was die Variable bedeutet MNO | 5.67000000e+03 | Kommentar was die Variable bedeutet PQR | 8.9000000e-01 | Kommentar was die Variable bedeutet STU | VERZEICHNIS/VERZEICHNIS/DATEIPFAD1| Kommentar VWX | VERZEICHNIS/VERZEICHNIS/DATEIPFAD2| KommentarFür jedes Projekt gibt es eine solche Datei, in der die wichtigen Referenzparameter stehen. Die werden ausgelesen und dann mit ihnen weitergerechnet. Die Strings enthalten z.B. den Projektnamen oder einen Dateipfad. Bei den Variablen kann es jetzt z.B. noch vorkommen, dass da ein Integerwert steht.
Ich hoffe, dass Dir diese Informationen weiterhelfen.
-
LadyM schrieb:
Richtig, die Datei darf nicht verändert werden und im Normalfall benötigt man nur die Zahlen, höchstens zu Informationszwecken werden irgendwann die Strings eingelesen.
Ok - dann sollte man vielleicht doch immer alles lesen und beim Typ double eine Sonderbehandlung einbauen.
Die Schwierigkeit besteht auch noch darin, dass das ja mit dem Template auch noch funktionieren soll. Wenn Du die Werte als string liest, soll das folgende Wort sicher korrekt gelesen werden. Das ist nicht einfach, aber es gibt für alles eine Lösung.Zunächst führe ich den schon erwähnten Manipulator ein und rufe ihn in der read-Funktion auf.
template< typename T > T& robust( T& x ) { return x; // default: tue nichts } template< typename T > std::istream& read( std::istream& in, std::map< std::string, T >& m ) { using namespace std; char pipe; for( std::pair< string, T > x ; (in >> x.first >> pipe >> robust( x.second )).ignore( numeric_limits< streamsize >::max(), '\n' ); ) { if( pipe != '|' ) { in.setstate( ios_base::failbit ); return in; } if( !m.insert( x ).second ) cerr << "Warnung: doppelter Eintrag - " << x.first << endl; } return in; }Für den Datentyp double führt man eine Sonderbehandlung ein
struct DblReader { explicit DblReader( double& x ) : m_x( x ) {} friend std::istream& operator>>( std::istream& in, const DblReader& r ) { if( check_on_number( in ) ) return in >> r.m_x; // nur Lesen, wenn auch eine Zahl folgt if( in ) r.m_x = 0.0; // falls kein Lesefehler, double definiert zu 0 setzen return in; } private: double& m_x; }; DblReader robust( double& d ) { return DblReader( d ); }Das hier benutzte 'check_on_number' sieht so aus:
// -- liefert true, falls eine Ziffer oder ein Vorzeichen folgt bool check_on_number( std::istream& in ) { if( (in >> std::ws).good() ) { const char c = in.peek(); const std::ctype< char >& ct = std::use_facet< std::ctype< char > >( in.getloc() ); if( ct.is( std::ctype_base::digit, c ) || c == '-' || c == '+' ) return true; } return false; }das sollte jetzt sowohl bei double als auch beim Typ string funktionieren. Die Einträge der Nicht-Zahlenwerte werden in die map eingetragen, aber auf 0 gesetzt.
Falls in einem Eintrag ein int steht, das heißt eine Zahl ohne Dezimalpunkt, so ist das kein Problem.
Gruß
Werner