Java ha un comodo metodo split:
String str = "The quick brown fox";
String[] results = str.split(" ");
C'è un modo semplice per farlo in C ++?
Java ha un comodo metodo split:
String str = "The quick brown fox";
String[] results = str.split(" ");
C'è un modo semplice per farlo in C ++?
Risposte:
Gli algoritmi di libreria standard C ++ si basano piuttosto universalmente su iteratori piuttosto che su contenitori concreti. Sfortunatamente ciò rende difficile fornire una split
funzione simile a Java nella libreria standard C ++, anche se nessuno sostiene che sarebbe conveniente. Ma quale sarebbe il suo tipo di ritorno?std::vector<std::basic_string<…>>
? Forse, ma poi siamo costretti a eseguire allocazioni (potenzialmente ridondanti e costose).
Al contrario, C ++ offre una moltitudine di modi per dividere le stringhe in base a delimitatori arbitrariamente complessi, ma nessuno di essi è incapsulato in modo altrettanto preciso che in altre lingue. I numerosi modi riempiono interi post del blog .
Nella sua forma più semplice, puoi iterare usando std::string::find
fino a quando non colpisci std::string::npos
ed estrarre i contenuti usando std::string::substr
.
Una versione più fluida (e idiomatica, ma di base) per la divisione su spazi bianchi userebbe un std::istringstream
:
auto iss = std::istringstream{"The quick brown fox"};
auto str = std::string{};
while (iss >> str) {
process(str);
}
Usando std::istream_iterator
s , il contenuto del flusso di stringhe può anche essere copiato in un vettore usando il suo costruttore di intervalli iteratore.
Più librerie (come Boost.Tokenizer ) offrono tokeniser specifici.
Una divisione più avanzata richiede espressioni regolari. C ++ fornisce std::regex_token_iterator
a questo scopo in particolare:
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{}
);
La classe di tokenizer Boost può rendere questo genere di cose abbastanza semplice:
#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;
}
}
Aggiornato per 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
costruttore ( drop_empty_tokens
è l'impostazione predefinita, l'alternativa è keep_empty_tokens
).
.h
per le intestazioni C)
Eccone uno molto semplice:
#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;
}
Usa strtok. A mio avviso, non è necessario creare una classe sul tokenizzazione a meno che strtok non ti fornisca ciò di cui hai bisogno. Potrebbe non esserlo, ma in oltre 15 anni di scrittura di vari codici di analisi in C e C ++, ho sempre usato strtok. Ecco un esempio
char myString[] = "The quick brown fox";
char *p = strtok(myString, " ");
while (p) {
printf ("Token: %s\n", p);
p = strtok(NULL, " ");
}
Alcune avvertenze (che potrebbero non soddisfare le tue esigenze). La stringa viene "distrutta" nel processo, il che significa che i caratteri EOS vengono posizionati in linea nei punti del delimitatore. L'uso corretto potrebbe richiedere di creare una versione non const della stringa. È inoltre possibile modificare l'elenco dei delimitatori durante l'analisi.
A mio avviso, il codice sopra è molto più semplice e facile da usare rispetto alla scrittura di una classe separata per esso. Per me, questa è una di quelle funzioni che la lingua fornisce e lo fa bene e in modo pulito. È semplicemente una soluzione a "C". È appropriato, è facile e non devi scrivere molto codice extra :-)
Un altro modo rapido è usare getline
. Qualcosa di simile a:
stringstream ss("bla bla");
string s;
while (getline(ss, s, ' ')) {
cout << s << endl;
}
Se lo desideri, puoi creare un split()
metodo semplice per restituire a vector<string>
, il che è davvero utile.
È possibile utilizzare stream, iteratori e algoritmo di copia per farlo in modo abbastanza diretto.
#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
io sappia da dove viene il mio oggetto, è solo una questione di stile.
Nessun gente offesa, ma per un semplice problema così, si stanno facendo le cose modo troppo complicato. Ci sono molti motivi per usare Boost . Ma per qualcosa di così semplice, è come colpire una mosca con una slitta da 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());
}
}
Ad esempio (per il caso di Doug),
#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] );
}
E sì, avremmo potuto dividere () restituire un nuovo vettore invece di passarne uno. È banale avvolgere e sovraccaricare. Ma a seconda di cosa sto facendo, trovo spesso meglio riutilizzare oggetti preesistenti anziché crearne sempre di nuovi. (Fintanto che non dimentico di svuotare il vettore in mezzo!)
Riferimento: http://www.cplusplus.com/reference/string/string/ .
(Inizialmente stavo scrivendo una risposta alla domanda di Doug: Modifica ed estrazione di stringhe C ++ basate sui separatori (chiuso) . Ma da quando Martin York ha chiuso quella domanda con un puntatore qui ... Generalizzerò semplicemente il mio codice.)
std::string
classe non include una funzione split ()?
start = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());
e il ciclo while dovrebbe essere while (start != string::npos)
. Inoltre, controllo la sottostringa per essere sicuro che non sia vuota prima di inserirla nel vettore.
Una soluzione che utilizza 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 ha una forte funzione split: boost :: algoritmo :: split .
Programma di esempio:
#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;
}
Produzione:
"a"
"b"
" c "
""
"e"
"f"
""
So che hai chiesto una soluzione C ++, ma potresti considerarlo utile:
Qt
#include <QString>
...
QString str = "The quick brown fox";
QStringList results = str.split(" ");
Il vantaggio rispetto a Boost in questo esempio è che è un mapping diretto uno a uno al codice del tuo post.
Vedi di più nella documentazione di Qt
Ecco una classe di tokenizer di esempio che potrebbe fare quello che vuoi
//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;
}
Esempio:
std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())
{
v.push_back(s.GetToken());
}
Questa è una semplice soluzione solo STL (~ 5 righe!) Che usa std::find
e std::find_first_not_of
gestisce le ripetizioni del delimitatore (come spazi o punti per esempio), così come i delimitatori iniziali e finali:
#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);
}
}
Provalo vivo !
pystring è una piccola libreria che implementa un sacco di funzioni di stringa di Python, incluso il metodo 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, "-");
Ho pubblicato questa risposta per una domanda simile.
Non reinventare la ruota. Ho usato un certo numero di librerie e la più veloce e flessibile che abbia mai incontrato è: C ++ String Toolkit Library .
Ecco un esempio di come usarlo che ho pubblicato altrove sullo 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;
}
Guarda questo esempio. Potrebbe aiutarti ..
#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 ha un tokenizer molto bello. Da 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
Se vuoi usare C, puoi usare la funzione strtok . Dovresti prestare attenzione ai problemi multi-threading quando lo usi.
Per cose semplici uso solo quanto segue:
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;
}
Disclaimer codardo: scrivo software di elaborazione dati in tempo reale in cui i dati arrivano attraverso file binari, socket o alcune chiamate API (schede I / O, fotocamera). Non utilizzo mai questa funzione per qualcosa di più complicato o critico in termini di tempo rispetto alla lettura di file di configurazione esterni all'avvio.
Puoi semplicemente usare una libreria di espressioni regolari e risolverla usando espressioni regolari.
Usa expression (\ w +) e la variabile in \ 1 (o $ 1 a seconda dell'implementazione della libreria di espressioni regolari).
Molti suggerimenti eccessivamente complicati qui. Prova questa semplice soluzione 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;
}
Ho pensato che fosse quello a cui l' >>
operatore su stream di string era:
string word; sin >> word;
La risposta di Adam Pierce fornisce un tokenizer filato a mano che contiene a const char*
. È un po 'più problematico avere a che fare con gli iteratori perché l' incremento di un string
iteratore finale non è definito . Detto questo, dato string str{ "The quick brown fox" }
che possiamo certamente realizzare questo:
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;
}
Se stai cercando una complessità astratta utilizzando la funzionalità standard, come suggerisce On Freund strtok
è un'opzione semplice:
vector<string> tokens;
for (auto i = strtok(data(str), " "); i != nullptr; i = strtok(nullptr, " ")) tokens.push_back(i);
Se non hai accesso a C ++ 17 dovrai sostituire data(str)
come in questo esempio: http://ideone.com/8kAGoa
Sebbene non sia dimostrato nell'esempio, strtok
non è necessario utilizzare lo stesso delimitatore per ciascun token. Insieme a questo vantaggio, ci sono diversi svantaggi:
strtok
non possono essere utilizzati contemporaneamente su più strings
: O nullptr
deve essere passato a per continuare a tokenizzare la corrente string
o char*
deve essere passato un nuovo tokenize (ci sono comunque alcune implementazioni non standard che supportano questo, come ad esempio:strtok_s
)strtok
non può essere utilizzato contemporaneamente su più thread (ciò può tuttavia essere definito dall'implementazione, ad esempio: l'implementazione di Visual Studio è thread-safe )strtok
modifica la string
funzione su cui sta operando, quindi non può essere utilizzata su stringhe const string
s, const char*
s o letterali, per tokenizzare una di queste strtok
o per operare su un string
contenuto che deve essere preservato, str
dovrebbe essere copiato, quindi la copia potrebbe essere operatoc ++ 20ci fornisce split_view
tokenize stringhe, in modo non distruttivo: https://topanswers.xyz/cplusplus?q=749#a874
I metodi precedenti non possono generare un token vector
sul posto, il che significa che senza inizializzarli in una funzione di supporto che non possono essere inizializzati const vector<string> tokens
. Quella funzionalità e la capacità di accettare qualsiasi delimitatore di spazi bianchi possono essere sfruttate usando un istream_iterator
. Ad esempio dato: const string str{ "The quick \tbrown \nfox" }
possiamo fare questo:
istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };
La costruzione richiesta di istringstream
un'opzione per ha un costo molto maggiore rispetto alle precedenti 2 opzioni, tuttavia questo costo è in genere nascosto nelle spese di string
allocazione.
Se nessuna delle opzioni di cui sopra è sufficientemente flessibile per le tue esigenze di tokenizzazione, l'opzione più flessibile sta regex_token_iterator
ovviamente utilizzando una flessibilità che comporta costi maggiori, ma di nuovo è probabilmente nascosto nel string
costo di allocazione. Diciamo ad esempio che vogliamo tokenizzare sulla base di virgole non di escape, anche mangiando spazi bianchi, dato il seguente input: const string str{ "The ,qu\\,ick ,\tbrown, fox" }
possiamo fare questo:
const regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };
strtok_s
è C11 standard, a proposito. strtok_r
è uno standard POSIX2001. Tra entrambi, esiste una versione standard di rientro strtok
per la maggior parte delle piattaforme.
#include <cstring>
include solo la versione c99 di strtok
. Quindi la mia ipotesi è che stai solo fornendo questo commento come materiale di supporto, dimostrando la disponibilità specifica delle strtok
estensioni per l'implementazione ?
strtok_s
è fornito sia da C11 sia come estensione standalone nel runtime C di Microsoft. C'è un curioso po 'di storia qui in cui le _s
funzioni di Microsoft sono diventate lo standard C.
So che questa domanda ha già una risposta, ma voglio contribuire. Forse la mia soluzione è un po 'semplice ma questo è quello che mi è venuto in mente:
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;
}
Si prega di commentare se c'è un approccio migliore a qualcosa nel mio codice o se qualcosa non va.
AGGIORNAMENTO: aggiunto separatore generico
Ecco un approccio che ti consente di controllare se i token vuoti sono inclusi (come strsep) o esclusi (come 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;
}
Mi sembra strano che con tutti noi secchioni attenti alla velocità qui su SO nessuno abbia presentato una versione che utilizza una tabella di ricerca generata dal tempo di compilazione per il delimitatore (esempio di implementazione più in basso). Utilizzando una tabella di ricerca e gli iteratori dovrebbero battere std :: regex in efficienza, se non hai bisogno di battere regex, basta usarlo, il suo standard a partire da C ++ 11 e super flessibile.
Alcuni hanno già suggerito regex ma per i noob ecco un esempio impacchettato che dovrebbe fare esattamente ciò che l'OP si aspetta:
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;
}
Se dobbiamo essere più veloci e accettare il vincolo che tutti i caratteri devono essere 8 bit, possiamo creare una tabella di ricerca al momento della compilazione usando la metaprogrammazione:
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>;
Con quello sul posto fare una getNextToken
funzione è facile:
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);
}
Usarlo è anche facile:
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;
}
Ecco un esempio dal vivo: http://ideone.com/GKtkLQ
puoi approfittare di boost :: make_find_iterator. Qualcosa di simile a questo:
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;
}
Ecco il mio Swiss® Army Knife di tokenizer per stringhe per suddividere le stringhe in base agli spazi bianchi, tenendo conto delle stringhe avvolte a virgoletta singola e doppia, nonché di eliminare quei caratteri dai risultati. Ho usato RegexBuddy 4.x per generare la maggior parte dello snippet di codice, ma ho aggiunto una gestione personalizzata per le citazioni di stripping e poche altre cose.
#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;
}
Se è nota la lunghezza massima della stringa di input da tokenizzare, è possibile sfruttarla e implementare una versione molto veloce. Sto abbozzando l'idea di base di seguito, che è stata ispirata da strtok () e dalla struttura dei dati "suffix array", descritta nella seconda edizione di "Programming Perls" di Jon Bentley, capitolo 15. La classe C ++ in questo caso offre solo un po 'di organizzazione e convenienza d'uso. L'implementazione mostrata può essere facilmente estesa per rimuovere i caratteri spazi bianchi iniziali e finali nei token.
Fondamentalmente si possono sostituire i caratteri di separazione con caratteri "\ 0" che terminano la stringa e impostare i puntatori sui token all'interno della stringa modificata. Nel caso estremo in cui la stringa è composta solo da separatori, si ottiene la lunghezza della stringa più 1 risultante token vuoti. È pratico duplicare la stringa da modificare.
File di intestazione:
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;
}
};
File di implementazione:
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++ ] );
}
Uno scenario di utilizzo sarebbe:
// 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 ) );
}
produzione:
Item1
Item2
Item3
boost::tokenizer
è tuo amico, ma considera di rendere il tuo codice portatile con riferimento ai problemi di internazionalizzazione (i18n) utilizzando wstring
/ wchar_t
anziché i legacy string
/ char
tipi.
#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
è un tipo dipendente dall'implementazione orribile che nessuno dovrebbe usare se non assolutamente necessario.
Semplice codice C ++ (standard C ++ 98), accetta più delimitatori (specificati in uno std :: string), utilizza solo vettori, stringhe e iteratori.
#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;
}