Я пытаюсь итерировать слова строки.
Можно предположить, что строка состоит из слов, разделенных пробелами.
Обратите внимание, что меня не интересуют строковые функции языка 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);
}
Есть ли более элегантный способ сделать это?
Я использовать это, чтобы разбить строку по разделителю. Первый помещает результаты в предварительно построенный вектор, второй возвращает новый вектор.
в
#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", ':');
За то, что он's стоит, здесь'ы другой способ извлечения лексем из входной строки, опираясь только на стандартные библиотеки. Это'ы пример мощь и элегантность за дизайн стл.
#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"));
}
Вместо копирования добытых жетонов в выходной поток, можно вставить их в контейнер, используя те же схемы копировать алгоритм.
vector<string> tokens;
copy(istream_iterator<string>(iss),
istream_iterator<string>(),
back_inserter(tokens));
... или создать "вектор" напрямую:
vector<string> tokens{istream_iterator<string>{iss},
istream_iterator<string>{}};
Возможное решение с использованием 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), используя все виды разделителей.
См. В документации для деталей.
#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;
}
Для тех, с кем он не уживается в жертву все эффективности для кода, размер и посмотреть на "эффективная" в качестве типа элегантности, следующие должны ударить сладкое место (и я думаю, что шаблон класса контейнера-это удивительно элегантное дополнение.):
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;
}
}
Я обычно выбираю, чтобы использовать СТД::вектор<СТД::строка>типы, как мой второй параметр (
ContainerT)... но список<>
это способ быстрее, чем вектор<>когда прямой доступ не нужен, и вы даже можете создать свой собственный класс String и использовать что-то типа
СТД::список<подстрока>где
подстрока` не делать никаких копий для невероятной скоростью увеличивается.
Это's больше чем дважды как быстро как быстро разметить на этой странице и почти в 5 раз быстрее, чем некоторые другие. Также с идеальным типы параметров можно удалить все строки и список копий дополнительную скорость увеличивается.
Кроме того, он не делает (крайне неэффективно) возвращают результат, а он проходит в качестве эталона, таким образом позволяя вам создать маркеры, используя несколько звонков, если вы так пожелали.
Наконец, он позволяет указать, следует ли обрезать пустые фишки от результатов через последний необязательный параметр.
Все это нуждается в СТД::строка`... остальные являются необязательными. Не использовать потоки или библиотека Boost, но является достаточно гибкой, чтобы быть в состоянии принять некоторые из этих иностранных, естественно видах.
Здесь's другое решение. Это's компактный и достаточно эффективный:
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;
}
Это может легко быть templatised для обработки строковых разделителей, ширину строки и т. д.
Обратите внимание, что разделение в ""
в результаты в одной пустой строкой и разделение в ","
в (т. е. сентября) результаты через две пустые строки.
Она также может быть легко расширен, чтобы пропустить пустые маркеры:
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;
}
Это мой любимый способ итерации по строке. Вы можете делать все, что хотите, для каждого слова.
string line = "a line of text to iterate through";
string word;
istringstream iss(line, istringstream::in);
while( iss >> word )
{
// Do something on `word` here...
}
Это похоже на стек&ампер;усилитель; nbsp;переливом вопрос Как же я разбиения на лексемы строку в 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;
}
}
Люблю, потому что он помещает результаты в вектор, поддерживает строку делим и дает контроль над соблюдением пустые значения. Но, это не'т выглядеть так хорошо.
#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'ы сплит с is_any_of()
работает отлично.
В STL такой метод уже отсутствует.
Однако вы можете либо использовать функцию strtok()
языка C, используя член 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
под названием iterator или не перегружен оператор <<
, не означает, что это плохой код. Я довольно часто использую функции языка Си. Например, printf
и scanf
обе быстрее, чем std::cin
и std::cout
(значительно), синтаксис fopen
гораздо более дружественный для бинарных типов, и они также имеют тенденцию производить меньшие EXE.
Не ведитесь на эту "Элегантность превыше производительности" сделку.
Вот сплит функцию, которая:
шаблон<имя_типа Т> вектор<П> сплит(с const Т &, как const Т & ул.; разделители) { вектор<П> в; параметр typename Т::size_type начало = 0; ул. авто поз =.find_first_of(разделители, пуск); а(поз != Т::НСС) { если(поз != начало) // Игнорировать пустые жетоны В. emplace_back(ул., начало, пос - старт); пуск = поз + 1; ул. пос =.find_first_of(разделители, пуск); } если STR(пуск <.длина()) // игнорировать продольный разделитель В. emplace_back(ул., начало, ул. длина() - старт); // добавить что'с левого края строки вернуться в; }
Пример использования:
vector<string> v = split<string>("Hello, there; World", ";,");
vector<wstring> v = split<wstring>(L"Hello, there; World", L";,");
У меня есть решение 2 строки этой проблемы:
char sep = ' ';
std::string s="1 This is an example";
for(size_t p=0, q=0; p!=s.npos; p=q)
std::cout << s.substr(p+(p!=0), (q=s.find(sep, p+1))-p-(p!=0)) << std::endl;
Тогда вместо печати вы можете поместить его в вектор.
Еще один гибкий и быстрый способ
template<typename Operator>
void tokenize(Operator& op, const char* input, const char* delimiters) {
const char* s = input;
const char* e = s;
while (*e != 0) {
e = s;
while (*e != 0 && strchr(delimiters, *e) == 0) ++e;
if (e - s > 0) {
op(s, e - s);
}
s = e + 1;
}
}
Чтобы использовать его с вектором строк (редактировать: поскольку кто-то указал не наследовать классы стл... рамочной программы является ;) ) :
template<class ContainerType>
class Appender {
public:
Appender(ContainerType& container) : container_(container) {;}
void operator() (const char* s, unsigned length) {
container_.push_back(std::string(s,length));
}
private:
ContainerType& container_;
};
std::vector<std::string> strVector;
Appender v(strVector);
tokenize(v, "A number of words to be tokenized", " \t");
Что's это! И что's только один способ использовать токенизатор, как только подсчет слов:
class WordCounter {
public:
WordCounter() : noOfWords(0) {}
void operator() (const char*, unsigned) {
++noOfWords;
}
unsigned noOfWords;
};
WordCounter wc;
tokenize(wc, "A number of words to be counted", " \t");
ASSERT( wc.noOfWords == 7 );
Ограничивается фантазией ;)
Здесь'ы простое решение, которое использует только стандартную библиотеку регулярных выражений
#include <regex>
#include <string>
#include <vector>
std::vector<string> Tokenize( const string str, const std::regex regex )
{
using namespace std;
std::vector<string> result;
sregex_token_iterator it( str.begin(), str.end(), regex, -1 );
sregex_token_iterator reg_end;
for ( ; it != reg_end; ++it ) {
if ( !it->str().empty() ) //token could be empty:check
result.emplace_back( it->str() );
}
return result;
}
Аргумента регулярное выражение позволяет проверять несколько аргументов (пробелы, запятые и т. д.)
Я обычно только проверить, чтобы разделить на пробелы и запятые, так что у меня тоже есть эта функция по умолчанию:
std::vector<string> TokenizeDefault( const string str )
{
using namespace std;
regex re( "[\\s,]+" );
return Tokenize( str, re );
}
На в " [\и\S,]+" и чеки для пространств (
\S) и запятых (,
).
Обратите внимание, если вы хотите разделить wstring, котораявместо
строка`,
СТД::регулярное выражение
до СТД::wregex
sregex_token_iterator
до wsregex_token_iterator
Обратите внимание, вы также можете воспользоваться строкой аргумента по ссылке, в зависимости от вашего компилятора.
Использование std::stringstream
, как вы сделали, работает совершенно нормально, и делает именно то, что вы хотели. Если вы ищете другой способ сделать это, вы можете использовать std::find()
/std::find_first_of()
и std::string::substr()
.
Вот пример:
#include <iostream>
#include <string>
int main()
{
std::string s("Somewhere down the road");
std::string::size_type prev_pos = 0, pos = 0;
while( (pos = s.find(' ', pos)) != std::string::npos )
{
std::string substring( s.substr(prev_pos, pos-prev_pos) );
std::cout << substring << '\n';
prev_pos = ++pos;
}
std::string substring( s.substr(prev_pos, pos-prev_pos) ); // Last word
std::cout << substring << '\n';
return 0;
}
Если вы хотите, чтобы использовать толчок, но хотите использовать всю строку в качестве разделителя (вместо одиночных символов, как в большинстве предложенных ранее решений), вы можете использовать boost_split_iterator
.
Пример кода, включая удобный шаблон:
#include <iostream>
#include <vector>
#include <boost/algorithm/string.hpp>
template<typename _OutputIterator>
inline void split(
const std::string& str,
const std::string& delim,
_OutputIterator result)
{
using namespace boost::algorithm;
typedef split_iterator<std::string::const_iterator> It;
for(It iter=make_split_iterator(str, first_finder(delim, is_equal()));
iter!=It();
++iter)
{
*(result++) = boost::copy_range<std::string>(*iter);
}
}
int main(int argc, char* argv[])
{
using namespace std;
vector<string> splitted;
split("HelloFOOworldFOO!", "FOO", back_inserter(splitted));
// or directly to console, for example
split("HelloFOOworldFOO!", "FOO", ostream_iterator<string>(cout, "\n"));
return 0;
}
Есть функция с именем функции strtok
.
#include<string>
using namespace std;
vector<string> split(char* str,const char* delim)
{
char* saveptr;
char* token = strtok_r(str,delim,&saveptr);
vector<string> result;
while(token != NULL)
{
result.push_back(token);
token = strtok_r(NULL,delim,&saveptr);
}
return result;
}
Вот решение регулярное выражение, которое использует только стандартную библиотеку регулярных выражений. (Я'м немного подзабыл, так что причин может быть несколько синтаксических ошибок, но это по крайней мере общую идею)
#include <regex.h>
#include <string.h>
#include <vector.h>
using namespace std;
vector<string> split(string s){
regex r ("\\w+"); //regex matches whole words, (greedy, so no fragment words)
regex_iterator<string::iterator> rit ( s.begin(), s.end(), r );
regex_iterator<string::iterator> rend; //iterators to iterate thru words
vector<string> result<regex_iterator>(rit, rend);
return result; //iterates through the matches to fill the vector
}
В stringstream может быть удобно, если вам нужно разобрать строку не-пробельных символов:
string s = "Name:JAck; Spouse:Susan; ...";
string dummy, name, spouse;
istringstream iss(s);
getline(iss, dummy, ':');
getline(iss, name, ';');
getline(iss, dummy, ':');
getline(iss, spouse, ';')
До сих пор я использовал один в увеличить, но мне нужно что-то, что не'т зависит, поэтому я пришел к этому:
static void Split(std::vector<std::string>& lst, const std::string& input, const std::string& separators, bool remove_empty = true)
{
std::ostringstream word;
for (size_t n = 0; n < input.size(); ++n)
{
if (std::string::npos == separators.find(input[n]))
word << input[n];
else
{
if (!word.str().empty() || !remove_empty)
lst.push_back(word.str());
word.str("");
}
}
if (!word.str().empty() || !remove_empty)
lst.push_back(word.str());
}
Хорошим моментом является то, что в сепаратов
вы можете пройти более чем один символ.