Bagaimana cara saya mengulang kata-kata string?

2986

Saya mencoba untuk mengulangi kata-kata string.

String dapat dianggap terdiri dari kata-kata yang dipisahkan oleh spasi.

Perhatikan bahwa saya tidak tertarik pada fungsi string C atau jenis manipulasi / akses semacam itu. Selain itu, mohon didahulukan keanggunan daripada efisiensi dalam jawaban Anda.

Solusi terbaik yang saya miliki saat ini adalah:

#include <iostream>
#include <sstream>
#include <string>

using namespace std;

int main()
{
    string s = "Somewhere down the road";
    istringstream iss(s);

    do
    {
        string subs;
        iss >> subs;
        cout << "Substring: " << subs << endl;
    } while (iss);
}

Apakah ada cara yang lebih elegan untuk melakukan ini?

Ashwin Nanjappa
sumber
617
Kawan ... Keanggunan hanyalah cara mewah untuk mengatakan "efisiensi-yang-terlihat-cantik" dalam buku saya. Jangan menghindar untuk menggunakan fungsi C dan metode cepat untuk mencapai apa pun hanya karena tidak ada di dalam templat;)
14
while (iss) { string subs; iss >> subs; cout << "Substring: " << sub << endl; }
pyon
21
@Eduardo: itu salah juga ... Anda perlu menguji iss antara mencoba mengalirkan nilai lain dan menggunakan nilai itu, yaitustring sub; while (iss >> sub) cout << "Substring: " << sub << '\n';
Tony Delroy
9
Berbagai opsi dalam C ++ untuk melakukan ini secara default: cplusplus.com/faq/
followingences
14
Ada lebih banyak keanggunan daripada efisiensi yang cantik. Atribut elegan termasuk jumlah garis rendah dan keterbacaan tinggi. IMHO Elegance bukan proksi untuk efisiensi tetapi pemeliharaan.
Matt

Jawaban:

1369

Untuk apa nilainya, inilah cara lain untuk mengekstrak token dari string input, hanya mengandalkan fasilitas perpustakaan standar. Ini adalah contoh kekuatan dan keanggunan di balik desain STL.

#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>

int main() {
    using namespace std;
    string sentence = "And I feel fine...";
    istringstream iss(sentence);
    copy(istream_iterator<string>(iss),
         istream_iterator<string>(),
         ostream_iterator<string>(cout, "\n"));
}

Alih-alih menyalin token yang diekstraksi ke aliran output, orang bisa memasukkannya ke dalam wadah, menggunakan copyalgoritma generik yang sama .

vector<string> tokens;
copy(istream_iterator<string>(iss),
     istream_iterator<string>(),
     back_inserter(tokens));

... atau buat vectorlangsung:

vector<string> tokens{istream_iterator<string>{iss},
                      istream_iterator<string>{}};
Zunino
sumber
164
Apakah mungkin untuk menentukan pembatas untuk ini? Seperti misalnya membelah koma?
l3dx
15
@ Jonathan: \ n bukan pembatas dalam kasus ini, ini pembatas untuk keluaran ke cout.
beli
772
Ini adalah solusi yang buruk karena tidak memerlukan pembatas lain, oleh karena itu tidak dapat diskalakan dan tidak dapat dipertahankan.
HelloWorld
37
Sebenarnya, ini bisa bekerja dengan baik dengan pembatas lain (meskipun melakukan beberapa agak jelek). Anda membuat segi jenis ctype yang mengklasifikasikan pembatas yang diinginkan sebagai spasi putih, membuat lokal yang mengandung aspek itu, lalu menambahkan aliran string dengan lokal itu sebelum mengekstraksi string.
Jerry Coffin
53
@Kinderchocolate "String dapat dianggap terdiri dari kata-kata yang dipisahkan oleh spasi putih" - Hmm, tidak terdengar seperti solusi yang buruk untuk masalah pertanyaan. "tidak terukur dan tidak terurus" - Hah, bagus.
Christian Rau
2426

Saya menggunakan ini untuk membagi string dengan pembatas. Yang pertama menempatkan hasilnya dalam vektor pra-dibangun, yang kedua mengembalikan vektor baru.

#include <string>
#include <sstream>
#include <vector>
#include <iterator>

template <typename Out>
void split(const std::string &s, char delim, Out result) {
    std::istringstream iss(s);
    std::string item;
    while (std::getline(iss, item, delim)) {
        *result++ = item;
    }
}

std::vector<std::string> split(const std::string &s, char delim) {
    std::vector<std::string> elems;
    split(s, delim, std::back_inserter(elems));
    return elems;
}

Perhatikan bahwa solusi ini tidak melewati token kosong, jadi yang berikut ini akan menemukan 4 item, salah satunya kosong:

std::vector<std::string> x = split("one:two::three", ':');
Evan Teran
sumber
86
Untuk menghindari melompati token kosong, lakukan empty()pemeriksaan:if (!item.empty()) elems.push_back(item)
0x499602D2
11
Bagaimana dengan delim yang berisi dua karakter ->?
herohuyongtao
7
@herohuyongtao, solusi ini hanya berfungsi untuk pembatas char tunggal.
Evan Teran
4
@JeshwanthKumarNK, itu tidak perlu, tetapi memungkinkan Anda melakukan hal-hal seperti meneruskan hasil langsung ke fungsi seperti ini: f(split(s, d, v))sambil tetap mendapat manfaat dari pra-alokasi vectorjika Anda suka.
Evan Teran
8
Peringatan: split ("one: two :: three", ':') dan split ("one: two :: three:", ':') mengembalikan nilai yang sama.
dshin
834

Solusi yang mungkin menggunakan Boost mungkin:

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));

Pendekatan ini mungkin bahkan lebih cepat daripada stringstream pendekatan itu. Dan karena ini adalah fungsi templat generik, ia dapat digunakan untuk membagi jenis string lainnya (wchar, dll. Atau UTF-8) menggunakan semua jenis pembatas.

Lihat dokumentasi untuk detailnya.

ididak
sumber
35
Kecepatan tidak relevan di sini, karena kedua kasus ini jauh lebih lambat daripada fungsi seperti strtok.
Tom
45
Dan bagi mereka yang belum meningkatkan ... bcp menyalin lebih dari 1.000 file untuk ini :)
Roman Starkov
12
Peringatan, ketika diberi string kosong (""), metode ini mengembalikan vektor yang berisi string "". Jadi tambahkan "jika (! String_to_split.empty ())" sebelum pemisahan.
Offirmo
29
@Ian Pengembang yang disematkan tidak semua menggunakan dorongan.
ACK_stoverflow
31
sebagai tambahan: Saya menggunakan boost hanya ketika saya harus, biasanya saya lebih suka menambahkan ke perpustakaan kode saya sendiri yang berdiri sendiri dan portabel sehingga saya dapat mencapai kode spesifik kecil yang tepat, yang mencapai tujuan tertentu. Dengan begitu kode ini bersifat non-publik, berkinerja, sepele, dan portabel. Boost memiliki tempatnya, tetapi saya akan menyarankan bahwa itu sedikit berlebihan untuk string tokenising: Anda tidak akan memiliki seluruh rumah Anda diangkut ke sebuah perusahaan teknik untuk mendapatkan palu baru dipalu ke dinding untuk menggantung gambar .... mereka mungkin melakukannya sangat baik, tetapi prosare jauh lebih besar daripada yang kontra.
GMasucci
362
#include <vector>
#include <string>
#include <sstream>

int main()
{
    std::string str("Split me by whitespaces");
    std::string buf;                 // Have a buffer string
    std::stringstream ss(str);       // Insert the string into a stream

    std::vector<std::string> tokens; // Create vector to hold our words

    while (ss >> buf)
        tokens.push_back(buf);

    return 0;
}
kev
sumber
12
Anda juga dapat membelah pembatas lain jika Anda menggunakan getlinedalam whilekondisi misalnya untuk membelah dengan koma, gunakan while(getline(ss, buff, ',')).
Ali
181

Bagi mereka yang tidak duduk dengan baik untuk mengorbankan semua efisiensi untuk ukuran kode dan melihat "efisien" sebagai jenis keanggunan, berikut ini akan menyentuh sweet spot (dan saya pikir kelas templat templat adalah tambahan yang luar biasa elegan.):

template < class ContainerT >
void tokenize(const std::string& str, ContainerT& tokens,
              const std::string& delimiters = " ", bool trimEmpty = false)
{
   std::string::size_type pos, lastPos = 0, length = str.length();

   using value_type = typename ContainerT::value_type;
   using size_type  = typename ContainerT::size_type;

   while(lastPos < length + 1)
   {
      pos = str.find_first_of(delimiters, lastPos);
      if(pos == std::string::npos)
      {
         pos = length;
      }

      if(pos != lastPos || !trimEmpty)
         tokens.push_back(value_type(str.data()+lastPos,
               (size_type)pos-lastPos ));

      lastPos = pos + 1;
   }
}

Saya biasanya memilih untuk menggunakan std::vector<std::string>tipe sebagai parameter kedua saya ( ContainerT) ... tetapi list<>jauh lebih cepat daripada vector<>ketika akses langsung tidak diperlukan, dan Anda bahkan dapat membuat kelas string Anda sendiri dan menggunakan sesuatu seperti di std::list<subString>mana subStringtidak melakukan salinan untuk kecepatan luar biasa meningkat.

Ini lebih dari dua kali lipat tokenize tercepat di halaman ini dan hampir 5 kali lebih cepat daripada yang lain. Juga dengan tipe parameter sempurna Anda dapat menghilangkan semua string dan menyalin daftar untuk peningkatan kecepatan tambahan.

Selain itu itu tidak melakukan pengembalian hasil (sangat tidak efisien), melainkan melewati token sebagai referensi, sehingga juga memungkinkan Anda untuk membangun token menggunakan beberapa panggilan jika Anda menginginkannya.

Terakhir memungkinkan Anda menentukan apakah akan memangkas token kosong dari hasil melalui parameter opsional terakhir.

Yang dibutuhkan hanyalah std::string... sisanya opsional. Itu tidak menggunakan aliran atau meningkatkan perpustakaan, tetapi cukup fleksibel untuk dapat menerima beberapa tipe asing ini secara alami.

Marius
sumber
5
Saya cukup penggemar ini, tetapi untuk g ++ (dan mungkin praktik yang baik) siapa pun yang menggunakan ini akan ingin mengetik dan mengetikkan: typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType; Kemudian untuk mengganti value_type dan size_types sesuai.
aws
11
Bagi kita yang barang template dan komentar pertama benar-benar asing, contoh penggunaan cmplete dengan menyertakan wajib akan menyenangkan.
Wes Miller
3
Ahh well, saya sudah menemukannya. Saya menempatkan baris C ++ dari komentar aws 'di dalam fungsi tokenize (), kemudian mengedit baris token.push_back () untuk mengubah ContainerT :: value_type menjadi ValueType dan mengubah (ContainerT :: value_type :: size_type) menjadi ( Tipe ukuran). Memperbaiki bit yang telah dikeluhkan g ++. Hanya memintanya sebagai tokenize (some_string, some_vector);
Wes Miller
2
Selain menjalankan beberapa tes kinerja pada data sampel, terutama saya telah menguranginya menjadi sesedikit mungkin instruksi dan juga sesedikit mungkin salinan memori diaktifkan oleh penggunaan kelas substring yang hanya mereferensikan offset / panjang pada string lain. (Saya menggulung sendiri, tetapi ada beberapa implementasi lainnya). Sayangnya tidak ada terlalu banyak yang bisa dilakukan seseorang untuk memperbaiki ini, tetapi peningkatan tambahan dimungkinkan.
Marius
3
Itulah output yang tepat untuk kapan trimEmpty = true. Ingatlah bahwa "abo"ini bukan pembatas dalam jawaban ini, tetapi daftar karakter pembatas. Akan mudah untuk memodifikasinya untuk mengambil serangkaian karakter pembatas tunggal (saya pikir str.find_first_ofharus berubah menjadi str.find_first, tapi saya bisa salah ... tidak dapat menguji)
Marius
158

Ini solusi lain. Ini kompak dan cukup efisien:

std::vector<std::string> split(const std::string &text, char sep) {
  std::vector<std::string> tokens;
  std::size_t start = 0, end = 0;
  while ((end = text.find(sep, start)) != std::string::npos) {
    tokens.push_back(text.substr(start, end - start));
    start = end + 1;
  }
  tokens.push_back(text.substr(start));
  return tokens;
}

Ini dapat dengan mudah templatised untuk menangani pemisah string, string lebar, dll.

Perhatikan bahwa pemisahan ""menghasilkan string tunggal kosong dan pemisahan ","(mis. Sep) menghasilkan dua string kosong.

Itu juga dapat dengan mudah diperluas untuk melewati token kosong:

std::vector<std::string> split(const std::string &text, char sep) {
    std::vector<std::string> tokens;
    std::size_t start = 0, end = 0;
    while ((end = text.find(sep, start)) != std::string::npos) {
        if (end != start) {
          tokens.push_back(text.substr(start, end - start));
        }
        start = end + 1;
    }
    if (end != start) {
       tokens.push_back(text.substr(start));
    }
    return tokens;
}

Jika memisahkan string pada beberapa pembatas saat melewatkan token kosong diinginkan, versi ini dapat digunakan:

std::vector<std::string> split(const std::string& text, const std::string& delims)
{
    std::vector<std::string> tokens;
    std::size_t start = text.find_first_not_of(delims), end = 0;

    while((end = text.find_first_of(delims, start)) != std::string::npos)
    {
        tokens.push_back(text.substr(start, end - start));
        start = text.find_first_not_of(delims, end);
    }
    if(start != std::string::npos)
        tokens.push_back(text.substr(start));

    return tokens;
}
Alec Thomas
sumber
10
Versi pertama sederhana dan menyelesaikan pekerjaan dengan sempurna. Satu-satunya perubahan yang saya lakukan adalah mengembalikan hasilnya secara langsung, alih-alih meneruskannya sebagai parameter.
gregschlom
2
Output dilewatkan sebagai parameter untuk efisiensi. Jika hasilnya dikembalikan maka akan membutuhkan salinan vektor, atau alokasi tumpukan yang kemudian harus dibebaskan.
Alec Thomas
2
Sedikit tambahan komentar saya di atas: fungsi ini dapat mengembalikan vektor tanpa penalti jika menggunakan semantik C ++ 11 move.
Alec Thomas
7
@AlecThomas: Bahkan sebelum C ++ 11, bukankah kebanyakan penyusun mengoptimalkan salinan yang dikembalikan melalui NRVO? (Pokoknya +1; sangat ringkas)
Marcelo Cantos
11
Dari semua jawaban ini tampaknya menjadi salah satu yang paling menarik dan fleksibel. Bersama dengan getline dengan pembatas, meskipun itu solusi yang kurang jelas. Apakah standar c ++ 11 tidak memiliki apa pun untuk ini? Apakah c ++ 11 mendukung kartu punch hari ini?
Spacen Jasset
123

Ini adalah cara favorit saya untuk beralih melalui string. Anda dapat melakukan apa pun yang Anda inginkan per kata.

string line = "a line of text to iterate through";
string word;

istringstream iss(line, istringstream::in);

while( iss >> word )     
{
    // Do something on `word` here...
}
gnomed
sumber
Apakah mungkin untuk menyatakan wordsebagai char?
abatishchev
Maaf abatishchev, C ++ bukan poin kuat saya. Tapi saya membayangkan tidak akan sulit untuk menambahkan lingkaran dalam untuk mengulang setiap karakter di setiap kata. Tapi sekarang saya percaya loop saat ini tergantung pada ruang untuk pemisahan kata. Kecuali Anda tahu bahwa hanya ada satu karakter di antara setiap ruang, dalam hal ini Anda hanya bisa memberikan "kata" ke karakter ... maaf saya tidak bisa membantu, saya sudah bermaksud untuk memoles C ++
gnomed
11
jika Anda mendeklarasikan kata sebagai char, ia akan beralih ke setiap karakter yang bukan spasi. Cukup sederhana untuk dicoba:stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c;
Wayne Werner
79

Ini mirip dengan pertanyaan Stack Overflow Bagaimana cara tokenize sebuah string dalam C ++? .

#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>

using namespace std;
using namespace boost;

int main(int argc, char** argv)
{
    string text = "token  test\tstring";

    char_separator<char> sep(" \t");
    tokenizer<char_separator<char>> tokens(text, sep);
    for (const string& t : tokens)
    {
        cout << t << "." << endl;
    }
}
Ferruccio
sumber
Apakah ini mematerialisasi salinan semua token, atau apakah itu hanya menjaga posisi awal dan akhir dari token saat ini?
einpoklum
66

Saya suka yang berikut ini karena menempatkan hasilnya ke dalam vektor, mendukung string sebagai delim dan memberikan kontrol untuk menjaga nilai kosong. Tapi, itu tidak terlihat sebagus itu.

#include <ostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;

vector<string> split(const string& s, const string& delim, const bool keep_empty = true) {
    vector<string> result;
    if (delim.empty()) {
        result.push_back(s);
        return result;
    }
    string::const_iterator substart = s.begin(), subend;
    while (true) {
        subend = search(substart, s.end(), delim.begin(), delim.end());
        string temp(substart, subend);
        if (keep_empty || !temp.empty()) {
            result.push_back(temp);
        }
        if (subend == s.end()) {
            break;
        }
        substart = subend + delim.size();
    }
    return result;
}

int main() {
    const vector<string> words = split("So close no matter how far", " ");
    copy(words.begin(), words.end(), ostream_iterator<string>(cout, "\n"));
}

Tentu saja, Boost memiliki split()yang berfungsi sebagian seperti itu. Dan, jika dengan 'ruang putih', Anda benar-benar berarti semua jenis ruang putih, menggunakan split Boost dengan is_any_of()karya-karya hebat.

Shadow2531
sumber
Akhirnya solusi yang menangani token kosong dengan benar di kedua sisi string
fmuecke
53

STL belum memiliki metode seperti itu.

Namun, Anda bisa menggunakan strtok()fungsi C dengan menggunakanstd::string::c_str() anggota, atau Anda dapat menulis sendiri. Berikut adalah contoh kode yang saya temukan setelah pencarian cepat Google ( "STL string split" ):

void Tokenize(const string& str,
              vector<string>& tokens,
              const string& delimiters = " ")
{
    // Skip delimiters at beginning.
    string::size_type lastPos = str.find_first_not_of(delimiters, 0);
    // Find first "non-delimiter".
    string::size_type pos     = str.find_first_of(delimiters, lastPos);

    while (string::npos != pos || string::npos != lastPos)
    {
        // Found a token, add it to the vector.
        tokens.push_back(str.substr(lastPos, pos - lastPos));
        // Skip delimiters.  Note the "not_of"
        lastPos = str.find_first_not_of(delimiters, pos);
        // Find next "non-delimiter"
        pos = str.find_first_of(delimiters, lastPos);
    }
}

Diambil dari: http://oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html

Jika Anda memiliki pertanyaan tentang contoh kode, tinggalkan komentar dan saya akan menjelaskan.

Dan hanya karena itu tidak menerapkan typedefiterator yang disebut atau kelebihan <<operator tidak berarti itu adalah kode yang buruk. Saya menggunakan fungsi C cukup sering. Misalnya, printfdan scanfkeduanya lebih cepat daripada std::cindanstd::cout (secara signifikan), fopensintaksisnya jauh lebih ramah untuk tipe biner, dan mereka juga cenderung menghasilkan EXE yang lebih kecil.

Jangan dijual berdasarkan kesepakatan "Keanggunan atas kinerja" ini .

pengguna19302
sumber
Saya menyadari fungsi string C dan saya menyadari masalah kinerja juga (keduanya saya catat dalam pertanyaan saya). Namun, untuk pertanyaan khusus ini, saya mencari solusi C ++ yang elegan.
Ashwin Nanjappa
11
@Nelson LaQuet: Biar saya tebak: Karena strtok tidak reentrant?
paercebal
40
@Nelson jangan pernah mengirim string.c_str () ke strtok! strtok menghapus string input (menyisipkan karakter '\ 0' untuk mengganti setiap pembatas foudn) dan c_str () mengembalikan string yang tidak dapat dimodifikasi.
Evan Teran
3
@Nelson: Array itu harus berukuran str.size () +1 di komentar terakhir Anda. Tapi saya setuju dengan tesis Anda bahwa konyol untuk menghindari fungsi C karena alasan "estetika".
j_random_hacker
2
@ paulm: Tidak, lambatnya aliran C ++ disebabkan oleh segi. Mereka masih lebih lambat daripada fungsi stdio.h bahkan ketika sinkronisasi dinonaktifkan (dan pada stringstreams, yang tidak dapat disinkronkan).
Ben Voigt
42

Berikut adalah fungsi split yang:

  • generik
  • menggunakan standar C ++ (tanpa dorongan)
  • menerima beberapa pembatas
  • mengabaikan token kosong (dapat dengan mudah diubah)

    template<typename T>
    vector<T> 
    split(const T & str, const T & delimiters) {
        vector<T> v;
        typename T::size_type start = 0;
        auto pos = str.find_first_of(delimiters, start);
        while(pos != T::npos) {
            if(pos != start) // ignore empty tokens
                v.emplace_back(str, start, pos - start);
            start = pos + 1;
            pos = str.find_first_of(delimiters, start);
        }
        if(start < str.length()) // ignore trailing delimiter
            v.emplace_back(str, start, str.length() - start); // add what's left of the string
        return v;
    }

Contoh penggunaan:

    vector<string> v = split<string>("Hello, there; World", ";,");
    vector<wstring> v = split<wstring>(L"Hello, there; World", L";,");
Marco M.
sumber
Anda lupa menambahkan untuk menggunakan daftar: "sangat tidak efisien"
Xander Tulip
1
@XanderTulip, dapatkah Anda lebih konstruktif dan menjelaskan bagaimana atau mengapa?
Marco M.
3
@ XanderTulip: Saya berasumsi Anda merujuknya mengembalikan vektor dengan nilai. Pengembalian Nilai-Optimasi (RVO, google it) harus mengurus ini. Juga di C ++ 11 Anda dapat kembali dengan memindahkan referensi.
Joseph Garvin
3
Ini sebenarnya dapat dioptimalkan lebih lanjut: alih-alih .push_back (str.substr (...)) orang dapat menggunakan .emplace_back (str, start, pos-start). Dengan cara ini objek string dibangun dalam wadah dan dengan demikian kita menghindari operasi pemindahan + shenanigans lain yang dilakukan oleh fungsi substr.
Mihai Bişog
@zoopp ya. Ide bagus. VS10 tidak memiliki dukungan emplace_back ketika saya menulis ini. Saya akan memperbarui jawaban saya. Terima kasih
Marco M.
36

Saya punya solusi 2 baris untuk masalah ini:

char sep = ' ';
std::string s="1 This is an example";

for(size_t p=0, q=0; p!=s.npos; p=q)
  std::cout << s.substr(p+(p!=0), (q=s.find(sep, p+1))-p-(p!=0)) << std::endl;

Maka alih-alih mencetak Anda bisa memasukkannya ke dalam vektor.

rhomu
sumber
35

Namun cara lain yang fleksibel dan cepat

template<typename Operator>
void tokenize(Operator& op, const char* input, const char* delimiters) {
  const char* s = input;
  const char* e = s;
  while (*e != 0) {
    e = s;
    while (*e != 0 && strchr(delimiters, *e) == 0) ++e;
    if (e - s > 0) {
      op(s, e - s);
    }
    s = e + 1;
  }
}

Untuk menggunakannya dengan vektor string (Edit: Karena seseorang menunjukkan tidak mewarisi kelas STL ... hrmf;)):

template<class ContainerType>
class Appender {
public:
  Appender(ContainerType& container) : container_(container) {;}
  void operator() (const char* s, unsigned length) { 
    container_.push_back(std::string(s,length));
  }
private:
  ContainerType& container_;
};

std::vector<std::string> strVector;
Appender v(strVector);
tokenize(v, "A number of words to be tokenized", " \t");

Itu dia! Dan itu hanya satu cara untuk menggunakan tokenizer, seperti cara menghitung kata:

class WordCounter {
public:
  WordCounter() : noOfWords(0) {}
  void operator() (const char*, unsigned) {
    ++noOfWords;
  }
  unsigned noOfWords;
};

WordCounter wc;
tokenize(wc, "A number of words to be counted", " \t"); 
ASSERT( wc.noOfWords == 7 );

Terbatas oleh imajinasi;)

Robert
sumber
Bagus. Mengenai Appendercatatan "Mengapa kita tidak mewarisi kelas dari kelas STL?"
Andreas Spindler
32

Berikut adalah solusi sederhana yang hanya menggunakan pustaka regex standar

#include <regex>
#include <string>
#include <vector>

std::vector<string> Tokenize( const string str, const std::regex regex )
{
    using namespace std;

    std::vector<string> result;

    sregex_token_iterator it( str.begin(), str.end(), regex, -1 );
    sregex_token_iterator reg_end;

    for ( ; it != reg_end; ++it ) {
        if ( !it->str().empty() ) //token could be empty:check
            result.emplace_back( it->str() );
    }

    return result;
}

Argumen regex memungkinkan memeriksa beberapa argumen (spasi, koma, dll.)

Saya biasanya hanya memeriksa untuk membagi spasi dan koma, jadi saya juga memiliki fungsi default ini:

std::vector<string> TokenizeDefault( const string str )
{
    using namespace std;

    regex re( "[\\s,]+" );

    return Tokenize( str, re );
}

The "[\\s,]+"memeriksa ruang ( \\s) dan koma ( ,).

Catatan, jika Anda ingin membagi wstringbukan string,

  • ubah semua std::regexmenjadistd::wregex
  • ubah semua sregex_token_iteratormenjadiwsregex_token_iterator

Catatan, Anda mungkin juga ingin mengambil argumen string dengan referensi, tergantung pada kompiler Anda.

dk123
sumber
Ini akan menjadi jawaban favorit saya, tetapi std :: regex rusak di GCC 4.8. Mereka mengatakan bahwa mereka menerapkannya dengan benar di GCC 4.9. Saya masih memberi Anda +1
mchiasson
1
Ini adalah favorit saya dengan perubahan kecil: vektor dikembalikan sebagai referensi seperti yang Anda katakan, dan argumen "str" ​​dan "regex" juga diberikan oleh referensi. Terima kasih.
QuantumKarl
1
String mentah cukup berguna saat berurusan dengan pola regex. Dengan begitu, Anda tidak harus menggunakan urutan pelarian ... Anda bisa menggunakannya R"([\s,]+)".
Sam
26

Menggunakan std::stringstreamseperti yang Anda miliki berfungsi dengan sangat baik, dan melakukan apa yang Anda inginkan. Jika Anda hanya mencari cara berbeda dalam melakukan sesuatu, Anda dapat menggunakan std::find()/ std::find_first_of()dan std::string::substr().

Ini sebuah contoh:

#include <iostream>
#include <string>

int main()
{
    std::string s("Somewhere down the road");
    std::string::size_type prev_pos = 0, pos = 0;

    while( (pos = s.find(' ', pos)) != std::string::npos )
    {
        std::string substring( s.substr(prev_pos, pos-prev_pos) );

        std::cout << substring << '\n';

        prev_pos = ++pos;
    }

    std::string substring( s.substr(prev_pos, pos-prev_pos) ); // Last word
    std::cout << substring << '\n';

    return 0;
}
KTC
sumber
Ini hanya berfungsi untuk pembatas karakter tunggal. Perubahan sederhana memungkinkannya bekerja dengan multicharacter:prev_pos = pos += delimiter.length();
David Doria
25

Jika Anda ingin menggunakan boost, tetapi ingin menggunakan seluruh string sebagai pembatas (alih-alih karakter tunggal seperti dalam sebagian besar solusi yang diusulkan sebelumnya), Anda dapat menggunakan boost_split_iterator .

Kode contoh termasuk template yang mudah digunakan:

#include <iostream>
#include <vector>
#include <boost/algorithm/string.hpp>

template<typename _OutputIterator>
inline void split(
    const std::string& str, 
    const std::string& delim, 
    _OutputIterator result)
{
    using namespace boost::algorithm;
    typedef split_iterator<std::string::const_iterator> It;

    for(It iter=make_split_iterator(str, first_finder(delim, is_equal()));
            iter!=It();
            ++iter)
    {
        *(result++) = boost::copy_range<std::string>(*iter);
    }
}

int main(int argc, char* argv[])
{
    using namespace std;

    vector<string> splitted;
    split("HelloFOOworldFOO!", "FOO", back_inserter(splitted));

    // or directly to console, for example
    split("HelloFOOworldFOO!", "FOO", ostream_iterator<string>(cout, "\n"));
    return 0;
}
zerm
sumber
20

Inilah solusi regex yang hanya menggunakan pustaka regex standar. (Saya agak berkarat, jadi mungkin ada beberapa kesalahan sintaksis, tapi ini setidaknya merupakan ide umum)

#include <regex.h>
#include <string.h>
#include <vector.h>

using namespace std;

vector<string> split(string s){
    regex r ("\\w+"); //regex matches whole words, (greedy, so no fragment words)
    regex_iterator<string::iterator> rit ( s.begin(), s.end(), r );
    regex_iterator<string::iterator> rend; //iterators to iterate thru words
    vector<string> result<regex_iterator>(rit, rend);
    return result;  //iterates through the matches to fill the vector
}
AJMansfield
sumber
Respons serupa dengan pendekatan regex yang mungkin lebih baik: di sini , dan di sini .
nobar
20

Ada fungsi bernama strtok.

#include<string>
using namespace std;

vector<string> split(char* str,const char* delim)
{
    char* saveptr;
    char* token = strtok_r(str,delim,&saveptr);

    vector<string> result;

    while(token != NULL)
    {
        result.push_back(token);
        token = strtok_r(NULL,delim,&saveptr);
    }
    return result;
}
Pratik Deoghare
sumber
3
strtokberasal dari pustaka standar C, bukan C ++. Tidak aman untuk digunakan dalam program multithreaded. Ini memodifikasi string input.
Kevin Panko
13
Karena menyimpan pointer char dari panggilan pertama dalam variabel statis, sehingga pada panggilan berikutnya ketika NULL dilewatkan, ia mengingat pointer apa yang harus digunakan. Jika utas kedua memanggil strtoksaat utas lain masih memproses, pointer char ini akan ditimpa, dan kedua utas tersebut akan memiliki hasil yang salah. mkssoftware.com/docs/man3/strtok.3.asp
Kevin Panko
1
seperti yang disebutkan sebelumnya strtok tidak aman dan bahkan dalam C strtok_r direkomendasikan untuk digunakan
systemsfault
4
strtok_r dapat digunakan jika Anda berada di bagian kode yang dapat diakses. ini adalah satu - satunya solusi dari semua yang di atas yang bukan "line noise", dan merupakan bukti apa yang salah dengan c ++
Erik Aronesty
Diperbarui sehingga tidak ada keberatan dengan alasan keamanan benang dari C ++ wonks.
Erik Aronesty
17

The stringstream dapat nyaman jika Anda perlu untuk mengurai string dengan simbol non-space:

string s = "Name:JAck; Spouse:Susan; ...";
string dummy, name, spouse;

istringstream iss(s);
getline(iss, dummy, ':');
getline(iss, name, ';');
getline(iss, dummy, ':');
getline(iss, spouse, ';')
lukmac
sumber
14

Sejauh ini saya menggunakan yang ada di Boost , tapi saya butuh sesuatu yang tidak bergantung padanya, jadi saya sampai pada ini:

static void Split(std::vector<std::string>& lst, const std::string& input, const std::string& separators, bool remove_empty = true)
{
    std::ostringstream word;
    for (size_t n = 0; n < input.size(); ++n)
    {
        if (std::string::npos == separators.find(input[n]))
            word << input[n];
        else
        {
            if (!word.str().empty() || !remove_empty)
                lst.push_back(word.str());
            word.str("");
        }
    }
    if (!word.str().empty() || !remove_empty)
        lst.push_back(word.str());
}

Poin yang bagus adalah separatorsAnda dapat melewati lebih dari satu karakter.

Goran
sumber
13

Saya telah menggulung saya sendiri menggunakan strtok dan menggunakan dorongan untuk membagi string. Metode terbaik yang saya temukan adalah C + + String Toolkit Library . Ini sangat fleksibel dan cepat.

#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;
}

Toolkit ini memiliki lebih banyak fleksibilitas daripada yang ditunjukkan contoh sederhana ini tetapi kegunaannya dalam mengurai string menjadi elemen yang berguna sangat luar biasa.

DannyK
sumber
13

Pendek dan elegan

#include <vector>
#include <string>
using namespace std;

vector<string> split(string data, string token)
{
    vector<string> output;
    size_t pos = string::npos; // size_t to avoid improbable overflow
    do
    {
        pos = data.find(token);
        output.push_back(data.substr(0, pos));
        if (string::npos != pos)
            data = data.substr(pos + token.size());
    } while (string::npos != pos);
    return output;
}

dapat menggunakan string apa pun sebagai pembatas, juga dapat digunakan dengan data biner (std :: string mendukung data biner, termasuk nol)

menggunakan:

auto a = split("this!!is!!!example!string", "!!");

keluaran:

this
is
!example!string
user1438233
sumber
1
Saya suka solusi ini karena memungkinkan pemisah menjadi string dan bukan char, namun, ia memodifikasi string, jadi memaksa penciptaan salinan string asli.
Alessandro Teruzzi
11

Saya membuat ini karena saya membutuhkan cara mudah untuk memisahkan string dan string berbasis-c ... Semoga orang lain dapat menemukannya berguna juga. Juga tidak bergantung pada token dan Anda dapat menggunakan bidang sebagai pembatas, yang merupakan kunci lain yang saya butuhkan.

Saya yakin ada perbaikan yang dapat dilakukan untuk lebih meningkatkan keanggunannya dan silakan lakukan dengan segala cara

StringSplitter.hpp:

#include <vector>
#include <iostream>
#include <string.h>

using namespace std;

class StringSplit
{
private:
    void copy_fragment(char*, char*, char*);
    void copy_fragment(char*, char*, char);
    bool match_fragment(char*, char*, int);
    int untilnextdelim(char*, char);
    int untilnextdelim(char*, char*);
    void assimilate(char*, char);
    void assimilate(char*, char*);
    bool string_contains(char*, char*);
    long calc_string_size(char*);
    void copy_string(char*, char*);

public:
    vector<char*> split_cstr(char);
    vector<char*> split_cstr(char*);
    vector<string> split_string(char);
    vector<string> split_string(char*);
    char* String;
    bool do_string;
    bool keep_empty;
    vector<char*> Container;
    vector<string> ContainerS;

    StringSplit(char * in)
    {
        String = in;
    }

    StringSplit(string in)
    {
        size_t len = calc_string_size((char*)in.c_str());
        String = new char[len + 1];
        memset(String, 0, len + 1);
        copy_string(String, (char*)in.c_str());
        do_string = true;
    }

    ~StringSplit()
    {
        for (int i = 0; i < Container.size(); i++)
        {
            if (Container[i] != NULL)
            {
                delete[] Container[i];
            }
        }
        if (do_string)
        {
            delete[] String;
        }
    }
};

StringSplitter.cpp:

#include <string.h>
#include <iostream>
#include <vector>
#include "StringSplit.hpp"

using namespace std;

void StringSplit::assimilate(char*src, char delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }

        }
        else
        {
            delete[] temp;
        }
    }
}

void StringSplit::assimilate(char*src, char* delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }
        }
        else
        {
            delete[] temp;
        }
    }
}

long StringSplit::calc_string_size(char* _in)
{
    long i = 0;
    while (*_in++)
    {
        i++;
    }
    return i;
}

bool StringSplit::string_contains(char* haystack, char* needle)
{
    size_t len = calc_string_size(needle);
    size_t lenh = calc_string_size(haystack);
    while (lenh--)
    {
        if (match_fragment(haystack + lenh, needle, len))
        {
            return true;
        }
    }
    return false;
}

bool StringSplit::match_fragment(char* _src, char* cmp, int len)
{
    while (len--)
    {
        if (*(_src + len) != *(cmp + len))
        {
            return false;
        }
    }
    return true;
}

int StringSplit::untilnextdelim(char* _in, char delim)
{
    size_t len = calc_string_size(_in);
    if (*_in == delim)
    {
        _in += 1;
        return len - 1;
    }

    int c = 0;
    while (*(_in + c) != delim && c < len)
    {
        c++;
    }

    return c;
}

int StringSplit::untilnextdelim(char* _in, char* delim)
{
    int s = calc_string_size(delim);
    int c = 1 + s;

    if (!string_contains(_in, delim))
    {
        return calc_string_size(_in);
    }
    else if (match_fragment(_in, delim, s))
    {
        _in += s;
        return calc_string_size(_in);
    }

    while (!match_fragment(_in + c, delim, s))
    {
        c++;
    }

    return c;
}

void StringSplit::copy_fragment(char* dest, char* src, char delim)
{
    if (*src == delim)
    {
        src++;
    }

    int c = 0;
    while (*(src + c) != delim && *(src + c))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

void StringSplit::copy_string(char* dest, char* src)
{
    int i = 0;
    while (*(src + i))
    {
        *(dest + i) = *(src + i);
        i++;
    }
}

void StringSplit::copy_fragment(char* dest, char* src, char* delim)
{
    size_t len = calc_string_size(delim);
    size_t lens = calc_string_size(src);

    if (match_fragment(src, delim, len))
    {
        src += len;
        lens -= len;
    }

    int c = 0;
    while (!match_fragment(src + c, delim, len) && (c < lens))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

vector<char*> StringSplit::split_cstr(char Delimiter)
{
    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char Delimiter)
{
    do_string = true;

    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

vector<char*> StringSplit::split_cstr(char* Delimiter)
{
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while(*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String,Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char* Delimiter)
{
    do_string = true;
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while (*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

Contoh:

int main(int argc, char*argv[])
{
    StringSplit ss = "This:CUT:is:CUT:an:CUT:example:CUT:cstring";
    vector<char*> Split = ss.split_cstr(":CUT:");

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

Akan menghasilkan:

Ini
adalah
sebuah
contoh
cstring

int main(int argc, char*argv[])
{
    StringSplit ss = "This:is:an:example:cstring";
    vector<char*> Split = ss.split_cstr(':');

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This[SPLIT]is[SPLIT]an[SPLIT]example[SPLIT]string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string("[SPLIT]");

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This|is|an|example|string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string('|');

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

Untuk menyimpan entri kosong (secara default akan dikosongkan):

StringSplit ss = mystring;
ss.keep_empty = true;
vector<string> Split = ss.split_string(":DELIM:");

Tujuannya adalah membuatnya mirip dengan metode C # 's Split () di mana pemisahan string semudah:

String[] Split = 
    "Hey:cut:what's:cut:your:cut:name?".Split(new[]{":cut:"}, StringSplitOptions.None);

foreach(String X in Split)
{
    Console.Write(X);
}

Saya harap orang lain dapat menemukan ini sama bermanfaatnya dengan saya.

Steve Dell
sumber
10

Bagaimana dengan ini:

#include <string>
#include <vector>

using namespace std;

vector<string> split(string str, const char delim) {
    vector<string> v;
    string tmp;

    for(string::const_iterator i; i = str.begin(); i <= str.end(); ++i) {
        if(*i != delim && i != str.end()) {
            tmp += *i; 
        } else {
            v.push_back(tmp);
            tmp = ""; 
        }   
    }   

    return v;
}
gibbz
sumber
Ini adalah jawaban terbaik di sini, jika Anda hanya ingin membagi karakter pembatas tunggal. Pertanyaan aslinya ingin dipisah pada spasi putih, artinya kombinasi dari satu atau lebih spasi atau tab yang berurutan. Anda sebenarnya telah menjawab stackoverflow.com/questions/53849
Oktalist
10

Jawaban ini mengambil string dan memasukkannya ke dalam vektor string. Ini menggunakan perpustakaan boost.

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));
NL628
sumber
9

Inilah cara lain untuk melakukannya ..

void split_string(string text,vector<string>& words)
{
  int i=0;
  char ch;
  string word;

  while(ch=text[i++])
  {
    if (isspace(ch))
    {
      if (!word.empty())
      {
        words.push_back(word);
      }
      word = "";
    }
    else
    {
      word += ch;
    }
  }
  if (!word.empty())
  {
    words.push_back(word);
  }
}
user246110
sumber
9

Saya suka menggunakan metode boost / regex untuk tugas ini karena mereka memberikan fleksibilitas maksimum untuk menentukan kriteria pemisahan.

#include <iostream>
#include <string>
#include <boost/regex.hpp>

int main() {
    std::string line("A:::line::to:split");
    const boost::regex re(":+"); // one or more colons

    // -1 means find inverse matches aka split
    boost::sregex_token_iterator tokens(line.begin(),line.end(),re,-1);
    boost::sregex_token_iterator end;

    for (; tokens != end; ++tokens)
        std::cout << *tokens << std::endl;
}
Marty B
sumber
9

Baru-baru ini saya harus membagi kata yang berhias unta menjadi subword. Tidak ada pembatas, hanya karakter atas.

#include <string>
#include <list>
#include <locale> // std::isupper

template<class String>
const std::list<String> split_camel_case_string(const String &s)
{
    std::list<String> R;
    String w;

    for (String::const_iterator i = s.begin(); i < s.end(); ++i) {  {
        if (std::isupper(*i)) {
            if (w.length()) {
                R.push_back(w);
                w.clear();
            }
        }
        w += *i;
    }

    if (w.length())
        R.push_back(w);
    return R;
}

Misalnya, ini membagi "AQueryTrades" menjadi "A", "Query" dan "Trades". Fungsi ini bekerja dengan string sempit dan lebar. Karena itu menghormati lokal saat ini ia membagi "RaumfahrtÜberwachungsVerordnung" menjadi "Raumfahrt", "Überwachungs" dan "Verordnung".

Catatan std::upperharus benar-benar diteruskan sebagai argumen templat fungsi. Maka yang lebih umum dari fungsi ini dapat dibagi pada pembatas seperti ",", ";"atau " "juga.

Andreas Spindler
sumber
2
Ada 2 putaran. Itu bagus. Sepertinya bahasa Inggris saya harus banyak "Jerman". Namun, revisionis tidak memperbaiki dua bug kecil mungkin karena mereka sudah jelas: std::isupperbisa dilewatkan sebagai argumen, bukan std::upper. Kedua, letakkan typenamesebelum String::const_iterator.
Andreas Spindler
9
#include<iostream>
#include<string>
#include<sstream>
#include<vector>
using namespace std;

    vector<string> split(const string &s, char delim) {
        vector<string> elems;
        stringstream ss(s);
        string item;
        while (getline(ss, item, delim)) {
            elems.push_back(item);
        }
        return elems;
    }

int main() {

        vector<string> x = split("thi is an sample test",' ');
        unsigned int i;
        for(i=0;i<x.size();i++)
            cout<<i<<":"<<x[i]<<endl;
        return 0;
}
san45
sumber
9

Menggunakan std::string_viewdan range-v3perpustakaan Eric Niebler :

https://wandbox.org/permlink/kW5lwRCL1pxjp2pW

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"
#include "range/v3/algorithm.hpp"

int main() {
    std::string s = "Somewhere down the range v3 library";
    ranges::for_each(s  
        |   ranges::view::split(' ')
        |   ranges::view::transform([](auto &&sub) {
                return std::string_view(&*sub.begin(), ranges::distance(sub));
            }),
        [](auto s) {std::cout << "Substring: " << s << "\n";}
    );
}

Dengan menggunakan rentang forloop alih-alih ranges::for_eachalgoritma:

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"

int main()
{
    std::string str = "Somewhere down the range v3 library";
    for (auto s : str | ranges::view::split(' ')
                      | ranges::view::transform([](auto&& sub) { return std::string_view(&*sub.begin(), ranges::distance(sub)); }
                      ))
    {
        std::cout << "Substring: " << s << "\n";
    }
}
Porsche9II
sumber
Yepp, kisaran untuk berbasis terlihat lebih baik - Saya setuju
Porsche9II