String parsen



  • Ich weiß, dass Werner der Guru in den Dingen ist, aber der macht nicht wenige schlaue Sachen, sondern viele schlaue Sachen... Na ja, ich mache mich Mal auf die Suche.

    Also hab bisher so was:

    std::stringstream ss(std::string("1,2,3"));
    std::string bla;
    double x;
    while(getline(ss, bla, ',') >> x)
        std::cout << x << '\n';
    

    Aber das gibt nur 2 und 3 aus. Ist auch mehr geraten als alles andere. Mein Verständnis ist einfach zu dünn. Das ist aber sowieso Unsinn, weil ich jetzt noch den String bla brauche. Dann könnt ich auch das >> x weglassen und atoi nutzen. 🙄



  • Eine Low-Tech-Lösung wäre

    double x;
    char c;
    
    do {
      if(ss >> x) {
        // x verarbeiten
      } else {
        // Parserfehler
      }
    } while(ss >> c && c == ',');
    

    Ansonsten ist Boost.Spirit dein Freund.



  • Eine Lowtech-Lösung ist das Ersetzen des Zeichens durch ein Leerzeichen.

    std::string str;
    	getline(std::cin, str);
    
    	size_t pos;
    	while((pos = str.find(','))!=std::string::npos)
            str[pos] = ' ';
    
    	std::vector<double> doubles;
    	std::stringstream ss(str);
    
    	double value; 
    	while(ss >> value) 
    		doubles.push_back(value);
    
    	std::copy(doubles.begin(), doubles.end(), std::ostream_iterator<double>(std::cout));
    


  • Danke schon Mal. Denkt ihr nicht, es gibt hier ne elegante Werner-Lösung?



  • Eisflamme schrieb:

    Danke schon Mal. Denkt ihr nicht, es gibt hier ne elegante Werner-Lösung?

    Ich glaube eine gesehen zu haben, aber keine Zeit danach zu suchen. Eventuell hilft dir das Stichwort Tokenizer.



  • Schreib dir halt ne Funktion dafür.

    bool extract_cs_number(std::istream &in, double &val) {
      char c;
      return in >> val && ((in >> c && c == ',') || in.eof());
    }
    
    int main() {
      std::string line = "1,2,3";
      std::istringstream parser(line);
      double d;
    
      while(extract_cs_number(parser, d)) {
        std::cout << d << std::endl;
      }
    }
    

    High-Tech-Lösung:

    #include <boost/spirit/include/qi.hpp>
    
    #include <stdexcept>
    #include <string>
    #include <vector>
    
    std::vector<double> parse_numbers(std::string const &data)
    {
      namespace bs = boost::spirit;
      using bs::qi::double_;
      using bs::qi::phrase_parse;
      using bs::ascii::space;
    
      std::vector<double> result;
      std::string::const_iterator first = data.begin();
    
      bool r = phrase_parse(first, data.end(), double_ % ',', space, result);
      if(!r || first != data.end()) {
        throw std::invalid_argument("Eingabe ungültig");
      }
    
      return result;
    }
    


  • seldon, ok, das ist schon chic. 🙂 Finde low_tech feiner. Ungern schmeiße ich die armen Elefanten auf Erbsen oder wie das Sprichwort ging.



  • Was spricht gegen das einfache Ersetzen des Zeichens? Stört dich die dazu notwendige Schleife?

    In dem Fall bietet auch hier die STL eine nette Funktion:

    std::replace(x.begin(), x.end(), ',', ' ');
    


  • kljkl schrieb:

    In dem Fall bietet auch hier die STL eine nette Funktion:

    std::replace(x.begin(), x.end(), ',', ' ');
    

    ja genau, das war auch mein Gedanke.

    Aber zurück zur ursprünglichen Frage:

    Eisflamme schrieb:

    Aber ich weiß nicht, wie ich >> dazu anweisen kann, zusätzlich zu ' ' auch ',' als Trennzeichen zu erkennen.

    Wenn (also wenn(!)) die STL-Implementierung sich an den Standard hält, so kann man die ctype-Facette austauschen. Diese ist nämlich dafür zuständig, zu beurteilen, ob ein Zeichen ein White Character ist oder eben nicht.
    Folgendes sollte funktionieren:

    #include <iostream>
    #include <locale>
    #include <sstream>
    
    template< char X >
    class ctype_extraspace : public std::ctype< char >
    {
    protected:
        typedef std::ctype< char > base_type;
        virtual bool do_is( mask m, char_type c ) const
        {
            return ((m & std::ctype_base::space) == std::ctype_base::space && c == X)
                || base_type::do_is( m, c );
        }
    };
    
    int main()
    {
        using namespace std;
        istringstream ss("1,2,3\n4,5,6");
        ss.imbue( locale( ss.getloc(), new ctype_extraspace<','> ) );
        for( double d; ss >> d; )
            cout << d << endl;
        return 0;
    }
    

    Unter einer Microsoft-IDE erhält man jedoch den Compile-Fehler:

    Zeile (13) : error C2039: 'do_is': Ist kein Element von 'std::ctype<char>'
    

    die mit VC8 mitgelieferte STL hält sich hier nicht an den Standard. Möge es mal jemand unter gcc++ oder anderen Compilern probieren.

    Stellt sich noch die Frage, warum so kompliziert, wenn's so einfach mit replace geht. Erste Anwort ist, weil i.A. keine dieser beiden Variante eine Lösung ist, da eine CSV-Datei auch Text-Elemete enthalten kann, wo man auf die trennenden ',' angewiesen ist und zweitens bleibt die Gretchenfrage: wie kommt die CSV-Datei in den String? Natürlich indem man die Datei liest, aber dann liegt ja als Schnittstelle wieder ein Stream vor - ergo ist der Umweg über den String unnötig.

    Die IMHO korrekte und praktikable Lösung wäre, die CSV-Datei direkt zu lesen und sich dafür Helferlein zu schnitzen, wie das Char<','> und das is_endl. Letzteres nur falls nötig; das Format der CSV-Datei sollte eigentlich bekannt sein.
    Die Anwendung in voller Schönheit:

    istringstream ss("1,2,3\n4,5,6");
        while( ss )
        {
            bool first = true;
            for( double d; !is_endl( ss ) && (first || ss >> Char<','>) && ss >> d; first = false )
                cout << d << " ";
            cout << endl;
        }
    

    Die Ausgabe ist:

    1 2 3
    4 5 6
    

    .. auch auf die Gefahr hin, dass die Zeile 5 einen bekannten Moderator erschrecken könnte. 😉

    Gruß
    Werner


  • Mod

    Werner Salomon schrieb:

    Unter einer Microsoft-IDE erhält man jedoch den Compile-Fehler:

    Zeile (13) : error C2039: 'do_is': Ist kein Element von 'std::ctype<char>'
    

    die mit VC8 mitgelieferte STL hält sich hier nicht an den Standard. Möge es mal jemand unter gcc++ oder anderen Compilern probieren.

    Der g++ macht da exakt den gleichen Fehler. Bist du 100% sicher, dass man das machen darf? Ich bin mit den Stream-Interna leider nicht so vertraut, um das auf die schnelle Beurteilen zu können.

    .. auch auf die Gefahr hin, dass die Zeile 5 einen bekannten Moderator erschrecken könnte. 😉

    Ich möchte mich mal im Gegensatz zu volkard als Fan dieser Technik outen. Mache das auch öfters so.



  • Werner Salomon schrieb:

    Unter einer Microsoft-IDE erhält man jedoch den Compile-Fehler:

    Zeile (13) : error C2039: 'do_is': Ist kein Element von 'std::ctype<char>'
    

    die mit VC8 mitgelieferte STL hält sich hier nicht an den Standard. Möge es mal jemand unter gcc++ oder anderen Compilern probieren.

    Ich hab' zwar nicht den ANSI-Standard griffbereit, aber mein Referenzwerk zur Standardbibliothek meint dazu:

    Nicolai M. Jossuttis schrieb:

    **Specialization of ctype<> for type char **
    For better performance of the character classification functions, the facet ctype is spzialized for the character type char. This spezialisation does not delegate the functions dealing with charakter classification ( is() , scan_is() , and scan_not ) to corresponding virtual functions. Instead, these functions are implemented inline using table lookup. For this case additional members are provided.

    (unter den genannten Member-Funktionen ist auch eine, die die "klassische" Übersetzungstabelle liefert und ein Konstruktor, der eine Tabelle definiert.



  • CStoll schrieb:

    Ich hab' zwar nicht den ANSI-Standard griffbereit, aber mein Referenzwerk zur Standardbibliothek meint dazu:

    Nicolai M. Jossuttis schrieb:

    **Specialization of ctype<> for type char **
    For better performance of the character classification functions, the facet ctype is spzialized for the character type char. This spezialisation does not delegate the functions dealing with charakter classification ( is() , scan_is() , and scan_not ) to corresponding virtual functions. Instead, these functions are implemented inline using table lookup. For this case additional members are provided.

    (unter den genannten Member-Funktionen ist auch eine, die die "klassische" Übersetzungstabelle liefert und ein Konstruktor, der eine Tabelle definiert.

    Danke CStoll für den Hinweis.
    Ich war mir gestern abend gar nicht sicher, habe dann ein wenig im Standard gesucht, weil ich schon so was in der Art vermutet hatte, aber auf die Schnelle nichts gefunden. Und da die MS-C++-Lib bei den IOStreams eh' immer wieder schwächelt, habe ich das Problem da vermutet. Das Kapitel mit der Spezialisierung von ctype kommt erst ein paar Seiten später und enthält praktisch keine Erklärung 😞 (22.2.1.3 ctype specializations) .. man lernt doch nie aus.

    Gruß
    Werner



  • wie kommt die CSV-Datei in den String? Natürlich indem man die Datei liest, aber dann liegt ja als Schnittstelle wieder ein Stream vor - ergo ist der Umweg über den String unnötig.

    Genau an die Aussage von Dir hatte ich mich erinnert, wieso ich den Thread aufgemacht hab und nicht erst in den String einlesen und dann parsen wollte. 🙂

    Danke! Die letztere Lösung entspricht genau meiner Vorstellung. 🙂

    Die erste klappt ja leider nicht. Aber grundsätzlich: räumen die selbst mit delete den Extra-ctype auf? Wieso nutzt man hier überhaupt den Heap? Fällt die Art der Lösung des Problems dann weg?



  • Eisflamme schrieb:

    Aber grundsätzlich: räumen die selbst mit delete den Extra-ctype auf?

    Ja - jede Facette besitzt einen Referenz-Zähler. Ist dieser =0, wenn die locale gelöscht wird, so löscht die locale auch die Facette. Man kann eine eigene Facette auch auf den Stack anlegen und dann nur den Pointer übergeben, muss dann aber die Facette mit einem Reference Count von 1 initialisieren. Default wäre 0.

    Eisflamme schrieb:

    Wieso nutzt man hier überhaupt den Heap?

    muss man nicht (s.o.), aber so braucht man sich nicht weiter um das Facetten-Objekt zu kümmern.

    Eisflamme schrieb:

    Fällt die Art der Lösung des Problems dann weg?

    na ja - es gibt wohl doch ein Möglichkeit es mit std::ctype<char> zu machen .. aber da muss ich noch ein wenig forschen.

    Gruß
    Werner


Anmelden zum Antworten