Parsing (pisahkan) string dalam C ++ menggunakan pembatas string (standar C ++)


363

Saya mengurai string dalam C ++ menggunakan yang berikut ini:

using namespace std;

string parsed,input="text to be parsed";
stringstream input_stringstream(input);

if (getline(input_stringstream,parsed,' '))
{
     // do some processing.
}

Parsing dengan pembatas char tunggal baik-baik saja. Tetapi bagaimana jika saya ingin menggunakan string sebagai pembatas.

Contoh: Saya ingin membagi:

scott>=tiger

dengan >=sebagai pembatas sehingga saya bisa mendapatkan scott dan harimau.

Jawaban:


576

Anda dapat menggunakan std::string::find()fungsi ini untuk menemukan posisi pembatas string Anda, lalu gunakan std::string::substr()untuk mendapatkan token.

Contoh:

std::string s = "scott>=tiger";
std::string delimiter = ">=";
std::string token = s.substr(0, s.find(delimiter)); // token is "scott"
  • The find(const string& str, size_t pos = 0)fungsi mengembalikan posisi kejadian pertama dari strdalam string, atau nposjika string tidak ditemukan.

  • The substr(size_t pos = 0, size_t n = npos)mengembalikan fungsi substring objek, mulai dari posisi posdan panjang npos.


Jika Anda memiliki beberapa pembatas, setelah Anda mengekstrak satu token, Anda dapat menghapusnya (termasuk pembatas) untuk melanjutkan dengan ekstraksi berikutnya (jika Anda ingin mempertahankan string asli, gunakan saja s = s.substr(pos + delimiter.length());):

s.erase(0, s.find(delimiter) + delimiter.length());

Dengan cara ini Anda dapat dengan mudah melakukan loop untuk mendapatkan setiap token.

Contoh Lengkap

std::string s = "scott>=tiger>=mushroom";
std::string delimiter = ">=";

size_t pos = 0;
std::string token;
while ((pos = s.find(delimiter)) != std::string::npos) {
    token = s.substr(0, pos);
    std::cout << token << std::endl;
    s.erase(0, pos + delimiter.length());
}
std::cout << s << std::endl;

Keluaran:

scott
tiger
mushroom

66
Bagi mereka yang tidak ingin memodifikasi string input, lakukansize_t last = 0; size_t next = 0; while ((next = s.find(delimiter, last)) != string::npos) { cout << s.substr(last, next-last) << endl; last = next + 1; } cout << s.substr(last) << endl;
hayk.mart

30
CATATAN: mushroomoutput di luar loop, yaitus = mushroom
Don Larynx

1
Sampel tersebut tidak mengekstraksi token terakhir dari string. Contoh tambang yang mengekstraksi IPV4 dari satu string: <code> size_t last = 0; size_t berikutnya = 0; indeks int = 0; while (index <4) {next = str.find (pembatas, terakhir); auto number = str.substr (terakhir, berikutnya - terakhir); IPv4 [index ++] = atoi (number.c_str ()); terakhir = berikutnya + 1; } </code>
rfog

2
@ hayk.mart Hanya sebuah catatan, yang akan menjadi yang berikut, Anda perlu menambahkan 2 bukan 1 karena ukuran pembatas yang adalah 2 karakter :): std :: string s = "scott> = tiger> = mushroom"; std :: string delimiter = "> ="; size_t last = 0; size_t berikutnya = 0; while ((next = s.find (delimiter, last))! = std :: string :: npos) {std :: cout << s.substr (terakhir, terakhir-terakhir) << std :: endl; last = next + 2; } std :: cout << s.substr (terakhir) << std :: endl;
ervinbosenbacher

Untuk mendapatkan "harimau", gunakan std::string token = s.substr(s.find(delimiter) + 1);, jika Anda yakin itu ada (saya menggunakan +1 panjangnya) ...
gsamaras

64

Metode ini menggunakan std::string::findtanpa mengubah string asli dengan mengingat awal dan akhir token substring sebelumnya.

#include <iostream>
#include <string>

int main()
{
    std::string s = "scott>=tiger";
    std::string delim = ">=";

    auto start = 0U;
    auto end = s.find(delim);
    while (end != std::string::npos)
    {
        std::cout << s.substr(start, end - start) << std::endl;
        start = end + delim.length();
        end = s.find(delim, start);
    }

    std::cout << s.substr(start, end);
}

34

Anda dapat menggunakan fungsi berikutnya untuk memisahkan string:

vector<string> split(const string& str, const string& delim)
{
    vector<string> tokens;
    size_t prev = 0, pos = 0;
    do
    {
        pos = str.find(delim, prev);
        if (pos == string::npos) pos = str.length();
        string token = str.substr(prev, pos-prev);
        if (!token.empty()) tokens.push_back(token);
        prev = pos + delim.length();
    }
    while (pos < str.length() && prev < str.length());
    return tokens;
}

5
IMO tidak berfungsi seperti yang diharapkan: split("abc","a")akan mengembalikan vektor atau string tunggal "bc",, di mana saya pikir akan lebih masuk akal jika telah mengembalikan vektor elemen ["", "bc"]. Menggunakan str.split()Python, itu intuitif bagi saya bahwa itu harus mengembalikan string kosong jika delimditemukan di awal atau di akhir, tapi itu hanya pendapat saya. Bagaimanapun, saya hanya berpikir itu harus disebutkan
kyriakosSt

1
Akan sangat menyarankan menghapus if (!token.empty()) mencegah masalah yang disebutkan oleh @kyriakosSt serta masalah lain yang terkait dengan pembatas berturut-turut.
Steve

1
Saya akan menghapus upvote saya jika saya bisa, tetapi SO tidak akan membiarkan saya. Masalah yang diangkat oleh @kyriakosSt adalah masalah, dan menghapus if (!token.empty())sepertinya tidak cukup untuk memperbaikinya.
bhaller

1
@bhaller sniplet ini dirancang persis untuk melewati fragmen kosong. Jika Anda perlu menyimpan yang kosong, saya khawatir Anda perlu menulis implementasi split lainnya. Harap menyarankan Anda untuk mempostingnya di sini untuk kebaikan komunitas.
Sviatoslav

32

Untuk pembatas string

Membagi string berdasarkan pembatas string . Seperti memisahkan string "adsf-+qwret-+nvfkbdsj-+orthdfjgh-+dfjrleih"berdasarkan pembatas string "-+", output akan{"adsf", "qwret", "nvfkbdsj", "orthdfjgh", "dfjrleih"}

#include <iostream>
#include <sstream>
#include <vector>

using namespace std;

// for string delimiter
vector<string> split (string s, string delimiter) {
    size_t pos_start = 0, pos_end, delim_len = delimiter.length();
    string token;
    vector<string> res;

    while ((pos_end = s.find (delimiter, pos_start)) != string::npos) {
        token = s.substr (pos_start, pos_end - pos_start);
        pos_start = pos_end + delim_len;
        res.push_back (token);
    }

    res.push_back (s.substr (pos_start));
    return res;
}

int main() {
    string str = "adsf-+qwret-+nvfkbdsj-+orthdfjgh-+dfjrleih";
    string delimiter = "-+";
    vector<string> v = split (str, delimiter);

    for (auto i : v) cout << i << endl;

    return 0;
}


Keluaran

adsf
qwret
nvfkbdsj
orthdfjgh
dfjrleih




Untuk pembatas karakter tunggal

Pisahkan string berdasarkan pembatas karakter. Seperti memisahkan string "adsf+qwer+poui+fdgh"dengan pembatas "+"akan ditampilkan{"adsf", "qwer", "poui", "fdg"h}

#include <iostream>
#include <sstream>
#include <vector>

using namespace std;

vector<string> split (const string &s, char delim) {
    vector<string> result;
    stringstream ss (s);
    string item;

    while (getline (ss, item, delim)) {
        result.push_back (item);
    }

    return result;
}

int main() {
    string str = "adsf+qwer+poui+fdgh";
    vector<string> v = split (str, '+');

    for (auto i : v) cout << i << endl;

    return 0;
}


Keluaran

adsf
qwer
poui
fdgh

Anda kembali vector<string>saya pikir itu akan memanggil copy constructor.
Mayur

2
Setiap referensi yang saya lihat menunjukkan bahwa panggilan ke copy constructor dihilangkan dalam konteks itu.
David Diberikan

Dengan kompiler "modern" (C ++ 03?) Saya percaya ini benar, RVO dan / atau memindahkan semantik akan menghilangkan copy constructor.
Kevin

Saya mencoba satu untuk pembatas karakter tunggal, dan jika string berakhir pada pembatas (yaitu, kolom csv kosong di akhir baris), itu tidak mengembalikan string kosong. Ini hanya mengembalikan satu string lebih sedikit. Sebagai contoh: 1,2,3,4 \ nA, B, C,
kounoupis

Saya juga mencoba satu untuk pembatas string, dan jika string berakhir pada pembatas, pembatas terakhir menjadi bagian dari string terakhir yang diekstraksi.
kounoupis

20

Kode ini memisahkan garis dari teks, dan menambahkan semua orang ke dalam vektor.

vector<string> split(char *phrase, string delimiter){
    vector<string> list;
    string s = string(phrase);
    size_t pos = 0;
    string token;
    while ((pos = s.find(delimiter)) != string::npos) {
        token = s.substr(0, pos);
        list.push_back(token);
        s.erase(0, pos + delimiter.length());
    }
    list.push_back(s);
    return list;
}

Dipanggil oleh:

vector<string> listFilesMax = split(buffer, "\n");

ini bekerja dengan baik! Saya telah menambahkan list.push_back (s); karena itu hilang.
Stoica Mircea

1
itu merindukan bagian terakhir dari string. Setelah loop sementara berakhir, kita perlu menambahkan sisa s sebagai token baru.
whihathac

Saya telah mengedit kode sampel untuk memperbaiki push_back yang hilang.
fret

1
Akan lebih baikvector<string> split(char *phrase, const string delimiter="\n")
Mayur

15

strtok memungkinkan Anda untuk melewati beberapa karakter sebagai pembatas. Saya bertaruh jika Anda memasukkan "> =" string contoh Anda akan dipisah dengan benar (meskipun> dan = dihitung sebagai pembatas individu).

EDIT jika Anda tidak ingin menggunakan c_str()untuk mengkonversi dari string ke char *, Anda dapat menggunakan substr dan find_first_of untuk tokenize.

string token, mystring("scott>=tiger");
while(token != mystring){
  token = mystring.substr(0,mystring.find_first_of(">="));
  mystring = mystring.substr(mystring.find_first_of(">=") + 1);
  printf("%s ",token.c_str());
}

3
Terima kasih. Tapi saya ingin menggunakan hanya C ++ dan tidak ada fungsi C seperti strtok()karena akan mengharuskan saya untuk menggunakan array char bukan string.
TheCrazyProgrammer

2
@TheCrazyProgrammer Jadi? Jika fungsi C melakukan apa yang Anda butuhkan, gunakan itu. Ini bukan dunia di mana fungsi C tidak tersedia di C ++ (sebenarnya, mereka harus). .c_str()murah dan mudah juga.
Qix - MONICA DISALAHKAN

1
Pemeriksaan jika (token! = Mystring) memberikan hasil yang salah jika Anda memiliki elemen berulang dalam string Anda. Saya menggunakan kode Anda untuk membuat versi yang tidak memiliki ini. Ini memiliki banyak perubahan yang mengubah jawaban secara mendasar, jadi saya menulis jawaban saya sendiri alih-alih mengedit. Lihat di bawah.
Amber Elferink

5

Inilah pendapat saya tentang ini. Ini menangani kasus tepi dan mengambil parameter opsional untuk menghapus entri kosong dari hasil.

bool endsWith(const std::string& s, const std::string& suffix)
{
    return s.size() >= suffix.size() &&
           s.substr(s.size() - suffix.size()) == suffix;
}

std::vector<std::string> split(const std::string& s, const std::string& delimiter, const bool& removeEmptyEntries = false)
{
    std::vector<std::string> tokens;

    for (size_t start = 0, end; start < s.length(); start = end + delimiter.length())
    {
         size_t position = s.find(delimiter, start);
         end = position != string::npos ? position : s.length();

         std::string token = s.substr(start, end - start);
         if (!removeEmptyEntries || !token.empty())
         {
             tokens.push_back(token);
         }
    }

    if (!removeEmptyEntries &&
        (s.empty() || endsWith(s, delimiter)))
    {
        tokens.push_back("");
    }

    return tokens;
}

Contohnya

split("a-b-c", "-"); // [3]("a","b","c")

split("a--c", "-"); // [3]("a","","c")

split("-b-", "-"); // [3]("","b","")

split("--c--", "-"); // [5]("","","c","","")

split("--c--", "-", true); // [1]("c")

split("a", "-"); // [1]("a")

split("", "-"); // [1]("")

split("", "-", true); // [0]()

4

Ini harus bekerja dengan sempurna untuk pembatas string (atau karakter tunggal). Jangan lupa untuk memasukkan #include <sstream>.

std::string input = "Alfa=,+Bravo=,+Charlie=,+Delta";
std::string delimiter = "=,+"; 
std::istringstream ss(input);
std::string token;
std::string::iterator it;

while(std::getline(ss, token, *(it = delimiter.begin()))) {
    while(*(++it)) ss.get();
    std::cout << token << " " << '\n';
}

Loop sementara pertama mengekstraksi token menggunakan karakter pertama dari pembatas string. Loop kedua sementara melompati sisa pembatas dan berhenti pada awal token berikutnya.


3

Saya akan menggunakan boost::tokenizer. Berikut dokumentasi yang menjelaskan cara membuat fungsi tokenizer yang sesuai: http://www.boost.org/doc/libs/1_52_0/libs/tokenizer/tokenizerfunction.htm

Ini salah satu yang sesuai untuk kasus Anda.

struct my_tokenizer_func
{
    template<typename It>
    bool operator()(It& next, It end, std::string & tok)
    {
        if (next == end)
            return false;
        char const * del = ">=";
        auto pos = std::search(next, end, del, del + 2);
        tok.assign(next, pos);
        next = pos;
        if (next != end)
            std::advance(next, 2);
        return true;
    }

    void reset() {}
};

int main()
{
    std::string to_be_parsed = "1) one>=2) two>=3) three>=4) four";
    for (auto i : boost::tokenizer<my_tokenizer_func>(to_be_parsed))
        std::cout << i << '\n';
}

3
Terima kasih. Tapi saya ingin hanya berharap C ++ standar dan bukan perpustakaan pihak ketiga.
TheCrazyProgrammer

@TheCrazyProgrammer: Oke, ketika saya membaca "Standard C ++", saya pikir itu berarti tidak ada ekstensi non-standar, bukan berarti Anda tidak bisa menggunakan standar yang sesuai dengan perpustakaan pihak ketiga.
Benjamin Lindley

3

Jawaban sudah ada, tetapi dipilih-jawaban menggunakan fungsi hapus yang sangat mahal, pikirkan beberapa string yang sangat besar (dalam MB). Karena itu saya menggunakan fungsi di bawah ini.

vector<string> split(const string& i_str, const string& i_delim)
{
    vector<string> result;

    size_t found = i_str.find(i_delim);
    size_t startIndex = 0;

    while(found != string::npos)
    {
        string temp(i_str.begin()+startIndex, i_str.begin()+found);
        result.push_back(temp);
        startIndex = found + i_delim.size();
        found = i_str.find(i_delim, startIndex);
    }
    if(startIndex != i_str.size())
        result.push_back(string(i_str.begin()+startIndex, i_str.end()));
    return result;      
}

Saya menguji ini, dan itu berhasil. Terima kasih! Menurut pendapat saya, ini adalah jawaban terbaik karena seperti yang dinyatakan sebagai jawaban pertama, solusi ini mengurangi overhead memori, dan hasilnya disimpan dengan nyaman dalam vektor. (meniru string.split()metode Python .)
Robbie Capps

2

Ini adalah metode lengkap yang memisahkan string pada pembatas apa pun dan mengembalikan vektor string yang dicacah.

Ini adalah adaptasi dari jawaban dari ryanbwork. Namun, pemeriksaannya untuk: if(token != mystring)memberikan hasil yang salah jika Anda memiliki elemen berulang dalam string Anda. Ini solusi saya untuk masalah itu.

vector<string> Split(string mystring, string delimiter)
{
    vector<string> subStringList;
    string token;
    while (true)
    {
        size_t findfirst = mystring.find_first_of(delimiter);
        if (findfirst == string::npos) //find_first_of returns npos if it couldn't find the delimiter anymore
        {
            subStringList.push_back(mystring); //push back the final piece of mystring
            return subStringList;
        }
        token = mystring.substr(0, mystring.find_first_of(delimiter));
        mystring = mystring.substr(mystring.find_first_of(delimiter) + 1);
        subStringList.push_back(token);
    }
    return subStringList;
}

1
Sesuatu seperti while (true)ini biasanya menakutkan untuk dilihat dalam kode seperti ini. Secara pribadi saya akan merekomendasikan menulis ulang ini sehingga perbandingan std::string::npos(atau masing-masing cek terhadap mystring.size()) membuat while (true)usang.
Joel Bodenmann

1

Jika Anda tidak ingin memodifikasi string (seperti dalam jawaban oleh Vincenzo Pii) dan ingin menampilkan token terakhir juga, Anda mungkin ingin menggunakan pendekatan ini:

inline std::vector<std::string> splitString( const std::string &s, const std::string &delimiter ){
    std::vector<std::string> ret;
    size_t start = 0;
    size_t end = 0;
    size_t len = 0;
    std::string token;
    do{ end = s.find(delimiter,start); 
        len = end - start;
        token = s.substr(start, len);
        ret.emplace_back( token );
        start += len + delimiter.length();
        std::cout << token << std::endl;
    }while ( end != std::string::npos );
    return ret;
}

0
#include<iostream>
#include<algorithm>
using namespace std;

int split_count(string str,char delimit){
return count(str.begin(),str.end(),delimit);
}

void split(string str,char delimit,string res[]){
int a=0,i=0;
while(a<str.size()){
res[i]=str.substr(a,str.find(delimit));
a+=res[i].size()+1;
i++;
}
}

int main(){

string a="abc.xyz.mno.def";
int x=split_count(a,'.')+1;
string res[x];
split(a,'.',res);

for(int i=0;i<x;i++)
cout<<res[i]<<endl;
  return 0;
}

PS: Hanya berfungsi jika panjang senar setelah pemisahan sama


Ini menggunakan ekstensi GCC - array panjang variabel.
user202729

0

Fungsi:

std::vector<std::string> WSJCppCore::split(const std::string& sWhat, const std::string& sDelim) {
    std::vector<std::string> vRet;
    int nPos = 0;
    int nLen = sWhat.length();
    int nDelimLen = sDelim.length();
    while (nPos < nLen) {
        std::size_t nFoundPos = sWhat.find(sDelim, nPos);
        if (nFoundPos != std::string::npos) {
            std::string sToken = sWhat.substr(nPos, nFoundPos - nPos);
            vRet.push_back(sToken);
            nPos = nFoundPos + nDelimLen;
            if (nFoundPos + nDelimLen == nLen) { // last delimiter
                vRet.push_back("");
            }
        } else {
            std::string sToken = sWhat.substr(nPos, nLen - nPos);
            vRet.push_back(sToken);
            break;
        }
    }
    return vRet;
}

Unit-tes:

bool UnitTestSplit::run() {
bool bTestSuccess = true;

    struct LTest {
        LTest(
            const std::string &sStr,
            const std::string &sDelim,
            const std::vector<std::string> &vExpectedVector
        ) {
            this->sStr = sStr;
            this->sDelim = sDelim;
            this->vExpectedVector = vExpectedVector;
        };
        std::string sStr;
        std::string sDelim;
        std::vector<std::string> vExpectedVector;
    };
    std::vector<LTest> tests;
    tests.push_back(LTest("1 2 3 4 5", " ", {"1", "2", "3", "4", "5"}));
    tests.push_back(LTest("|1f|2п|3%^|44354|5kdasjfdre|2", "|", {"", "1f", "2п", "3%^", "44354", "5kdasjfdre", "2"}));
    tests.push_back(LTest("|1f|2п|3%^|44354|5kdasjfdre|", "|", {"", "1f", "2п", "3%^", "44354", "5kdasjfdre", ""}));
    tests.push_back(LTest("some1 => some2 => some3", "=>", {"some1 ", " some2 ", " some3"}));
    tests.push_back(LTest("some1 => some2 => some3 =>", "=>", {"some1 ", " some2 ", " some3 ", ""}));

    for (int i = 0; i < tests.size(); i++) {
        LTest test = tests[i];
        std::string sPrefix = "test" + std::to_string(i) + "(\"" + test.sStr + "\")";
        std::vector<std::string> vSplitted = WSJCppCore::split(test.sStr, test.sDelim);
        compareN(bTestSuccess, sPrefix + ": size", vSplitted.size(), test.vExpectedVector.size());
        int nMin = std::min(vSplitted.size(), test.vExpectedVector.size());
        for (int n = 0; n < nMin; n++) {
            compareS(bTestSuccess, sPrefix + ", element: " + std::to_string(n), vSplitted[n], test.vExpectedVector[n]);
        }
    }

    return bTestSuccess;
}

0
std::vector<std::string> parse(std::string str,std::string delim){
    std::vector<std::string> tokens;
    char *str_c = strdup(str.c_str()); 
    char* token = NULL;

    token = strtok(str_c, delim.c_str()); 
    while (token != NULL) { 
        tokens.push_back(std::string(token));  
        token = strtok(NULL, delim.c_str()); 
    }

    delete[] str_c;

    return tokens;
}

-4
std::vector<std::string> split(const std::string& s, char c) {
  std::vector<std::string> v;
  unsigned int ii = 0;
  unsigned int j = s.find(c);
  while (j < s.length()) {
    v.push_back(s.substr(i, j - i));
    i = ++j;
    j = s.find(c, j);
    if (j >= s.length()) {
      v.push_back(s.substr(i, s,length()));
      break;
    }
  }
  return v;
}

1
Harap lebih akurat. Kode Anda tidak akan dikompilasi. Lihat deklarasi "i" dan koma bukan titik.
jstuardo
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.