Как перебрать слова в строке?
Как мне пройтись по словам строки, состоящей из слов, разделенных пробелами?
Обратите внимание, что меня не интересуют функции работы со строками в 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 ответ(ов)
Вы можете использовать данный код для разделения строки по заданному разделителю. В первом случае результаты помещаются в заранее созданный вектор, во втором код возвращает новый вектор.
#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", ':');
Если вам нужно игнорировать пустые токены, вам потребуется добавить дополнительную проверку перед тем, как вставлять элемент в результирующий контейнер.
Чтобы разделить строку на слова по пробелам в 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
будут содержаться все слова из исходной строки.
Вот пример эффективной, компактной и изящной реализации функции для разделения строк на токены с использованием шаблонной функции:
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);
Вот мой любимый способ итерировать по строке. Вы можете выполнять любые действия для каждого слова.
string line = "строка текста для итерации";
string word;
istringstream iss(line, istringstream::in);
while (iss >> word)
{
// Выполните здесь действия с `word`...
}
Такой подход позволяет легко обрабатывать каждое слово строки, используя потоковый ввод.
Вот еще одно решение. Оно компактное и достаточно эффективное:
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;
}
Как разделить строку в Java?
Как преобразовать экземпляр std::string в нижний регистр
Как преобразовать std::string в const char* или char*
`std::wstring` против `std::string`: когда использовать и в чем разница?
Как преобразовать строку, разделённую запятыми, в массив?