Vraag De meest elegante manier om de woorden van een tekenreeks te herhalen [gesloten]


Wat is de meest elegante manier om de woorden van een string te herhalen? Er kan worden aangenomen dat de tekenreeks bestaat uit woorden gescheiden door witruimten.

Merk op dat ik niet geïnteresseerd ben in C-snaarfuncties of dat soort manipulatie / toegang tot tekens. Geef ook alsjeblieft voorrang aan elegantie boven efficiëntie in je antwoord.

De beste oplossing die ik nu heb is:

#include <iostream>
#include <sstream>
#include <string>

using namespace std;

int main()
{
    string s = "Somewhere down the road";
    istringstream iss(s);

    do
    {
        string subs;
        iss >> subs;
        cout << "Substring: " << subs << endl;
    } while (iss);
}

2638


oorsprong


antwoorden:


Voor wat het waard is, is hier een andere manier om tokens uit een invoerreeks te extraheren, alleen vertrouwend op standaard bibliotheekfaciliteiten. Het is een voorbeeld van de kracht en elegantie achter het ontwerp van de STL.

#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>

int main() {
    using namespace std;
    string sentence = "And I feel fine...";
    istringstream iss(sentence);
    copy(istream_iterator<string>(iss),
         istream_iterator<string>(),
         ostream_iterator<string>(cout, "\n"));
}

In plaats van de uitgepakte tokens naar een uitvoerstroom te kopiëren, zou je ze in een container kunnen invoegen, met behulp van dezelfde generieke copy algoritme.

vector<string> tokens;
copy(istream_iterator<string>(iss),
     istream_iterator<string>(),
     back_inserter(tokens));

... of maak het vector direct:

vector<string> tokens{istream_iterator<string>{iss},
                      istream_iterator<string>{}};

1188



Ik gebruik dit om string te splitsen door een scheidingsteken. De eerste plaatst de resultaten in een vooraf geconstrueerde vector, de tweede levert een nieuwe vector op.

#include <string>
#include <sstream>
#include <vector>
#include <iterator>

template<typename Out>
void split(const std::string &s, char delim, Out result) {
    std::stringstream ss(s);
    std::string item;
    while (std::getline(ss, item, delim)) {
        *(result++) = item;
    }
}

std::vector<std::string> split(const std::string &s, char delim) {
    std::vector<std::string> elems;
    split(s, delim, std::back_inserter(elems));
    return elems;
}

Merk op dat deze oplossing geen lege tokens overslaat, dus het volgende zal 4 items vinden, waarvan er één leeg is:

std::vector<std::string> x = split("one:two::three", ':');

2307



Een mogelijke oplossing met Boost kan zijn:

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));

Deze aanpak kan zelfs sneller zijn dan de stringstream nadering. En aangezien dit een generieke sjabloonfunctie is, kan deze worden gebruikt om andere soorten tekenreeksen (wchar, enz. Of UTF-8) te splitsen met behulp van allerlei scheidingstekens.

Zie de documentatie voor details.


794



#include <vector>
#include <string>
#include <sstream>

int main()
{
    std::string str("Split me by whitespaces");
    std::string buf;                 // Have a buffer string
    std::stringstream ss(str);       // Insert the string into a stream

    std::vector<std::string> tokens; // Create vector to hold our words

    while (ss >> buf)
        tokens.push_back(buf);

    return 0;
}

321



Voor degenen met wie het niet goed zit om alle efficiëntie voor de codeafmeting op te offeren en 'efficiënt' als een vorm van elegantie te zien, moet het volgende een goede plek raken (en ik denk dat de sjablooncontainerklasse een ontzettend elegante toevoeging is.):

template < class ContainerT >
void tokenize(const std::string& str, ContainerT& tokens,
              const std::string& delimiters = " ", bool trimEmpty = false)
{
   std::string::size_type pos, lastPos = 0, length = str.length();

   using value_type = typename ContainerT::value_type;
   using size_type  = typename ContainerT::size_type;

   while(lastPos < length + 1)
   {
      pos = str.find_first_of(delimiters, lastPos);
      if(pos == std::string::npos)
      {
         pos = length;
      }

      if(pos != lastPos || !trimEmpty)
         tokens.push_back(value_type(str.data()+lastPos,
               (size_type)pos-lastPos ));

      lastPos = pos + 1;
   }
}

Ik kies meestal voor gebruik std::vector<std::string> typen als mijn tweede parameter (ContainerT)... maar list<> is veel sneller dan vector<> voor als directe toegang niet nodig is, en je kunt zelfs je eigen stringklasse maken en zoiets gebruiken std::list<subString> waar subString doet geen kopieën voor ongelooflijke snelheidsverhogingen.

Het is meer dan het dubbele zo snel dan de snelste tokenize op deze pagina en bijna 5 keer sneller dan sommige andere. Ook met de perfecte parametertypen kunt u alle tekenreeksen en lijstkopieën verwijderen voor extra snelheidsverhogingen.

Bovendien doet het niet de (extreem inefficiënte) resultaatretour, maar geeft het eerder de tokens als referentie, waardoor je ook tokens kunt opbouwen met behulp van meerdere oproepen als je dat wilt.

Ten slotte kunt u opgeven of lege tokens uit de resultaten moeten worden bijgesneden via een laatste optionele parameter.

Het enige wat nodig is, is std::string... de rest is optioneel. Het maakt geen gebruik van streams of de boost-bibliotheek, maar is flexibel genoeg om sommige van deze vreemde typen natuurlijk te kunnen accepteren.


168



Hier is nog een oplossing. Het is compact en redelijk efficiënt:

std::vector<std::string> split(const std::string &text, char sep) {
  std::vector<std::string> tokens;
  std::size_t start = 0, end = 0;
  while ((end = text.find(sep, start)) != std::string::npos) {
    tokens.push_back(text.substr(start, end - start));
    start = end + 1;
  }
  tokens.push_back(text.substr(start));
  return tokens;
}

Het kan eenvoudig worden aangepast om stringscheiders, brede reeksen, enz. Te hanteren.

Merk op dat splitsen "" resulteert in een enkele lege reeks en splitsen "," (dat wil zeggen, sep) resulteert in twee lege reeksen.

Het kan ook gemakkelijk worden uitgebreid om lege tokens over te slaan:

std::vector<std::string> split(const std::string &text, char sep) {
    std::vector<std::string> tokens;
    std::size_t start = 0, end = 0;
    while ((end = text.find(sep, start)) != std::string::npos) {
        if (end != start) {
          tokens.push_back(text.substr(start, end - start));
        }
        start = end + 1;
    }
    if (end != start) {
       tokens.push_back(text.substr(start));
    }
    return tokens;
}

Als het splitsen van een tekenreeks op meerdere scheidingstekens tijdens het overslaan van lege tokens gewenst is, kan deze versie worden gebruikt:

std::vector<std::string> split(const std::string& text, const std::string& delims)
{
    std::vector<std::string> tokens;
    std::size_t start = text.find_first_not_of(delims), end = 0;

    while((end = text.find_first_of(delims, start)) != std::string::npos)
    {
        tokens.push_back(text.substr(start, end - start));
        start = text.find_first_not_of(delims, end);
    }
    if(start != std::string::npos)
        tokens.push_back(text.substr(start));

    return tokens;
}

150



Dit is mijn favoriete manier om door een string te itereren. Je kunt doen wat je wilt per woord.

string line = "a line of text to iterate through";
string word;

istringstream iss(line, istringstream::in);

while( iss >> word )     
{
    // Do something on `word` here...
}

106



Dit lijkt op de vraag Stack Overflow Hoe tokenize ik een string in C ++?.

#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>

using namespace std;
using namespace boost;

int main(int argc, char** argv)
{
    string text = "token  test\tstring";

    char_separator<char> sep(" \t");
    tokenizer<char_separator<char>> tokens(text, sep);
    for (const string& t : tokens)
    {
        cout << t << "." << endl;
    }
}

76