У 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
.
Більш текуча (і ідіоматична, але основна) версія для розділення на пробіл використовує std::istringstream
:
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 може зробити подібні речі досить простими:
#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
).
.h
для заголовків C)
Ось справжній простий:
#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;
}
Використовуйте 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 розміщуються в рядку в розділових місцях. Правильне використання може зажадати, щоб ви зробили не const версію рядка. Ви також можете змінити список роздільників середнього розбору.
На мою думку, наведений вище код набагато простіший і простіший у використанні, ніж написання для нього окремого класу. Для мене це одна з тих функцій, яку надає мова, і вона виконує це добре і чисто. Це просто рішення на основі "С". Це підходить, це легко, і вам не доведеться писати багато зайвого коду :-)
Ще один швидкий спосіб - це використовувати getline
. Щось на зразок:
stringstream ss("bla bla");
string s;
while (getline(ss, s, ' ')) {
cout << s << endl;
}
Якщо ви хочете, ви можете зробити простий split()
метод повернення a vector<string>
, який справді корисний.
Ви можете використовувати потоки, ітератори та алгоритм копіювання, щоб зробити це досить безпосередньо.
#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
я знав, звідки мій об'єкт, це лише питання стилю.
Не ображайтеся , хлопці, але для такого простого завдання, ви робите речі шлях дуже складний. Є багато причин використовувати 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] );
}
І так, ми могли б розділити () повернути новий вектор, а не передати його. Тривожно обертати і перевантажувати. Але залежно від того, що я роблю, мені часто краще повторно використовувати наявні об’єкти, а не завжди створювати нові. (Тільки до тих пір, поки я не забуду спорожнити вектор між ними!)
Довідка: http://www.cplusplus.com/reference/string/string/ .
(Я спочатку писав відповідь на запитання Дуга: Змінення рядків C ++ і витягування на основі роздільників (закрито) . Але оскільки Мартін Йорк закрив це питання вказівником сюди ... я просто узагальнив свій код.)
std::string
клас не включає функцію split ()?
start = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());
а цикл while повинен бути while (start != string::npos)
. Також я перевіряю підрядку, щоб переконатися, що вона не порожня, перш ніж вставляти її у вектор.
Рішення з використанням 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 має сильну функцію спліт: boost :: алгоритм :: 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
Ось зразок класу токенізаторів, який може робити те, що ви хочете
//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());
}
Це простий СТЛ-єдиним рішення (~ 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);
}
}
Спробуйте це наживо !
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 Library .
Ось приклад того, як його використовувати, який я розмістив ще десь у 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"; }
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 . Слід звернути увагу на проблеми з багатопотоковою передачею під час її використання.
Для простих речей я просто використовую наступне:
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;
Відповідь Адама Пірса надає рукодільний токенізатор, який приймає a 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 strtok
, це простий варіант:
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
s, const char*
s або в прямому рядку, щоб токенізувати будь-яке з них strtok
або оперувати string
вмістом, який має бути збережений, str
потрібно було б скопіювати, тоді копія могла б працювати наc ++ 20надає нам split_view
токенізацію рядків неруйнівним чином: https://topanswers.xyz/cplusplus?q=749#a874
Попередні методи не можуть генерувати токенізовані vector
місця, тобто без абстрагування їх функцією помічника, яку вони не можуть ініціалізувати const vector<string> tokens
. Ця функціональність і можливість приймати будь -який роздільник пробілів можна використовувати за допомогою istream_iterator
. Для прикладу: const string str{ "The quick \tbrown \nfox" }
ми можемо це зробити:
istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };
Потрібна конструкція istringstream
для цього варіанту має набагато більшу вартість, ніж попередні два варіанти, однак ці витрати зазвичай приховуються за рахунок 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
це, до речі, стандарт С11. strtok_r
є стандартом POSIX2001. Між обома з них існує стандартна версія strtok
для більшості платформ.
#include <cstring>
входить лише версія c99strtok
. Тож моє припущення полягає в тому, що ви просто надаєте цей коментар як допоміжний матеріал, демонструючи доступність strtok
розширень щодо конкретної реалізації ?
strtok_s
надається як C11, так і як окреме розширення в процесі виконання Microsoft C. Тут є цікава історія, де _s
функції Microsoft стали стандартом C.
Я знаю, що на це питання вже відповіли, але я хочу зробити свій внесок. Можливо, моє рішення трохи просте, але ось що я придумав:
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;
}
Будь ласка, прокоментуйте, чи є кращий підхід до чогось у моєму коді чи якщо щось не так.
ОНОВЛЕННЯ: додано загальний роздільник
Ось підхід, який дозволяє контролювати, чи включені порожні маркери (як 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 і супер гнучкий.
Деякі з них вже запропонували регулярний вираз, але для нообів ось приклад, який повинен робити саме те, що очікує ОП:
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
ви можете скористатися 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;
}
Ось мій швейцарський армійський нож шнурових токенізаторів для розбиття рядків на пробіл, облік одиночних і подвійних цитат, обвитих рядків, а також вилучення цих символів з результатів. Я використовував 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 (), так і структура "суфіксний масив" -даних, описана "Програмуванням Перлів" другого видання Джона Бентлі, глава 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), використовуючи wstring
/ wchar_t
замість спадщини string
/ char
типів.
#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
є жахливим залежним від впровадження типом, яким ніхто не повинен користуватися, якщо це абсолютно не потрібно.
Простий код 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;
}