wordcount mit string



  • Ich wollte ihm nur std::count zeigen und habe seinen Code darauf angepasst - nicht mehr. Optimal ist es natürlich Wortanfänge zu zählen, also:

    Ist der Charakter alphabetisch?
    ---> Ja
    Ist das Zeichen davor ein Whitespace oder außerhalb des Input Strings?
    ---> Ja => Wortzähler erhöhen.
    ---> Nein => Nichts tun.
    ---> Nein => Nichts tun.

    Was man natürlich noch verbessern könnte um sinnlose Tests zu vermeiden.


  • Mod

    Quisslich schrieb:

    Also vorher den String trimmen wie z.B. hier
    http://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring
    beschrieben?

    Nein, Trimmen geht am Problem vorbei. Das eigentliche Problem ist, dass aus der Definition des Wortes "Wörter sind durch Leerzeichen getrennt" die falschen Schlüsse gezogen wurden, wie man Wörter erkennt. Zählen der Leerzeichen ist der falsche Weg, denn die Anzahl der trennenden Zeichen hat nicht direkt damit zu tun, wie viele getrennte Wörter es gibt. Der Versuch nun den String zu trimmen und tausend Fallunterscheidungen einzubauen, behebt bloß die Symptome des Problems.

    Der bessere Weg wäre, die Trennungen, nicht die Trennzeichen zu zählen. Damit haben wir ebenfalls einen sehr einfachen Algorithmus, der aber richtig zählt:
    https://ideone.com/eL8Mrb



  • Ein Vorschlag:

    #include <cctype>
    #include <iostream>
    #include <string>
    #include <boost/range/adaptors.hpp>
    #include <boost/range/algorithm.hpp>
    
    int word_count(std::string const& s)
    {
       namespace ba = boost::adaptors;
       return boost::count(
         s | ba::transformed(static_cast<int(*)(int)>(std::isspace))
           | ba::uniqued,
         0
       );
    }
    
    int main()
    {
        std::string test = "Hallo  Welt,\n"
                  "\tdu     grausame     Welt,\n"
                  "\thast Tabs und auch Zeilen\n"
                  "\tsowie      Steuerzeichen.";
        std::cout << word_count(test) << '\n';
        return 0;
    }
    

    getestet:

    12
    


  • SeppJ schrieb:

    Der bessere Weg wäre, die Trennungen, nicht die Trennzeichen zu zählen. Damit haben wir ebenfalls einen sehr einfachen Algorithmus, der aber richtig zählt:
    https://ideone.com/eL8Mrb

    Igitt, eine for-Schleife.

    int wordcount_Unreg(string satz)
    {
      struct info { int w; bool l; } init{0,true};
      return std::accumulate(satz.begin(), satz.end(), init,
          [](info i,char c){bool b=isspace(c); return info{i.w+(!b&&i.l),b};}).w;
    }
    


  • Solange nur Leerzeichen auftauchen, ist das schön und gut, aber...

    #include<algorithm>
    #include<cstdlib>
    #include<iostream>
    #include<iterator>
    #include<sstream>
    #include<string>
    
    using namespace std;
    
    int wordcount_TE(string satz)
    {
        int words=1;
        int L=satz.length();
        for(int i=0;i<L;i++)
        {
            if(satz.at(i)==' ')
            {
                words=words+1;
            }
        }
        return words;
    }
    
    int wordcount_Ethon(string satz)
    {
       int words = count(satz.begin(), satz.end(), ' ');
       return words;
    }
    
    int wordcount_SeppJ(string satz)
    {
        int words=0;
        bool last_was_space = true;
        for(char c : satz)
        {
            if(c != ' ')
            {
                if (last_was_space)
                   words += 1;
                 last_was_space = false;
            }
            else
              last_was_space = true;
        }
        return words;
    }
    
    int wordcount_seldon(string satz) {
      istringstream in(satz);
      return distance(istream_iterator<string>(in), istream_iterator<string>());
    }
    
    void vergleich(string satz)
    {
        cout << "Satz ist: \"" << satz << "\"\n"
             << " Phil123 zählt darin "       << wordcount_TE(satz)
             << " Wörter, Ethon zählt "       << wordcount_Ethon(satz)
             << " Wörter, SeppJ zählt "       << wordcount_SeppJ(satz)
             << " Wörter, seldon zählt "      << wordcount_seldon(satz)
             << '\n';
    }
    
    int main()
    {
        vergleich("Hallo Welt");
        vergleich("");
        vergleich(" ");
        vergleich("  ");
        vergleich("Hallo  Welt");
        vergleich("Hallo");
        vergleich(" Hallo");
        vergleich("Hallo ");
        vergleich(" Hallo ");
        vergleich("Hallo  Welt,\n"
                  "\tdu     grausame     Welt,\n"
                  "\thast Tabs und auch Zeilen\n"
                  "\tsowie      Steuerzeichen.");
    }
    
    Satz ist: "Hallo  Welt,
    	du     grausame     Welt,
    	hast Tabs und auch Zeilen
    	sowie      Steuerzeichen."
     Phil123 zählt darin 23 Wörter, Ethon zählt 22 Wörter, SeppJ zählt 9 Wörter, seldon zählt 12
    

    Richtig spannend wird das allerdings erst, wenn auch Satzzeichen, Bindestriche und derlei berücksichtigt werden sollen.


  • Mod

    Die Aufgabenstellung war eindeutig nur mit Leerzeichen. Aber alle Lösungen, außer Ethons, sind flexibel was die Definition der Trennung angeht und können leicht auf ein isspace umgestellt werden.

    Flexibilität ist übrigens auch bei dir eine Schwäche. Die Lösung des TE (nach Korrektur), meine, krümelkackers (nach Korrektur) und die des Unregs sind auch leicht anpassbar, Satzzeichen nicht als Wörter zu zählen. Bei dir müsste das auch gehen, aber dazu wären wohl tiefgehende Fummeleien in der Locale nötig, die hier im Forum vielleicht ein oder zwei Personen beherrschen. Umgekehrt kann deine Lösung sich gar nicht an die eigentliche Definition (nur Leerzeichen) halten, außer wieder durch fortgeschrittene Locale-Manipulation.



  • Da ist was wahres dran. Gut, ich nehme alles zurück.



  • SeppJ schrieb:

    krümelkackers (nach Korrektur)

    Tja, boost::adaptors::transformed (Boost 1.53) mag doch tatsächlich keime Lambdas, da sie nicht sowas wie einen result_type -typedef haben. 😞


  • Mod

    krümelkacker schrieb:

    SeppJ schrieb:

    krümelkackers (nach Korrektur)

    Tja, boost::adaptors::transformed (Boost 1.53) mag doch tatsächlich keime Lambdas, da sie nicht sowas wie einen result_type -typedef haben. 😞

    Da ist übrigens noch ein anderer Fehler drin. Aber den editierst du einfach raus, dann erzähle ich auch niemandem, was dir da peinliches passiert ist 😉 .

    edit: Oh, ich sehe, das hattest du ohnehin schon geändert. Dann hat's sich erledigt.



  • SeppJ schrieb:

    Bei dir müsste das auch gehen, aber dazu wären wohl tiefgehende Fummeleien in der Locale nötig, die hier im Forum vielleicht ein oder zwei Personen beherrschen.

    Das geht zwar (copy/paste von http://en.cppreference.com/w/cpp/locale/ctype_char beherrschen bis auf ein, zwei Personen alle hier im Forum), ist aber nicht mal nötig.

    struct mystr {};
    std::istream& operator>>(std::istream& in, mystr&)
    { std::istreambuf_iterator<char> it(in), end;
      std::find_if(std::find_if(it,end,[](char c){return  isspace(c);}),
                   end,                [](char c){return !isspace(c);});
      return in;
    }
    int wordcount_fummler(string satz) {
      istringstream in(satz);
      return distance(istream_iterator<mystr>(in), istream_iterator<mystr>());
    }
    


  • Edit: Yikes! Da hat schon jemand auf eine praktisch gleiche Lösung verlinkt!
    :duck und weg:



  • Uihh! - ein kleiner Programmier-Contest; da darf mein Beitrag nicht fehlen. std::inner_product hat noch keiner:

    #include <functional> // std::plus<>
    #include <iostream>
    #include <numeric> // std::inner_product
    #include <string>
    #include <cctype> // std::isalnum, etc.
    
    namespace werner
    {
        int wordcount( const std::string& satz )
        {
            return std::inner_product( begin(satz), end(satz)-1, begin(satz)+1, !satz.empty() && std::isalnum( satz.front() )? 1: 0, std::plus< int >(), 
                []( char prev, char next )->int { return std::isspace( prev ) && std::isalnum( next )? 1: 0; } );
        }
    }
    
    int main()
    {
        using namespace std;
        for( string satz; cout << "\nGeben Sie einen Satz ein: ", getline( cin, satz ) && satz != "x"; )
            cout << "Sie haben " << werner::wordcount( satz ) << " Woerter eingegeben" << endl;
    }
    

    Beispiel:

    Geben Sie einen Satz ein: Karl-Heinz treibt's um 12:00 bunt -    oder ?
    Sie haben 6 Woerter eingegeben
    
    Geben Sie einen Satz ein: Hallo Welt, Du grausame    Welt, hast Tabs und auch Zeilen sowie      Steuerzeichen
    Sie haben 12 Woerter eingegeben
    

    Erkennt "Karl-Heinz" als ein Wort; "-" als kein Wort; leider auch "-oder" als kein Wort ... das würde mehr erfordern, als zwei hinter einander stehende Zeichen zu interpretieren.

    Gruß
    Werner



  • Phil123 schrieb:

    Aufgabe:
    Der Funktion wordcount wird ein String als Argument u¨bergeben. ...

    Derartige Aufgabenstellungen schränken doch bereits die Möglichkeiten der Lösung ein. Wieso eigentlich String? Irgendwie muss der Satz doch auch in den Rechner kommen, also per Input - in C++ auch als std::istream bekannt: zunächst mal das main()

    #include <iostream>
    #include <locale> // std::ctype<>
    
    struct WordCounter
    {
        WordCounter( int& cnt ) : cnt_( cnt ) {}
        friend std::istream& operator>>( std::istream& in, WordCounter wc );
    private:
        int& cnt_;
    };
    
    int main()
    {
        using namespace std;
        for( int words; cout << "\nGeben Sie einen Satz ein: ", cin >> WordCounter( words ); )
            cout << "Sie haben " << words << " Wort(e) eingegeben" << endl;
    }
    

    fehlt noch die Implementierung des Manipulators WordCounter :

    std::istream& operator>>( std::istream& in, WordCounter wc )
    {
        std::istream::sentry ok( in );
        if( ok )
        {
            std::ios_base::iostate state = std::ios_base::goodbit;
            try
            {
                enum Mode { Word, Space } mode = Space;
                wc.cnt_ = 0;
                typedef std::istream::traits_type Tr;
                const std::ctype< char >& ct = std::use_facet< std::ctype< char > >( in.getloc() );
                for( char c = 0; c != '\n'; ) // bis EOL; irgendwo muss Schluss sein
                {
                    const Tr::int_type m = in.rdbuf()->sbumpc();
                    if( Tr::eq_int_type( m, Tr::eof() ) ) // EOF
                    {
                        state |= std::ios_base::eofbit;
                        break;
                    }
                    c = Tr::to_char_type( m );
                    if( ct.is( std::ctype_base::alnum, c ) ) // Wort
                    {
                        if( mode == Space ) // Wechsel von Space zu Word -> Wortanfang
                            ++wc.cnt_;
                        mode = Word;
                    }
                    else if( ct.is( std::ctype_base::space, c ) ) // Leerzeichen
                        mode = Space;
                }
            }
            catch( ... )
            {
                state |= std::ios_base::badbit;
                if( in.exceptions() | std::ios_base::badbit )
                    throw;
            }
            in.setstate( state );
        }
        return in;
    }
    

    ein wenig länglich, dafür werden keine Umwege über einen String genommen (siehe auch hier).

    Eine hübsche Aufgabe, und eine nette Übung zur Darstellung von Möglichkeiten zur Programmierung 🕶

    Gruß
    Werner


  • Mod

    Werner Salomon schrieb:

    std::inner_product hat noch keiner:

    😃 👍

    Ich werfe als Herausforderung std::set_symmetric_difference in den Raum.



  • Ich wag ja kaum, eine Lösung fast ohne Hilfenahme der stl zu präsentieren, aber was sagt ihr hierzu?

    template<class Forward1, class Forward2> inline
    Forward1 find_first_notof(Forward1 first1, Forward1 last1,
    						  Forward2 first2, Forward2 last2) 
    {
    	for (; first1 != last1; ++first1)
    	{       
    		Forward2 mid2;
    		for (mid2 = first2; mid2 != last2; ++mid2)
    			if (*first1 == *mid2)
    				break;
    		if(mid2==last2)
    			return first1;
    	}
    	return first1;
    }
    
    size_t Count(const string& org, const string& separators)
    {
    	size_t c = 0;
    	string::const_iterator en, be=org.begin();
    	do
    	{
    		en=find_first_of(be, org.end(),separators.begin(), separators.end());
    		if(be!=en)
    			++c;
    		be=find_first_notof(en, org.end(), separators.begin(), separators.end());
    	} while(be!=org.end());
    	return c;
    }
    
    int wordcount_fricker(string satz)
    {
    	return Count(satz," \n\t -;,.:?!:"); // seperators hinzufügen
    }
    


  • [quote="SeppJ"]

    Werner Salomon schrieb:

    Ich werfe als Herausforderung std::set_symmetric_difference in den Raum.

    Wozu? Es gibt noch genug einfache Lösungen

    std::string tmp = ' '+s+' ';
    return (std::unique(tmp.begin(), tmp.end(),
                        [](char c, char d){return isspace(c)!=isspace(d);})
            - tmp.begin() + 1)/2 << '\n';
    


  • Euch zuliebe hab ich die count-Lösung mal angepasst:

    int wordcount_Ethon(string satz)
    {
        return count_if(satz.begin(), satz.end(), [&](char& c) { return &c == satz.c_str() ? !isspace(c) : isalnum(c) && isspace(*(&c - 1)); });
    }
    


  • Ethon schrieb:

    Euch zuliebe hab ich die count-Lösung mal angepasst:

    Bitte ohne UB.



  • fummler schrieb:

    Ethon schrieb:

    Euch zuliebe hab ich die count-Lösung mal angepasst:

    Bitte ohne UB.

    Seit C++11 doch kein UB mehr? Was stört?

    Und selbst wenn : Geht doch eh nur ums frickeln. 🤡



  • Habe auch noch eine Lösung gemacht:

    #include <algorithm>
    #include <bitset>
    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    
    void split( const string& text, vector<string>& words )
    {
    	bitset<255> separator_lookup;
    	separator_lookup.flip();
    
    	const char* alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    
    	for(; *alphabet; ++alphabet)
    	{
    		separator_lookup[ *alphabet ] = false;
    	}
    
    	string::const_iterator start;
    	bool word = false;
    
    	for(string::const_iterator it=text.begin(); it!=text.end(); ++it)
    	{
    		if( separator_lookup[ *it ] )
    		{
    			if( word )
    			{
    				words.push_back( string(start,it) );
    				word = false;
    			}
    		}
    		else if( !word )
    		{
    			start = it;
    			word = true;
    		}
    	}
    
    	if( word )
    		words.push_back( string(start,text.end()) );
    }
    
    unsigned wordcount( const string& text )
    {
    	vector<string> words;
    	split( text, words );
    	return words.size();
    }
    
    int main()
    {
    	string text = "   Hallo Welt, Du grausame    Welt , hast Tabs und auch Zeilen sowie      Steuerzeichen Sie haben 12 Woerter eingegeben   ";
    
    	cout << wordcount( text ) << '\n';
    }
    

Anmelden zum Antworten