[C++] Strings teilen und in Variablen speichern
-
drakon schrieb:
Du benutzt
std::vector
undstd::string
.- Das ist schon mal ein guter Anfang.
(dann mach aber den Funktionsparameter auch gleich zu einem string. Für die Übergabe anstd::ifstream
kannst dustd::string::c_str()
benutzen )Nun du könntest jetzt den vector durchgehen und jede Zeile in einen
std::stringstream
schieben und mit dem<< - Operator
auslesen. (Was allerdings bedingt, dass die einzelnen Werte, also z.B "Name" keine Whitespaces beinhaltet. Das ist aber nicht gerade geschickt, darum würde ich eher empfehlen das Format zu ändern. Sprich ein CSV draus zu machen und dann kannst du den string ganz einfach nach dem delimiter durchsuchen und ihn anhand dessen trennen.D.h. du würdest einfach die Dateiendung von txt zu csv machen?
Ursprünglich war auch mal von csv die Rede, keine Ahnung, warum ich ein txt bekommen habe.Welche Übergabe meinst Du?
Kannst Du genauer auf das mit dem delimiter eingehen?
Wie geht das?Ich danke dir für deine schnelle Antwort
ich sitze hier seit halb 9 und suche, suche und suche. Bis jetzt kam nur das dabei raus.
Ich mache mir schon die ganze Zeit Gedanken, ob ich zu blöd dafür bin oder ob die Aufgabe zu schwer ist für mich als Anfänger
-
suche mit
std::string::find_first_of
den ersten Tab (\t
) in der Zeile und Schneide den Teil vom Anfang des Strings bis zum dem von der obigen Methode zurückgelieferten Wert ab und speicher in einem Extra String...also in etwa so:
std::vector<std::string> split( std::string str, char delimiter ) { std::vector<std::string> ret; size_t pos = str.find_first_of( delimiter ); while ( !str.empty() ) { std::string cur = str.substr( 0, pos ); if ( !cur.empty() ) ret.push_back( cur ); if ( pos == std::string::npos ) break; str = str.substr( pos + 1 ); pos = str.find_first_of( delimiter ); } return ret; }
sollte funktionieren (auch, wenn ich die Schleife ein wenig unglücklich finde ^^)
Aufruf sollte selbsterklärend sein
-
zwutz schrieb:
suche mit
std::string::find_first_of
den ersten Tab (\t
) in der Zeile und Schneide den Teil vom Anfang des Strings bis zum dem von der obigen Methode zurückgelieferten Wert ab und speicher in einem Extra String...also in etwa so:
std::vector<std::string> split( std::string str, char delimiter ) { std::vector<std::string> ret; size_t pos = str.find_first_of( delimiter ); while ( !str.empty() ) { std::string cur = str.substr( 0, pos ); if ( !cur.empty() ) ret.push_back( cur ); if ( pos == std::string::npos ) break; str = str.substr( pos + 1 ); pos = str.find_first_of( delimiter ); } return ret; }
sollte funktionieren (auch, wenn ich die Schleife ein wenig unglücklich finde ^^)
Aufruf sollte selbsterklärend sein
Abgesehen von der etwas eigenwilligen Vorgehensweise
würde ich
if ( !cur.empty() )
weglassen, also auch Leerstrings in den Vector schieben. Auf diese Weise hat jedes Datum immer denselben Index im Vector, und man kann's leichter weiterverarbeiten.Vielleicht sollte man die Anführungszeichen am Anfang und am Ende der Teilstrings noch entfernen?
Stefan.
-
zwutz schrieb:
Na das geht aber einfacher:
std::vector<std::string> split(const std::string& str, char delimiter) { std::istringstream is(str); std::vector<std::string> result; for(std::string cur; std::getline(is, cur, delimiter); cur.push_back(result)); return result; }
-
Ryuzaki schrieb:
upps, dummer Fehler.
std::vector<std::string> split(const std::string& str, char delimiter) { std::istringstream is(str); std::vector<std::string> result; for(std::string cur; std::getline(is, cur, delimiter); result.push_back(cur)); return result; }
-
Ryuzaki schrieb:
zwutz schrieb:
Na das geht aber einfacher:
std::vector<std::string> split(const std::string& str, char delimiter) { std::istringstream is(str); std::vector<std::string> result; for(std::string cur; std::getline(is, cur, delimiter); cur.push_back(result)); return result; }
Aber soll man das wirklich einem "totalen Anfänger" empfehlen? (Hab ich mich bei dem Code von zwutz allerdings auch gefragt.)
Stefan.
-
Ist das jetzt gut so, oder eher nicht
ich peil relativ wenig durch da.
Wo soll ich denn das jetzt einfügen?Und wie und wo soll ich es aufrufen?
Grüße
-
DStefan schrieb:
Aber soll man das wirklich einem "totalen Anfänger" empfehlen? (Hab ich mich bei dem Code von zwutz allerdings auch gefragt.)
meine Version war zumindest das was man zusammenbekommt, wenn man mithilfe einer Funktionsübersicht und etwas Menschenverstand an die Sache rangeht
@Habit: die Funktion trennt einen übergebenen String anhand eines ebenfalls übergebenen Zeichens auf und speichert die einzelnen Bestandteile in einem vector, den sie dir zurückgibt
Du kannst sie auch als Methode innerhalb deiner "Auftraege"-Klasse realisieren
etwas umgearbeitet und kommentiert (da du wohl
using namespace std;
irgendwo stehen hast, spare ich mir den Namespace jetzt)vector<string> split( string str, char delimiter ) { // schonmal den Rückgabewert anlegen, den wir im Verlauf der Funktion füllen vector<string> ret; // Endlosschleife, der eigentliche Schleifenabbruch ist innerhalb der Schleife... eigentlich unschön, aber es funktioniert :) while ( true ) { // size_t ist ein Datentyp, der innerhalb der Standardlib für Größenangaben und Zählvariablen verwendet wird. Ist aber gleichbedeutend zu int // mit find_first_of suchen wir das erste vorkommen des übergebenen Zeichens, hier der delimiter, der in deinem Fall der Tab ist (\t) size_t pos = str.find_first_of( delimiter ); // den aktuellen Wert holen. Vom Anfang der Zeile bis zu dem oben ermittelten Wert. Enthält allerdings noch die Anführungszeichen string cur = str.substr( 0, pos ); // und abspeichern ret.push_back( cur ); // sollten wir oben npos (entspricht -1) herausbekommen haben, // ist das Trennzeichen im String nicht (mehr) enthalten und wir können uns weitere durchläufe sparen if ( pos == string::npos ) break; // die Zeile mit dem restlichen Teil überschreiben. Keine angst, es wird auf einer Kopie der Ursprungszeile gearbeitet str = str.substr( pos + 1 ); } // das Ergebnis zurückgeben return ret; }
wie gesagt geht es deutlich besser, wie das Beispiel von Ryuzaki zeigt. Aber als Anfänger kann man ruhig auch mal die umständliche Methode wählen, wenn es denn funktioniert
wenn du fragen zu einzelnen verwendeten Funktionen hast, kann ich cplusplus.com empfehlen. Da steht alles recht gut beschrieben, oft auch mit Beispielen
Der Aufruf in deinem Beispiel könnte so aussehen:
vector<vector<string> > lines; // ein vector aus vectoren. Erste Ebene ist die Zeile, die zweite Ebene die Werte innerhalb der Zeile string line; while( getline(datei, line) ) { lines.push_back( split( line, '\t' ) ); }
was dir jetzt noch überlassen bleibt, ist die Anführungszeichen zu entfernen
-
Hallo Habit,
willkommen im Cpp-Forum.Habit schrieb:
D.h. du würdest einfach die Dateiendung von txt zu csv machen?
Nein, das reicht nicht. Er meint sicher - wie ich - dass es sich bei dem Textfile um einen Output eines Datenbankprogramms oder von Excel handelt. Diese Programme könnten ihren Inhalt auch z.B. als csv-File ausgeben. Was ist es denn?
Habit schrieb:
Ich mache mir schon die ganze Zeit Gedanken, ob ich zu blöd dafür bin oder ob die Aufgabe zu schwer ist für mich als Anfänger
Die Aufgabe ist für einen Anfänger schon (zu!?) schwer. Einlesen an sich ist schon schwierig .. und dann noch mit diesem "-Format - na ja.
drakon schrieb:
Du benutzt
std::vector
undstd::string
.- Das ist schon mal ein guter Anfang.
.. das mein' ich auch.
drakon schrieb:
Nun du könntest jetzt den vector durchgehen und jede Zeile in einen
std::stringstream
schieben und mit dem<< - Operator
auslesen.Ist doch eigentlich umständlich. Warum dann erst vom File in den stringstream schieben, wenn man es dann doch 'nur liest'.
Im allgemeinen sollte man versuchen, den Inhalt einer Datei gleich in die dafür vorgesehenen Variablen einzulesen. Das heißt man benötigt zunächst eine Variable - also etwa ein Objekt einer Struktur mit folgendem Aussehen:
struct Record // bitte nach eigenem Gutdünken umbenennen { int m_filialNr; int m_auftragsNr; std::string m_auftragsDatum; // ggf. eigener Datum-Typ std::string m_name; std::string m_vorname; std::string m_geburtsdatum; // ggf. eigener Datum-Typ std::string m_strasse; };
Ich unterstelle mal, dass 'Filialnummer' und 'Auftragsnummer' wirklich Zahlen sind, die man als int abspeichern kann.
Das (in meinen Augen) Standard-Vorgehen ist: verpasse der Struktur einen Streaming-operator (erklär' ich gleich) und lese die einzelnen Elemente (hier den Inhalt der Zeilen) ein. Das sähe dann aus Sicht des Aufrufers so aus:
int main() { using namespace std; vector< Record > records; ifstream datei( "input.txt" ); if( !datei.is_open() ) { cerr << "Fehler beim Oeffnen" << endl; return 1; } for( Record r; datei >> r; ) // Lese, bis es nicht mehr geht { records.push_back( r ); // korrekt gelesenen Element 'r' abspeichern } if( datei.eof() ) cout << "Datei bis Ende gelesen - alles ok" << endl; return 0; }
So jetzt fehlt nur(!) noch der Streaming-operator - so sieht der aus:
std::istream& operator>>( std::istream& in, Record& r )
er ermöglicht das Einlesen eines einzelnen Elements der Struktur Rekord wie in Zeile 11 (s.o.)
Wenn die Hochkomma nicht wären und kein String durch eine Leerzeichen unterbrochen wäre, dann könnte die Implementierung dieses Streaming-Operators so aussehen:
std::istream& operator>>( std::istream& in, Record& r ) { return in >> r.m_filialNr >> r.m_auftragsNr >> r.m_auftragsDatum >> r.m_name >> r.m_vorname >> r.m_geburtsdatum >> r.m_strasse; }
was ich auch einem Anfänger zumuten würde. Sieht doch recht straight-forward aus - oder. Aber leider reicht das hier nicht aus!
Die vollständige Lösung sieht aus wie der Code, der jetzt folgt. Nein - es ist definitiv kein Anfänger-Code, aber das String-Auseinander-Fummeln ist es meines Erachtens auch nicht.
#include <fstream> #include <iostream> #include <string> #include <vector> using namespace std; template< char C > // überliest ein vorher festgelegtes Zeichen 'C' std::istream& Char( std::istream& in ) { char c; if( in >> c && c != C ) in.setstate( std::ios_base::failbit ); return in; } template< char C, typename T > // liest eine Variable beliebigen Typs, auf die ein 'Trennzeichen' folgt std::istream& lese_bis_trenner( std::istream& in, T& var ) { return in >> var >> Char<C>; } template< char C > // Spezialisierung für einen std::string, so können auch Texte gelesen werden, die Leerzeichen enthalten std::istream& lese_bis_trenner( std::istream& in, std::string& var ) { return getline( in, var, C ); } template< typename T > struct HkLeser // liest eine Variable vom Typ T in der Form: "<-- hier steht's -->" { explicit HkLeser( T& var ) : m_var( var ) {} friend std::istream& operator>>( std::istream& in, const HkLeser& rdr ) { return lese_bis_trenner<'"'>( in >> Char<'"'>, rdr.m_var ); // Variable inklusive zweitem Hochkomma lesen } private: T& m_var; HkLeser& operator=( const HkLeser& ); // hat keine Funktion! nur um den Compiler zu beruhigen }; // Factory-Funktion, // damit ein User 'T var; in >> hk( var );' statt 'T var; in >> HkLeser< T >( var );' aufrufen kann template< typename T > HkLeser< T > hk( T& var ) { return HkLeser< T >( var ); } struct Record // bitte nach eigenem Gutdünken umbenennen { int m_filialNr; int m_auftragsNr; std::string m_auftragsDatum; // ggf. eigener Datum-Typ std::string m_name; std::string m_vorname; std::string m_geburtsdatum; // ggf. eigener Datum-Typ std::string m_strasse; }; std::istream& operator>>( std::istream& in, Record& r ) { return in >> hk( r.m_filialNr ) >> hk( r.m_auftragsNr ) >> hk( r.m_auftragsDatum ) >> hk( r.m_name ) >> hk( r.m_vorname ) >> hk( r.m_geburtsdatum ) >> hk( r.m_strasse ); } int main() { using namespace std; vector< Record > records; ifstream datei( "input.txt" ); if( !datei.is_open() ) { cerr << "Fehler beim Oeffnen" << endl; return 1; } for( Record r; datei >> r; ) // Lese, bis es nicht mehr geht { records.push_back( r ); // korrekt gelesenes Element 'r' abspeichern } if( datei.eof() ) cout << "Datei bis Ende gelesen - alles ok" << endl; return 0; }
Gruß
Werner
-
Hallo Werner,
ich danke dir auf jedenfall für deine ausführliche Antwort.
Das ist nur schon ein bisschen arg schwierig, ich will das alles ja auch selber verstehen.Ich habe es jetzt wie folgt angefangen zu implementieren:
#include <iostream> #include "Auftraege.h" #include "Auftrag.h" using namespace std; void Auftraege::tabTrennen( istream &input, vector< vector<string> >& output ) { string line; // Zeilenweise einlesen while( getline( input, line ) ) { istringstream stream( line ); vector<string> column; string element; // Trennzeichen Tabulator, jedes Element in Vector einlesen while( getline( stream, element, '\t') ) { column.push_back( element ); } // Zeile an Ergebnis anhängen output.push_back( column ); } } int Auftraege::Einlesen (const char *filename) { // Datei öffnen fstream file(filename, ios::in ); if( !file.is_open()) { cout << "Datei wurde nicht gefunden!" << endl; system ("PAUSE"); return 1; } // Vector für Daten definieren typedef vector< vector<string> > cVector; cVector data; // Datei in Vector einlesen tabTrennen( file, data ); // Vector ausgeben for( cVector::iterator i = data.begin(); i != data.end(); ++i ) { for( vector<string>::iterator j = i->begin(); j != i->end(); ++j ) { cout << *j; } cout << endl; } } int main () { Auftraege *aufVorher = new Auftraege ("data\\vorhermini.txt"); // Auftraege *aufNachher = new Auftraege ("data\\nachher.txt"); // aufVorher->Differenz (*aufNachher); system ("PAUSE"); return 0; }
Ich kann also jetzt den Vektor ohne Tabulatoren ausgeben.
Sieht dann wie folgt aus:"Auftragsnummer""Auftragsdatum""Name""Vorname"....
Als nächste müsste man die Teile zwischen den " " in einzelne Variablen speichern. Wie gehe ich weiter vor?
Wäre cool, wenn noch jemand darauf was weißVielen Dank Leute.