Phân tích cú pháp (tách) một chuỗi trong C ++ bằng cách sử dụng dấu phân cách chuỗi (C ++ chuẩn)


362

Tôi đang phân tích một chuỗi trong C ++ bằng cách sử dụng như sau:

using namespace std;

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

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

Phân tích cú pháp với một dấu phân cách char là tốt. Nhưng nếu tôi muốn sử dụng một chuỗi như dấu phân cách.

Ví dụ: Tôi muốn tách:

scott>=tiger

với >=dấu phân cách để tôi có thể lấy scott và tiger.

Câu trả lời:


575

Bạn có thể sử dụng std::string::find()hàm để tìm vị trí của dấu phân cách chuỗi của bạn, sau đó sử dụng std::string::substr()để nhận mã thông báo.

Thí dụ:

std::string s = "scott>=tiger";
std::string delimiter = ">=";
std::string token = s.substr(0, s.find(delimiter)); // token is "scott"
  • Các find(const string& str, size_t pos = 0)chức năng trả về vị trí của sự xuất hiện đầu tiên của strtrong chuỗi, hoặc nposnếu chuỗi không được tìm thấy.

  • Các substr(size_t pos = 0, size_t n = npos)hàm trả về một chuỗi con của đối tượng, bắt đầu từ vị trí posvà chiều dài npos.


Nếu bạn có nhiều dấu phân cách, sau khi bạn trích xuất một mã thông báo, bạn có thể xóa nó (bao gồm dấu phân cách) để tiến hành trích xuất tiếp theo (nếu bạn muốn giữ chuỗi gốc, chỉ cần sử dụng s = s.substr(pos + delimiter.length());):

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

Bằng cách này, bạn có thể dễ dàng lặp để nhận từng mã thông báo.

Ví dụ hoàn chỉnh

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;

Đầu ra:

scott
tiger
mushroom

66
Đối với những người không muốn sửa đổi chuỗi đầu vào, hãy làmsize_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
LƯU Ý: mushroomđầu ra bên ngoài vòng lặp, tức làs = mushroom
Don Larynx

1
Những mẫu đó không trích xuất mã thông báo cuối cùng từ chuỗi. Một mẫu của tôi trích xuất một IpV4 từ một chuỗi: <code> size_t last = 0; size_t tiếp theo = 0; chỉ số int = 0; while (index <4) {next = str.find (delimiter, last); số tự động = str.substr (cuối cùng, tiếp theo - cuối cùng); IPv4 [index ++] = atoi (number.c_str ()); cuối cùng = tiếp theo + 1; } </ code>
rfog 17/08/2015

2
@ hayk.mart Chỉ cần một lưu ý, đó sẽ là như sau, bạn cần thêm 2 chứ không phải 1 do kích thước của dấu phân cách là 2 ký tự :): std :: string s = "scott> = tiger> = Mush"; std :: chuỗi delimiter = "> ="; size_t cuối = 0; size_t tiếp theo = 0; while ((next = s.find (delimiter, last))! = std :: string :: npose) {std :: cout << s.substr (cuối cùng, tiếp theo cuối cùng) << std :: endl; cuối cùng = tiếp theo + 2; } std :: cout << s.substr (cuối cùng) << std :: endl;
ervinbosenbacher

Để có được "con hổ", hãy sử dụng std::string token = s.substr(s.find(delimiter) + 1);, nếu bạn chắc chắn rằng nó tồn tại (tôi sử dụng +1 theo chiều dài) ...
gsamaras

64

Phương pháp này sử dụng std::string::findmà không làm thay đổi chuỗi gốc bằng cách nhớ đầu và cuối của mã thông báo chuỗi con trước đó.

#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

Bạn có thể sử dụng chức năng tiếp theo để phân tách chuỗi:

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 nó không hoạt động như mong đợi: split("abc","a")sẽ trả về một vectơ hoặc một chuỗi "bc", trong đó tôi nghĩ nó sẽ có ý nghĩa hơn nếu nó trả về một vectơ các phần tử ["", "bc"]. Sử dụng str.split()trong Python, với tôi nó trực quan rằng nó sẽ trả về một chuỗi rỗng trong trường hợp delimđược tìm thấy ở đầu hoặc cuối, nhưng đó chỉ là ý kiến ​​của tôi. Dù sao, tôi chỉ nghĩ rằng nó nên được đề cập
kyriakosSt

1
Rất khuyến nghị loại bỏ if (!token.empty()) ngăn chặn sự cố được đề cập bởi @kyriakosSt cũng như các vấn đề khác liên quan đến các dấu phân cách liên tiếp.
Steve

1
Tôi sẽ xóa upvote của mình nếu có thể, nhưng SO sẽ không cho phép tôi. Vấn đề được đưa ra bởi @kyriakosSt là một vấn đề và việc gỡ bỏ if (!token.empty())dường như không đủ để khắc phục nó.
bhaller

1
@bhaller sniplet này được thiết kế chính xác để bỏ qua các mảnh trống. Nếu bạn cần để trống, tôi sợ bạn cần phải viết một triển khai phân tách khác. Vui lòng đề nghị bạn đăng nó ở đây vì lợi ích của sự hài hước.
Sviat Tư

32

Đối với dấu phân cách chuỗi

Chia chuỗi dựa trên một dấu phân cách chuỗi . Chẳng hạn như tách chuỗi "adsf-+qwret-+nvfkbdsj-+orthdfjgh-+dfjrleih"dựa trên dấu phân cách chuỗi "-+", đầu ra sẽ là{"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;
}


Đầu ra

quảng cáo
qwret
nvfkbdsj
orthdfjgh
dfjrleih




Đối với dấu phân cách ký tự đơn

Chia chuỗi dựa trên một dấu phân cách ký tự. Chẳng hạn như tách chuỗi "adsf+qwer+poui+fdgh"với dấu phân cách "+"sẽ xuất ra{"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;
}


Đầu ra

quảng cáo
qwer
poui
fdgh

Bạn đang trở lại vector<string>tôi nghĩ rằng nó sẽ gọi nhà xây dựng sao chép.
Mayur

2
Mọi tham chiếu tôi đã thấy cho thấy rằng lệnh gọi đến hàm tạo sao chép được loại bỏ trong ngữ cảnh đó.
David đưa ra

Với trình biên dịch "hiện đại" (C ++ 03?), Tôi tin rằng điều này là chính xác, RVO và / hoặc di chuyển ngữ nghĩa sẽ loại bỏ hàm tạo sao chép.
Kevin

Tôi đã thử một ký tự cho dấu phân cách ký tự đơn và nếu chuỗi kết thúc bằng dấu phân cách (nghĩa là cột csv trống ở cuối dòng), nó sẽ không trả về chuỗi trống. Nó chỉ đơn giản trả về một chuỗi ít hơn. Ví dụ: 1,2,3,4 \ nA, B, C,
kounoupis

Tôi cũng đã thử một chuỗi cho dấu phân cách chuỗi và nếu chuỗi kết thúc bằng một dấu phân cách, dấu phân cách cuối cùng trở thành một phần của chuỗi cuối cùng được trích xuất.
kounoupis

20

Mã này chia dòng từ văn bản và thêm mọi người vào một vectơ.

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

Gọi bằng:

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

nó hoạt động rất tốt Tôi đã thêm list.push_back (s); bởi vì nó đã bị mất
Stoica Mircea

1
nó bỏ lỡ phần cuối của chuỗi. Sau khi vòng lặp while kết thúc, chúng ta cần thêm phần còn lại của s làm mã thông báo mới.
whihathac

Tôi đã thực hiện chỉnh sửa mẫu mã để sửa lỗi Push_back bị thiếu.
băn khoăn

1
Nó sẽ đẹp hơnvector<string> split(char *phrase, const string delimiter="\n")
Mayur

15

strtok cho phép bạn vượt qua trong nhiều ký tự như dấu phân cách. Tôi đặt cược nếu bạn chuyển vào "> =" chuỗi ví dụ của bạn sẽ được phân chia chính xác (mặc dù> và = được tính là các dấu phân cách riêng lẻ).

EDIT nếu bạn không muốn sử dụng c_str()để chuyển đổi từ chuỗi char *, bạn có thể sử dụng substrfind_first_of 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
Cảm ơn. Nhưng tôi muốn chỉ sử dụng C ++ chứ không phải bất kỳ hàm C nào strtok()như nó sẽ yêu cầu tôi sử dụng mảng char thay vì chuỗi.
TheCrazyProgrammer

2
@TheCrazyProgrammer Vậy sao? Nếu một hàm C làm những gì bạn cần, hãy sử dụng nó. Đây không phải là một thế giới nơi các hàm C không có sẵn trong C ++ (thực tế, chúng phải như vậy). .c_str()là rẻ và dễ dàng, quá.
Qix - MONICA ĐƯỢC PHÂN BIỆT

1
Việc kiểm tra if (token! = Mystring) cho kết quả sai nếu bạn có các phần tử lặp lại trong chuỗi của mình. Tôi đã sử dụng mã của bạn để tạo một phiên bản không có cái này. Nó có nhiều thay đổi làm thay đổi căn bản câu trả lời, vì vậy tôi đã viết câu trả lời của riêng mình thay vì chỉnh sửa. Kiểm tra nó dưới đây.
Amber Elferink

5

Đây là của tôi về điều này. Nó xử lý các trường hợp cạnh và lấy một tham số tùy chọn để loại bỏ các mục trống khỏi kết quả.

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

Ví dụ

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

Điều này sẽ hoạt động hoàn hảo cho các dấu phân cách chuỗi (hoặc ký tự đơn). Đừng quên bao gồm #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';
}

Vòng lặp while đầu tiên trích xuất mã thông báo bằng ký tự đầu tiên của dấu phân cách chuỗi. Vòng lặp while thứ hai bỏ qua phần còn lại của dấu phân cách và dừng ở đầu mã thông báo tiếp theo.


3

Tôi sẽ sử dụng boost::tokenizer. Đây là tài liệu giải thích cách tạo chức năng mã thông báo phù hợp: http://www.boost.org/doc/libs/1_52_0/libs/tokenizer/tokenizerfeft.htmlm

Đây là một trong những hoạt động cho trường hợp của bạn.

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
Cảm ơn. Nhưng tôi muốn chỉ muốn C ++ chuẩn chứ không phải thư viện của bên thứ ba.
TheCrazyProgrammer

@TheCrazyProgrammer: Được rồi, khi tôi đọc "Standard C ++", tôi nghĩ rằng điều đó có nghĩa là không có phần mở rộng không chuẩn, không phải là bạn không thể sử dụng các tiêu chuẩn phù hợp với thư viện của bên thứ ba.
Benjamin Lindley

3

Câu trả lời đã có, nhưng câu trả lời được chọn sử dụng chức năng xóa rất tốn kém, hãy nghĩ đến một chuỗi rất lớn (tính bằng MB). Vì vậy, tôi sử dụng chức năng dưới đây.

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

Tôi đã thử nghiệm điều này, và nó hoạt động. Cảm ơn! Theo tôi, đây là câu trả lời tốt nhất vì như trạng thái trả lời ban đầu, giải pháp này làm giảm chi phí bộ nhớ và kết quả được lưu trữ thuận tiện trong một vectơ. (sao chép string.split()phương thức Python .)
Robbie Capps

2

Đây là một phương thức hoàn chỉnh để phân tách chuỗi trên bất kỳ dấu phân cách nào và trả về một vectơ của các chuỗi được xắt nhỏ.

Nó là một sự thích nghi từ câu trả lời từ ryanbwork. Tuy nhiên, kiểm tra của anh ta cho: if(token != mystring)cho kết quả sai nếu bạn có các yếu tố lặp lại trong chuỗi của bạn. Đây là giải pháp của tôi cho vấn đề đó.

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
Một cái gì đó như while (true)thường là đáng sợ để nhìn thấy trong một đoạn mã như thế này. Cá nhân tôi khuyên bạn nên viết lại điều này để việc so sánh với std::string::npos(hoặc tương ứng là kiểm tra mystring.size()) làm cho while (true)lỗi thời.
Joel Bodenmann

1

Nếu bạn không muốn sửa đổi chuỗi (như trong câu trả lời của Vincenzo Pii) và cũng muốn xuất mã thông báo cuối cùng, bạn có thể muốn sử dụng phương pháp này:

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: Chỉ hoạt động nếu độ dài của chuỗi sau khi chia bằng nhau


Điều này sử dụng phần mở rộng GCC - mảng chiều dài thay đổi.
dùng202729

0

Chức năng:

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

Bài kiểm tra đơn vị:

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
Hãy chính xác hơn. Mã của bạn sẽ không biên dịch. Xem khai báo "i" và dấu phẩy thay vì dấu chấm.
jstuardo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.