В Java есть удобный метод разделения:
String str = "The quick brown fox";
String[] results = str.split(" ");
Есть ли простой способ сделать это в C ++?
В Java есть удобный метод разделения:
String str = "The quick brown fox";
String[] results = str.split(" ");
Есть ли простой способ сделать это в C ++?
Алгоритмы стандартной библиотеки C ++ почти всегда основаны на итераторах, а не на конкретных контейнерах. К сожалению, это затрудняет предоставление Java-подобной split
функции в стандартной библиотеке C ++, хотя никто не спорит, что это было бы удобно. Но каков будет его возвращаемый тип? std::vector<std::basic_string<…>>
? Возможно, но тогда мы вынуждены выполнять (потенциально избыточное и дорогостоящее) распределение.
Вместо этого C ++ предлагает множество способов разделения строк на основе произвольно сложных разделителей, но ни один из них не инкапсулирован так хорошо, как в других языках. Многочисленные способы заполнить сообщения целиком < / а>.
В простейшем случае вы можете выполнять итерацию с помощью std::string::find
, пока не нажмете std::string::npos
, и извлеките содержимое с помощью std::string::substr
.
Более гибкая (и идиоматическая, но базовая) версия для разделения по пробелам будет использовать _6 _ а>:
auto iss = std::istringstream{"The quick brown fox"};
auto str = std::string{};
while (iss >> str) {
process(str);
}
Используя std::istream_iterator
s, содержимое строкового потока также можно скопировать в вектор с помощью его конструктор диапазона итератора.
Несколько библиотек (например, Boost.Tokenizer) предлагают конкретные токенизаторы.
Более продвинутое разбиение требует регулярных выражений. C ++ предоставляет std::regex_token_iterator
для этой цели, в частности:
auto const str = "The quick brown fox"s;
auto const re = std::regex{R"(\s+)"};
auto const vec = std::vector<std::string>(
std::sregex_token_iterator{begin(str), end(str), re, -1},
std::sregex_token_iterator{}
);
Класс Boost tokenizer может сделать подобные вещи весьма удобными. просто:
#include <iostream>
#include <string>
#include <boost/foreach.hpp>
#include <boost/tokenizer.hpp>
using namespace std;
using namespace boost;
int main(int, char**)
{
string text = "token, test string";
char_separator<char> sep(", ");
tokenizer< char_separator<char> > tokens(text, sep);
BOOST_FOREACH (const string& t, tokens) {
cout << t << "." << endl;
}
}
Обновлено для C ++ 11:
#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>
using namespace std;
using namespace boost;
int main(int, char**)
{
string text = "token, test string";
char_separator<char> sep(", ");
tokenizer<char_separator<char>> tokens(text, sep);
for (const auto& t : tokens) {
cout << t << "." << endl;
}
}
char_separator
(по умолчанию drop_empty_tokens
, альтернатива keep_empty_tokens
).
- person Benoit; 17.02.2012
.h
для заголовков C)
- person Ferruccio; 13.12.2013
Вот очень простой:
#include <vector>
#include <string>
using namespace std;
vector<string> split(const char *str, char c = ' ')
{
vector<string> result;
do
{
const char *begin = str;
while(*str != c && *str)
str++;
result.push_back(string(begin, str));
} while (0 != *str++);
return result;
}
Еще один быстрый способ - использовать getline
. Что-то вроде:
stringstream ss("bla bla");
string s;
while (getline(ss, s, ' ')) {
cout << s << endl;
}
Если хотите, вы можете создать простой split()
метод, возвращающий vector<string>
, что действительно полезно.
Используйте strtok. На мой взгляд, нет необходимости создавать класс вокруг токенизации, если strtok не предоставит вам то, что вам нужно. Возможно, и нет, но за 15 с лишним лет написания различного кода синтаксического анализа на C и C ++ я всегда использовал strtok. Вот пример
char myString[] = "The quick brown fox";
char *p = strtok(myString, " ");
while (p) {
printf ("Token: %s\n", p);
p = strtok(NULL, " ");
}
Несколько предостережений (которые могут не соответствовать вашим потребностям). Строка «уничтожается» в процессе, что означает, что символы EOS помещаются в строки в местах-разделителях. При правильном использовании может потребоваться создать неконстантную версию строки. Вы также можете изменить список разделителей в середине синтаксического анализа.
На мой взгляд, приведенный выше код намного проще и проще в использовании, чем писать для него отдельный класс. Для меня это одна из тех функций, которые предоставляет язык, и он делает это хорошо и чисто. Это просто решение на основе C. Это уместно, это просто, и вам не нужно писать много лишнего кода :-)
strtok_r
является потокобезопасным, и его лучше использовать, когда он доступен. Просто попробуйте, чтобы узнать, поддерживает ли это ваш компилятор. Наверное, да.
- person Gabriel Staples; 25.09.2019
vector<char> foo(str.begin(), str.end()+1); char *p = strtok(foo.data(), " ");
Таким образом, вектор владеет доступная для записи копия данных строки и освободит ее в случае возникновения исключения. (+1 означает, что нулевой признак конца строки скопирован в вектор.)
- person Some Guy; 03.07.2020
Вы можете использовать потоки, итераторы и алгоритм копирования, чтобы делать это напрямую.
#include <string>
#include <vector>
#include <iostream>
#include <istream>
#include <ostream>
#include <iterator>
#include <sstream>
#include <algorithm>
int main()
{
std::string str = "The quick brown fox";
// construct a stream from the string
std::stringstream strstr(str);
// use stream iterators to copy the stream to the vector as whitespace separated strings
std::istream_iterator<std::string> it(strstr);
std::istream_iterator<std::string> end;
std::vector<std::string> results(it, end);
// send the vector to stdout.
std::ostream_iterator<std::string> oit(std::cout);
std::copy(results.begin(), results.end(), oit);
}
std
так я знал, откуда взялся мой объект, это просто вопрос стиля.
- person Matthieu M.; 02.04.2010
std::wstring
s? Кажется, я слишком тупой, чтобы заставить его компилировать :(
- person YePhIcK; 06.11.2015
#include <istream>
или <ostream>
.
- person Martin Broadhurst; 09.02.2016
Boost
и стандартной библиотеки (<regex>
), которая может быть boost::regex
или std::regex
- person Nubcake; 13.08.2017
Решение с использованием regex_token_iterator
s:
#include <iostream>
#include <regex>
#include <string>
using namespace std;
int main()
{
string str("The quick brown fox");
regex reg("\\s+");
sregex_token_iterator iter(str.begin(), str.end(), reg, -1);
sregex_token_iterator end;
vector<string> vec(iter, end);
for (auto a : vec)
{
cout << a << endl;
}
}
Не обижайтесь, ребята, но для такой простой задачи вы делаете вещи способом слишком сложными. Есть много причин использовать Boost. Но для чего-то такого простого, это все равно, что сбить муху сани 20 #.
void
split( vector<string> & theStringVector, /* Altered/returned value */
const string & theString,
const string & theDelimiter)
{
UASSERT( theDelimiter.size(), >, 0); // My own ASSERT macro.
size_t start = 0, end = 0;
while ( end != string::npos)
{
end = theString.find( theDelimiter, start);
// If at end, use length=maxLength. Else use length=end-start.
theStringVector.push_back( theString.substr( start,
(end == string::npos) ? string::npos : end - start));
// If at end, use start=maxSize. Else use start=end+delimiter.
start = ( ( end > (string::npos - theDelimiter.size()) )
? string::npos : end + theDelimiter.size());
}
}
Например (для случая Дуга),
#define SHOW(I,X) cout << "[" << (I) << "]\t " # X " = \"" << (X) << "\"" << endl
int
main()
{
vector<string> v;
split( v, "A:PEP:909:Inventory Item", ":" );
for (unsigned int i = 0; i < v.size(); i++)
SHOW( i, v[i] );
}
И да, мы могли бы использовать split (), чтобы вернуть новый вектор, а не передавать его. Это тривиально обернуть и перегрузить. Но в зависимости от того, что я делаю, я часто считаю, что лучше повторно использовать уже существующие объекты, чем всегда создавать новые. (Пока я не забываю очищать вектор между ними!)
Ссылка: http://www.cplusplus.com/reference/string/string/ а>.
(Первоначально я писал ответ на вопрос Дуга: Изменение и извлечение строк C ++ на основе разделителей (закрыто). Но поскольку Мартин Йорк закрыл этот вопрос указателем здесь ... я просто обобщу свой код.)
std::string
не включает функцию split ()?
- person Mr. Shickadance; 19.04.2012
start = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());
, а цикл while - while (start != string::npos)
. Кроме того, я проверяю подстроку, чтобы убедиться, что она не пуста, прежде чем вставлять ее в вектор.
- person John K; 01.08.2012
Boost имеет сильную функцию разделения: boost :: algorithm :: split.
Пример программы:
#include <vector>
#include <boost/algorithm/string.hpp>
int main() {
auto s = "a,b, c ,,e,f,";
std::vector<std::string> fields;
boost::split(fields, s, boost::is_any_of(","));
for (const auto& field : fields)
std::cout << "\"" << field << "\"\n";
return 0;
}
Выход:
"a"
"b"
" c "
""
"e"
"f"
""
Я знаю, что вы просили решение на C ++, но вы можете счесть это полезным:
Qt
#include <QString>
...
QString str = "The quick brown fox";
QStringList results = str.split(" ");
Преимущество перед Boost в этом примере заключается в том, что это прямое сопоставление с кодом вашего сообщения.
См. Дополнительную информацию в документации Qt.
Это простое решение только для STL (~ 5 строк!) С использованием std::find
и std::find_first_not_of
, которое обрабатывает повторы разделителя (например, пробелы или точки), а также начальные и конечные разделители:
#include <string>
#include <vector>
void tokenize(std::string str, std::vector<string> &token_v){
size_t start = str.find_first_not_of(DELIMITER), end=start;
while (start != std::string::npos){
// Find next occurence of delimiter
end = str.find(DELIMITER, start);
// Push back the token found into vector
token_v.push_back(str.substr(start, end-start));
// Skip all occurences of the delimiter to find new start
start = str.find_first_not_of(DELIMITER, end);
}
}
Попробуйте вживую!
Вот образец класса токенизатора, который может делать то, что вы хотите
//Header file
class Tokenizer
{
public:
static const std::string DELIMITERS;
Tokenizer(const std::string& str);
Tokenizer(const std::string& str, const std::string& delimiters);
bool NextToken();
bool NextToken(const std::string& delimiters);
const std::string GetToken() const;
void Reset();
protected:
size_t m_offset;
const std::string m_string;
std::string m_token;
std::string m_delimiters;
};
//CPP file
const std::string Tokenizer::DELIMITERS(" \t\n\r");
Tokenizer::Tokenizer(const std::string& s) :
m_string(s),
m_offset(0),
m_delimiters(DELIMITERS) {}
Tokenizer::Tokenizer(const std::string& s, const std::string& delimiters) :
m_string(s),
m_offset(0),
m_delimiters(delimiters) {}
bool Tokenizer::NextToken()
{
return NextToken(m_delimiters);
}
bool Tokenizer::NextToken(const std::string& delimiters)
{
size_t i = m_string.find_first_not_of(delimiters, m_offset);
if (std::string::npos == i)
{
m_offset = m_string.length();
return false;
}
size_t j = m_string.find_first_of(delimiters, i);
if (std::string::npos == j)
{
m_token = m_string.substr(i);
m_offset = m_string.length();
return true;
}
m_token = m_string.substr(i, j - i);
m_offset = j;
return true;
}
Пример:
std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())
{
v.push_back(s.GetToken());
}
pystring - это небольшая библиотека, которая реализует набор строковых функций Python, включая метод разделения:
#include <string>
#include <vector>
#include "pystring.h"
std::vector<std::string> chunks;
pystring::split("this string", chunks);
// also can specify a separator
pystring::split("this-string", chunks, "-");
Я отправил этот ответ на аналогичный вопрос.
Не изобретайте велосипед. Я использовал несколько библиотек, и самая быстрая и гибкая, с которой я столкнулся, это: C ++ String Toolkit Библиотека.
Вот пример его использования, который я опубликовал в другом месте в stackoverflow.
#include <iostream>
#include <vector>
#include <string>
#include <strtk.hpp>
const char *whitespace = " \t\r\n\f";
const char *whitespace_and_punctuation = " \t\r\n\f;,=";
int main()
{
{ // normal parsing of a string into a vector of strings
std::string s("Somewhere down the road");
std::vector<std::string> result;
if( strtk::parse( s, whitespace, result ) )
{
for(size_t i = 0; i < result.size(); ++i )
std::cout << result[i] << std::endl;
}
}
{ // parsing a string into a vector of floats with other separators
// besides spaces
std::string s("3.0, 3.14; 4.0");
std::vector<float> values;
if( strtk::parse( s, whitespace_and_punctuation, values ) )
{
for(size_t i = 0; i < values.size(); ++i )
std::cout << values[i] << std::endl;
}
}
{ // parsing a string into specific variables
std::string s("angle = 45; radius = 9.9");
std::string w1, w2;
float v1, v2;
if( strtk::parse( s, whitespace_and_punctuation, w1, v1, w2, v2) )
{
std::cout << "word " << w1 << ", value " << v1 << std::endl;
std::cout << "word " << w2 << ", value " << v2 << std::endl;
}
}
return 0;
}
Посмотрите этот пример. Это может вам помочь ..
#include <iostream>
#include <sstream>
using namespace std;
int main ()
{
string tmps;
istringstream is ("the dellimiter is the space");
while (is.good ()) {
is >> tmps;
cout << tmps << "\n";
}
return 0;
}
while ( is >> tmps ) { std::cout << tmps << "\n"; }
- person jordix; 05.04.2016
Ответ Адама Пирса предоставляет созданный вручную токенизатор, принимающий const char*
. С итераторами работать немного сложнее, потому что приращение конечного итератора string
не определено. Тем не менее, учитывая string str{ "The quick brown fox" }
, мы определенно можем добиться этого:
auto start = find(cbegin(str), cend(str), ' ');
vector<string> tokens{ string(cbegin(str), start) };
while (start != cend(str)) {
const auto finish = find(++start, cend(str), ' ');
tokens.push_back(string(start, finish));
start = finish;
}
Если вы стремитесь к абстрактной сложности с помощью стандартных функций, как On Freund предлагает _ 5_ - простой вариант:
vector<string> tokens;
for (auto i = strtok(data(str), " "); i != nullptr; i = strtok(nullptr, " ")) tokens.push_back(i);
Если у вас нет доступа к C ++ 17, вам необходимо заменить data(str)
, как в этом примере: http://ideone.com/8kAGoa
Хотя это не показано в примере, strtok
не обязательно использовать один и тот же разделитель для каждого токена. Однако наряду с этим преимуществом есть несколько недостатков:
strtok
нельзя использовать на нескольких strings
одновременно: либо необходимо передать nullptr
для продолжения токенизации текущего string
, либо необходимо передать новый char*
для токенизации (однако существуют некоторые нестандартные реализации, которые поддерживают это, например: strtok_s
)strtok
нельзя использовать одновременно в нескольких потоках (однако это может быть определено реализацией, например: Реализация Visual Studio ориентирована на многопотоковое исполнение)strtok
изменяет string
, с которым он работает, поэтому его нельзя использовать для const string
, const char*
или буквальных строк, для токенизации любого из них с помощью strtok
или для работы с string
, содержимое которого необходимо сохранить, str
придется скопировать , то с копией можно работатьc ++ 20 предоставляет нам split_view
для разметки строк неразрушающим способом: https://topanswers.xyz/cplusplus?q=749#a874
Предыдущие методы не могут генерировать токенизированные vector
на месте, то есть без их абстрагирования во вспомогательную функцию они не могут инициализировать const vector<string> tokens
. Эту функциональность и возможность принимать любой разделитель пробелов можно использовать с помощью _ 26_. Например, дано: const string str{ "The quick \tbrown \nfox" }
, мы можем сделать это:
istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };
Требуемая конструкция istringstream
для этого варианта имеет гораздо большую стоимость, чем предыдущие 2 варианта, однако эта стоимость обычно скрывается за счет string
распределения.
Если ни один из вышеперечисленных вариантов не является достаточно гибким для ваших нужд токенизации, наиболее гибким вариантом является использование regex_token_iterator
Конечно, такая гибкость влечет за собой большие расходы, но, опять же, это, вероятно, скрыто в string
стоимости размещения. Скажем, например, мы хотим токенизировать на основе запятых без экранирования, а также использовать пробелы, учитывая следующий ввод: const string str{ "The ,qu\\,ick ,\tbrown, fox" }
мы можем сделать это:
const regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };
strtok_s
- это, кстати, стандарт C11. strtok_r
- это стандарт POSIX2001. Между ними есть стандартная реентерабельная версия strtok
для большинства платформ.
- person Andon M. Coleman; 14.12.2016
strtok_s
предоставляется как C11, так и как отдельное расширение в среде выполнения Microsoft C. Здесь есть любопытная история, когда _s
функции Microsoft стали стандартом C.
- person Andon M. Coleman; 14.12.2016
MFC / ATL имеет очень хороший токенизатор. Из MSDN:
CAtlString str( "%First Second#Third" );
CAtlString resToken;
int curPos= 0;
resToken= str.Tokenize("% #",curPos);
while (resToken != "")
{
printf("Resulting token: %s\n", resToken);
resToken= str.Tokenize("% #",curPos);
};
Output
Resulting Token: First
Resulting Token: Second
Resulting Token: Third
Если вы хотите использовать C, вы можете использовать strtok функция. При его использовании следует обращать внимание на проблемы с многопоточностью.
strtok_s
, что в основном strtok
с явной передачей состояния .
- person Matthias; 02.06.2018
Для простых вещей я просто использую следующее:
unsigned TokenizeString(const std::string& i_source,
const std::string& i_seperators,
bool i_discard_empty_tokens,
std::vector<std::string>& o_tokens)
{
unsigned prev_pos = 0;
unsigned pos = 0;
unsigned number_of_tokens = 0;
o_tokens.clear();
pos = i_source.find_first_of(i_seperators, pos);
while (pos != std::string::npos)
{
std::string token = i_source.substr(prev_pos, pos - prev_pos);
if (!i_discard_empty_tokens || token != "")
{
o_tokens.push_back(i_source.substr(prev_pos, pos - prev_pos));
number_of_tokens++;
}
pos++;
prev_pos = pos;
pos = i_source.find_first_of(i_seperators, pos);
}
if (prev_pos < i_source.length())
{
o_tokens.push_back(i_source.substr(prev_pos));
number_of_tokens++;
}
return number_of_tokens;
}
Трусливый отказ от ответственности: я пишу программное обеспечение для обработки данных в реальном времени, где данные поступают через двоичные файлы, сокеты или некоторые вызовы API (карты ввода-вывода, камеры). Я никогда не использую эту функцию для чего-то более сложного или критичного по времени, чем чтение внешних файлов конфигурации при запуске.
Вы можете просто использовать библиотеку регулярных выражений и решить эту проблему с помощью регулярных выражений.
Используйте выражение (\ w +) и переменную в \ 1 (или $ 1 в зависимости от реализации регулярных выражений в библиотеке).
Здесь много слишком сложных предложений. Попробуйте это простое решение std :: string:
using namespace std;
string someText = ...
string::size_type tokenOff = 0, sepOff = tokenOff;
while (sepOff != string::npos)
{
sepOff = someText.find(' ', sepOff);
string::size_type tokenLen = (sepOff == string::npos) ? sepOff : sepOff++ - tokenOff;
string token = someText.substr(tokenOff, tokenLen);
if (!token.empty())
/* do something with token */;
tokenOff = sepOff;
}
Я думал, что для этого нужен оператор >>
в строковых потоках:
string word; sin >> word;
Вот подход, который позволяет вам контролировать, включены ли пустые токены (например, strsep) или исключены (например, strtok).
#include <string.h> // for strchr and strlen
/*
* want_empty_tokens==true : include empty tokens, like strsep()
* want_empty_tokens==false : exclude empty tokens, like strtok()
*/
std::vector<std::string> tokenize(const char* src,
char delim,
bool want_empty_tokens)
{
std::vector<std::string> tokens;
if (src and *src != '\0') // defensive
while( true ) {
const char* d = strchr(src, delim);
size_t len = (d)? d-src : strlen(src);
if (len or want_empty_tokens)
tokens.push_back( std::string(src, len) ); // capture token
if (d) src += len+1; else break;
}
return tokens;
}
Мне кажется странным, что, несмотря на то, что все мы, ботаники, осознающие скорость, здесь, на SO, никто не представил версию, которая использует сгенерированную во время компиляции таблицу поиска для разделителя (пример реализации ниже). Использование таблицы поиска и итераторов должно превзойти std :: regex по эффективности, если вам не нужно превзойти регулярное выражение, просто используйте его, его стандарт C ++ 11 и сверхгибкий.
Некоторые уже предложили регулярное выражение, но для новичков вот упакованный пример, который должен делать именно то, что ожидает OP:
std::vector<std::string> split(std::string::const_iterator it, std::string::const_iterator end, std::regex e = std::regex{"\\w+"}){
std::smatch m{};
std::vector<std::string> ret{};
while (std::regex_search (it,end,m,e)) {
ret.emplace_back(m.str());
std::advance(it, m.position() + m.length()); //next start position = match position + match length
}
return ret;
}
std::vector<std::string> split(const std::string &s, std::regex e = std::regex{"\\w+"}){ //comfort version calls flexible version
return split(s.cbegin(), s.cend(), std::move(e));
}
int main ()
{
std::string str {"Some people, excluding those present, have been compile time constants - since puberty."};
auto v = split(str);
for(const auto&s:v){
std::cout << s << std::endl;
}
std::cout << "crazy version:" << std::endl;
v = split(str, std::regex{"[^e]+"}); //using e as delim shows flexibility
for(const auto&s:v){
std::cout << s << std::endl;
}
return 0;
}
Если нам нужно быть быстрее и принять ограничение, что все символы должны быть 8-битными, мы можем создать таблицу поиска во время компиляции, используя метапрограммирование:
template<bool...> struct BoolSequence{}; //just here to hold bools
template<char...> struct CharSequence{}; //just here to hold chars
template<typename T, char C> struct Contains; //generic
template<char First, char... Cs, char Match> //not first specialization
struct Contains<CharSequence<First, Cs...>,Match> :
Contains<CharSequence<Cs...>, Match>{}; //strip first and increase index
template<char First, char... Cs> //is first specialization
struct Contains<CharSequence<First, Cs...>,First>: std::true_type {};
template<char Match> //not found specialization
struct Contains<CharSequence<>,Match>: std::false_type{};
template<int I, typename T, typename U>
struct MakeSequence; //generic
template<int I, bool... Bs, typename U>
struct MakeSequence<I,BoolSequence<Bs...>, U>: //not last
MakeSequence<I-1, BoolSequence<Contains<U,I-1>::value,Bs...>, U>{};
template<bool... Bs, typename U>
struct MakeSequence<0,BoolSequence<Bs...>,U>{ //last
using Type = BoolSequence<Bs...>;
};
template<typename T> struct BoolASCIITable;
template<bool... Bs> struct BoolASCIITable<BoolSequence<Bs...>>{
/* could be made constexpr but not yet supported by MSVC */
static bool isDelim(const char c){
static const bool table[256] = {Bs...};
return table[static_cast<int>(c)];
}
};
using Delims = CharSequence<'.',',',' ',':','\n'>; //list your custom delimiters here
using Table = BoolASCIITable<typename MakeSequence<256,BoolSequence<>,Delims>::Type>;
С этим легко сделать getNextToken
функцию:
template<typename T_It>
std::pair<T_It,T_It> getNextToken(T_It begin,T_It end){
begin = std::find_if(begin,end,std::not1(Table{})); //find first non delim or end
auto second = std::find_if(begin,end,Table{}); //find first delim or end
return std::make_pair(begin,second);
}
Пользоваться им также просто:
int main() {
std::string s{"Some people, excluding those present, have been compile time constants - since puberty."};
auto it = std::begin(s);
auto end = std::end(s);
while(it != std::end(s)){
auto token = getNextToken(it,end);
std::cout << std::string(token.first,token.second) << std::endl;
it = token.second;
}
return 0;
}
Вот живой пример: http://ideone.com/GKtkLQ
Я знаю, что на этот вопрос уже дан ответ, но я хочу внести свой вклад. Может быть, мое решение немного простое, но вот что я придумал:
vector<string> get_words(string const& text, string const& separator)
{
vector<string> result;
string tmp = text;
size_t first_pos = 0;
size_t second_pos = tmp.find(separator);
while (second_pos != string::npos)
{
if (first_pos != second_pos)
{
string word = tmp.substr(first_pos, second_pos - first_pos);
result.push_back(word);
}
tmp = tmp.substr(second_pos + separator.length());
second_pos = tmp.find(separator);
}
result.push_back(tmp);
return result;
}
Прокомментируйте, пожалуйста, есть ли в моем коде лучший подход к чему-то или что-то не так.
ОБНОВЛЕНИЕ: добавлен общий разделитель
Если вы используете диапазоны C ++ - полную библиотеку range-v3, а не ограниченную функциональность принято в C ++ 20 - это можно сделать так:
auto results = str | ranges::views::tokenize(" ",1);
... и это вычисляется лениво, то есть O (1) время и пространство. В качестве альтернативы вы можете установить вектор в этот диапазон:
auto results = str | ranges::views::tokenize(" ",1) | ranges::to<std::vector>();
это займет O (m) пробела и O (n) времени, если str
имеет n символов, составляющих m слов.
См. Также пример токенизации библиотеки, здесь.
вы можете воспользоваться boost :: make_find_iterator. Что-то похожее на это:
template<typename CH>
inline vector< basic_string<CH> > tokenize(
const basic_string<CH> &Input,
const basic_string<CH> &Delimiter,
bool remove_empty_token
) {
typedef typename basic_string<CH>::const_iterator string_iterator_t;
typedef boost::find_iterator< string_iterator_t > string_find_iterator_t;
vector< basic_string<CH> > Result;
string_iterator_t it = Input.begin();
string_iterator_t it_end = Input.end();
for(string_find_iterator_t i = boost::make_find_iterator(Input, boost::first_finder(Delimiter, boost::is_equal()));
i != string_find_iterator_t();
++i) {
if(remove_empty_token){
if(it != i->begin())
Result.push_back(basic_string<CH>(it,i->begin()));
}
else
Result.push_back(basic_string<CH>(it,i->begin()));
it = i->end();
}
if(it != it_end)
Result.push_back(basic_string<CH>(it,it_end));
return Result;
}
Вот мой Swiss® Army Knife со строковыми токенизаторами для разделения строк по пробелам, учета строк, заключенных в одинарные и двойные кавычки, а также удаления этих символов из результатов. Я использовал RegexBuddy 4.x для создания большей части фрагмента кода, но я добавил настраиваемую обработку для удаления кавычек и некоторых других вещей.
#include <string>
#include <locale>
#include <regex>
std::vector<std::wstring> tokenize_string(std::wstring string_to_tokenize) {
std::vector<std::wstring> tokens;
std::wregex re(LR"(("[^"]*"|'[^']*'|[^"' ]+))", std::regex_constants::collate);
std::wsregex_iterator next( string_to_tokenize.begin(),
string_to_tokenize.end(),
re,
std::regex_constants::match_not_null );
std::wsregex_iterator end;
const wchar_t single_quote = L'\'';
const wchar_t double_quote = L'\"';
while ( next != end ) {
std::wsmatch match = *next;
const std::wstring token = match.str( 0 );
next++;
if (token.length() > 2 && (token.front() == double_quote || token.front() == single_quote))
tokens.emplace_back( std::wstring(token.begin()+1, token.begin()+token.length()-1) );
else
tokens.emplace_back(token);
}
return tokens;
}
Если известна максимальная длина токенизируемой входной строки, можно воспользоваться этим и реализовать очень быструю версию. Ниже я набросал основную идею, которая была вдохновлена как strtok (), так и структурой данных «суффиксный массив», описанной Джоном Бентли «Программирование Perls», 2-е издание, глава 15. Класс C ++ в этом случае дает лишь некоторую организацию и удобство. использования. Показанная реализация может быть легко расширена для удаления начальных и конечных пробелов в токенах.
В принципе, можно заменить символы-разделители на завершающие строку символами '\ 0' и установить указатели на токены в измененной строке. В крайнем случае, когда строка состоит только из разделителей, получается длина строки плюс 1 результирующий пустой токен. Целесообразно продублировать изменяемую строку.
Заголовочный файл:
class TextLineSplitter
{
public:
TextLineSplitter( const size_t max_line_len );
~TextLineSplitter();
void SplitLine( const char *line,
const char sep_char = ',',
);
inline size_t NumTokens( void ) const
{
return mNumTokens;
}
const char * GetToken( const size_t token_idx ) const
{
assert( token_idx < mNumTokens );
return mTokens[ token_idx ];
}
private:
const size_t mStorageSize;
char *mBuff;
char **mTokens;
size_t mNumTokens;
inline void ResetContent( void )
{
memset( mBuff, 0, mStorageSize );
// mark all items as empty:
memset( mTokens, 0, mStorageSize * sizeof( char* ) );
// reset counter for found items:
mNumTokens = 0L;
}
};
Файл реализации:
TextLineSplitter::TextLineSplitter( const size_t max_line_len ):
mStorageSize ( max_line_len + 1L )
{
// allocate memory
mBuff = new char [ mStorageSize ];
mTokens = new char* [ mStorageSize ];
ResetContent();
}
TextLineSplitter::~TextLineSplitter()
{
delete [] mBuff;
delete [] mTokens;
}
void TextLineSplitter::SplitLine( const char *line,
const char sep_char /* = ',' */,
)
{
assert( sep_char != '\0' );
ResetContent();
strncpy( mBuff, line, mMaxLineLen );
size_t idx = 0L; // running index for characters
do
{
assert( idx < mStorageSize );
const char chr = line[ idx ]; // retrieve current character
if( mTokens[ mNumTokens ] == NULL )
{
mTokens[ mNumTokens ] = &mBuff[ idx ];
} // if
if( chr == sep_char || chr == '\0' )
{ // item or line finished
// overwrite separator with a 0-terminating character:
mBuff[ idx ] = '\0';
// count-up items:
mNumTokens ++;
} // if
} while( line[ idx++ ] );
}
Сценарий использования будет:
// create an instance capable of splitting strings up to 1000 chars long:
TextLineSplitter spl( 1000 );
spl.SplitLine( "Item1,,Item2,Item3" );
for( size_t i = 0; i < spl.NumTokens(); i++ )
{
printf( "%s\n", spl.GetToken( i ) );
}
выход:
Item1
Item2
Item3
boost::tokenizer
- ваш друг, но подумайте о том, чтобы сделать свой код переносимым со ссылкой на проблемы интернационализации (i18n) с использованием _2 _ / _ 3_ вместо устаревших типов _4 _ / _ 5_.
#include <iostream>
#include <boost/tokenizer.hpp>
#include <string>
using namespace std;
using namespace boost;
typedef tokenizer<char_separator<wchar_t>,
wstring::const_iterator, wstring> Tok;
int main()
{
wstring s;
while (getline(wcin, s)) {
char_separator<wchar_t> sep(L" "); // list of separator characters
Tok tok(s, sep);
for (Tok::iterator beg = tok.begin(); beg != tok.end(); ++beg) {
wcout << *beg << L"\t"; // output (or store in vector)
}
wcout << L"\n";
}
return 0;
}
wchar_t
- ужасный тип, зависящий от реализации, который никто не должен использовать без крайней необходимости.
- person RamblingMad; 21.05.2014
Простой код C ++ (стандарт C ++ 98), принимает несколько разделителей (указанных в std :: string), использует только векторы, строки и итераторы.
#include <iostream>
#include <vector>
#include <string>
#include <stdexcept>
std::vector<std::string>
split(const std::string& str, const std::string& delim){
std::vector<std::string> result;
if (str.empty())
throw std::runtime_error("Can not tokenize an empty string!");
std::string::const_iterator begin, str_it;
begin = str_it = str.begin();
do {
while (delim.find(*str_it) == std::string::npos && str_it != str.end())
str_it++; // find the position of the first delimiter in str
std::string token = std::string(begin, str_it); // grab the token
if (!token.empty()) // empty token only when str starts with a delimiter
result.push_back(token); // push the token into a vector<string>
while (delim.find(*str_it) != std::string::npos && str_it != str.end())
str_it++; // ignore the additional consecutive delimiters
begin = str_it; // process the remaining tokens
} while (str_it != str.end());
return result;
}
int main() {
std::string test_string = ".this is.a.../.simple;;test;;;END";
std::string delim = "; ./"; // string containing the delimiters
std::vector<std::string> tokens = split(test_string, delim);
for (std::vector<std::string>::const_iterator it = tokens.begin();
it != tokens.end(); it++)
std::cout << *it << std::endl;
}
/// split a string into multiple sub strings, based on a separator string
/// for example, if separator="::",
///
/// s = "abc" -> "abc"
///
/// s = "abc::def xy::st:" -> "abc", "def xy" and "st:",
///
/// s = "::abc::" -> "abc"
///
/// s = "::" -> NO sub strings found
///
/// s = "" -> NO sub strings found
///
/// then append the sub-strings to the end of the vector v.
///
/// the idea comes from the findUrls() function of "Accelerated C++", chapt7,
/// findurls.cpp
///
void split(const string& s, const string& sep, vector<string>& v)
{
typedef string::const_iterator iter;
iter b = s.begin(), e = s.end(), i;
iter sep_b = sep.begin(), sep_e = sep.end();
// search through s
while (b != e){
i = search(b, e, sep_b, sep_e);
// no more separator found
if (i == e){
// it's not an empty string
if (b != e)
v.push_back(string(b, e));
break;
}
else if (i == b){
// the separator is found and right at the beginning
// in this case, we need to move on and search for the
// next separator
b = i + sep.length();
}
else{
// found the separator
v.push_back(string(b, i));
b = i;
}
}
}
Библиотеки наддува хороши, но они не всегда доступны. Выполнение подобных действий вручную - тоже хорошее упражнение для мозга. Здесь мы просто используем алгоритм std :: search () из STL, см. Приведенный выше код.
Я искал способ разбить строку разделителем любой длины, поэтому начал писать ее с нуля, так как существующие решения меня не устраивали.
Вот мой маленький алгоритм, использующий только STL:
//use like this
//std::vector<std::wstring> vec = Split<std::wstring> (L"Hello##world##!", L"##");
template <typename valueType>
static std::vector <valueType> Split (valueType text, const valueType& delimiter)
{
std::vector <valueType> tokens;
size_t pos = 0;
valueType token;
while ((pos = text.find(delimiter)) != valueType::npos)
{
token = text.substr(0, pos);
tokens.push_back (token);
text.erase(0, pos + delimiter.length());
}
tokens.push_back (text);
return tokens;
}
Насколько я тестировал, его можно использовать с разделителями любой длины и формы. Создайте экземпляр с типом строки или wstring.
Все, что делает алгоритм, - это ищет разделитель, получает часть строки, которая соответствует разделителю, удаляет разделитель и выполняет поиск снова, пока не перестанет находить его.
Надеюсь, это поможет.
Раньше я делал лексер / токенизатор с использованием только стандартных библиотек. Вот код:
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
using namespace std;
string seps(string& s) {
if (!s.size()) return "";
stringstream ss;
ss << s[0];
for (int i = 1; i < s.size(); i++) {
ss << '|' << s[i];
}
return ss.str();
}
void Tokenize(string& str, vector<string>& tokens, const string& delimiters = " ")
{
seps(str);
// 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);
}
}
int main(int argc, char *argv[])
{
vector<string> t;
string s = "Tokens for everyone!";
Tokenize(s, t, "|");
for (auto c : t)
cout << c << endl;
system("pause");
return 0;
}
Это простой цикл для токенизации только с файлами стандартной библиотеки.
#include <iostream.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <conio.h>
class word
{
public:
char w[20];
word()
{
for(int j=0;j<=20;j++)
{w[j]='\0';
}
}
};
void main()
{
int i=1,n=0,j=0,k=0,m=1;
char input[100];
word ww[100];
gets(input);
n=strlen(input);
for(i=0;i<=m;i++)
{
if(context[i]!=' ')
{
ww[k].w[j]=context[i];
j++;
}
else
{
k++;
j=0;
m++;
}
}
}