33

Как перебрать слова в строке?

15

Как мне пройтись по словам строки, состоящей из слов, разделенных пробелами?

Обратите внимание, что меня не интересуют функции работы со строками в 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 << "Подстрока: " << subs << endl;
    } while (iss);
}

Может ли кто-то предложить более изящный способ итерации по словам в строке на C++?

5 ответ(ов)

25

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

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

template <typename Out>
void split(const std::string &s, char delim, Out result) {
    std::istringstream iss(s);
    std::string item;
    while (std::getline(iss, 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", ':');

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

4

Чтобы разделить строку на слова по пробелам в C++, вы можете воспользоваться std::stringstream и std::vector. Вот пример кода, который демонстрирует, как это сделать:

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

int main()
{
    std::string str("Split me by whitespaces"); // Изначальная строка
    std::string buf;                            // Буферная строка для хранения найденного слова
    std::stringstream ss(str);                  // Помещаем строку в стрим

    std::vector<std::string> tokens;            // Вектор для хранения слов

    while (ss >> buf)                           // Извлекаем слова из стрима
        tokens.push_back(buf);                  // Добавляем найденное слово в вектор

    return 0;
}

В этом коде мы создаем std::stringstream из строки, а затем используем оператор >> для извлечения слов, разделенных пробелами. Каждое найденное слово помещается в буфер buf, а затем добавляется в вектор tokens. Таким образом, после завершения цикла в tokens будут содержаться все слова из исходной строки.

2

Вот пример эффективной, компактной и изящной реализации функции для разделения строк на токены с использованием шаблонной функции:

template <class ContainerT>
void split(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.emplace_back(value_type(str.data() + lastPos,
               (size_type)pos - lastPos));

      lastPos = pos + 1;
   }
}

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

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

Для работы функции вам потребуется подключить заголовочный файл <string>. Не применяется использование потоков или библиотеки Boost, но функция может принимать некоторые из этих типов.

С версии C++17 вы можете использовать std::vector<std::string_view>, что намного быстрее и экономнее по памяти, чем использование std::string. Вот пересмотренная версия, которая также поддерживает контейнер как возвращаемый тип:

#include <vector>
#include <string_view>
#include <utility>
    
template < typename StringT,
           typename DelimiterT = char,
           typename ContainerT = std::vector<std::string_view> >
ContainerT split(StringT const& str, DelimiterT const& delimiters = ' ', bool trimEmpty = true, ContainerT&& tokens = {})
{
    typename StringT::size_type pos, lastPos = 0, length = str.length();

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

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

        lastPos = pos + 1;
    }

    return std::forward<ContainerT>(tokens);
}

В этом коде уделено внимание избеганию ненужных копирований.

Вы можете использовать эту функцию следующим образом:

for (auto const& line : split(str, '\n'))

Или:

auto& lines = split(str, '\n');

Обе конструкции возвращают контейнер по умолчанию — std::vector<std::string_view>.

Чтобы получить конкретный тип контейнера или передать существующий контейнер, используйте параметр tokens, передавая либо инициализированный контейнер, либо переменную существующего контейнера:

auto& lines = split(str, '\n', false, std::vector<std::string>());

Или:

std::vector<std::string> lines;
split(str, '\n', false, lines);
1

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

string line = "строка текста для итерации";
string word;

istringstream iss(line, istringstream::in);

while (iss >> word)     
{
    // Выполните здесь действия с `word`...
}

Такой подход позволяет легко обрабатывать каждое слово строки, используя потоковый ввод.

1

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

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;
}
Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь