Java a une méthode de partage pratique:
String str = "The quick brown fox";
String[] results = str.split(" ");
Existe-t-il un moyen simple de le faire en C ++?
Java a une méthode de partage pratique:
String str = "The quick brown fox";
String[] results = str.split(" ");
Existe-t-il un moyen simple de le faire en C ++?
Réponses:
Les algorithmes de bibliothèque standard C ++ sont assez universellement basés autour d'itérateurs plutôt que de conteneurs concrets. Malheureusement, cela rend difficile la fourniture d'une split
fonction de type Java dans la bibliothèque standard C ++, même si personne ne prétend que ce serait pratique. Mais quel serait son type de retour?std::vector<std::basic_string<…>>
? Peut-être, mais alors nous sommes obligés d'effectuer des allocations (potentiellement redondantes et coûteuses).
Au lieu de cela, C ++ offre une multitude de façons de diviser les chaînes en fonction de délimiteurs arbitrairement complexes, mais aucune d'entre elles n'est aussi bien encapsulée que dans d'autres langages. Les nombreuses façons de remplir des articles de blog entiers .
À sa plus simple expression, vous pouvez répéter l’utilisation std::string::find
jusqu’à ce que vous frappiez std::string::npos
et extraire le contenu à l’aide destd::string::substr
.
Une version plus fluide (et idiomatique, mais basique) pour le fractionnement sur un espace blanc utiliserait std::istringstream
:
auto iss = std::istringstream{"The quick brown fox"};
auto str = std::string{};
while (iss >> str) {
process(str);
}
À l'aide de std::istream_iterator
s , le contenu du flux de chaînes peut également être copié dans un vecteur à l'aide de son constructeur de plage d'itérateur.
Plusieurs bibliothèques (telles que Boost.Tokenizer ) proposent des tokenisers spécifiques.
Un fractionnement plus avancé nécessite des expressions régulières. C ++ fournit notamment std::regex_token_iterator
à cet effet:
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{}
);
le classe de tokenizer Boost peut rendre ce genre de chose assez 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;
}
}
char_separator
constructeur ( drop_empty_tokens
c'est la valeur par défaut, l'alternative l'est keep_empty_tokens
).
.h
pour les en-têtes C)
En voici une 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. À mon avis, il n'est pas nécessaire de créer une classe autour de la tokenisation à moins que strtok ne vous fournisse pas ce dont vous avez besoin. Ce n'est peut-être pas le cas, mais en 15 ans et plus d'écriture de divers 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" au cours du processus, ce qui signifie que les caractères EOS sont placés en ligne dans les points de délimitation. Une utilisation correcte peut vous obliger à créer une version non constante de la chaîne. Vous pouvez également modifier la liste des délimiteurs en cours d'analyse.
À mon avis, le code ci-dessus est beaucoup plus simple et plus facile à utiliser que d'écrire une classe distincte pour lui. Pour moi, c'est l'une de ces fonctions que le langage fournit et il le fait bien et proprement. C'est simplement une solution "basée sur C". C'est approprié, c'est facile, et vous n'avez pas besoin d'écrire beaucoup de code supplémentaire :-)
Un autre moyen rapide consiste à utiliser getline
. Quelque chose comme:
stringstream ss("bla bla");
string s;
while (getline(ss, s, ' ')) {
cout << s << endl;
}
Si vous le souhaitez, vous pouvez créer une split()
méthode simple renvoyant un vector<string>
, ce qui est vraiment utile.
Vous pouvez utiliser des flux, des itérateurs et l'algorithme de copie pour le faire 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);
}
std
façon dont je sais d'où vient mon objet, ce n'est qu'une question de style.
Pas de gens d'infraction, mais pour un problème simple, vous faites des choses façon trop compliquée. Il existe de nombreuses raisons d'utiliser Boost . Mais pour quelque chose d'aussi simple, c'est comme frapper une mouche avec un traîneau 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 split () retourner un nouveau vecteur plutôt que d'en passer un. Il est trivial d'envelopper et de surcharger. 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. (Tant que je n'oublie pas de vider le vecteur entre les deux!)
Référence: http://www.cplusplus.com/reference/string/string/ .
(J'écrivais à l'origine une réponse à la question de Doug: modification et extraction de chaînes C ++ basées sur des séparateurs (fermé) . Mais puisque Martin York a fermé cette question avec un pointeur ici ... je vais simplement généraliser mon code.)
std::string
classe n'inclut-elle pas une fonction split ()?
start = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());
et la boucle while devrait être while (start != string::npos)
. De plus, je vérifie la sous-chaîne pour m'assurer qu'elle n'est pas vide avant de l'insérer dans le vecteur.
Une solution utilisant 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 a une forte fonction de partage: boost :: algorithm :: 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;
}
Production:
"a"
"b"
" c "
""
"e"
"f"
""
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'un mappage direct un à un avec le code de votre message.
En savoir plus sur la documentation Qt
Voici un exemple de classe de 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 simple en STL (~ 5 lignes!) 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 début et de fin:
#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);
}
}
Essayez-le en direct !
pystring est une petite bibliothèque qui implémente un tas de fonctions de chaîne de Python, y compris la méthode de fractionnement:
#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 réinventez pas la roue. J'ai utilisé un certain nombre de bibliothèques et la plus rapide et la plus flexible que j'ai rencontrée est la suivante: C ++ String Toolkit Library .
Voici un exemple d'utilisation que j'ai publié 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 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;
}
Vérifiez cet exemple. Cela 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;
}
while ( is >> tmps ) { std::cout << tmps << "\n"; }
MFC / ATL a un très joli tokenizer. Depuis 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
Si vous êtes prêt à utiliser C, vous pouvez utiliser la fonction strtok . Vous devez faire attention aux problèmes de multi-threading lors de son utilisation.
Pour des trucs simples, j'utilise simplement 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 arrivent via des fichiers binaires, des sockets ou un appel API (cartes d'E / S, caméras). Je n'utilise jamais cette fonction pour quelque chose de plus compliqué ou de temps critique que la lecture de fichiers de configuration externes au démarrage.
Vous pouvez simplement utiliser une bibliothèque d'expressions régulières et résoudre cela en utilisant des expressions régulières.
Utilisez l'expression (\ w +) et la variable dans \ 1 (ou $ 1 selon l'implémentation de la bibliothèque d'expressions régulières).
Beaucoup de suggestions trop compliquées ici. Essayez cette solution simple 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;
}
Je pensais que c'était pour cela que l' >>
opérateur sur les flux de chaînes:
string word; sin >> word;
La réponse d'Adam Pierce fournit un jeton tourné à la main prenant en a const char*
. C'est un peu plus problématique avec les itérateurs car l' incrémentation de l' string
itérateur de fin n'est pas définie . Cela dit, étant donné que string str{ "The quick brown fox" }
nous pouvons certainement y parvenir:
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 recherchez une complexité abstraite en utilisant des fonctionnalités standard, comme le suggère On Freund, strtok
c'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 que cela ne soit pas démontré dans l'exemple, il strtok
n'est pas nécessaire d'utiliser le même délimiteur pour chaque jeton. Parallèlement à cet avantage, il existe cependant plusieurs inconvénients:
strtok
ne peut pas être utilisé sur plusieurs strings
en même temps: Soit un nullptr
doit être adopté pour continuer tokenizing le courant string
ou une nouvelle char*
à tokenize doit être adopté (il y a quelques implémentations non standard qui ne supportent ce cependant, comme: strtok_s
)strtok
ne peut pas être utilisé sur plusieurs threads simultanément (cela peut cependant être défini par l'implémentation, par exemple: l'implémentation de Visual Studio est thread-safe )strtok
modifie le string
système sur lequel il fonctionne, il ne peut donc pas être utilisé sur des chaînes const string
s, const char*
s ou littérales, pour symboliser l'un d'entre eux avec strtok
ou pour fonctionner sur un string
contenu dont le contenu doit être conservé, str
doit être copié, puis la copie peut être opéréc ++ 20nous fournit split_view
pour tokeniser les chaînes, de manière non destructive: https://topanswers.xyz/cplusplus?q=749#a874
Les méthodes précédentes ne peuvent pas générer un vector
in- tokenized , c'est-à-dire sans les abstraire dans une fonction d'assistance qu'ils ne peuvent pas initialiser const vector<string> tokens
. Cette fonctionnalité et la capacité d'accepter tout délimiteur d'espace blanc peuvent être exploitées à l'aide d'un istream_iterator
. Par exemple donné: const string str{ "The quick \tbrown \nfox" }
nous pouvons le faire:
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 généralement caché dans les frais d' string
allocation.
Si aucune des options ci-dessus n'est suffisamment flexible pour vos besoins de tokenisation, l'option la plus flexible utilise regex_token_iterator
bien sûr une flexibilité qui entraîne des dépenses plus importantes, mais là encore, cela est probablement caché dans le string
coût d'allocation. Supposons par exemple que nous souhaitons effectuer une tokenisation basée sur des virgules non échappées, en mangeant également des espaces blancs, étant donné l'entrée suivante: const string str{ "The ,qu\\,ick ,\tbrown, fox" }
nous pouvons le faire:
const regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };
strtok_s
est d'ailleurs la norme C11. strtok_r
est une norme POSIX2001. Entre les deux, il existe une version standard réentrante strtok
pour la plupart des plateformes.
#include <cstring>
ne comprend que la version c99 de strtok
. Donc, mon hypothèse est que vous fournissez simplement ce commentaire comme matériel de support, démontrant la disponibilité spécifique des implémentations des strtok
extensions?
strtok_s
est fourni par C11 et en tant qu'extension autonome dans le runtime C de Microsoft. Il y a un peu d'histoire ici où les _s
fonctions de Microsoft sont devenues la norme C.
Je sais que cette question a déjà reçu une réponse, mais je souhaite y contribuer. Peut-être que ma solution est un peu simple, mais voici ce que j'ai trouvé:
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;
}
Veuillez commenter s'il existe une meilleure approche de quelque chose dans mon code ou si quelque chose ne va pas.
MISE À JOUR: ajout d'un séparateur générique
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 != '\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;
}
Il me semble étrange qu'avec tous les nerds soucieux de la vitesse ici sur SO, personne n'a 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 en termes d'efficacité, si vous n'avez pas besoin de battre regex, utilisez-le simplement, son standard en C ++ 11 et super flexible.
Certains ont déjà suggéré l'expression régulière, mais pour les noobs, voici un exemple compact qui 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 devons être plus rapides et accepter la contrainte que tous les caractères doivent être 8 bits, nous pouvons faire une table de recherche au moment de la compilation 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 cela en place, faire une getNextToken
fonction 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);
}
Son utilisation est également simple:
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
vous pouvez profiter de boost :: make_find_iterator. Quelque chose de similaire à 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;
}
Voici mon couteau Swiss® Army de jetons de chaîne pour séparer les chaînes par des espaces, en tenant compte des chaînes enveloppées de guillemets simples et doubles ainsi que de supprimer ces caractères des résultats. J'ai utilisé RegexBuddy 4.x pour générer la plupart des extraits de code, mais j'ai ajouté une gestion personnalisée pour supprimer les devis et quelques autres choses.
#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;
}
Si la longueur maximale de la chaîne d'entrée à tokeniser est connue, on peut l'exploiter et implémenter une version très rapide. J'esquisse l'idée de base ci-dessous, qui a été inspirée à la fois par strtok () et la structure de données "tableau de suffixes" décrit la 2ème édition de "Programmation Perls" de Jon Bentley, chapitre 15. La classe C ++ dans ce cas ne donne qu'une certaine organisation et commodité utile. L'implémentation illustrée peut être facilement étendue pour supprimer les caractères d'espacement avant et arrière dans les jetons.
Fondamentalement, on peut remplacer les caractères de séparation par des caractères «\ 0» de terminaison de chaîne et définir des pointeurs sur 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 chaîne plus 1 jetons vides résultants. Il est pratique de dupliquer la chaîne à modifier.
En tête de fichier:
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 d'implémentation:
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++ ] );
}
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 ) );
}
production:
Item1
Item2
Item3
boost::tokenizer
est votre ami, mais envisagez de rendre votre code portable en référence aux problèmes d'internationalisation (i18n) 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;
}
wchar_t
est un type dépendant de la mise en œuvre horrible que personne ne devrait utiliser sauf si absolument nécessaire.
Le code C ++ simple (C ++ 98 standard), accepte plusieurs délimiteurs (spécifiés dans une chaîne std ::), 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;
}