Как перебрать слова строки? - PullRequest
2822 голосов
/ 25 октября 2008

Я пытаюсь перебрать слова строки.

Можно предположить, что строка состоит из слов, разделенных пробелом.

Обратите внимание, что меня не интересуют строковые функции C или подобные манипуляции / доступ к символам Кроме того, в своем ответе просьба отдавать предпочтение элегантности, а не эффективности.

Лучшее решение, которое у меня есть сейчас:

#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);
}

Есть ли более элегантный способ сделать это?

Ответы [ 76 ]

2374 голосов
/ 25 октября 2008

Я использую это, чтобы разделить строку разделителем. Первый помещает результаты в предварительно построенный вектор, второй возвращает новый вектор.

#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;
}

Обратите внимание, что это решение не пропускает пустые токены, поэтому следующие найдут 4 элемента, один из которых пуст:

std::vector<std::string> x = split("one:two::three", ':');
1283 голосов
/ 26 октября 2008

Для чего стоит, вот еще один способ извлечь токены из входной строки, полагаясь только на стандартные библиотечные средства. Это пример силы и элегантности дизайна 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"));
}

Вместо копирования извлеченных токенов в выходной поток их можно вставить в контейнер, используя тот же общий алгоритм copy.

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

... или создайте vector напрямую:

vector<string> tokens{istream_iterator<string>{iss},
                      istream_iterator<string>{}};
818 голосов
/ 26 октября 2008

Возможное решение с использованием Boost может быть:

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

Этот подход может быть даже быстрее, чем stringstream. А поскольку это универсальная функция шаблона, ее можно использовать для разделения других типов строк (wchar и т. Д. Или UTF-8), используя все виды разделителей.

Подробнее см. В документации .

344 голосов
/ 06 марта 2011
#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;
}
176 голосов
/ 29 сентября 2009

Для тех, кому не очень удобно жертвовать всей эффективностью ради размера кода и рассматривать «работоспособность» как тип элегантности, следующее должно быть приятно: (и я думаю, что класс контейнера шаблонов является удивительно элегантным дополнением .):

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;
   }
}

Обычно я выбираю std::vector<std::string> типы в качестве второго параметра (ContainerT) ... но list<> намного быстрее, чем vector<>, когда прямой доступ не требуется, и вы даже можете создать свой собственный Строка класса и использовать что-то вроде std::list<subString>, где subString не делает никаких копий для невероятного увеличения скорости.

Это более чем в два раза быстрее, чем самый быстрый токен на этой странице, и почти в 5 раз быстрее, чем некоторые другие. Также с идеальными типами параметров вы можете исключить все копии строк и списков для дополнительного увеличения скорости.

Кроме того, он не выполняет (крайне неэффективный) возврат результата, а скорее передает токены в качестве ссылки, что также позволяет вам создавать токены, используя несколько вызовов, если вы того пожелаете.

Наконец, он позволяет указать, следует ли обрезать пустые токены из результатов с помощью последнего необязательного параметра.

Все, что ему нужно, это std::string ... остальные необязательны. Он не использует потоки или библиотеку надстройки, но достаточно гибок, чтобы можно было естественным образом принимать некоторые из этих внешних типов.

157 голосов
/ 14 сентября 2011

Вот еще одно решение. Он компактен и достаточно эффективен:

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;
}

Он может быть легко шаблонизирован для работы с разделителями строк, широкими строками и т. Д.

Обратите внимание, что разбиение "" приводит к одной пустой строке, а разбиение "," (т.е. sep) приводит к двум пустым строкам.

Он также может быть легко расширен для пропуска пустых токенов:

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;
}

Если желательно разделение строки на несколько разделителей при пропуске пустых токенов, можно использовать эту версию:

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;
}
114 голосов
/ 25 октября 2008

Это мой любимый способ перебора строки. Вы можете делать все, что хотите для каждого слова.

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

istringstream iss(line, istringstream::in);

while( iss >> word )     
{
    // Do something on `word` here...
}
79 голосов
/ 25 октября 2008

Это похоже на вопрос переполнения стека Как мне токенизировать строку в 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;
    }
}
66 голосов
/ 25 октября 2008

Мне нравится следующее, потому что это помещает результаты в вектор, поддерживает строку как разделитель и дает контроль над сохранением пустых значений. Но тогда это выглядит не так хорошо.

#include <ostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;

vector<string> split(const string& s, const string& delim, const bool keep_empty = true) {
    vector<string> result;
    if (delim.empty()) {
        result.push_back(s);
        return result;
    }
    string::const_iterator substart = s.begin(), subend;
    while (true) {
        subend = search(substart, s.end(), delim.begin(), delim.end());
        string temp(substart, subend);
        if (keep_empty || !temp.empty()) {
            result.push_back(temp);
        }
        if (subend == s.end()) {
            break;
        }
        substart = subend + delim.size();
    }
    return result;
}

int main() {
    const vector<string> words = split("So close no matter how far", " ");
    copy(words.begin(), words.end(), ostream_iterator<string>(cout, "\n"));
}

Конечно, у Boost есть split(), который работает частично так. И, если под «пробелами» вы действительно подразумеваете любой тип пробелов, использование разделения Boost с is_any_of() прекрасно работает.

52 голосов
/ 25 октября 2008

В STL такой метод еще не доступен.

Однако вы можете использовать функцию C strtok(), используя элемент std::string::c_str(), или написать свой собственный. Вот пример кода, который я нашел после быстрого поиска в Google ( "STL string split" ):

void Tokenize(const string& str,
              vector<string>& tokens,
              const string& delimiters = " ")
{
    // Skip delimiters at beginning.
    string::size_type lastPos = str.find_first_not_of(delimiters, 0);
    // Find first "non-delimiter".
    string::size_type pos     = str.find_first_of(delimiters, lastPos);

    while (string::npos != pos || string::npos != lastPos)
    {
        // Found a token, add it to the vector.
        tokens.push_back(str.substr(lastPos, pos - lastPos));
        // Skip delimiters.  Note the "not_of"
        lastPos = str.find_first_not_of(delimiters, pos);
        // Find next "non-delimiter"
        pos = str.find_first_of(delimiters, lastPos);
    }
}

Взято из: http://oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html

Если у вас есть вопросы по поводу примера кода, оставьте комментарий, и я объясню.

И то, что он не реализует typedef, называемый итератор или перегружает оператор <<, не означает, что это плохой код. Я использую функции C довольно часто. Например, printf и scanf оба быстрее, чем std::cin и std::cout (значительно), fopen синтаксис намного более удобен для двоичных типов, и они также имеют тенденцию создавать меньшие EXE-файлы.

Не продавайся по этой сделке "Элегантность над производительностью" .

...