Java memiliki metode split yang mudah:
String str = "The quick brown fox";
String[] results = str.split(" ");
Apakah ada cara mudah untuk melakukan ini di C ++?
Java memiliki metode split yang mudah:
String str = "The quick brown fox";
String[] results = str.split(" ");
Apakah ada cara mudah untuk melakukan ini di C ++?
Jawaban:
Algoritma perpustakaan standar C ++ cukup universal berbasis di sekitar iterator daripada wadah beton. Sayangnya ini membuatnya sulit untuk menyediakan split
fungsi mirip Java di pustaka standar C ++, meskipun tidak ada yang berpendapat bahwa ini akan lebih mudah. Tapi seperti apa tipe pengembaliannya?std::vector<std::basic_string<…>>
? Mungkin, tapi kemudian kami terpaksa melakukan alokasi (berpotensi berlebihan dan mahal).
Alih-alih, C ++ menawarkan sejumlah besar cara untuk membagi string berdasarkan pembatas yang kompleks secara arbitrer, tetapi tidak satupun dari mereka yang dienkapsulasi sebaik di bahasa lain. Banyak cara mengisi seluruh posting blog .
Paling sederhana, Anda bisa beralih menggunakan std::string::find
sampai Anda menekan std::string::npos
, dan ekstrak konten menggunakan std::string::substr
.
Versi yang lebih lancar (dan idiomatik, tetapi mendasar) untuk membelah di whitespace akan menggunakan std::istringstream
:
auto iss = std::istringstream{"The quick brown fox"};
auto str = std::string{};
while (iss >> str) {
process(str);
}
Menggunakan std::istream_iterator
s , isi dari stream string juga dapat disalin ke dalam vektor menggunakan konstruktor rentang iteratornya.
Beberapa perpustakaan (seperti Boost.Tokenizer ) menawarkan tokeniser tertentu.
Pemecahan yang lebih maju membutuhkan ekspresi reguler. C ++ menyediakan std::regex_token_iterator
untuk tujuan ini khususnya:
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{}
);
Kelas tokenizer Boost dapat membuat hal semacam ini cukup sederhana:
#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;
}
}
Diperbarui untuk 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
konstruktor ( drop_empty_tokens
adalah default, alternatifnya adalah keep_empty_tokens
).
.h
untuk header C)
Inilah yang sangat sederhana:
#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;
}
Gunakan strtok. Menurut pendapat saya, tidak ada kebutuhan untuk membangun kelas di sekitar tokenizing kecuali strtok tidak memberi Anda apa yang Anda butuhkan. Mungkin tidak, tetapi dalam 15+ tahun menulis berbagai kode parsing dalam C dan C ++, saya selalu menggunakan strtok. Berikut ini sebuah contoh
char myString[] = "The quick brown fox";
char *p = strtok(myString, " ");
while (p) {
printf ("Token: %s\n", p);
p = strtok(NULL, " ");
}
Beberapa peringatan (yang mungkin tidak sesuai dengan kebutuhan Anda). String "dihancurkan" dalam proses, yang berarti bahwa karakter EOS ditempatkan sejajar di tempat pembatas. Penggunaan yang benar mungkin mengharuskan Anda membuat versi string non-const. Anda juga dapat mengubah daftar pembatas mid parse.
Menurut pendapat saya sendiri, kode di atas jauh lebih sederhana dan lebih mudah digunakan daripada menulis kelas terpisah untuk itu. Bagi saya, ini adalah salah satu fungsi yang disediakan oleh bahasa dan berfungsi dengan baik dan bersih. Ini hanyalah solusi "berbasis C". Sangat tepat, mudah, dan Anda tidak perlu menulis banyak kode tambahan :-)
Cara cepat lainnya adalah menggunakan getline
. Sesuatu seperti:
stringstream ss("bla bla");
string s;
while (getline(ss, s, ' ')) {
cout << s << endl;
}
Jika mau, Anda dapat membuat split()
metode sederhana untuk mengembalikan vector<string>
, yang sangat berguna.
Anda dapat menggunakan stream, iterators, dan algoritma salin untuk melakukan ini secara langsung.
#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
cara ini saya tahu dari mana objek saya berasal, itu hanya masalah gaya.
Tidak ada orang tersinggung, tapi untuk suatu masalah sederhana, Anda membuat hal-hal cara terlalu rumit. Ada banyak alasan untuk menggunakan Peningkatan . Tapi untuk sesuatu yang sederhana ini, rasanya seperti memukul lalat dengan 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());
}
}
Misalnya (untuk kasus 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] );
}
Dan ya, kita bisa membagi () mengembalikan vektor baru daripada melewati satu. Ini sepele untuk membungkus dan membebani. Tetapi tergantung pada apa yang saya lakukan, saya sering merasa lebih baik untuk menggunakan kembali objek yang sudah ada daripada selalu membuat yang baru. (Asalkan saya tidak lupa mengosongkan vektor di antaranya!)
Referensi: http://www.cplusplus.com/reference/string/string/ .
(Awalnya saya menulis jawaban untuk pertanyaan Doug: C ++ Strings Modifying and Extracting berdasarkan Separators (closed) . Tapi karena Martin York menutup pertanyaan itu dengan sebuah pointer di sini ... Saya hanya akan menggeneralisasi kode saya.)
std::string
kelas tidak menyertakan fungsi split ()?
start = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());
dan while loop seharusnya while (start != string::npos)
. Saya juga memeriksa substring untuk memastikan tidak kosong sebelum memasukkannya ke vektor.
Solusi menggunakan 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 memiliki fungsi pemisahan yang kuat: boost :: algoritme :: split .
Program sampel:
#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;
}
Keluaran:
"a"
"b"
" c "
""
"e"
"f"
""
Saya tahu Anda meminta solusi C ++, tetapi Anda mungkin menganggap ini berguna:
Qt
#include <QString>
...
QString str = "The quick brown fox";
QStringList results = str.split(" ");
Keuntungan dari Boost dalam contoh ini adalah pemetaan langsung ke kode pos Anda.
Lihat lebih lanjut di dokumentasi Qt
Berikut adalah contoh tokenizer kelas yang mungkin melakukan apa yang Anda inginkan
//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;
}
Contoh:
std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())
{
v.push_back(s.GetToken());
}
Ini adalah solusi STL-only sederhana (~ 5 baris!) Menggunakan std::find
dan std::find_first_not_of
yang menangani pengulangan pembatas (seperti spasi atau periode misalnya), serta pembatas memimpin dan mengikuti:
#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);
}
}
Cobalah langsung !
pystring adalah perpustakaan kecil yang mengimplementasikan banyak fungsi string Python, termasuk metode 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, "-");
Saya memposting jawaban ini untuk pertanyaan serupa.
Jangan menemukan kembali roda. Saya telah menggunakan sejumlah perpustakaan dan yang tercepat dan paling fleksibel yang pernah saya temui adalah: C ++ String Toolkit Library .
Berikut adalah contoh cara menggunakannya yang saya posting di tempat lain di 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;
}
Lihat contoh ini. Mungkin membantu Anda ..
#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 memiliki tokenizer yang sangat bagus. Dari 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
Jika Anda ingin menggunakan C, Anda dapat menggunakan fungsi strtok . Anda harus memperhatikan masalah multi-threading saat menggunakannya.
Untuk hal-hal sederhana, saya hanya menggunakan yang berikut ini:
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;
}
Sangkalan pengecut: Saya menulis perangkat lunak pengolah data waktu-nyata di mana data masuk melalui file biner, soket, atau panggilan API (kartu I / O, kamera). Saya tidak pernah menggunakan fungsi ini untuk sesuatu yang lebih rumit atau kritis waktu daripada membaca file konfigurasi eksternal saat startup.
Anda cukup menggunakan pustaka ekspresi reguler dan mengatasinya dengan menggunakan ekspresi reguler.
Gunakan ekspresi (\ w +) dan variabel dalam \ 1 (atau $ 1 tergantung pada implementasi perpustakaan dari ekspresi reguler).
Banyak saran yang terlalu rumit di sini. Coba std :: string solution sederhana ini:
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;
}
Saya pikir itulah gunanya >>
operator pada string stream:
string word; sin >> word;
Jawaban Adam Pierce menyediakan tokenizer pintal tangan dengan a const char*
. Ini sedikit lebih bermasalah untuk dilakukan dengan iterator karena penambahan string
iterator akhir tidak terdefinisi . Yang mengatakan, mengingat string str{ "The quick brown fox" }
kita pasti bisa mencapai ini:
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;
}
Jika Anda mencari kompleksitas abstrak dengan menggunakan fungsionalitas standar, seperti yang disarankan On Freund strtok
adalah opsi sederhana:
vector<string> tokens;
for (auto i = strtok(data(str), " "); i != nullptr; i = strtok(nullptr, " ")) tokens.push_back(i);
Jika Anda tidak memiliki akses ke C ++ 17, Anda harus mengganti data(str)
seperti pada contoh ini: http://ideone.com/8kAGoa
Meskipun tidak diperlihatkan dalam contoh, strtok
tidak perlu menggunakan pembatas yang sama untuk setiap token. Seiring dengan keunggulan ini, ada beberapa kelemahan:
strtok
tidak dapat digunakan pada multipel strings
pada waktu yang bersamaan: Entah nullptr
harus dilewati untuk melanjutkan tokenizing the currentstring
atau yang baru char*
untuk tokenize harus dilewati (ada beberapa implementasi non-standar yang mendukung ini, seperti:strtok_s
)strtok
tidak dapat digunakan pada banyak utas secara bersamaan (namun ini mungkin implementasi yang ditentukan, misalnya: Implementasi Visual Studio adalah utas yang aman )strtok
mengubah string
itu beroperasi, sehingga tidak dapat digunakan pada const string
s, const char*
s, atau string literal, untuk tokenize semua ini dengan strtok
atau untuk beroperasi padastring
siapa yang perlu dilestarikan konten, str
harus disalin, maka salinan dapat Dioperasikanc ++ 20 memberi kita split_view
tokenize string, dengan cara yang tidak merusak: https://topanswers.xyz/cplusplus?q=749#a874
Metode sebelumnya tidak dapat menghasilkan tokenized vector
di tempat, artinya tanpa mengabstraksikannya menjadi fungsi pembantu yang tidak dapat diinisialisasi const vector<string> tokens
. Fungsionalitas itu dan kemampuan untuk menerima pembatas ruang putih apa pun dapat dimanfaatkan menggunakan istream_iterator
. Misalnya diberikan: const string str{ "The quick \tbrown \nfox" }
kita bisa melakukan ini:
istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };
Diperlukan pembangunan sebuah istringstream
untuk opsi ini memiliki biaya yang jauh lebih besar daripada 2 opsi sebelumnya, namun biaya ini biasanya tersembunyi dalam biaya string
alokasi.
Jika tidak ada opsi di atas yang cukup fleksibel untuk kebutuhan tokenization Anda, opsi yang paling fleksibel adalah dengan menggunakan regex_token_iterator
tentu saja dengan fleksibilitas ini muncul biaya yang lebih besar, tetapi sekali lagi ini kemungkinan tersembunyi dalam string
biaya alokasi. Katakan misalnya kita ingin tokenize berdasarkan koma yang tidak diloloskan, juga memakan ruang putih, diberi masukan berikut: const string str{ "The ,qu\\,ick ,\tbrown, fox" }
kita bisa melakukan ini:
const regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };
strtok_s
adalah standar C11, omong-omong. strtok_r
adalah standar POSIX2001. Di antara keduanya, ada versi standar strtok
untuk sebagian besar platform.
#include <cstring>
hanya menyertakan versi c99strtok
. Jadi asumsi saya adalah Anda hanya memberikan komentar ini sebagai bahan pendukung, menunjukkan penerapan strtok
ekstensi yang spesifik ?
strtok_s
disediakan oleh C11 dan sebagai ekstensi mandiri dalam runtime C Microsoft. Ada sedikit sejarah yang aneh di sini di mana _s
fungsi Microsoft menjadi standar C.
Saya tahu pertanyaan ini sudah dijawab tetapi saya ingin berkontribusi. Mungkin solusi saya agak sederhana tetapi inilah yang saya buat:
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;
}
Berikan komentar jika ada pendekatan yang lebih baik untuk sesuatu dalam kode saya atau jika ada sesuatu yang salah.
UPDATE: menambahkan pemisah generik
Berikut ini pendekatan yang memungkinkan Anda mengontrol apakah token kosong disertakan (seperti strsep) atau dikecualikan (seperti 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;
}
Tampak aneh bagi saya bahwa dengan kita semua kutu buku sadar kecepatan di sini pada SO tidak ada yang menyajikan versi yang menggunakan waktu kompilasi yang dihasilkan mencari tabel untuk pembatas (contoh implementasi lebih jauh ke bawah). Menggunakan tabel pencarian dan iterator harus mengalahkan std :: regex dalam efisiensi, jika Anda tidak perlu mengalahkan regex, cukup gunakan, standarnya seperti pada C ++ 11 dan super fleksibel.
Beberapa sudah menyarankan regex tetapi untuk noobs di sini adalah contoh paket yang harus melakukan persis apa yang diharapkan OP:
std::vector<std::string> split(std::string::const_iterator it, std::string::const_iterator end, std::regex e = std::regex{"\\w+"}){
std::smatch m{};
std::vector<std::string> ret{};
while (std::regex_search (it,end,m,e)) {
ret.emplace_back(m.str());
std::advance(it, m.position() + m.length()); //next start position = match position + match length
}
return ret;
}
std::vector<std::string> split(const std::string &s, std::regex e = std::regex{"\\w+"}){ //comfort version calls flexible version
return split(s.cbegin(), s.cend(), std::move(e));
}
int main ()
{
std::string str {"Some people, excluding those present, have been compile time constants - since puberty."};
auto v = split(str);
for(const auto&s:v){
std::cout << s << std::endl;
}
std::cout << "crazy version:" << std::endl;
v = split(str, std::regex{"[^e]+"}); //using e as delim shows flexibility
for(const auto&s:v){
std::cout << s << std::endl;
}
return 0;
}
Jika kita perlu lebih cepat dan menerima batasan bahwa semua karakter harus 8 bit, kita bisa membuat tabel pencarian pada waktu kompilasi menggunakan metaprogramming:
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>;
Dengan itu, membuat getNextToken
fungsi menjadi mudah:
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);
}
Menggunakannya juga mudah:
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;
}
Berikut ini adalah contoh langsung: http://ideone.com/GKtkLQ
Anda dapat memanfaatkan boost :: make_find_iterator. Sesuatu yang mirip dengan ini:
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;
}
Inilah Swiss-Army Knife saya yang menggunakan tokenizer string untuk memisahkan string berdasarkan spasi, menghitung string yang dibungkus dengan tanda kutip tunggal dan ganda serta menghilangkan karakter-karakter tersebut dari hasil. Saya menggunakan RegexBuddy 4.x untuk menghasilkan sebagian besar snipet kode, tetapi saya menambahkan penanganan khusus untuk pengupasan kutipan dan beberapa hal lainnya.
#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;
}
Jika panjang maksimum dari string input yang akan dipatuhi diketahui, seseorang dapat mengeksploitasi ini dan mengimplementasikan versi yang sangat cepat. Saya membuat sketsa ide dasar di bawah ini, yang terinspirasi oleh strtok () dan struktur "suffix array" -data yang dijelaskan Jon Bentley "Programming Perls" edisi ke-2, bab 15. Kelas C ++ dalam hal ini hanya memberikan beberapa organisasi dan kenyamanan penggunaan. Implementasi yang ditunjukkan dapat dengan mudah diperluas untuk menghapus karakter spasi putih terkemuka dan tertinggal di dalam token.
Pada dasarnya seseorang dapat mengganti karakter pemisah dengan karakter penghentian string '\ 0' dan mengatur pointer ke token dengan string yang dimodifikasi. Dalam kasus ekstrim ketika string hanya terdiri dari pemisah, satu mendapat panjang string ditambah 1 yang dihasilkan token kosong. Praktis untuk menduplikasi string yang akan dimodifikasi.
File tajuk:
class TextLineSplitter
{
public:
TextLineSplitter( const size_t max_line_len );
~TextLineSplitter();
void SplitLine( const char *line,
const char sep_char = ',',
);
inline size_t NumTokens( void ) const
{
return mNumTokens;
}
const char * GetToken( const size_t token_idx ) const
{
assert( token_idx < mNumTokens );
return mTokens[ token_idx ];
}
private:
const size_t mStorageSize;
char *mBuff;
char **mTokens;
size_t mNumTokens;
inline void ResetContent( void )
{
memset( mBuff, 0, mStorageSize );
// mark all items as empty:
memset( mTokens, 0, mStorageSize * sizeof( char* ) );
// reset counter for found items:
mNumTokens = 0L;
}
};
File implementasi:
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++ ] );
}
Skenario penggunaan adalah:
// 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 ) );
}
keluaran:
Item1
Item2
Item3
boost::tokenizer
adalah teman Anda, tetapi pertimbangkan untuk menjadikan kode Anda portabel dengan mengacu pada masalah internasionalisasi (i18n) dengan menggunakan wstring
/ wchar_t
bukan warisan string
/ char
tipe.
#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
merupakan tipe ketergantungan implementasi yang mengerikan yang tidak boleh digunakan siapa pun kecuali benar-benar diperlukan.
Kode C ++ sederhana (standar C ++ 98), menerima banyak pembatas (ditentukan dalam std :: string), hanya menggunakan vektor, string, dan iterator.
#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;
}