Java ma wygodną metodę podziału:
String str = "The quick brown fox";
String[] results = str.split(" ");
Czy istnieje prosty sposób na zrobienie tego w C ++?
Java ma wygodną metodę podziału:
String str = "The quick brown fox";
String[] results = str.split(" ");
Czy istnieje prosty sposób na zrobienie tego w C ++?
Odpowiedzi:
Standardowe algorytmy biblioteki C ++ są dość powszechnie oparte na iteratorach, a nie na konkretnych kontenerach. Niestety utrudnia to udostępnienie split
funkcji podobnej do języka Java w standardowej bibliotece C ++, nawet jeśli nikt nie twierdzi, że byłoby to wygodne. Ale jaki byłby typ zwrotu? std::vector<std::basic_string<…>>
? Być może, ale wtedy jesteśmy zmuszeni wykonać (potencjalnie zbędne i kosztowne) alokacje.
Zamiast tego C ++ oferuje mnóstwo sposobów podziału ciągów w oparciu o dowolnie złożone ograniczniki, ale żaden z nich nie jest tak dobrze zamknięty jak w innych językach. Liczne sposoby wypełniania całych postów na blogu .
W najprostszym przypadku możesz iterować za pomocą std::string::find
aż do naciśnięcia std::string::npos
i wyodrębnić zawartość za pomocąstd::string::substr
.
Bardziej płynna (i idiomatyczna, ale podstawowa) wersja do podziału na białe znaki użyłaby std::istringstream
:
auto iss = std::istringstream{"The quick brown fox"};
auto str = std::string{};
while (iss >> str) {
process(str);
}
Korzystanie z std::istream_iterator
s zawartość strumienia łańcucha można również skopiować do wektora za pomocą konstruktora zakresu iteratora.
Wiele bibliotek (takich jak Boost.Tokenizer ) oferuje określone tokenyzery.
Bardziej zaawansowane dzielenie wymaga wyrażeń regularnych. C ++ zapewnia w std::regex_token_iterator
tym celu w szczególności:
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{}
);
The Doładowania tokenizer klasa może zrobić tego rodzaju rzeczy dość prosty:
#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;
}
}
Zaktualizowano dla 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
konstruktora ( drop_empty_tokens
domyślnie, alternatywą jest keep_empty_tokens
).
.h
dla nagłówków C)
Oto naprawdę prosty:
#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;
}
Użyj strtok. Moim zdaniem nie ma potrzeby budowania klasy wokół tokenizacji, chyba że strtok nie zapewni ci tego, czego potrzebujesz. Może nie, ale przez ponad 15 lat pisania różnych parsów w C i C ++ zawsze używałem strtok. Oto przykład
char myString[] = "The quick brown fox";
char *p = strtok(myString, " ");
while (p) {
printf ("Token: %s\n", p);
p = strtok(NULL, " ");
}
Kilka ostrzeżeń (które mogą nie odpowiadać Twoim potrzebom). Ciąg jest „niszczony” w procesie, co oznacza, że znaki EOS są umieszczane w liniach w miejscach separatora. Prawidłowe użycie może wymagać wykonania niestałej wersji ciągu. Możesz także zmienić listę ograniczników w trakcie analizy.
Moim zdaniem powyższy kod jest znacznie prostszy i łatwiejszy w użyciu niż pisanie dla niego osobnej klasy. Dla mnie jest to jedna z tych funkcji, które zapewnia język i robi to dobrze i czysto. To po prostu rozwiązanie oparte na „C”. Jest odpowiedni, łatwy i nie musisz pisać dużo dodatkowego kodu :-)
Innym szybkim sposobem jest użycie getline
. Coś jak:
stringstream ss("bla bla");
string s;
while (getline(ss, s, ' ')) {
cout << s << endl;
}
Jeśli chcesz, możesz zrobić prostą split()
metodę zwracającą a vector<string>
, co jest naprawdę przydatne.
Możesz użyć strumieni, iteratorów i algorytmu kopiowania, aby zrobić to dość bezpośrednio.
#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
ten sposób wiedziałem, skąd pochodzi mój przedmiot, to tylko kwestia stylu.
No ludzie urazy, ale dla takiego prostego problemu, robisz rzeczy, sposób zbyt skomplikowane. Istnieje wiele powodów, dla których warto korzystać ze wzmocnienia . Ale dla czegoś tak prostego jest to jak uderzenie w muchę saniami 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());
}
}
Na przykład (w przypadku Douga)
#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] );
}
I tak, moglibyśmy mieć funkcję split () zwracającą nowy wektor, zamiast go przekazywać. Tajemnicze jest pakowanie i przeciążanie. Ale w zależności od tego, co robię, często lepiej jest używać wcześniej istniejących obiektów niż zawsze tworzyć nowe. (Tak długo, jak nie zapomnę opróżnić wektora pomiędzy!)
Odniesienie: http://www.cplusplus.com/reference/string/string/ .
(Pierwotnie pisałem odpowiedź na pytanie Douga: ciągi C ++ modyfikujące i wyodrębniające na podstawie separatorów (zamknięte) . Ale ponieważ Martin York zamknął to pytanie wskaźnikiem tutaj ... Po prostu uogólnię mój kod.)
std::string
klasa nie zawiera funkcji split ()?
start = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());
a pętla while powinna być while (start != string::npos)
. Sprawdzam również podciąg, aby upewnić się, że nie jest pusty przed wstawieniem go do wektora.
Rozwiązanie wykorzystujące 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;
}
}
Zwiększenie ma silną funkcję podziału: boost :: algorytm :: split .
Przykładowy program:
#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;
}
Wynik:
"a"
"b"
" c "
""
"e"
"f"
""
Wiem, że poprosiłeś o rozwiązanie C ++, ale możesz uznać to za pomocne:
Qt
#include <QString>
...
QString str = "The quick brown fox";
QStringList results = str.split(" ");
Przewagą nad Boostem w tym przykładzie jest to, że jest to bezpośrednie mapowanie jeden do jednego na kod Twojego postu.
Zobacz więcej w dokumentacji Qt
Oto przykładowa klasa tokenizera, która może robić, co chcesz
//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;
}
Przykład:
std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())
{
v.push_back(s.GetToken());
}
Jest to proste rozwiązanie tylko STL (~ 5 linii!) Używając std::find
i std::find_first_not_of
który obsługuje powtórzenia ogranicznika (jak spacjami lub okresów, na przykład), a także natarcia i spływu ograniczników:
#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);
}
}
Wypróbuj na żywo !
pystring to mała biblioteka, która implementuje kilka funkcji łańcuchowych Pythona, w tym metodę split:
#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, "-");
Odpowiedziałem na podobne pytanie.
Nie wymyślaj koła ponownie. Użyłem wielu bibliotek, a najszybszą i najbardziej elastyczną, z jaką się zetknąłem jest: C ++ String Toolkit Library .
Oto przykład tego, jak go użyć, który zamieściłem gdzie indziej w przepełnieniu stosu.
#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;
}
Sprawdź ten przykład. To może ci pomóc ...
#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 ma bardzo ładny tokenizer. Z 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
Jeśli chcesz użyć C, możesz użyć funkcji strtok . Podczas korzystania z niego należy zwrócić uwagę na problemy związane z wielowątkowością.
Do prostych rzeczy używam tylko:
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;
}
Tchórzliwe zrzeczenie się odpowiedzialności: piszę oprogramowanie do przetwarzania danych w czasie rzeczywistym, w którym dane przychodzą przez pliki binarne, gniazda lub niektóre wywołania API (karty we / wy, kamery). Nigdy nie używam tej funkcji do czegoś bardziej skomplikowanego lub krytycznego czasowo niż czytanie zewnętrznych plików konfiguracyjnych podczas uruchamiania.
Możesz po prostu użyć biblioteki wyrażeń regularnych i rozwiązać ją za pomocą wyrażeń regularnych.
Użyj wyrażenia (\ w +) i zmiennej w \ 1 (lub 1 $ w zależności od implementacji bibliotek wyrażeń regularnych).
Wiele tutaj zbyt skomplikowanych sugestii. Wypróbuj to proste rozwiązanie 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;
}
Myślałem, że po to jest >>
operator strumieni strumieniowych:
string word; sin >> word;
Odpowiedź Adama Pierce'a zapewnia ręcznie obracany tokenizer przyjmujący const char*
. Jest to nieco bardziej problematyczne w przypadku iteratorów, ponieważ zwiększenie string
iteratora końcowego jest niezdefiniowane . To powiedziawszy, biorąc pod uwagę, string str{ "The quick brown fox" }
że z pewnością możemy to osiągnąć:
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;
}
Jeśli szukasz abstrakcyjnej złożoności za pomocą standardowej funkcjonalności, jak sugeruje On Freund, strtok
jest to prosta opcja:
vector<string> tokens;
for (auto i = strtok(data(str), " "); i != nullptr; i = strtok(nullptr, " ")) tokens.push_back(i);
Jeśli nie masz dostępu do C ++ 17, musisz go zastąpić, data(str)
jak w tym przykładzie: http://ideone.com/8kAGoa
Chociaż nie pokazano tego w przykładzie, strtok
nie trzeba używać tego samego separatora dla każdego tokena. Oprócz tej przewagi istnieje jednak kilka wad:
strtok
nie można używać jednocześnie na wielu strings
: albo nullptr
należy przekazać, aby kontynuować tokenizowanie bieżącej, string
albo nową, char*
aby tokenizować, należy przekazać (istnieją jednak niestandardowe implementacje, które to obsługują, takie jak:strtok_s
)strtok
nie można używać jednocześnie w wielu wątkach (może to być jednak określona implementacja, na przykład: Implementacja programu Visual Studio jest bezpieczna dla wątków )strtok
modyfikuje string
to, na którym działa, więc nie można go używać na ciągach const string
s, const char*
s lub dosłownych, aby tokenizować dowolne z nich strtok
lub operować na tym, string
komu należy zachować treść, str
trzeba by było skopiować, a następnie kopia być obsługiwanymc ++ 20zapewnia nam split_view
tokenizację ciągów w sposób nieniszczący: https://topanswers.xyz/cplusplus?q=749#a874
Poprzednie metody nie mogą wygenerować tokenizowanego vector
w miejscu, co oznacza, że bez abstrakcyjnego przekształcenia ich w funkcję pomocnika, której nie mogą zainicjować const vector<string> tokens
. Funkcjonalność i możliwość akceptowania dowolnego separatora białych znaków można wykorzystać za pomocą komendy istream_iterator
. Na przykład: const string str{ "The quick \tbrown \nfox" }
możemy to zrobić:
istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };
Wymagana konstrukcja istringstream
tej opcji ma znacznie większy koszt niż poprzednie 2 opcje, jednak koszt ten zwykle jest ukryty w kosztach string
alokacji.
Jeśli żadna z powyższych opcji nie jest wystarczająco elastyczna dla twoich potrzeb tokenizacji, najbardziej elastyczną opcją jest regex_token_iterator
oczywiście użycie tej elastyczności wiąże się z większymi wydatkami, ale znowu jest to prawdopodobnie ukryte w string
koszcie alokacji. Powiedzmy na przykład, że chcemy tokenizować na podstawie przecinków bez znaku ucieczki, również jedząc białe znaki, biorąc pod uwagę następujące dane wejściowe: const string str{ "The ,qu\\,ick ,\tbrown, fox" }
możemy to zrobić:
const regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };
strtok_s
nawiasem mówiąc, jest standardem C11. strtok_r
jest standardem POSIX2001. Pomiędzy nimi znajduje się standardowa wersja strtok
dla większości platform.
#include <cstring>
zawiera tylko wersję c99strtok
. Więc zakładam, że przekazujesz ten komentarz jako materiał pomocniczy, demonstrując dostępność strtok
rozszerzeń dla konkretnej implementacji ?
strtok_s
jest dostarczany zarówno przez C11, jak i jako samodzielne rozszerzenie w środowisku wykonawczym C. Microsoft. Jest tu ciekawa historia, w której _s
funkcje Microsoftu stały się standardem C.
Wiem, że na to pytanie już udzielono odpowiedzi, ale chcę się przyłączyć. Być może moje rozwiązanie jest nieco proste, ale oto co wymyśliłem:
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;
}
Proszę o komentarz, jeśli w moim kodzie jest lepsze podejście do czegoś lub coś jest nie tak.
AKTUALIZACJA: dodano ogólny separator
Oto podejście, które pozwala kontrolować, czy puste tokeny są uwzględniane (jak strsep), czy wykluczane (jak 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;
}
Wydaje mi się dziwne, że przy wszystkich naszych nerdach świadomych prędkości tutaj, na SO, nikt nie przedstawił wersji, która korzysta z tabeli wyszukiwania wygenerowanej w czasie kompilacji dla ogranicznika (przykładowa implementacja poniżej). Używając tabeli przeglądowej i iteratorów powinno pokonać efektywność std :: regex, jeśli nie potrzebujesz pokonać wyrażenia regularnego, po prostu użyj go, jest to standard C ++ 11 i super elastyczny.
Niektórzy sugerowali już wyrażenia regularne, ale dla noobów tutaj jest spakowany przykład, który powinien zrobić dokładnie to, czego oczekuje 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;
}
Jeśli musimy być szybsi i zaakceptować ograniczenie, że wszystkie znaki muszą mieć 8 bitów, możemy utworzyć tabelę wyszukiwania w czasie kompilacji, używając metaprogramowania:
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>;
Dzięki temu tworzenie getNextToken
funkcji jest łatwe:
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);
}
Korzystanie z niego jest również łatwe:
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;
}
Oto przykład na żywo: http://ideone.com/GKtkLQ
możesz skorzystać z boost :: make_find_iterator. Coś podobnego do tego:
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;
}
Oto mój szwajcarski scyzoryk z tokenizerem ciągów do dzielenia ciągów znaków za pomocą białych znaków, rozliczania pojedynczych i podwójnie cytowanych ciągów znaków, a także usuwania tych znaków z wyników. Użyłem RegexBuddy 4.x do wygenerowania większości fragmentu kodu, ale dodałem niestandardową obsługę usuwania wycen i kilka innych rzeczy.
#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;
}
Jeśli znana jest maksymalna długość ciągu wejściowego, który ma być tokenizowany, można to wykorzystać i zaimplementować bardzo szybką wersję. Naszkicowałem poniżej podstawową ideę, która została zainspirowana zarówno przez strtok (), jak i strukturę „tablicy przyrostków” opisaną w drugim wydaniu Jona Bentleya „Programming Perls”, rozdział 15. Klasa C ++ w tym przypadku zapewnia jedynie pewną organizację i wygodę użytkowania. Przedstawioną implementację można łatwo rozszerzyć w celu usunięcia wiodących i końcowych białych znaków w tokenach.
Zasadniczo można zastąpić znaki separatora znakami kończącymi ciąg „\ 0” i ustawić zmienne na tokeny za pomocą zmodyfikowanego ciągu. W skrajnym przypadku, gdy łańcuch składa się tylko z separatorów, jeden otrzymuje długość łańcucha plus 1 wynikowy pusty token. Praktyczne jest zduplikowanie ciągu, który ma zostać zmodyfikowany.
Plik nagłówka:
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;
}
};
Plik implementacji:
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++ ] );
}
Scenariusz użytkowania wyglądałby następująco:
// 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 ) );
}
wynik:
Item1
Item2
Item3
boost::tokenizer
jest twoim przyjacielem, ale rozważ uczynienie swojego kodu przenośnym w odniesieniu do problemów związanych z internacjonalizacją (i18n) za pomocą wstring
/ wchar_t
zamiast starszego typu string
/ char
typów.
#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
jest okropnym typem zależnym od implementacji, którego nikt nie powinien używać, chyba że jest to absolutnie konieczne.
Prosty kod C ++ (standard C ++ 98), akceptuje wiele separatorów (określonych w std :: string), używa tylko wektorów, ciągów i iteratorów.
#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;
}