Comment tokenize une chaîne en C++?
Java a une méthode de partage pratique:
String str = "The quick brown fox";
String[] results = str.split(" ");
Est-il un moyen facile de faire cela en C++?
30 réponses
votre boîtier simple peut facilement être construit en utilisant la méthode std::string::find
. Cependant, jetez un oeil à Boost.Tokenizer . C'est génial. Boost a généralement quelques outils de corde très cool.
la classe Boost tokenizer peut rendre ce genre de chose très simple:
#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;
}
}
mis à jour pour 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;
}
}
En voici un très simple:
#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;
}
utilisez strtok. A mon avis, il n'est pas nécessaire de construire une classe autour de la tokenisation à moins que strtok ne vous fournisse pas ce dont vous avez besoin. Peut-être pas, mais en plus de 15 ans d'écriture de différents codes d'analyse en C et C++, j'ai toujours utilisé strtok. Voici un exemple
char myString[] = "The quick brown fox";
char *p = strtok(myString, " ");
while (p) {
printf ("Token: %s\n", p);
p = strtok(NULL, " ");
}
quelques mises en garde (qui pourraient ne pas convenir à vos besoins). La chaîne est "détruite" dans le processus, ce qui signifie que les caractères EOS sont placés en ligne dans les points délimités. L'utilisation correcte pourrait vous obliger à prendre une non-const version de la chaîne. Vous pouvez également modifier la liste des délimiteurs milieu de l'analyser.
à mon avis, le code ci-dessus est beaucoup plus simple et plus facile à utiliser que d'écrire une classe séparée pour lui. Pour moi, c'est une de ces fonctions que la langue fournit et elle le fait bien et proprement. Il s'agit simplement d'une solution "C". C'est approprié, c'est facile, et vous n'avez pas à écrire beaucoup de code en plus :-)
un autre moyen rapide est d'utiliser getline
. Quelque chose comme:
stringstream ss("bla bla");
string s;
while (getline(ss, s, ' ')) {
cout << s << endl;
}
si vous voulez, vous pouvez faire un simple split()
méthode de retour d'un vector<string>
, qui est
vraiment utile.
vous pouvez utiliser des flux, des itérateurs, et l'algorithme de copie pour faire ceci assez directement.
#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);
}
N'en déplaise aux gens, mais pour un problème simple, vous faites des choses chemin trop compliqué. Il ya beaucoup de raisons d'utiliser Boost . Mais pour quelque chose d'aussi simple, c'est comme frapper une mouche avec un sledge 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());
}
}
par exemple (pour le cas de 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] );
}
et oui, nous aurions pu diviser() retourner un nouveau vecteur plutôt que d'en passer un. C'est trivial pour les envelopper et les surcharges. Mais selon ce que je fais, je trouve souvent préférable de réutiliser des objets préexistants plutôt que d'en créer toujours de nouveaux. (Pourvu que je n'oublie pas de vider le vecteur entre les deux!)
référence: http://www.cplusplus.com/reference/string/string / .
(j'étais à l'origine en train d'écrire une réponse à la question de Doug: C++ chaînes de modification et D'extraction basées sur des séparateurs (fermé) . Mais depuis que Martin York a clos cette question avec un pointeur ici... Je vais juste généraliser mon code.)
Boost a une forte fonction de division: boost::algorithme::split .
exemple de programme:
#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;
}
sortie:
"a"
"b"
" c "
""
"e"
"f"
""
Une solution à l'aide de 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;
}
}
je sais que vous avez demandé une solution C++, mais vous pourriez considérer cela utile:
Qt
#include <QString>
...
QString str = "The quick brown fox";
QStringList results = str.split(" ");
l'avantage par rapport à Boost dans cet exemple est qu'il s'agit d'une correspondance directe avec le code de votre poste.
voir plus à documentation Qt
voici un exemple de classe tokenizer qui pourrait faire ce que vous voulez""
//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;
}
exemple:
std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())
{
v.push_back(s.GetToken());
}
il s'agit d'une solution STL simple (~5 lignes!) en utilisant std::find
et std::find_first_not_of
qui gère les répétitions du délimiteur (comme les espaces ou les périodes par exemple), ainsi que les délimiteurs de tête et de queue:
#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);
}
}
l'Essayer live !
pystring est une petite bibliothèque qui implémente un tas de fonctions de chaîne de Python, y compris la méthode 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, "-");
j'ai posté cette réponse pour une question similaire.
Ne pas réinventer la roue. J'ai utilisé un certain nombre de bibliothèques et la plus rapide et la plus flexible que j'ai rencontré est: C++ String Toolkit Library .
voici un exemple d'utilisation que j'ai posté ailleurs sur le 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 t("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 u("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;
}
cochez cet exemple. Il pourrait vous aider..
#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;
}
vous pouvez simplement utiliser une bibliothèque d'expressions régulières et résoudre cela en utilisant des expressions régulières.
utilise l'expression (\w+) et la variable dans \1 (ou $1 selon l'implémentation de la bibliothèque des expressions régulières).
pour les choses simples j'utilise juste ce qui suit:
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;
}
avertissement lâche: j'écris un logiciel de traitement de données en temps réel où les données viennent par des fichiers binaires, sockets, ou un appel API (cartes d'E/S, Appareil photo). Je n'utilise jamais cette fonction pour quelque chose de plus compliqué ou de plus critique que la lecture de fichiers de configuration externes au démarrage.
MFC/ATL a un très joli tokenizer. De 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
beaucoup de suggestions trop compliquées ici. Essayez cette std simple:: string solution:
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;
}
je pensais que c'était le but de l'opérateur >>
sur string streams:
string word; sin >> word;
la réponse D'Adam Pierce fournit un tokenizer fait à la main avec un const char*
. C'est un peu plus problématique à faire avec les itérateurs parce que incrementing a string
fin itérateur est non défini . Cela dit, étant donné string str{ "The quick brown fox" }
nous pouvons certainement accomplir ceci:
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;
}
si vous cherchez à la complexité abstraite en utilisant la fonctionnalité standard, comme sur Freund suggère strtok
est une option simple:
vector<string> tokens;
for (auto i = strtok(data(str), " "); i != nullptr; i = strtok(nullptr, " ")) tokens.push_back(i);
si vous n'avez pas accès à C++17 vous devrez remplacer data(str)
comme dans cet exemple: http://ideone.com/8kAGoa
bien qu'il ne soit pas démontré dans l'exemple, strtok
n'est pas nécessaire d'utiliser le même séparateur pour chaque jeton. En plus de cet avantage cependant, il y a plusieurs inconvénients:
-
strtok
ne peut pas être utilisé sur le multiplestrings
en même temps: soit unnullptr
doit être passé pour continuer à tokenizer l'actuelstring
ou un nouveauchar*
pour tokenizer doit être passé (Il ya des mises en œuvre non-standard qui ne soutiennent ce toutefois, comme:strtok_s
) - pour la même raison
strtok
ne peut pas être utilisé sur plusieurs threads simultanément (ceci peut toutefois être défini comme une implémentation, par exemple: L'implémentation de Visual Studio est sécurisée pour les threads ) - Appel
strtok
modifiestring
c'est de l'exploitation, de sorte qu'il ne peut pas être utilisé sur desconst string
s,const char*
s, ou des chaînes de caractères littérales, pour marquer ces derniers avecstrtok
ou pour fonctionner sur unstring
qui est contenu doivent être préservés,str
devrait être copié, puis la copie pourrait être exploité sur
les deux méthodes précédentes ne peuvent pas générer un tokenized vector
en place, ce qui signifie sans les abstraire dans une fonction d'aide qu'ils ne peuvent pas initialiser const vector<string> tokens
. Que la fonctionnalité et la capacité d'accepter n'importe quel" délimiteur d'espace blanc peut être harnachée en utilisant un istream_iterator
. Par exemple donné: const string str{ "The quick \tbrown \nfox" }
nous pouvons faire ceci:
istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };
la construction requise d'un istringstream
pour cette option a un coût beaucoup plus élevé que les 2 options précédentes, mais ce coût est habituellement caché dans la dépense de string
allocation.
Si aucun des les options ci-dessus sont suffisamment flexibles pour vos besoins de tokenization, l'option la plus flexible est d'utiliser un regex_token_iterator
bien sûr avec cette flexibilité vient plus de dépenses, mais encore une fois, ce est probablement caché dans le coût d'attribution string
. Disons par exemple que nous voulons tokenize basé sur des virgules Non-échappées, mangeant aussi de l'espace blanc, étant donné l'entrée suivante: const string str{ "The ,qu\,ick ,\tbrown, fox" }
nous pouvons faire ceci:
const regex re{ "\s*((?:[^\\,]|\\.)*?)\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };
Voici une approche qui vous permet de contrôler si les jetons vides sont inclus (comme strsep) ou exclus (comme 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 != '"151900920"') // 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;
}
me semble étrange qu'avec tous les nerds américains soucieux de la vitesse, personne n'ait présenté une version qui utilise une table de recherche générée par le temps de compilation pour le délimiteur (exemple d'implémentation plus bas). Utiliser une table de recherche et des itérateurs devrait battre std::regex dans l'efficacité, si vous n'avez pas besoin de battre regex, il suffit de l'utiliser, son standard à partir de C++11 et super flexible.
certains ont déjà suggéré regex mais pour les noobs voici un exemple emballé que devrait faire exactement ce que L'OP attend:
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;
}
si nous avons besoin d'être plus rapide et accepter la contrainte que tous les caractères doivent être de 8 bits, nous pouvons faire une table de recherche au moment de compiler en utilisant la métaprogrammation:
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>;
avec celui en place faisant une fonction getNextToken
est 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);
}
L'utiliser est aussi 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;
}
voici un exemple en direct: http://ideone.com/GKtkLQ
il n'y a pas de moyen direct de le faire. Référez-vous à ce code projet de code source pour savoir comment construire une classe pour cela.
vous pouvez profiter de boost::make_find_iterator. Quelque chose de semblable à ceci:
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;
}
si la longueur maximale de la chaîne à tokeniser est connue, on peut l'exploiter et implémenter une version très rapide. Je esquisse l'idée de base ci-dessous, qui a été inspirée à la fois par strtok() et le "tableau de suffixe" - structure de données décrit Jon Bentley "Perls de programmation" 2e édition, Chapitre 15. La Classe C++ dans ce cas ne donne qu'une certaine organisation et commodité d'utilisation. L'implémentation montrée peut être facilement étendue pour supprimer les espaces de tête et de fuite les personnages dans les jetons.
fondamentalement, on peut remplacer les caractères séparateurs par des caractères '\0'à terminaison de chaîne et mettre des pointeurs vers les jetons avec la chaîne modifiée. Dans le cas extrême où la chaîne se compose uniquement de séparateurs, on obtient la longueur de la chaîne plus 1 tokens vides résultant. C'est pratique pour dupliquer la chaîne à modifier.
fichier d'en-Tête:
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;
}
};
fichier de mise en œuvre:
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 != '"151910920"' );
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 == '"151910920"' )
{ // item or line finished
// overwrite separator with a 0-terminating character:
mBuff[ idx ] = '"151910920"';
// count-up items:
mNumTokens ++;
} // if
} while( line[ idx++ ] );
}
, Un scénario d'utilisation serait:
// 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 ) );
}
sortie:
Item1
Item2
Item3
boost::tokenizer
est votre ami, mais envisager de rendre votre code portable avec référence à l'internationalisation (i18n) questions en utilisant wstring
/ wchar_t
au lieu de l'héritage string
/ char
types.
#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;
}
code C++ Simple (standard C++98), accepte les délimiteurs multiples (spécifiés dans une norme std::string), utilise uniquement des vecteurs, des chaînes et des itérateurs.
#include <iostream>
#include <vector>
#include <string>
#include <stdexcept>
std::vector<std::string>
split(const std::string& str, const std::string& delim){
std::vector<std::string> result;
if (str.empty())
throw std::runtime_error("Can not tokenize an empty string!");
std::string::const_iterator begin, str_it;
begin = str_it = str.begin();
do {
while (delim.find(*str_it) == std::string::npos && str_it != str.end())
str_it++; // find the position of the first delimiter in str
std::string token = std::string(begin, str_it); // grab the token
if (!token.empty()) // empty token only when str starts with a delimiter
result.push_back(token); // push the token into a vector<string>
while (delim.find(*str_it) != std::string::npos && str_it != str.end())
str_it++; // ignore the additional consecutive delimiters
begin = str_it; // process the remaining tokens
} while (str_it != str.end());
return result;
}
int main() {
std::string test_string = ".this is.a.../.simple;;test;;;END";
std::string delim = "; ./"; // string containing the delimiters
std::vector<std::string> tokens = split(test_string, delim);
for (std::vector<std::string>::const_iterator it = tokens.begin();
it != tokens.end(); it++)
std::cout << *it << std::endl;
}
/// split a string into multiple sub strings, based on a separator string
/// for example, if separator="::",
///
/// s = "abc" -> "abc"
///
/// s = "abc::def xy::st:" -> "abc", "def xy" and "st:",
///
/// s = "::abc::" -> "abc"
///
/// s = "::" -> NO sub strings found
///
/// s = "" -> NO sub strings found
///
/// then append the sub-strings to the end of the vector v.
///
/// the idea comes from the findUrls() function of "Accelerated C++", chapt7,
/// findurls.cpp
///
void split(const string& s, const string& sep, vector<string>& v)
{
typedef string::const_iterator iter;
iter b = s.begin(), e = s.end(), i;
iter sep_b = sep.begin(), sep_e = sep.end();
// search through s
while (b != e){
i = search(b, e, sep_b, sep_e);
// no more separator found
if (i == e){
// it's not an empty string
if (b != e)
v.push_back(string(b, e));
break;
}
else if (i == b){
// the separator is found and right at the beginning
// in this case, we need to move on and search for the
// next separator
b = i + sep.length();
}
else{
// found the separator
v.push_back(string(b, i));
b = i;
}
}
}
la bibliothèque boost est bonne, mais ils ne sont pas toujours disponibles. Faire ce genre de choses à la main est aussi un bon exercice de cerveau. Ici, nous n'utilisons que l'algorithme std::search() de la STL, voir le code ci-dessus.