string einlesen + zeileninhalt trennen



  • Werner Salomon schrieb:

    ...

    std::istream& trenner( std::istream& in )
    {
        char t;
        if( in >> t && t != '.' )
            in.setstate( std::ios_base::failbit );
        return in;
    }
    

    ...

    Hmmm - ich wusste erst nicht, ob ich dem if-statement glauben kann, hab's dann aber begriffen: Wenn !(in >> t) , dann wird zwar das setstate() nicht mehr ausgeführt, aber das braucht's auch nicht, weil in dann sowieso schon den richtigen (="falschen" 😉 ) state hat.

    Schönes Idiom, werde ich mir merken (hofffentlich).

    Gruß,

    Simon2.



  • ich finds ja schon sehr beeindruckend, was Werner Salomon so hinbekommt.
    finds nur (für meine verhältnisse) etwas kompliziert und unverständlich 😃

    aber riesen lob und dank 👍



  • End0X schrieb:

    finds nur (für meine verhältnisse) etwas kompliziert und unverständlich 😃

    .. Ihr würdet in solchen Fällen mir - und damit letztlich auch Euch - helfen, wenn man mir genau sagen würde, was genau kompliziert aussieht und unverständlich ist. Klar ist das kein Anfänger Code, aber leider fällt es mir inzwischen schwer, zwischen Dingen die für Anfänger leicht zu verstehen sind und Dingen, die für mich leicht zu verstehen sind, zu unterscheiden. Deshalb bräuchte ich da etwas Support.

    Also: was genau erscheint kompliziert und unverständlich?

    Gruß
    Werner



  • Simon2 schrieb:

    Hmmm - ich wusste erst nicht, ob ich dem if-statement glauben kann, hab's dann aber begriffen: Wenn !(in >> t) , dann wird zwar das setstate() nicht mehr ausgeführt, aber das braucht's auch nicht, weil in dann sowieso schon den richtigen (="falschen" 😉 ) state hat.

    Also ich les' das so: erst wenn 'in >> t' gut gegangen ist - also true ist - erst dann ist das 't' gültig und darf auf Ungleichheit, oder was auch immer, geprüft werden.

    Gruß
    Werner



  • Werner Salomon

    naja, eigentlich kann man ihn schon verstehen, wenn man nachguckt,welche befehle was auslösen. 😉

    aber will nicht meinen ganzen code umschreiben müssen 😃 🙄

    ich hätte jetzt zbsp strstrok benutzt (alles andere "splitten" ging irgendwie nicht) um den teil vor dem trennzeichen in eine und den danach in eine andere variable zu packen.

    while(getline(infile,line))
    	vect.push_back(line);
    
    	char *token;
    
    	token = strtok(vect, ">");
        while( token != NULL )
        {
    	cout << "%s\n" << token;
    	token = strtok(NULL, ">");
        }
    

    so in der art. kommt aber dies 😃

    error C2664: 'strtok': Konvertierung des Parameters 1 von 'std::vector<_Ty>' in 'char *' nicht möglich
    

    ich merke schon, dass man vieles beachten muss,beim coden. sonst entstehen zu viele konflikte. naja, amateurstyle halt :p



  • aber wie ich das danach sortieren soll,wüsste ich ehrlich gesagt auhc nicht 🤡



  • @End0X:
    Überleg dir mal was du Schritt für Schritt machen willst. Was hat das für nen Sinn in ner while Schleife alles in nen vector zu packen und gleichzeitig zu teilen zu versuchen. Und bleib doch bei den Funktionen des std::strings und nimmt nicht wieder so strtok Zeugs.

    @Werner Salomons Code
    😮 Um sowas einfaches wie nen String bei nem Punkt zu teilen brauch ich doch nicht so n aufwendiges Zeug, dass man erst mal ne ganze Weile anschauen muss, um es zu verstehen. Findet ihr wirklich das sowas ne gute C++ Lösung ist?



  • blaurot schrieb:

    ...Findet ihr wirklich das sowas ne gute C++ Lösung ist?

    Japp - weil es einer allgemeingültigen und -bekannten Struktur folgt, anstatt nur eine "Punktsuchfunktion" zu sein:
    Den Datentyp entry kann man nun genauso über jeden Stream (cin/cout, i/ofstream, i/ostringstream oder jeden selbstgeschriebenen) einlesen und ausgeben wie wir das von bisherigen Typen (int, double, string, ...) kennen.

    Die Darstellung ist "dicht am Typen" und "dicht an der Ein/Ausgabe" implementiert: In "seinen" Streamoperatoren....
    OK, trenner() hätte man auch direkt in operator>>() implementieren können, aber das wär's auch schon - und so ist er "allgemeingültig" (sprich: Kann auch für andere Datentypen genutzt werden).

    Ich finde es jedenfalls ein schönes Idiom ... in schönem C++. 😃

    Gruß,

    Simon2.



  • Simon2 schrieb:

    blaurot schrieb:

    ...Findet ihr wirklich das sowas ne gute C++ Lösung ist?

    Japp - weil es einer allgemeingültigen und -bekannten Struktur folgt, anstatt nur eine "Punktsuchfunktion" zu sein:
    Den Datentyp entry kann man nun genauso über jeden Stream (cin/cout, i/ofstream, i/ostringstream oder jeden selbstgeschriebenen) einlesen und ausgeben wie wir das von bisherigen Typen (int, double, string, ...) kennen.

    Die Darstellung ist "dicht am Typen" und "dicht an der Ein/Ausgabe" implementiert: In "seinen" Streamoperatoren....
    OK, trenner() hätte man auch direkt in operator>>() implementieren können, aber das wär's auch schon - und so ist er "allgemeingültig" (sprich: Kann auch für andere Datentypen genutzt werden).

    Ich finde es jedenfalls ein schönes Idiom ... in schönem C++. 😃

    Also wenn du ne Funktion willst, die allgemain verwendbar ist, dann sollte aber das Trennzeichen nicht hart reincodiert sein oder mindestens ne Konstande sein. Und ob das so gut ist, dass sie streams verwendet, weiß ich auch nicht. Wenn du eine UI hast, die dir nur nen string gibt, dann musst du daraus erst wieder nen stream machen. Wieso nicht ne einfache split-Funktion die ein pair zurück gibt und nen string und das trennzeichen übernimmt? Sowas ist doch viel einfacher zu lesen als die nichtssagenden operatoren.



  • blaurot schrieb:

    Also wenn du ne Funktion willst, die allgemain verwendbar ist, dann sollte aber das Trennzeichen nicht hart reincodiert sein oder mindestens ne Konstande sein.

    Ja, das wäre noch eine Steigerung der Wiederverwendbarkeit - die Funktion 'trenner()' aufbohren zu einem Stream-Manipulator.

    Und ob das so gut ist, dass sie streams verwendet, weiß ich auch nicht. Wenn du eine UI hast, die dir nur nen string gibt, dann musst du daraus erst wieder nen stream machen. Wieso nicht ne einfache split-Funktion die ein pair zurück gibt und nen string und das trennzeichen übernimmt? Sowas ist doch viel einfacher zu lesen als die nichtssagenden operatoren.

    Es ist gut, weil es dem Operator egal ist, woher seine Daten kommen (und das Einlesen und Verteilen in einem Arbeitsgang erledigt werden kann). Davon abgesehen kannst du mit diesen Operatoren auch lexical_cast<> anwenden, um deine "einfache split-Funktion" zu bekommen 😉



  • Schau dir einfach mal die main funktion an. Wer kann da den sagen was hier gemacht wird. Ne einfache Schleife mit ner split Funktion wäre ein einfacher und lesbarer Code.

    int main()
    {
        using namespace std;
        std::ifstream quelle( "input.txt" );
        if( !quelle.is_open() )
        {
            std::cerr << "Fehler beim Oeffnen der Datei" << std::endl;
            return -1;
        }
        std::vector< entry > log( (std::istream_iterator< entry >( quelle )), (std::istream_iterator< entry >()) );
        std::sort( log.begin(), log.end() );
        std::copy( log.begin(), log.end(), std::ostream_iterator< entry >( std::cout ) );
        return 0;
    }
    


  • Ich kann's dir sagen - du legst einen vector als Kopie eines Iterator-Bereiches an (die Quelle besteht aus Stream-Iteratoren, also werden die Elemente per op>> aus der Datei gezogen), sotierst ihn und kopierst ihn anschließend in einen anderen Iterator-Bereich (wieder ein Stream-Iterator, der die Daten mit op<< nach cout schreibt). Das klappt für int, double und jeden anderen Typ, der op>> und op<< überladen hat - im Gegensatz zu deinem Ansatz, den du je nach Anforderungen umbauen müsstest.



  • blaurot schrieb:

    ...Wenn du eine UI hast, die dir nur nen string gibt, dann musst du daraus erst wieder nen stream machen....

    und wenn Du ein UI hast, das Dir die Daten in einem File gibt oder über Konsole oder über Netz oder ... müsstest Du mit Deinem Ansatz erst einen String draus machen. Du bist hier doch derjenige, der einen Umweg verlangt (fstream->string->vector, statt fstream -> vector). 😮
    Und spätestens, wenn da jemand einen kontinuierlichen Stream (z.B. weil die Daten byteweise anfallen) oder 7 Terabyte übergeben möchte, kommst Du mit dem string-Ansatz an Probleme...

    Was hier getan werden soll ist, Daten sequentiell durchzugehen und daraus ein Objekt zu erstellen - genau das, wofür ein Stream eben gemacht ist ... und nicht String.
    BTW: Aus string einen stringstream zu machen, ist nun wirklich nicht der Akt. Nicht mehr Arbeit als eine Datei zu öffnen ... (= "Aus einem String einen fstream machen").

    blaurot schrieb:

    Schau dir einfach mal die main funktion an. Wer kann da den sagen was hier gemacht wird. ...

    Mal abgesehen davon, dass ie Ausgabe des Vektors ein wenig "eye-candy" ist: Dann schreib mal Deinen Code daneben, der dieselbe Funktionalität (aus einem File in einen Vektor einlesen inkl. Fehlerbehandlung) bietet...
    Ich wette, dass das weder kürzer noch übersichtlicher ist, sondern unter die Kategorie fällt: Vorher: "Na, das habe ich schon 1000mal so gemacht; das wird schon nicht so schwer sein"; Nachher: "Hmmm, war dan im Detail doch ganz schön lang und umständlich." 😃

    Gruß,

    Simon2.



  • kann mich bitte mal jemand, der sich mit string splitting auskennt und etwas zeit hat im icq anschreiben? 278-126



  • Simon2 schrieb:

    Ich wette, dass das weder kürzer noch übersichtlicher ist, sondern unter die Kategorie fällt: Vorher: "Na, das habe ich schon 1000mal so gemacht; das wird schon nicht so schwer sein"; Nachher: "Hmmm, war dan im Detail doch ganz schön lang und umständlich." 😃

    Ich denke das ist auch derselbe Bereich, aus dem die Frage nach der Lesbarkeit kommt: "Ich habe das schon 1000 mal so gemacht, ich weiss was da passiert" vs. "Die Denkweise ist ungewohnt, das sieht furchtbar kompliziert aus."

    Ich kann bei obigem Abschnitt nämlich mit gutem Gewissen sagen:

    blaurot schrieb:

    Wer kann da den sagen was hier gemacht wird. Ne einfache Schleife mit ner split Funktion wäre ein einfacher und lesbarer Code.

    Ich kann sagen was hier gemacht wird, da ich es schon 1000 mal so gemacht habe (:D). Ne einfache Schleife mit ner Split-Funktion fände ich weniger lesbar.



  • CStoll schrieb:

    Ich kann's dir sagen - du legst einen vector als Kopie eines Iterator-Bereiches an (die Quelle besteht aus Stream-Iteratoren, also werden die Elemente per op>> aus der Datei gezogen), sotierst ihn und kopierst ihn anschließend in einen anderen Iterator-Bereich (wieder ein Stream-Iterator, der die Daten mit op<< nach cout schreibt). Das klappt für int, double und jeden anderen Typ, der op>> und op<< überladen hat - im Gegensatz zu deinem Ansatz, den du je nach Anforderungen umbauen müsstest.

    Hm, und wo sehe ich jetzt, das an einem bestimmten Zeichen getrennt wird? Es ist einfach unklar, man kann nix sehen. Es gibt keinen sprechenden Funktionsnamen. Bei nem einfachen split(line, separator) kann man doch auf den ersten Blick sehen was gemacht wird.

    template<typename A, typename T> const A lexicalCast(const T& source) {
        std::stringstream s;
        s << source;
    
        A destination;
        s >> destination;
    
        return (destination);
    } 
    
    template<typename F> std::pair<F, std::string> split(std::string& line, char sep) {
    	size_t pos = line.find(sep);
    	if(pos != std::string::npos) {
    		std::string num = line.substr(0, pos);
    		return std::make_pair(lexicalCast<F>(num),line.substr(pos + 1));
    	} else {
    		throw std::exception("invalid format");
    	}
    }
    
    int main() {
    	try {
    		std::vector<std::pair<double, std::string> > numLines;
    
    		std::ifstream file( "input.txt" );
    		if( !file.is_open() ) {
    			std::cerr << "Fehler beim Oeffnen der Datei" << std::endl;
    			return -1;
    		}
    
    		std::string line;
    		while(getline(file,line)){
    			numLines.push_back(split<double>(line,'@'));
    		}
    
    		std::sort(numLines.begin(), numLines.end() );
    		for(size_t i = 0; i<numLines.size(); i++) {
    			std::cout<< numLines[i].first << "\t" << numLines[i].second << std::endl;
    		}
    	} catch(std::exception e) {
    		std::cerr << e.what();
    	}
    }
    

    Sogar als template, damit ihr den Typ ändern könnt. Obwohl ich sowas so gut wie nie mache war es ganz einfach, damit es auch mit double geht. Das mit dem lexicalCast gefällt mir zwar nicht, aber ich kenn jetzt auch keine vernünftige Funktion in C++ die ne Exception wirft, wenn der Text garkeine Zahl ist. Aber ist schon total unleserlich so ne while schleife mit Funktionsaufruf.



  • blaurot schrieb:

    CStoll schrieb:

    Ich kann's dir sagen - du legst einen vector als Kopie eines Iterator-Bereiches an (die Quelle besteht aus Stream-Iteratoren, also werden die Elemente per op>> aus der Datei gezogen), sotierst ihn und kopierst ihn anschließend in einen anderen Iterator-Bereich (wieder ein Stream-Iterator, der die Daten mit op<< nach cout schreibt). Das klappt für int, double und jeden anderen Typ, der op>> und op<< überladen hat - im Gegensatz zu deinem Ansatz, den du je nach Anforderungen umbauen müsstest.

    Hm, und wo sehe ich jetzt, das an einem bestimmten Zeichen getrennt wird? Es ist einfach unklar, man kann nix sehen. Es gibt keinen sprechenden Funktionsnamen. Bei nem einfachen split(line, separator) kann man doch auf den ersten Blick sehen was gemacht wird.

    Wie der Datentyp seine Teile auf den Stream verteilt, ist seine Angelegenheit. Und mit den Stream-Operatoren wird genau das angesteuert.

    int main() {
    	try {
    		std::vector<std::pair<double, std::string> > numLines;
    		
    		std::ifstream file( "input.txt" );
    		if( !file.is_open() ) {
    			std::cerr << "Fehler beim Oeffnen der Datei" << std::endl;
    			return -1;
    		}
    
    		std::string line;
    		while(getline(file,line)){
    			numLines.push_back(split<double>(line,'@'));
    		}
    
    		std::sort(numLines.begin(), numLines.end() );
    		for(size_t i = 0; i<numLines.size(); i++) {
    			std::cout<< numLines[i].first << "\t" << numLines[i].second << std::endl;
    		}
    	} catch(std::exception e) {
    		std::cerr << e.what();
    	}
    }
    

    Und schon ist die main-Funktion dreimal so lang - und mußt sich neben ihrer eigentlichen Aufgabe (Daten einlesen und sortieren) auch noch darum kümmern, die eingelesenen Daten zu interpretieren.

    Sogar als template, damit ihr den Typ ändern könnt. Obwohl ich sowas so gut wie nie mache war es ganz einfach, damit es auch mit double geht. Das mit dem lexicalCast gefällt mir zwar nicht, aber ich kenn jetzt auch keine vernünftige Funktion in C++ die ne Exception wirft, wenn der Text garkeine Zahl ist. Aber ist schon total unleserlich so ne while schleife mit Funktionsaufruf.

    Der Datentyp ist eine Einheit aus Zahl und String, den auseinanderzunehmen macht keinen Sinn - und die Funktion split() ist beschränkt auf Eingabesequenzen "Zahl (beliebiger Typ dank Template) - Trennzeichen - Zeichenkette", ist zwar ein Schritt in die richtige Richtung, aber letztendlich will man irgendwelche Daten einlesen - und dazu gehst du nicht weit genug.

    (PS: stringstreams werfen per Default übrigens keine Exceptions ;))



  • blaurot schrieb:

    ...
    Hm, und wo sehe ich jetzt, das an einem bestimmten Zeichen getrennt wird? ..

    Gar nicht - und das ist gut so !
    Wo siehst Du denn an einem int, welcher Wert in welchem Byte seiner Speicherdarstellung steht ?
    Darum geht es hier auch: Ein Datentyp (entry) wird "de-/serialisiert" (nach Java-Nomanklatur). Wenn Du wirklich das Trennzeichen nach außen sichtbar haben möchtest, kannst Du trenner als Funktor mit entsprechendem CTor-Parameter implementieren.....

    Zu Deinem Source:
    - Was machst Du, wenn in der Zeile mehr als 2 Werte stehen und Du mit einem pair nicht mehr hinkommst ? Baust Du Dir dann einen "everything-Container" ? Oder gibst doch gleich ein entry zurück (was aber Deiner Generalität/Stringbezogenheit deutlichen Abbruch tut) ? ...
    - Sorry, aber ich finde "copy()", "sort()" und einen vector-Ctor deutlich sprechender als allgemeine while-Schleifen, deren Körper ich erstmal auf Effekt und Abbruchbedingung durchschauen muss.

    Gruß,

    Simon2.



  • CStoll schrieb:

    Und schon ist die main-Funktion dreimal so lang - und mußt sich neben ihrer eigentlichen Aufgabe (Daten einlesen und sortieren) auch noch darum kümmern, die eingelesenen Daten zu interpretieren.

    Ja, weil ich ein try catch rum gemacht und leerzeilen eingefügt hab. eigentlich sind nur 2 schleifen dazu gekommen. Wenn du willst, kannst du zeile 5 bis 14 in eine readFileData Funtion packen.

    Sogar als template, damit ihr den Typ ändern könnt. Obwohl ich sowas so gut wie nie mache war es ganz einfach, damit es auch mit double geht. Das mit dem lexicalCast gefällt mir zwar nicht, aber ich kenn jetzt auch keine vernünftige Funktion in C++ die ne Exception wirft, wenn der Text garkeine Zahl ist. Aber ist schon total unleserlich so ne while schleife mit Funktionsaufruf.

    Der Datentyp ist eine Einheit aus Zahl und String, den auseinanderzunehmen macht keinen Sinn - und die Funktion split() ist beschränkt auf Eingabesequenzen "Zahl (beliebiger Typ dank Template) - Trennzeichen - Zeichenkette", ist zwar ein Schritt in die richtige Richtung, aber letztendlich will man irgendwelche Daten einlesen - und dazu gehst du nicht weit genug.

    Hat mir keiner gesagt, dass die neue Aufgabe jetzt ist beliebige Strukturen einzulesen. Aber man kann die Aufgabe ja einfach erweitern, damit sie zur überdimensionierten Lösung passt.



  • blaurot schrieb:

    ...Hat mir keiner gesagt, dass die neue Aufgabe jetzt ist beliebige Strukturen einzulesen....

    Steht da aber:

    End0X schrieb:

    ...der zeileninhalt voneinander getrennt werden und in variablen abgelegt...

    Es mag sein, dass End0X nicht explizit gesagt hat (bzw. ihm nicht aufgefallen), dass damit seine "Zeile" zu einem neuen Datentypen mutiert, aber das ändert nichts daran, dass er genau das verlangt hat.

    Gruß,

    Simon2.


Anmelden zum Antworten