Java hat eine bequeme Split-Methode:
String str = "The quick brown fox";
String[] results = str.split(" ");
Gibt es eine einfache Möglichkeit, dies in C ++ zu tun?
Java hat eine bequeme Split-Methode:
String str = "The quick brown fox";
String[] results = str.split(" ");
Gibt es eine einfache Möglichkeit, dies in C ++ zu tun?
Antworten:
C ++ - Standardbibliotheksalgorithmen basieren ziemlich universell auf Iteratoren und nicht auf konkreten Containern. Leider ist es schwierig, eine Java-ähnliche split
Funktion in der C ++ - Standardbibliothek bereitzustellen , obwohl niemand argumentiert, dass dies praktisch wäre. Aber wie würde die Rückgabe aussehen? std::vector<std::basic_string<…>>
? Vielleicht, aber dann sind wir gezwungen, (möglicherweise redundante und kostspielige) Zuweisungen durchzuführen.
Stattdessen bietet C ++ eine Vielzahl von Möglichkeiten, Zeichenfolgen basierend auf beliebig komplexen Trennzeichen zu teilen, aber keine davon ist so gut gekapselt wie in anderen Sprachen. Die zahlreichen Möglichkeiten füllen ganze Blog-Beiträge .
Im einfachsten Fall können Sie mit iterieren, std::string::find
bis Sie drücken std::string::npos
, und den Inhalt mit extrahieren std::string::substr
.
Eine flüssigere (und idiomatischere, aber einfachere) Version zum Aufteilen auf Leerzeichen würde Folgendes verwenden std::istringstream
:
auto iss = std::istringstream{"The quick brown fox"};
auto str = std::string{};
while (iss >> str) {
process(str);
}
Mit std::istream_iterator
s kann der Inhalt des String-Streams auch mit seinem Iterator-Range-Konstruktor in einen Vektor kopiert werden.
Mehrere Bibliotheken (wie Boost.Tokenizer ) bieten bestimmte Tokenisierer.
Für eine erweiterte Aufteilung sind reguläre Ausdrücke erforderlich. C ++ bietet std::regex_token_iterator
zu diesem Zweck insbesondere Folgendes:
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{}
);
Die Boost-Tokenizer- Klasse kann so etwas ganz einfach machen:
#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;
}
}
Aktualisiert für 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
Konstruktors ( drop_empty_tokens
ist die Standardeinstellung, Alternative ist keep_empty_tokens
).
.h
für C-Header)
Hier ist eine ganz einfache:
#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;
}
Verwenden Sie strtok. Meiner Meinung nach ist es nicht erforderlich, eine Klasse für das Tokenisieren zu erstellen, es sei denn, strtok bietet Ihnen nicht das, was Sie benötigen. Möglicherweise nicht, aber in mehr als 15 Jahren, in denen ich verschiedene Parsing-Codes in C und C ++ geschrieben habe, habe ich immer strtok verwendet. Hier ist ein Beispiel
char myString[] = "The quick brown fox";
char *p = strtok(myString, " ");
while (p) {
printf ("Token: %s\n", p);
p = strtok(NULL, " ");
}
Ein paar Vorsichtsmaßnahmen (die möglicherweise nicht Ihren Anforderungen entsprechen). Die Zeichenfolge wird dabei "zerstört", was bedeutet, dass EOS-Zeichen in den Begrenzungspunkten inline platziert werden. Für eine korrekte Verwendung müssen Sie möglicherweise eine nicht konstante Version der Zeichenfolge erstellen. Sie können auch die Liste der Trennzeichen während der Analyse ändern.
Meiner Meinung nach ist der obige Code viel einfacher und benutzerfreundlicher als das Schreiben einer separaten Klasse dafür. Für mich ist dies eine der Funktionen, die die Sprache bietet und die sie gut und sauber macht. Es ist einfach eine "C-basierte" Lösung. Es ist angemessen, es ist einfach und Sie müssen nicht viel zusätzlichen Code schreiben :-)
Ein weiterer schneller Weg ist die Verwendung getline
. Etwas wie:
stringstream ss("bla bla");
string s;
while (getline(ss, s, ' ')) {
cout << s << endl;
}
Wenn Sie möchten, können Sie eine einfache split()
Methode erstellen vector<string>
, die a zurückgibt, was sehr nützlich ist.
Sie können Streams, Iteratoren und den Kopieralgorithmus verwenden, um dies ziemlich direkt zu tun.
#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
diese Weise zu wissen, woher mein Objekt kommt, das ist nur eine Frage des Stils.
Keine Beleidigung, aber für ein so einfaches Problem machen Sie die Dinge viel zu kompliziert. Es gibt viele Gründe, Boost zu verwenden . Aber für etwas so Einfaches ist es, als würde man eine Fliege mit einem 20 # Schlitten schlagen.
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());
}
}
Zum Beispiel (für Dougs Fall),
#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] );
}
Und ja, wir hätten split () einen neuen Vektor zurückgeben können, anstatt einen zu übergeben. Es ist trivial, zu wickeln und zu überladen. Aber je nachdem, was ich mache, finde ich es oft besser, bereits vorhandene Objekte wiederzuverwenden, als immer neue zu erstellen. (Nur solange ich nicht vergesse, den Vektor dazwischen zu leeren!)
Referenz: http://www.cplusplus.com/reference/string/string/ .
(Ich habe ursprünglich eine Antwort auf Dougs Frage geschrieben: Ändern und Extrahieren von C ++ - Zeichenfolgen basierend auf Trennzeichen (geschlossen) . Aber da Martin York diese Frage hier mit einem Zeiger geschlossen hat ... werde ich nur meinen Code verallgemeinern.)
std::string
Klasse keine split () -Funktion?
start = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());
und die while-Schleife sollte sein while (start != string::npos)
. Außerdem überprüfe ich den Teilstring, um sicherzustellen, dass er nicht leer ist, bevor ich ihn in den Vektor einfüge.
Eine Lösung mit 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 hat eine starke Split-Funktion: boost :: algorithm :: split .
Beispielprogramm:
#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;
}
Ausgabe:
"a"
"b"
" c "
""
"e"
"f"
""
Ich weiß, dass Sie nach einer C ++ - Lösung gefragt haben, aber Sie könnten dies als hilfreich erachten:
Qt
#include <QString>
...
QString str = "The quick brown fox";
QStringList results = str.split(" ");
Der Vorteil gegenüber Boost in diesem Beispiel besteht darin, dass es sich um eine direkte Eins-zu-Eins-Zuordnung zum Code Ihres Posts handelt.
Weitere Informationen finden Sie in der Qt-Dokumentation
Hier ist eine Beispiel-Tokenizer-Klasse, die möglicherweise das tut, was Sie wollen
//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;
}
Beispiel:
std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())
{
v.push_back(s.GetToken());
}
Dies ist eine einfache STL-only - Lösung (~ 5 Zeilen!) Mit std::find
und std::find_first_not_of
dass Griffe Wiederholungen des Trennzeichens (wie Leerzeichen oder Punkte zum Beispiel) sowie vorderen und hinteren Begrenzungszeichen:
#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);
}
}
Probieren Sie es live aus !
pystring ist eine kleine Bibliothek, die eine Reihe von Python-Zeichenfolgenfunktionen implementiert, einschließlich der Split-Methode:
#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, "-");
Ich habe diese Antwort für eine ähnliche Frage gepostet.
Das Rad nicht neu erfinden. Ich habe eine Reihe von Bibliotheken verwendet und die schnellste und flexibelste, die mir begegnet ist, ist: C ++ String Toolkit Library .
Hier ist ein Beispiel für die Verwendung, das ich an anderer Stelle im Stackoverflow gepostet habe.
#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;
}
Überprüfen Sie dieses Beispiel. Es könnte Ihnen helfen ..
#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 hat einen sehr schönen Tokenizer. Von 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
Wenn Sie bereit sind, C zu verwenden, können Sie die strtok- Funktion verwenden. Sie sollten bei der Verwendung auf Multithreading-Probleme achten.
Für einfache Sachen benutze ich einfach folgendes:
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;
}
Feiger Haftungsausschluss: Ich schreibe eine Echtzeit-Datenverarbeitungssoftware, bei der die Daten über Binärdateien, Sockets oder einen API-Aufruf (E / A-Karten, Kameras) eingehen. Ich verwende diese Funktion niemals für etwas Komplizierteres oder Zeitkritischeres als das Lesen externer Konfigurationsdateien beim Start.
Sie können einfach eine Bibliothek für reguläre Ausdrücke verwenden und diese mit regulären Ausdrücken lösen.
Verwenden Sie den Ausdruck (\ w +) und die Variable in \ 1 (oder $ 1, abhängig von der Bibliotheksimplementierung regulärer Ausdrücke).
Viele zu komplizierte Vorschläge hier. Probieren Sie diese einfache std :: string-Lösung aus:
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;
}
Ich dachte, dafür war der >>
Operator für String-Streams gedacht :
string word; sin >> word;
Die Antwort von Adam Pierce liefert einen handgesponnenen Tokenizer, der a aufnimmt const char*
. Es ist etwas problematischer mit Iteratoren zu tun, da das Inkrementieren des Enditerators eines string
undefiniert ist . Das heißt, vorausgesetzt, string str{ "The quick brown fox" }
wir können dies sicherlich erreichen:
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;
}
Wenn Sie die Komplexität mithilfe der Standardfunktionalität abstrahieren möchten, wie On Freund vorschlägt, strtok
ist dies eine einfache Option:
vector<string> tokens;
for (auto i = strtok(data(str), " "); i != nullptr; i = strtok(nullptr, " ")) tokens.push_back(i);
Wenn Sie keinen Zugriff auf C ++ 17 haben, müssen Sie data(str)
wie in diesem Beispiel ersetzen : http://ideone.com/8kAGoa
Obwohl im Beispiel nicht gezeigt, strtok
muss nicht für jedes Token das gleiche Trennzeichen verwendet werden. Zusammen mit diesem Vorteil gibt es jedoch mehrere Nachteile:
strtok
kann nicht für mehrere strings
gleichzeitig verwendet werden: Entweder nullptr
muss a übergeben werden, um die aktuelle Tokenisierung fortzusetzenstring
oder es muss eine neuechar*
übergeben werden (es gibt jedoch einige nicht standardmäßige Implementierungen, die dies unterstützen, wie z.strtok_s
)strtok
kann nicht für mehrere Threads gleichzeitig verwendet werden (dies kann jedoch eine definierte Implementierung sein, zum Beispiel: Die Implementierung von Visual Studio ist threadsicher ).strtok
ändert die Funktion, mit der string
es ausgeführt wird, sodass es nicht für const string
s-, const char*
s- oder Literal-Zeichenfolgen verwendet werden kann, um eine dieser Zeichenfolgen mit einem Token zu versehen strtok
oder zu bearbeiten string
, dessen Inhalt beibehalten werden muss. Es str
müsste kopiert werden, dann könnte die Kopie kopiert werden operiert werdenc ++ 20bietet uns die Möglichkeit, split_view
Zeichenfolgen zerstörungsfrei zu tokenisieren: https://topanswers.xyz/cplusplus?q=749#a874
Die vorherigen Methoden können kein direktes Token generieren vector
, dh ohne sie in eine Hilfsfunktion zu abstrahieren, die sie nicht initialisieren können const vector<string> tokens
. Diese Funktionalität und die Fähigkeit, jeden Leerraumbegrenzer zu akzeptieren , können mithilfe von a genutzt werden istream_iterator
. Zum Beispiel gegeben: const string str{ "The quick \tbrown \nfox" }
Wir können dies tun:
istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };
Die erforderliche Konstruktion einer istringstream
für diese Option hat weitaus höhere Kosten als die beiden vorherigen Optionen, diese Kosten sind jedoch in der Regel in den Kosten für die string
Zuweisung verborgen .
Wenn keine der oben genannten Optionen für Ihre Tokenisierungsanforderungen flexibel genug ist, ist die flexibelste Option die Verwendung einer regex_token_iterator
natürlich mit dieser Flexibilität verbundenen höheren Kosten, aber auch dies ist wahrscheinlich in den string
Zuweisungskosten verborgen . Nehmen wir zum Beispiel an, wir möchten ein Token basierend auf nicht maskierten Kommas erstellen und dabei auch Leerzeichen essen, wenn const string str{ "The ,qu\\,ick ,\tbrown, fox" }
wir folgende Eingaben machen: Wir können dies tun:
const regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };
strtok_s
ist übrigens C11-Standard. strtok_r
ist ein POSIX2001-Standard. Zwischen diesen beiden gibt es strtok
für die meisten Plattformen eine Standard-Wiedereintrittsversion von .
#include <cstring>
nur die c99- Version von enthält strtok
. Ich gehe also davon aus, dass Sie diesen Kommentar nur als unterstützendes Material bereitstellen, um die implementierungsspezifische Verfügbarkeit von strtok
Erweiterungen zu demonstrieren .
strtok_s
wird sowohl von C11 als auch als eigenständige Erweiterung in der C-Laufzeit von Microsoft bereitgestellt. Es gibt hier ein merkwürdiges Stück Geschichte, in dem die _s
Funktionen von Microsoft zum C-Standard wurden.
Ich weiß, dass diese Frage bereits beantwortet ist, aber ich möchte einen Beitrag leisten. Vielleicht ist meine Lösung ein bisschen einfach, aber das habe ich mir ausgedacht:
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;
}
Bitte kommentieren Sie, ob es einen besseren Ansatz für etwas in meinem Code gibt oder ob etwas nicht stimmt.
UPDATE: generisches Trennzeichen hinzugefügt
Hier ist ein Ansatz, mit dem Sie steuern können, ob leere Token eingeschlossen (wie strsep) oder ausgeschlossen (wie strtok) sind.
#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;
}
Mir kommt es seltsam vor, dass bei uns allen geschwindigkeitsbewussten Nerds hier auf SO niemand eine Version vorgestellt hat, die eine nach der Kompilierungszeit generierte Nachschlagetabelle für das Trennzeichen verwendet (Beispielimplementierung weiter unten). Die Verwendung einer Nachschlagetabelle und von Iteratoren sollte std :: regex in ihrer Effizienz übertreffen. Wenn Sie Regex nicht schlagen müssen, verwenden Sie es einfach, seinen Standard ab C ++ 11 und super flexibel.
Einige haben bereits Regex vorgeschlagen, aber für die Noobs ist hier ein Beispiel, das genau das tun sollte, was das OP erwartet:
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;
}
Wenn wir schneller sein und die Einschränkung akzeptieren müssen, dass alle Zeichen 8 Bit groß sein müssen, können wir zur Kompilierungszeit mithilfe der Metaprogrammierung eine Nachschlagetabelle erstellen:
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>;
Damit getNextToken
ist es einfach , eine Funktion zu erstellen:
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);
}
Die Verwendung ist auch einfach:
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;
}
Hier ist ein Live-Beispiel: http://ideone.com/GKtkLQ
Sie können boost :: make_find_iterator nutzen. Ähnliches:
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;
}
Hier ist mein Swiss® Army Knife von String-Tokenizern zum Aufteilen von Strings nach Leerzeichen, zum Berücksichtigen von Strings mit einfachen und doppelten Anführungszeichen sowie zum Entfernen dieser Zeichen aus den Ergebnissen. Ich habe RegexBuddy 4.x verwendet, um den größten Teil des Code-Snippets zu generieren , aber ich habe eine benutzerdefinierte Behandlung zum Entfernen von Anführungszeichen und ein paar anderen Dingen hinzugefügt.
#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;
}
Wenn die maximale Länge der zu tokenisierenden Eingabezeichenfolge bekannt ist, kann dies ausgenutzt und eine sehr schnelle Version implementiert werden. Ich skizziere die Grundidee unten, die sowohl von strtok () als auch von der Datenstruktur "Suffix Array" inspiriert wurde, die Jon Bentleys 2. Ausgabe von "Programming Perls", Kapitel 15, beschreibt. Die C ++ - Klasse bietet in diesem Fall nur eine gewisse Organisation und Bequemlichkeit von Nutzen. Die gezeigte Implementierung kann leicht erweitert werden, um führende und nachfolgende Leerzeichen in den Token zu entfernen.
Grundsätzlich kann man die Trennzeichen durch Zeichenfolgen-terminierende '\ 0'-Zeichen ersetzen und Zeiger auf die Token mit der geänderten Zeichenfolge setzen. Im Extremfall, wenn die Zeichenfolge nur aus Trennzeichen besteht, erhält man die Zeichenfolgenlänge plus 1 resultierende leere Token. Es ist praktisch, die zu ändernde Zeichenfolge zu duplizieren.
Header-Datei:
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;
}
};
Implementierungsdatei:
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++ ] );
}
Ein Nutzungsszenario wäre:
// 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 ) );
}
Ausgabe:
Item1
Item2
Item3
boost::tokenizer
ist Ihr Freund, aber erwägen Sie, Ihren Code in Bezug auf Internationalisierungsprobleme (i18n) portabel zu machen, indem Sie wstring
/ wchar_t
anstelle der Legacy string
/ char
-Typen verwenden.
#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
ein schrecklicher implementierungsabhängiger Typ, den niemand verwenden sollte, es sei denn, dies ist absolut notwendig.
Einfacher C ++ - Code (Standard C ++ 98) akzeptiert mehrere Trennzeichen (angegeben in einem std :: string) und verwendet nur Vektoren, Strings und Iteratoren.
#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;
}