Dize sınırlayıcı (C ++) kullanarak bir dizeyi C ++ içinde ayrıştırma (bölme)


364

Aşağıdaki kullanarak C ++ bir dize ayrıştırma:

using namespace std;

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

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

Tek bir karakter sınırlayıcı ile ayrıştırma iyidir. Ama ne bir dize sınırlayıcı olarak kullanmak istersem.

Örnek: Bölmek istiyorum:

scott>=tiger

ile >=ayırıcı olarak ben Scott ve kaplan mı, böylece.

Yanıtlar:


579

Bu std::string::find()işlevi, dize sınırlayıcınızın konumunu bulmak için kullanabilir, ardından std::string::substr()bir simge almak için kullanabilirsiniz .

Misal:

std::string s = "scott>=tiger";
std::string delimiter = ">=";
std::string token = s.substr(0, s.find(delimiter)); // token is "scott"
  • find(const string& str, size_t pos = 0)Fonksiyonu ilk geçtiği pozisyonunu geri strdizge içinde veya nposdize bulunmazsa.

  • substr(size_t pos = 0, size_t n = npos)İşlev pozisyonunda başlayarak nesnenin bir alt döner posve uzunluk npos.


Birden çok sınırlayıcınız varsa, bir jetonu çıkardıktan sonra, sonraki ayıklamalara devam etmek için (sınırlayıcı dahil) kaldırabilirsiniz (orijinal dizeyi korumak istiyorsanız, sadece şunu kullanın s = s.substr(pos + delimiter.length());):

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

Bu şekilde, her jetonu almak için kolayca döngü yapabilirsiniz.

Komple Örnek

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;

Çıktı:

scott
tiger
mushroom

66
Giriş dizesini değiştirmek istemeyenler için şunu yapınsize_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

31
NOT: mushroomdöngü dışında çıkışlar, yanis = mushroom
Don Larynx

1
Bu örnekler son belirteci dizeden ayıklamaz. Bir dizeden bir IpV4 ayıklayan bir örnek: <code> size_t last = 0; size_t sonraki = 0; int dizini = 0; while (dizin <4) {next = str.find (sınırlayıcı, son); otomatik numara = str.substr (son, sonraki - son); IPv4 [index ++] = atoi (sayı.c_str ()); son = sonraki + 1; } </code>
rfog

2
@ hayk.mart Sadece bir not, bu aşağıdaki olurdu, 2 karakter olan sınırlayıcı boyutu nedeniyle 2 değil 1 eklemeniz gerekir :): std :: string s = "scott> = tiger> = mushroom"; std :: string delimiter = "> ="; son boyut = 0; size_t sonraki = 0; while ((next = s.find (ayırıcı, son))! = std :: string :: npos) {std :: cout << s.substr (son, sonraki-son) << std :: endl; son = sonraki + 2; } std :: cout << s.substr (son) << std :: endl;
ervinbosenbacher

"Kaplan" almak için, kullanın std::string token = s.substr(s.find(delimiter) + 1);, var olduğundan eminseniz (uzunluğu +1 kullanın) ...
gsamaras

65

Bu yöntem std::string::find, önceki alt dize belirtecinin başlangıcını ve sonunu hatırlayarak orijinal dizgiyi değiştirmeden kullanır .

#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

Dizeyi bölmek için sonraki işlevi kullanabilirsiniz:

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 beklendiği gibi çalışmıyor: split("abc","a")bir vektör veya tek bir dize döndürecek "bc", burada bir elemanlar vektörü döndürürse daha mantıklı olacağını düşünüyorum ["", "bc"]. str.split()Python'da kullanmak delim, başlangıçta veya sonunda bulunması durumunda boş bir dize döndürmesi gerektiği için sezgiseldi , ama bu sadece benim düşüncem. Her neyse, sadece belirtilmesi gerektiğini düşünüyorum
kyriakosSt

1
if (!token.empty()) @KyriakosSt tarafından belirtilen sorunun yanı sıra art arda gelen sınırlayıcılarla ilgili diğer sorunların kaldırılmasını şiddetle tavsiye ederim .
Steve

1
Eğer yapabilirsem oyumu kaldıracağım, ama SO izin vermeyecek. @KyriakosSt tarafından ortaya çıkan sorun bir sorundur ve kaldırılması if (!token.empty())sorunu düzeltmek için yeterli görünmemektedir.
bhaller

1
@bhaller bu sniplet tam olarak boş parçaları atlamak için tasarlanmıştır. Boş olanları saklamanız gerekiyorsa korkarım başka bir bölünmüş uygulama yazmanız gerekir. Lütfen iyilik için buraya göndermenizi öneririz.
Sviatoslav

32

Dize sınırlayıcı için

Dizeyi bir dize sınırlayıcısına göre ayırın . "adsf-+qwret-+nvfkbdsj-+orthdfjgh-+dfjrleih"Dize sınırlayıcı dayalı bölme gibi "-+", çıkış olacak{"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;
}


Çıktı

adsf
qwret
nvfkbdsj
orthdfjgh
dfjrleih




Tek karakter sınırlayıcı için

Karakter sınırlayıcısına göre dizeyi ayır. "adsf+qwer+poui+fdgh"Sınırlayıcı ile yarma dize gibi "+"çıktı{"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;
}


Çıktı

adsf
qwer
poui
fdgh

Ben dönüyorum vector<string>sanırım kopya yapıcı diyelim.
Mayur

2
Gördüğüm her referans, kopya oluşturucuya yapılan çağrının bu bağlamda elendiğini gösteriyor.
David,

"Modern" (C ++ 03?) Derleyicileri ile bunun doğru olduğuna inanıyorum, RVO ve / veya hareket semantiği kopya yapıcısını ortadan kaldıracaktır.
Kevin

Tek karakter sınırlayıcı için denedim ve dize bir sınırlayıcı (yani satırın sonunda boş bir csv sütun) biterse, boş dize döndürmez. Sadece bir tane daha az dize döndürür. Örneğin: 1,2,3,4 \ nA, B, C,
kounoupis

Ben de dize sınırlayıcı için birini denedim ve dize bir sınırlayıcıda biterse, son sınırlayıcı ayıklanan son dize parçası olur.
kounoupis

20

Bu kod metinden satırları ayırır ve herkesi bir vektöre ekler.

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

Arayan:

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

harika çalışıyor! List.push_back (s) ekledim; çünkü eksikti.
Stoica Mircea

1
dizenin son kısmını kaçırır. While döngüsü sona erdikten sonra, kalan s'yi yeni bir jeton olarak eklememiz gerekir.
whihathac

Eksik push_back düzeltmek için kod örneğinde bir düzenleme yaptım.
fret

1
Daha güzel olacakvector<string> split(char *phrase, const string delimiter="\n")
Mayur

15

strtok , sınırlayıcı olarak birden fazla karakter geçirmenizi sağlar. Bahse girerim "> =" geçerseniz, örnek dizeniz doğru şekilde bölünür (> ve = ayrı ayrı sınırlayıcılar olarak sayılsa bile).

c_str()Dize karakterden char * dönüştürmek için kullanmak istemiyorsanız DÜZENLE , belirtmek için substr ve find_first_of kullanabilirsiniz.

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
Teşekkürler. Ama ben sadece C ++ kullanmak istiyorum ve herhangi bir C fonksiyonları gibi strtok()dize yerine char dizisi kullanmak için gerektirecek gibi kullanmak istiyorum.
TheCrazyProgrammer

2
@TheCrazyProgrammer Peki? Bir C işlevi ihtiyacınız olanı yaparsa, onu kullanın. Bu, C işlevlerinin C ++ 'da mevcut olmadığı bir dünya değil (aslında, olması gerekir). .c_str()ucuz ve kolaydır.
Qix - MONICA

1
Dizenizde yinelenen öğeler varsa if (token! = Gizem) denetimi yanlış sonuçlar verir. Kodunuzu, buna sahip olmayan bir sürüm yapmak için kullandım. Cevabı temelde değiştiren birçok değişiklik var, bu yüzden düzenlemek yerine kendi cevabımı yazdım. Aşağıdan kontrol edin.
Amber Elferink

5

İşte bunu benim almam. Kenar vakalarını işler ve sonuçlardan boş girişleri kaldırmak için isteğe bağlı bir parametre alır.

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

Örnekler

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

Bu, dize (veya tek karakter) sınırlayıcılar için mükemmel şekilde çalışmalıdır. Eklemeyi unutmayın #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';
}

İlk while döngüsü, dize sınırlayıcının ilk karakterini kullanarak bir simge çıkarır. İkinci while döngüsü sınırlayıcının geri kalanını atlar ve bir sonraki belirtecin başında durur.


3

Kullanırdım boost::tokenizer. Uygun bir belirteç işlevinin nasıl yapılacağını açıklayan belgeler: http://www.boost.org/doc/libs/1_52_0/libs/tokenizer/tokenizerfunction.htm

İşte davanız için uygun olanı.

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
Teşekkürler. Ama üçüncü taraf bir kütüphane değil, sadece standart C ++ diliyorum.
TheCrazyProgrammer

@TheCrazyProgrammer: Tamam, "Standart C ++" okuduğumda, bunun standart olmayan uzantılar anlamına gelmediğini, üçüncü taraf kitaplıklarına uygun standartları kullanamayacağınızı düşündüm.
Benjamin Lindley

3

Yanıt zaten var, ancak seçilen cevap çok pahalı olan silme işlevini kullanıyor, bazı çok büyük dize (MB cinsinden) düşünün. Bu nedenle aşağıdaki işlevi kullanıyorum.

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

Bunu test ettim ve işe yarıyor. Teşekkürler! Benim düşünceme göre, bu en iyi cevaptır çünkü orijinal cevaplayıcı belirttiği gibi, bu çözüm bellek yükünü azaltır ve sonuç bir vektörde rahatça saklanır. (Python string.split()yöntemini çoğaltır .)
Robbie Capps

2

Bu, dizeyi herhangi bir sınırlayıcıya bölerek ve kesilmiş dizelerin bir vektörünü döndüren eksiksiz bir yöntemdir.

Ryanbwork'ün cevabından bir uyarlamadır. Ancak, denetimi için: if(token != mystring)dizenizde yinelenen öğeler varsa yanlış sonuçlar verir. Bu benim bu soruna çözümüm.

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
Böyle bir şey, böyle while (true)bir kod parçasında görmek genellikle korkutucu. Şahsen bunu yeniden yazmanızı tavsiye ederim, böylece karşılaştırmayla std::string::npos(veya sırasıyla bir çekle mystring.size()) karşılaştırılması while (true)eskidir.
Joel Bodenmann

1

Dizeyi değiştirmek istemiyorsanız (Vincenzo Pii'nin cevabında olduğu gibi) ve son belirteci de çıkarmak istiyorsanız, bu yaklaşımı kullanmak isteyebilirsiniz:

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

Not: Sadece bölmeden sonraki dizelerin uzunlukları eşitse çalışır


Bu, GCC uzantısı - değişken uzunluk dizisini kullanır.
user202729

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

0

İşlev:

std::vector<std::string> WSJCppCore::split(const std::string& sWhat, const std::string& sDelim) {
    std::vector<std::string> vRet;
    size_t nPos = 0;
    size_t nLen = sWhat.length();
    size_t 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;
}

Birim-testler:

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

-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
Lütfen daha doğru olun. Kodunuz derlenmeyecek. Bkz. Nokta yerine "i" ve virgül.
jstuardo
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.