ฉันจะโทเค็นสตริงใน C ++ ได้อย่างไร


414

Java มีวิธีแบ่งสะดวก:

String str = "The quick brown fox";
String[] results = str.split(" ");

มีวิธีง่าย ๆ ใน C ++ หรือไม่


172
ฉันไม่อยากเชื่อว่างานประจำนี้จะปวดหัวใน c ++
wfbarksdale

6
มันไม่ปวดหัวใน c ++ - มีหลายวิธีที่จะทำให้สำเร็จ โปรแกรมเมอร์ตระหนักถึง c ++ น้อยกว่า c # - เกี่ยวกับการตลาดและการลงทุน ... ดูสิ่งนี้สำหรับตัวเลือก c ++ ที่หลากหลายเพื่อให้ได้สิ่งเดียวกัน: cplusplus.com/faq/sequences/strings/split
hB0

9
@ hB0 มีคำถามมากมายที่ต้องตอบ แต่ก็ยังไม่ได้หมายความว่าจะปวดหัว หนึ่งความต้องการที่ห้องสมุดอื่น ๆ ที่เป็นเพียงสำหรับพื้นที่อื่น ๆ ที่ไม่ได้จัดการกับช่องว่าง ..
Paschalis

1
เป็นไปได้ที่ซ้ำกันของการแยกสตริงใน C ++ หรือไม่
KOB

2
ทำไมทุกอย่างใน C ++ จึงต้องดิ้นรน?
Wael Assaf

คำตอบ:


145

อัลกอริธึมไลบรารีมาตรฐาน C ++ นั้นค่อนข้างเป็นสากลโดยรอบตัววนซ้ำมากกว่าคอนเทนเนอร์คอนกรีต น่าเสียดายที่นี่ทำให้ยากที่จะให้splitฟังก์ชั่นเหมือน Java ในไลบรารีมาตรฐาน C ++ แม้ว่าจะไม่มีใครโต้แย้งว่าสิ่งนี้จะสะดวก แต่ประเภทผลตอบแทนของมันจะเป็นอย่างไร std::vector<std::basic_string<…>>? อาจ แต่จากนั้นเราถูกบังคับให้ทำการจัดสรร (อาจซ้ำซ้อนและมีค่าใช้จ่ายสูง)

แต่ C ++ เสนอวิธีมากมายในการแยกสตริงตามตัวคั่นที่ซับซ้อนโดยพลการ แต่ไม่มีสิ่งใดที่ถูกห่อหุ้มเหมือนในภาษาอื่น หลายวิธีกรอกบล็อกโพสต์ทั้งหมด

ที่ง่ายที่สุดที่คุณสามารถย้ำใช้std::string::findจนกว่าคุณจะตีและดึงข้อมูลโดยใช้std::string::nposstd::string::substr

รุ่นที่มีความลื่นไหลมากขึ้น (และเป็นสำนวน แต่พื้นฐาน) สำหรับการแยกบนช่องว่างจะใช้std::istringstream:

auto iss = std::istringstream{"The quick brown fox"};
auto str = std::string{};

while (iss >> str) {
    process(str);
}

การใช้std::istream_iteratorsเนื้อหาของสตรีมสตริงสามารถคัดลอกไปยังเวกเตอร์โดยใช้ตัวสร้างช่วงตัววนซ้ำ

หลายไลบรารี (เช่นBoost.Tokenizer ) มีโทเค็นเฉพาะ

การแยกขั้นสูงเพิ่มเติมต้องการนิพจน์ทั่วไป C ++ จัดทำขึ้นstd::regex_token_iteratorเพื่อจุดประสงค์นี้โดยเฉพาะ:

auto const str = "The quick brown fox"s;
auto const re = std::regex{R"(\s+)"};
auto const vec = std::vector<std::string>(
    std::sregex_token_iterator{begin(str), end(str), re, -1},
    std::sregex_token_iterator{}
);

53
น่าเศร้าที่การเพิ่มประสิทธิภาพไม่พร้อมใช้งานสำหรับทุกโครงการ ฉันจะต้องมองหาคำตอบที่ไม่เพิ่ม
FuzzyBunnySlippers

36
ไม่ใช่ทุกโครงการที่เปิดให้ "โอเพ่นซอร์ส" ฉันทำงานในอุตสาหกรรมที่มีการควบคุมอย่างหนัก มันไม่ใช่ปัญหาจริงๆ มันเป็นความจริงของชีวิต Boost ไม่สามารถใช้ได้ทุกที่
FuzzyBunnySlippers

5
@NonlinearIdeas คำถาม / คำตอบอื่น ๆ ไม่ได้เกี่ยวกับโครงการโอเพ่นซอร์สเลย เช่นเดียวกับโครงการใด ๆ ที่กล่าวว่าฉันแน่นอนเข้าใจเกี่ยวกับมาตรฐานที่ถูก จำกัด เช่น MISRA C แต่แล้วก็เข้าใจว่าคุณสร้างทุกอย่างตั้งแต่เริ่มต้นอย่างไรก็ตาม (เว้นแต่คุณจะเกิดขึ้นเพื่อค้นหาห้องสมุดที่สอดคล้อง - หายาก) อย่างไรก็ตามประเด็นก็คือแทบจะไม่ว่า“Boost ไม่สามารถใช้ได้” - มันที่คุณมีความต้องการพิเศษที่เกือบใด ๆคำตอบวัตถุประสงค์ทั่วไปจะไม่เหมาะสม
Konrad Rudolph

1
@NonlinearIdeas ตรงประเด็นคำตอบอื่น ๆ ที่ไม่ใช่ Boost นั้นไม่สอดคล้องกับ MISRA
Konrad Rudolph

3
@Dmitry“ STL barf” คืออะไร! และชุมชนทั้งหมดก็สนับสนุนการแทนที่ตัวประมวลผลล่วงหน้า C มากในความเป็นจริงมีข้อเสนอให้ทำเช่นนั้น แต่ข้อเสนอแนะของคุณในการใช้ PHP หรือภาษาอื่น ๆ แทนที่จะเป็นขั้นตอนใหญ่ไปข้างหลัง
Konrad Rudolph

188

Boost tokenizerระดับสามารถทำให้การจัดเรียงของสิ่งที่ค่อนข้างง่ายนี้:

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

using namespace std;
using namespace boost;

int main(int, char**)
{
    string text = "token, test   string";

    char_separator<char> sep(", ");
    tokenizer< char_separator<char> > tokens(text, sep);
    BOOST_FOREACH (const string& t, tokens) {
        cout << t << "." << endl;
    }
}

อัปเดตสำหรับ C ++ 11:

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

using namespace std;
using namespace boost;

int main(int, char**)
{
    string text = "token, test   string";

    char_separator<char> sep(", ");
    tokenizer<char_separator<char>> tokens(text, sep);
    for (const auto& t : tokens) {
        cout << t << "." << endl;
    }
}

1
สิ่งที่ดีฉันเพิ่งใช้สิ่งนี้ คอมไพเลอร์ Visual Studio ของฉันมี whinge แปลก ๆ จนกว่าฉันจะใช้ช่องว่างเพื่อแยกอักขระ ">" สองตัวก่อนบิตโทเค็น (ข้อความ, กันยายน): (ข้อผิดพลาด C2947: คาดหวังว่า '>' เพื่อยุติเทมเพลตรายการอาร์กิวเมนต์ > ')
AndyUK

@AndyUK ใช่โดยไม่เว้นวรรคคอมไพเลอร์แยกวิเคราะห์มันเป็นตัวดำเนินการแยกมากกว่าสองแม่แบบปิด
EnabrenTane

ในทางทฤษฎีที่ได้รับการแก้ไขใน C ++ 0x
David Souther

3
ระวังพารามิเตอร์ตัวที่สามของตัวchar_separatorสร้าง ( drop_empty_tokensเป็นค่าเริ่มต้นทางเลือกคือkeep_empty_tokens)
เบอนัวต์

5
@puk - เป็นคำต่อท้ายที่ใช้กันทั่วไปสำหรับไฟล์ส่วนหัว C ++ (เช่น.hสำหรับส่วนหัว C)
Ferruccio

167

นี่คือตัวอย่างง่ายๆที่แท้จริง:

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

vector<string> split(const char *str, char c = ' ')
{
    vector<string> result;

    do
    {
        const char *begin = str;

        while(*str != c && *str)
            str++;

        result.push_back(string(begin, str));
    } while (0 != *str++);

    return result;
}

ฉันต้องเพิ่มต้นแบบสำหรับวิธีนี้ในไฟล์. h หรือไม่
Suhrob Samiev

5
นี่ไม่ใช่คำตอบที่ "ดีที่สุด" อย่างแน่นอนเนื่องจากยังคงใช้สตริงตัวอักษรซึ่งเป็นอาร์เรย์อักขระคงที่ C ธรรมดา ฉันเชื่อว่าผู้ถามถามว่าเขาสามารถโทเค็นสตริง C ++ ซึ่งเป็นประเภท "สตริง" ที่แนะนำโดยหลังหรือไม่
Vijay Kumar Kanta

สิ่งนี้ต้องการคำตอบใหม่เนื่องจากฉันสงสัยอย่างยิ่งว่าการรวมของนิพจน์ทั่วไปใน C ++ 11 ได้เปลี่ยนสิ่งที่คำตอบที่ดีที่สุด
Omnifarious

113

ใช้ strtok ในความคิดของฉันไม่จำเป็นต้องสร้างคลาสที่มีโทเค็นอยู่นอกเสียจากว่า strtok จะไม่ให้สิ่งที่คุณต้องการ มันอาจจะไม่ แต่ใน 15 ปีของการเขียนรหัสการแยกวิเคราะห์ใน C และ C ++ ฉันเคยใช้ strtok นี่คือตัวอย่าง

char myString[] = "The quick brown fox";
char *p = strtok(myString, " ");
while (p) {
    printf ("Token: %s\n", p);
    p = strtok(NULL, " ");
}

คำเตือนเล็กน้อย (ซึ่งอาจไม่เหมาะกับความต้องการของคุณ) สายอักขระถูก "ทำลาย" ในกระบวนการซึ่งหมายความว่าอักขระ EOS ถูกวางไว้ในจุดที่ตัวคั่น การใช้งานที่ถูกต้องอาจทำให้คุณต้องสร้างสตริงที่ไม่ใช่เวอร์ชัน นอกจากนี้คุณยังสามารถเปลี่ยนรายการของตัวคั่นกลางแยกวิเคราะห์

ในความเห็นของฉันโค้ดข้างต้นนั้นง่ายกว่าและใช้งานง่ายกว่าการเขียนคลาสแยกต่างหาก สำหรับฉันนี่เป็นหนึ่งในฟังก์ชั่นที่ภาษาให้และมันทำได้ดีและเรียบร้อย มันเป็นเพียงแค่โซลูชัน "C based" มันเหมาะสมมันง่ายและคุณไม่จำเป็นต้องเขียนโค้ดพิเศษจำนวนมาก :-)


42
ไม่ใช่ว่าฉันไม่ชอบ C อย่างไรก็ตาม strtok นั้นไม่ปลอดภัยต่อเธรดและคุณต้องแน่ใจว่าสตริงที่คุณส่งมีอักขระเป็นโมฆะเพื่อหลีกเลี่ยงบัฟเฟอร์ล้นที่อาจเกิดขึ้น
tloach

11
มี strtok_r แต่นี่เป็นคำถาม C ++
ศ. Falken ผิดสัญญาใน

3
@tloach: ในคอมไพเลอร์ MS C ++ strtok เป็นเธรดที่ปลอดภัยเนื่องจากตัวแปรสแตติกภายในถูกสร้างขึ้นบน TLS (หน่วยเก็บเธรดโลคัล) (อันที่จริงมันขึ้นอยู่กับคอมไพเลอร์)
Ahmed Said

3
@ahmed: ความปลอดภัยของเธรดมีความหมายมากกว่าเพียงแค่สามารถเรียกใช้ฟังก์ชันสองครั้งในเธรดที่แตกต่างกัน ในกรณีนี้ถ้าเธรดได้รับการแก้ไขในขณะที่ strtok กำลังทำงานอยู่มีความเป็นไปได้ที่จะมีสตริงที่ถูกต้องในระหว่างการเรียกใช้ strtok ทั้งหมด แต่ strtok จะยังคงสับสนเพราะสตริงเปลี่ยนไปตอนนี้มันผ่านอักขระ null แล้ว อ่านหน่วยความจำต่อไปจนกว่าจะได้รับการละเมิดความปลอดภัยหรือค้นหาอักขระที่เป็นโมฆะ นี่เป็นปัญหาของฟังก์ชั่นสตริง C ดั้งเดิมหากคุณไม่ได้ระบุความยาวที่คุณพบปัญหา
tloach

4
strtok ต้องการตัวชี้ไปยังอาร์เรย์ char ที่ไม่สิ้นสุด const ซึ่งไม่ใช่สิ่งมีชีวิตทั่วไปที่จะค้นหาในรหัส c ++ ... คุณชอบวิธีไหนในการแปลงเป็น std :: string?
fuzzyTew

105

getlineอีกวิธีที่รวดเร็วคือการใช้งาน สิ่งที่ต้องการ:

stringstream ss("bla bla");
string s;

while (getline(ss, s, ' ')) {
 cout << s << endl;
}

หากคุณต้องการคุณสามารถสร้างsplit()วิธีการคืนค่า a vector<string>ซึ่งเป็นประโยชน์จริงๆ


2
ฉันมีปัญหาในการใช้เทคนิคนี้กับอักขระ 0x0A ในสตริงซึ่งทำให้ขณะที่ลูปจบการทำงานก่อนกำหนด มิฉะนั้นจะเป็นวิธีที่ง่ายและรวดเร็วดี
Ryan H.

4
นี่เป็นสิ่งที่ดี แต่ต้องจำไว้ว่าการทำเช่นนี้จะไม่ถือว่าตัวคั่นเริ่มต้น '\ n' ตัวอย่างนี้จะใช้งานได้ แต่ถ้าคุณใช้สิ่งที่ชอบ: ในขณะที่ (getline (inFile, word, '')) โดยที่ inFile เป็นวัตถุ ifstream ที่มีหลายบรรทัดคุณจะได้ผลลัพธ์ที่
สนุกสนาน

มันเลวเกินไป getline ส่งคืนกระแสข้อมูลแทนที่จะเป็นสตริงทำให้ไม่สามารถใช้งานได้ในรายการเริ่มต้นโดยไม่มีที่เก็บข้อมูลชั่วคราว
fuzzyTew

1
เย็น! ไม่มีการเพิ่มและ C ++ 11 เป็นทางออกที่ดีสำหรับโครงการดั้งเดิมเหล่านั้น!
Deqing

1
นั่นคือคำตอบชื่อของฟังก์ชันนั้นค่อนข้างอึดอัดใจ
นิลล

82

คุณสามารถใช้สตรีมตัววนซ้ำและอัลกอริทึมการคัดลอกเพื่อทำสิ่งนี้โดยตรง

#include <string>
#include <vector>
#include <iostream>
#include <istream>
#include <ostream>
#include <iterator>
#include <sstream>
#include <algorithm>

int main()
{
  std::string str = "The quick brown fox";

  // construct a stream from the string
  std::stringstream strstr(str);

  // use stream iterators to copy the stream to the vector as whitespace separated strings
  std::istream_iterator<std::string> it(strstr);
  std::istream_iterator<std::string> end;
  std::vector<std::string> results(it, end);

  // send the vector to stdout.
  std::ostream_iterator<std::string> oit(std::cout);
  std::copy(results.begin(), results.end(), oit);
}

17
ฉันพบ std :: ที่น่ารำคาญที่จะอ่าน .. ทำไมไม่ใช้ "ใช้"?
user35978

80
@Vadi: เนื่องจากการแก้ไขโพสต์ของคนอื่นค่อนข้างน่ารำคาญ @ pheze: ฉันต้องการให้stdวิธีนี้ฉันรู้ว่าวัตถุของฉันมาจากไหนนั่นเป็นเพียงเรื่องของสไตล์
Matthieu M.

7
ฉันเข้าใจเหตุผลของคุณและฉันคิดว่าจริง ๆ แล้วมันเป็นทางเลือกที่ดีถ้ามันเหมาะกับคุณ แต่จากมุมมองของการสอนฉันเห็นด้วยกับ pheze ง่ายต่อการอ่านและทำความเข้าใจกับตัวอย่างต่างประเทศอย่างสมบูรณ์เช่นนี้โดยใช้ "namespace std" ที่ด้านบนเนื่องจากต้องการความพยายามน้อยในการตีความบรรทัดต่อไปนี้ ... โดยเฉพาะในกรณีนี้เพราะทุกอย่างมาจากไลบรารีมาตรฐาน คุณสามารถทำให้ง่ายต่อการอ่านและชัดเจนว่าวัตถุมาจากชุดของ "using std :: string;" เป็นต้นโดยเฉพาะอย่างยิ่งเนื่องจากฟังก์ชั่นนั้นสั้นมาก
cheshirekow

61
แม้จะมีคำนำหน้า "std ::" ที่น่ารำคาญหรือน่าเกลียด แต่ก็ควรรวมไว้ในโค้ดตัวอย่างเพื่อให้ชัดเจนว่าฟังก์ชั่นเหล่านี้มาจากไหน หากพวกเขารบกวนคุณมันเป็นเรื่องเล็กน้อยที่จะแทนที่พวกเขาด้วย "การใช้" หลังจากที่คุณขโมยตัวอย่างและอ้างว่าเป็นของคุณเอง
dlchambers

20
อ๋อ! เขาพูดอะไร! แนวปฏิบัติที่เหมาะสมที่สุดคือการใช้คำนำหน้า std ฐานรหัสขนาดใหญ่ไม่ต้องสงสัยเลยว่าจะมีไลบรารีและเนมสเปซเป็นของตัวเองและการใช้ "using namespace std" จะทำให้คุณปวดหัวเมื่อคุณเริ่มก่อให้เกิดความขัดแย้งของ namespace
Miek

48

ไม่มีคนที่กระทำผิดกฎหมาย แต่สำหรับปัญหาดังกล่าวที่เรียบง่ายที่คุณกำลังทำสิ่งที่วิธีที่ซับซ้อนเกินไป มีจำนวนมากของเหตุผลที่จะใช้เป็นBoost แต่สำหรับบางสิ่งบางอย่างที่เรียบง่ายแบบนี้มันเหมือนกับกดปุ่มด้วยการเลื่อน 20 #

void
split( vector<string> & theStringVector,  /* Altered/returned value */
       const  string  & theString,
       const  string  & theDelimiter)
{
    UASSERT( theDelimiter.size(), >, 0); // My own ASSERT macro.

    size_t  start = 0, end = 0;

    while ( end != string::npos)
    {
        end = theString.find( theDelimiter, start);

        // If at end, use length=maxLength.  Else use length=end-start.
        theStringVector.push_back( theString.substr( start,
                       (end == string::npos) ? string::npos : end - start));

        // If at end, use start=maxSize.  Else use start=end+delimiter.
        start = (   ( end > (string::npos - theDelimiter.size()) )
                  ?  string::npos  :  end + theDelimiter.size());
    }
}

ตัวอย่าง (สำหรับกรณีของดั๊ก)

#define SHOW(I,X)   cout << "[" << (I) << "]\t " # X " = \"" << (X) << "\"" << endl

int
main()
{
    vector<string> v;

    split( v, "A:PEP:909:Inventory Item", ":" );

    for (unsigned int i = 0;  i < v.size();   i++)
        SHOW( i, v[i] );
}

และใช่เรามีทางแยก () คืนเวกเตอร์ใหม่แทนที่จะผ่านอันหนึ่งมันไม่สำคัญที่จะห่อหุ้มและเกินพิกัด แต่ขึ้นอยู่กับสิ่งที่ฉันทำฉันมักพบว่าการนำวัตถุที่มีอยู่แล้วกลับมาใช้ใหม่ได้ดีกว่าการสร้างวัตถุใหม่เสมอ (ตราบใดที่ฉันไม่ลืมที่จะล้างเวกเตอร์ในระหว่าง!)

อ้างอิง: http://www.cplusplus.com/reference/string/string/

(ตอนแรกฉันเขียนคำตอบของคำถามของ Doug: C ++ Strings Modifying and Extracting โดยแยก (ปิด)แต่เนื่องจาก Martin York ปิดคำถามนั้นโดยมีตัวชี้อยู่ตรงนี้ ... ฉันจะสรุปรหัสของฉัน)


12
เหตุใดจึงกำหนดแมโครที่คุณใช้ในที่เดียวเท่านั้น และ UASSERT ของคุณเป็นอย่างไรดีกว่าการยืนยันมาตรฐาน แยกการเปรียบเทียบเป็นโทเค็น 3 แบบซึ่งไม่ทำอะไรเลยนอกจากต้องการคอมม่ามากกว่าที่คุณต้องการ
crelbor

1
บางทีมาโคร UASSERT แสดง (ในข้อความแสดงข้อผิดพลาด) ความสัมพันธ์ที่แท้จริงระหว่าง (และค่าของ) ทั้งสองค่าที่เปรียบเทียบกัน? นั่นเป็นความคิดที่ดีทีเดียว IMHO
GhassanPL

10
เอ๊ะทำไมstd::stringชั้นไม่รวมฟังก์ชั่น split ()?
Mr. Shickadance

ผมคิดว่าบรรทัดสุดท้ายในห่วงขณะที่ควรจะเป็นและในขณะที่ห่วงควรจะเป็นstart = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size()); while (start != string::npos)นอกจากนี้ฉันจะตรวจสอบสตริงย่อยเพื่อให้แน่ใจว่าไม่ว่างเปล่าก่อนที่จะแทรกลงในเวกเตอร์
John K

@JohnK หากอินพุตมีตัวคั่นสองตัวติดต่อกันให้ชัดเจนว่าสตริงระหว่างตัวนั้นว่างเปล่าและควรแทรกลงในเวกเตอร์ หากค่าว่างไม่เป็นที่ยอมรับสำหรับวัตถุประสงค์เฉพาะนั่นเป็นอีกสิ่งหนึ่ง แต่ IMHO ข้อ จำกัด ดังกล่าวควรถูกบังคับใช้นอกฟังก์ชันวัตถุประสงค์ทั่วไปประเภทนี้
Lauri Nurmi

46

วิธีแก้ปัญหาโดยใช้regex_token_iterators:

#include <iostream>
#include <regex>
#include <string>

using namespace std;

int main()
{
    string str("The quick brown fox");

    regex reg("\\s+");

    sregex_token_iterator iter(str.begin(), str.end(), reg, -1);
    sregex_token_iterator end;

    vector<string> vec(iter, end);

    for (auto a : vec)
    {
        cout << a << endl;
    }
}

5
นี่ควรเป็นคำตอบอันดับต้น ๆ นี่เป็นวิธีที่เหมาะสมในการทำเช่นนี้ใน C ++> = 11
Omnifarious

1
ฉันดีใจที่ฉันได้เลื่อนไปจนถึงคำตอบนี้ (ปัจจุบันมีเพียง 9 upvotes) นี่คือสิ่งที่รหัส C ++ 11 ควรมีลักษณะเหมือนสำหรับงานนี้!
YePhIcK

คำตอบที่ยอดเยี่ยมที่ไม่พึ่งพาไลบรารีภายนอกและใช้ไลบรารีที่มีอยู่แล้ว
แอนดรูว์

1
คำตอบที่ยอดเยี่ยมให้ความยืดหยุ่นสูงสุดในตัวคั่น คำเตือนเล็กน้อย: การใช้ \ s + regex หลีกเลี่ยงโทเค็นที่ว่างอยู่ตรงกลางของข้อความ แต่จะให้โทเค็นแรกที่ว่างเปล่าหากข้อความเริ่มต้นด้วยช่องว่าง regex ดูเหมือนว่าช้า: บนแล็ปท็อปของฉันสำหรับข้อความแบบสุ่ม 20 MB ใช้เวลา 0.6 วินาทีเทียบกับ 0.014 วินาทีสำหรับ strtok, strsep หรือคำตอบของ Parham โดยใช้ str.find_first_of หรือ 0.027 วินาทีสำหรับ Perl หรือ 0.021 วินาทีสำหรับ Python . สำหรับข้อความสั้น ๆ ความเร็วอาจไม่น่าเป็นห่วง
Mark Gates

2
ตกลงบางทีมันอาจดูเท่ห์ แต่นี่ก็ชัดเจนเกินไปสำหรับการแสดงออกปกติ มีเหตุผลเฉพาะในกรณีที่คุณไม่สนใจเกี่ยวกับประสิทธิภาพ
Marek R

35

Boostมีฟังก์ชั่นแยกเข้มแข็งเพิ่ม :: อัลกอริทึม :: แยก

โปรแกรมตัวอย่าง:

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

int main() {
    auto s = "a,b, c ,,e,f,";
    std::vector<std::string> fields;
    boost::split(fields, s, boost::is_any_of(","));
    for (const auto& field : fields)
        std::cout << "\"" << field << "\"\n";
    return 0;
}

เอาท์พุท:

"a"
"b"
" c "
""
"e"
"f"
""

26

ฉันรู้ว่าคุณขอวิธีแก้ปัญหา C ++ แต่คุณอาจพิจารณาว่ามีประโยชน์:

Qt

#include <QString>

...

QString str = "The quick brown fox"; 
QStringList results = str.split(" "); 

ข้อได้เปรียบเหนือ Boost ในตัวอย่างนี้คือมันเป็นการแมปแบบหนึ่งต่อหนึ่งกับรหัสโพสต์ของคุณโดยตรง

ดูเพิ่มเติมได้ที่เอกสารประกอบของ Qt


22

นี่คือคลาส tokenizer ตัวอย่างที่อาจทำสิ่งที่คุณต้องการ

//Header file
class Tokenizer 
{
    public:
        static const std::string DELIMITERS;
        Tokenizer(const std::string& str);
        Tokenizer(const std::string& str, const std::string& delimiters);
        bool NextToken();
        bool NextToken(const std::string& delimiters);
        const std::string GetToken() const;
        void Reset();
    protected:
        size_t m_offset;
        const std::string m_string;
        std::string m_token;
        std::string m_delimiters;
};

//CPP file
const std::string Tokenizer::DELIMITERS(" \t\n\r");

Tokenizer::Tokenizer(const std::string& s) :
    m_string(s), 
    m_offset(0), 
    m_delimiters(DELIMITERS) {}

Tokenizer::Tokenizer(const std::string& s, const std::string& delimiters) :
    m_string(s), 
    m_offset(0), 
    m_delimiters(delimiters) {}

bool Tokenizer::NextToken() 
{
    return NextToken(m_delimiters);
}

bool Tokenizer::NextToken(const std::string& delimiters) 
{
    size_t i = m_string.find_first_not_of(delimiters, m_offset);
    if (std::string::npos == i) 
    {
        m_offset = m_string.length();
        return false;
    }

    size_t j = m_string.find_first_of(delimiters, i);
    if (std::string::npos == j) 
    {
        m_token = m_string.substr(i);
        m_offset = m_string.length();
        return true;
    }

    m_token = m_string.substr(i, j - i);
    m_offset = j;
    return true;
}

ตัวอย่าง:

std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())
{
    v.push_back(s.GetToken());
}

19

นี่เป็นโซลูชัน STL อย่างเดียวอย่างง่าย (~ 5 บรรทัด!) โดยใช้std::findและstd::find_first_not_ofที่จัดการการซ้ำของตัวคั่น (เช่นช่องว่างหรือจุดเป็นต้น) รวมถึงตัวคั่นที่นำหน้าและต่อท้าย:

#include <string>
#include <vector>

void tokenize(std::string str, std::vector<string> &token_v){
    size_t start = str.find_first_not_of(DELIMITER), end=start;

    while (start != std::string::npos){
        // Find next occurence of delimiter
        end = str.find(DELIMITER, start);
        // Push back the token found into vector
        token_v.push_back(str.substr(start, end-start));
        // Skip all occurences of the delimiter to find new start
        start = str.find_first_not_of(DELIMITER, end);
    }
}

ลองสด !


3
นี่เป็นสิ่งที่ดี แต่ฉันคิดว่าคุณจำเป็นต้องใช้ find_first_of () แทน find () เพื่อให้สิ่งนี้ทำงานได้อย่างถูกต้องกับตัวคั่นหลายตัว

2
@ user755921 ตัวคั่นหลายตัวถูกข้ามเมื่อค้นหาตำแหน่งเริ่มต้นด้วย find_first_not_of
เริ่มต้น

16

pystringเป็นไลบรารี่ขนาดเล็กที่ใช้ฟังก์ชั่นสตริงของไพ ธ อนรวมถึงวิธีการแบ่ง:

#include <string>
#include <vector>
#include "pystring.h"

std::vector<std::string> chunks;
pystring::split("this string", chunks);

// also can specify a separator
pystring::split("this-string", chunks, "-");

3
ว้าวคุณตอบฉันคำถามทันทีและคำถามในอนาคตจำนวนมาก ฉันได้รับ c ++ นั้นทรงพลัง แต่เมื่อการแยกสตริงส่งผลให้เกิดซอร์สโค้ดเหมือนกับคำตอบข้างต้นมันจะทำให้หมดกำลังใจอย่างชัดเจน ฉันชอบที่จะรู้ว่าห้องสมุดอื่น ๆ เช่นนี้ที่ดึงระดับสูงกว่า langauges สะดวกสบายลง
Ross

ว้าวคุณทำวันของฉันอย่างจริงจังแล้ว !! ไม่ทราบเกี่ยวกับ pystring นี่จะช่วยฉันได้มากเวลา!
accraze

11

ฉันโพสต์คำตอบนี้สำหรับคำถามที่คล้ายกัน
อย่าประดิษฐ์ล้อใหม่ ฉันใช้ห้องสมุดจำนวนมากและเร็วที่สุดและยืดหยุ่นที่สุดที่ฉันเจอคือ: C ++ String Toolkit ห้องสมุด

นี่คือตัวอย่างของวิธีการใช้งานที่ฉันโพสต์ไว้ที่อื่นใน stackoverflow

#include <iostream>
#include <vector>
#include <string>
#include <strtk.hpp>

const char *whitespace  = " \t\r\n\f";
const char *whitespace_and_punctuation  = " \t\r\n\f;,=";

int main()
{
    {   // normal parsing of a string into a vector of strings
       std::string s("Somewhere down the road");
       std::vector<std::string> result;
       if( strtk::parse( s, whitespace, result ) )
       {
           for(size_t i = 0; i < result.size(); ++i )
            std::cout << result[i] << std::endl;
       }
    }

    {  // parsing a string into a vector of floats with other separators
       // besides spaces

       std::string s("3.0, 3.14; 4.0");
       std::vector<float> values;
       if( strtk::parse( s, whitespace_and_punctuation, values ) )
       {
           for(size_t i = 0; i < values.size(); ++i )
            std::cout << values[i] << std::endl;
       }
    }

    {  // parsing a string into specific variables

       std::string s("angle = 45; radius = 9.9");
       std::string w1, w2;
       float v1, v2;
       if( strtk::parse( s, whitespace_and_punctuation, w1, v1, w2, v2) )
       {
           std::cout << "word " << w1 << ", value " << v1 << std::endl;
           std::cout << "word " << w2 << ", value " << v2 << std::endl;
       }
    }

    return 0;
}

8

ตรวจสอบตัวอย่างนี้ มันอาจช่วยคุณ ..

#include <iostream>
#include <sstream>

using namespace std;

int main ()
{
    string tmps;
    istringstream is ("the dellimiter is the space");
    while (is.good ()) {
        is >> tmps;
        cout << tmps << "\n";
    }
    return 0;
}

1
ฉันจะทำwhile ( is >> tmps ) { std::cout << tmps << "\n"; }
jordix

6

MFC / ATL มีโทเค็นที่ดีมาก จาก MSDN:

CAtlString str( "%First Second#Third" );
CAtlString resToken;
int curPos= 0;

resToken= str.Tokenize("% #",curPos);
while (resToken != "")
{
   printf("Resulting token: %s\n", resToken);
   resToken= str.Tokenize("% #",curPos);
};

Output

Resulting Token: First
Resulting Token: Second
Resulting Token: Third

1
ฟังก์ชัน Tokenize () นี้จะข้ามโทเค็นที่ว่างเปล่าตัวอย่างเช่นหากมีสตริงย่อย "%%" ในสตริงหลักจะไม่มีโทเค็นว่างกลับมา มันถูกข้ามไป
Sheen

4

หากคุณยินดีที่จะใช้ C คุณสามารถใช้ฟังก์ชั่นstrtok คุณควรใส่ใจกับปัญหาหลายเธรดเมื่อใช้งาน


3
โปรดทราบว่า strtok แก้ไขสตริงที่คุณกำลังตรวจสอบดังนั้นคุณจึงไม่สามารถใช้กับสตริง const char * โดยไม่ต้องทำการคัดลอก
แกรมเพอร์โรว์

9
ปัญหามัลติเธรดคือ strtok ใช้ตัวแปรโกลบอลเพื่อติดตามว่ามันอยู่ที่ไหนดังนั้นถ้าคุณมีสองเธรดที่แต่ละการใช้ strtok คุณจะได้รับพฤติกรรมที่ไม่ได้กำหนด
JohnMcG

@JohnMcG หรือเพียงแค่ใช้strtok_sซึ่งโดยทั่วไปstrtokมีสถานะที่ชัดเจนผ่าน
Matthias

4

สำหรับสิ่งที่เรียบง่ายฉันเพิ่งใช้สิ่งต่อไปนี้:

unsigned TokenizeString(const std::string& i_source,
                        const std::string& i_seperators,
                        bool i_discard_empty_tokens,
                        std::vector<std::string>& o_tokens)
{
    unsigned prev_pos = 0;
    unsigned pos = 0;
    unsigned number_of_tokens = 0;
    o_tokens.clear();
    pos = i_source.find_first_of(i_seperators, pos);
    while (pos != std::string::npos)
    {
        std::string token = i_source.substr(prev_pos, pos - prev_pos);
        if (!i_discard_empty_tokens || token != "")
        {
            o_tokens.push_back(i_source.substr(prev_pos, pos - prev_pos));
            number_of_tokens++;
        }

        pos++;
        prev_pos = pos;
        pos = i_source.find_first_of(i_seperators, pos);
    }

    if (prev_pos < i_source.length())
    {
        o_tokens.push_back(i_source.substr(prev_pos));
        number_of_tokens++;
    }

    return number_of_tokens;
}

ข้อจำกัดความรับผิดชอบอย่างขี้ขลาด: ฉันเขียนซอฟต์แวร์ประมวลผลข้อมูลตามเวลาจริงที่ข้อมูลเข้ามาผ่านไฟล์ไบนารีซ็อกเก็ตหรือการโทร API บางอย่าง (การ์ด I / O, กล้อง) ฉันไม่เคยใช้ฟังก์ชั่นนี้สำหรับสิ่งที่ซับซ้อนหรือเวลาสำคัญกว่าการอ่านไฟล์กำหนดค่าภายนอกเมื่อเริ่มต้น


4

คุณสามารถใช้ไลบรารีนิพจน์ทั่วไปและแก้ปัญหาโดยใช้นิพจน์ทั่วไป

ใช้ expression (\ w +) และตัวแปรใน \ 1 (หรือ $ 1 ขึ้นอยู่กับการใช้ไลบรารี่ของนิพจน์ทั่วไป)


+1 สำหรับการแนะนำ regex หากคุณไม่ต้องการความเร็ววาร์ปมันเป็นโซลูชันที่ยืดหยุ่นที่สุด แต่ยังไม่รองรับทุกที่ แต่เมื่อเวลาผ่านไปสิ่งเหล่านี้จะมีความสำคัญน้อยลง
odinthenerd

+1 จากฉันเพิ่งลอง <regex> ใน c ++ 11 เรียบง่ายและสง่างาม
StahlRat

4

คำแนะนำที่ซับซ้อนมากเกินไปที่นี่ ลอง std :: string แบบง่ายๆนี้:

using namespace std;

string someText = ...

string::size_type tokenOff = 0, sepOff = tokenOff;
while (sepOff != string::npos)
{
    sepOff = someText.find(' ', sepOff);
    string::size_type tokenLen = (sepOff == string::npos) ? sepOff : sepOff++ - tokenOff;
    string token = someText.substr(tokenOff, tokenLen);
    if (!token.empty())
        /* do something with token */;
    tokenOff = sepOff;
}

4

ฉันคิดว่านั่นเป็นสิ่งที่>>ผู้ปฏิบัติงานในสายสตรีมใช้เพื่อ:

string word; sin >> word;

1
ความผิดของฉันในการให้ตัวอย่างที่ไม่ดี (ง่ายเกินไป) ไกลเท่าที่ฉันรู้ว่ามันทำงานได้เฉพาะเมื่อตัวคั่นของคุณเป็นช่องว่าง
Bill the Lizard

4

คำตอบอดัมเพียร์ซยังมีมือหมุน tokenizer const char*การใน เป็นบิตปัญหามากขึ้นจะทำอย่างไรกับ iterators เพราะการเพิ่มstringสิ้น 's iterator จะไม่ได้กำหนด ที่กล่าวว่าเนื่องจากstring str{ "The quick brown fox" }เราสามารถทำสิ่งนี้ได้อย่างแน่นอน:

auto start = find(cbegin(str), cend(str), ' ');
vector<string> tokens{ string(cbegin(str), start) };

while (start != cend(str)) {
    const auto finish = find(++start, cend(str), ' ');

    tokens.push_back(string(start, finish));
    start = finish;
}

Live Example


หากคุณกำลังมองหาความซับซ้อนเชิงนามธรรมโดยใช้ฟังก์ชั่นมาตรฐานเนื่องจากOn Freund แนะนำ strtokเป็นตัวเลือกที่ง่าย:

vector<string> tokens;

for (auto i = strtok(data(str), " "); i != nullptr; i = strtok(nullptr, " ")) tokens.push_back(i);

หากคุณไม่มีสิทธิ์เข้าถึง C ++ 17 คุณจะต้องแทนที่data(str)ดังเช่นในตัวอย่างนี้: http://ideone.com/8kAGoa

แม้ว่าจะไม่ได้แสดงในตัวอย่าง แต่strtokไม่จำเป็นต้องใช้ตัวคั่นเดียวกันสำหรับแต่ละโทเค็น ด้วยความได้เปรียบนี้แม้ว่าจะมีข้อเสียหลายประการ:

  1. strtokไม่สามารถใช้กับหลาย ๆ อย่างstringsในเวลาเดียวกัน: a nullptrต้องถูกส่งผ่านเพื่อดำเนินการโทเค็นปัจจุบันstringหรือchar*tokenize ใหม่ที่ต้องส่งต่อ (มีบางการใช้งานที่ไม่ได้มาตรฐานซึ่งสนับสนุนสิ่งนี้อย่างไรก็ตาม:strtok_s )
  2. ด้วยเหตุผลเดียวกันstrtokนี้ไม่สามารถใช้กับหลาย ๆ เธรดพร้อมกันได้ (ซึ่งอาจเป็นการนำไปใช้งานที่กำหนดไว้ตัวอย่างเช่น: การใช้งานของ Visual Studio คือเธรดที่ปลอดภัย )
  3. การโทรstrtokแก้ไขstringมันจะทำงานบนดังนั้นจึงไม่สามารถใช้กับconst strings, const char*s หรือสตริงตัวอักษรเพื่อโทเค็นใด ๆ เหล่านี้ด้วยstrtokหรือเพื่อดำเนินการกับstringผู้ที่ต้องรักษาเนื้อหาของstrจะต้องคัดลอกแล้วคัดลอกได้ จะดำเนินการใน

ทำให้เรามีsplit_viewtokenize สตริงในลักษณะที่ไม่ทำลาย: https://topanswers.xyz/cplusplus?q=749#a874


วิธีการก่อนหน้านี้ไม่สามารถสร้าง tokenized ในสถานที่ที่มีความหมายโดยไม่ต้องสรุปให้พวกเขาในการทำงานช่วยเหลือพวกเขาไม่สามารถเตรียมvector const vector<string> tokensฟังก์ชันการทำงานที่และความสามารถในการยอมรับใด ๆistream_iteratorคั่นพื้นที่สีขาวสามารถถูกควบคุมโดยใช้ ตัวอย่างที่กำหนด: const string str{ "The quick \tbrown \nfox" }เราสามารถทำสิ่งนี้:

istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };

Live Example

การสร้างที่จำเป็นistringstreamสำหรับตัวเลือกนี้มีค่าใช้จ่ายสูงกว่า 2 ตัวเลือกก่อนหน้านี้อย่างไรก็ตามโดยทั่วไปค่าใช้จ่ายนี้จะถูกซ่อนอยู่ในค่าใช้จ่ายในการstringจัดสรร


หากไม่มีตัวเลือกข้างต้นเป็น flexable เพียงพอสำหรับความต้องการของคุณ tokenization ตัวเลือกที่ยืดหยุ่นมากที่สุดคือการใช้regex_token_iteratorของหลักสูตรมีความยืดหยุ่นนี้มาค่าใช้จ่ายมากขึ้นอีกครั้ง แต่ครั้งนี้มีแนวโน้มที่จะถูกซ่อนอยู่ในstringค่าใช้จ่ายในการจัดสรร พูดเช่นเราต้องการทำเครื่องหมายตามเครื่องหมายจุลภาคที่ไม่ใช้ Escape รวมถึงการกิน white-space ด้วยการป้อนข้อมูลต่อไปนี้: const string str{ "The ,qu\\,ick ,\tbrown, fox" }เราสามารถทำสิ่งนี้:

const regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };

Live Example


strtok_sเป็นมาตรฐาน C11 โดยวิธีการ strtok_rเป็นมาตรฐาน POSIX2001 ระหว่างทั้งสองนั้นจะมีเวอร์ชัน re-entrant มาตรฐานstrtokสำหรับแพลตฟอร์มส่วนใหญ่
Andon M. Coleman

@ AndonM.Coleman แต่นี่เป็นคำถามc ++และใน C ++ #include <cstring>จะมีเฉพาะรุ่นc99strtokเท่านั้น ดังนั้นสมมติฐานของฉันคือคุณเพียงแค่แสดงความคิดเห็นนี้เป็นวัสดุสนับสนุนแสดงให้เห็นถึงการใช้งานที่เฉพาะเจาะจงของstrtokส่วนขยาย?
Jonathan Mee

1
เพียงแค่ว่ามันไม่ได้เป็นที่ไม่ได้มาตรฐานอย่างที่คนอื่นอาจเชื่อ strtok_sให้บริการโดยทั้ง C11 และเป็นส่วนขยายแบบสแตนด์อโลนใน C runtime ของ Microsoft มีประวัติที่น่าสนใจเล็กน้อยที่นี่ซึ่งการ_sทำงานของ Microsoft กลายเป็นมาตรฐาน C
Andon M. Coleman

@ AndonM.Coleman ใช่ฉันอยู่กับคุณ เห็นได้ชัดว่าถ้ามันอยู่ในมาตรฐาน C11 ส่วนต่อประสานและการนำไปใช้งานนั้นมีข้อ จำกัด อยู่ซึ่งจำเป็นต้องมีพฤติกรรมที่เหมือนกันโดยไม่ขึ้นกับแพลตฟอร์ม ในตอนนี้ปัญหาเดียวคือให้แน่ใจว่ามีฟังก์ชั่น C11 สำหรับเราในทุกแพลตฟอร์ม หวังว่ามาตรฐาน C11 จะเป็นสิ่งที่ C ++ 17 หรือ C ++ 20 เลือกที่จะรับ
Jonathan Mee

3

ฉันรู้ว่าคำถามนี้ตอบแล้ว แต่ฉันต้องการมีส่วนร่วม อาจจะแก้ปัญหาของฉันเป็นเรื่องง่าย แต่นี่คือสิ่งที่ฉันมาด้วย:

vector<string> get_words(string const& text, string const& separator)
{
    vector<string> result;
    string tmp = text;

    size_t first_pos = 0;
    size_t second_pos = tmp.find(separator);

    while (second_pos != string::npos)
    {
        if (first_pos != second_pos)
        {
            string word = tmp.substr(first_pos, second_pos - first_pos);
            result.push_back(word);
        }
        tmp = tmp.substr(second_pos + separator.length());
        second_pos = tmp.find(separator);
    }

    result.push_back(tmp);

    return result;
}

โปรดแสดงความคิดเห็นหากมีวิธีการที่ดีกว่าในบางสิ่งบางอย่างในรหัสของฉันหรือหากมีสิ่งผิดปกติ

UPDATE:เพิ่มตัวคั่นทั่วไป


ใช้วิธีแก้ปัญหาของคุณจากฝูงชน :) ฉันสามารถแก้ไขรหัสของคุณเพื่อเพิ่มตัวคั่นได้ไหม?
Zac

1
@ Zac ดีใจที่คุณชอบและคุณสามารถแก้ไขได้ ... เพียงแค่เพิ่มส่วนการอัพเดทที่กล้าหาญให้กับคำตอบของฉัน ...
23919

2

ต่อไปนี้เป็นวิธีการที่ช่วยให้คุณควบคุมว่ามีโทเค็นว่างอยู่หรือไม่ (เช่น strsep) หรือยกเว้น (เช่น strtok)

#include <string.h> // for strchr and strlen

/*
 * want_empty_tokens==true  : include empty tokens, like strsep()
 * want_empty_tokens==false : exclude empty tokens, like strtok()
 */
std::vector<std::string> tokenize(const char* src,
                                  char delim,
                                  bool want_empty_tokens)
{
  std::vector<std::string> tokens;

  if (src and *src != '\0') // defensive
    while( true )  {
      const char* d = strchr(src, delim);
      size_t len = (d)? d-src : strlen(src);

      if (len or want_empty_tokens)
        tokens.push_back( std::string(src, len) ); // capture token

      if (d) src += len+1; else break;
    }

  return tokens;
}

2

ดูเหมือนว่าแปลกสำหรับฉันที่พวกเราทุกคนเร่งความเร็วให้กับผู้สนใจที่นี่ดังนั้นไม่มีใครแสดงรุ่นที่ใช้เวลารวบรวมที่สร้างขึ้นค้นหาตารางสำหรับตัวคั่น (ตัวอย่างการนำไปใช้เพิ่มเติม) การใช้ lookup table และตัววนซ้ำควรใช้ std :: regex อย่างมีประสิทธิภาพหากคุณไม่จำเป็นต้องเอาชนะ regex ให้ใช้มันมาตรฐานของ C ++ 11 และความยืดหยุ่นสูงเป็นพิเศษ

บางคนได้แนะนำ regex แล้ว แต่สำหรับ noobs ที่นี่เป็นตัวอย่างของแพ็คเกจที่ควรทำตามที่ OP ต้องการ:

std::vector<std::string> split(std::string::const_iterator it, std::string::const_iterator end, std::regex e = std::regex{"\\w+"}){
    std::smatch m{};
    std::vector<std::string> ret{};
    while (std::regex_search (it,end,m,e)) {
        ret.emplace_back(m.str());              
        std::advance(it, m.position() + m.length()); //next start position = match position + match length
    }
    return ret;
}
std::vector<std::string> split(const std::string &s, std::regex e = std::regex{"\\w+"}){  //comfort version calls flexible version
    return split(s.cbegin(), s.cend(), std::move(e));
}
int main ()
{
    std::string str {"Some people, excluding those present, have been compile time constants - since puberty."};
    auto v = split(str);
    for(const auto&s:v){
        std::cout << s << std::endl;
    }
    std::cout << "crazy version:" << std::endl;
    v = split(str, std::regex{"[^e]+"});  //using e as delim shows flexibility
    for(const auto&s:v){
        std::cout << s << std::endl;
    }
    return 0;
}

ถ้าเราต้องเร็วขึ้นและยอมรับข้อ จำกัด ที่ตัวอักษรทั้งหมดต้องเป็น 8 บิตเราสามารถสร้างตารางค้นหาในเวลาคอมไพล์โดยใช้ metaprogramming:

template<bool...> struct BoolSequence{};        //just here to hold bools
template<char...> struct CharSequence{};        //just here to hold chars
template<typename T, char C> struct Contains;   //generic
template<char First, char... Cs, char Match>    //not first specialization
struct Contains<CharSequence<First, Cs...>,Match> :
    Contains<CharSequence<Cs...>, Match>{};     //strip first and increase index
template<char First, char... Cs>                //is first specialization
struct Contains<CharSequence<First, Cs...>,First>: std::true_type {}; 
template<char Match>                            //not found specialization
struct Contains<CharSequence<>,Match>: std::false_type{};

template<int I, typename T, typename U> 
struct MakeSequence;                            //generic
template<int I, bool... Bs, typename U> 
struct MakeSequence<I,BoolSequence<Bs...>, U>:  //not last
    MakeSequence<I-1, BoolSequence<Contains<U,I-1>::value,Bs...>, U>{};
template<bool... Bs, typename U> 
struct MakeSequence<0,BoolSequence<Bs...>,U>{   //last  
    using Type = BoolSequence<Bs...>;
};
template<typename T> struct BoolASCIITable;
template<bool... Bs> struct BoolASCIITable<BoolSequence<Bs...>>{
    /* could be made constexpr but not yet supported by MSVC */
    static bool isDelim(const char c){
        static const bool table[256] = {Bs...};
        return table[static_cast<int>(c)];
    }   
};
using Delims = CharSequence<'.',',',' ',':','\n'>;  //list your custom delimiters here
using Table = BoolASCIITable<typename MakeSequence<256,BoolSequence<>,Delims>::Type>;

เมื่อมีการสร้างgetNextTokenฟังก์ชั่นนั้นง่าย:

template<typename T_It>
std::pair<T_It,T_It> getNextToken(T_It begin,T_It end){
    begin = std::find_if(begin,end,std::not1(Table{})); //find first non delim or end
    auto second = std::find_if(begin,end,Table{});      //find first delim or end
    return std::make_pair(begin,second);
}

ใช้งานได้ง่าย:

int main() {
    std::string s{"Some people, excluding those present, have been compile time constants - since puberty."};
    auto it = std::begin(s);
    auto end = std::end(s);
    while(it != std::end(s)){
        auto token = getNextToken(it,end);
        std::cout << std::string(token.first,token.second) << std::endl;
        it = token.second;
    }
    return 0;
}

นี่คือตัวอย่างสด: http://ideone.com/GKtkLQ


1
เป็นไปได้ไหมที่จะส่งสัญญาณด้วยตัวคั่นสตริง?
กาแล็กซี

รุ่นนี้เหมาะสำหรับตัวคั่นอักขระเดียวโดยใช้ตารางการค้นหาไม่เหมาะสำหรับตัวคั่นหลายตัว (สตริง) ดังนั้นจึงยากที่จะเอาชนะ regex อย่างมีประสิทธิภาพ
odinthenerd

1

คุณสามารถใช้ประโยชน์จากการเพิ่ม :: make_find_iterator สิ่งที่คล้ายกับสิ่งนี้:

template<typename CH>
inline vector< basic_string<CH> > tokenize(
    const basic_string<CH> &Input,
    const basic_string<CH> &Delimiter,
    bool remove_empty_token
    ) {

    typedef typename basic_string<CH>::const_iterator string_iterator_t;
    typedef boost::find_iterator< string_iterator_t > string_find_iterator_t;

    vector< basic_string<CH> > Result;
    string_iterator_t it = Input.begin();
    string_iterator_t it_end = Input.end();
    for(string_find_iterator_t i = boost::make_find_iterator(Input, boost::first_finder(Delimiter, boost::is_equal()));
        i != string_find_iterator_t();
        ++i) {
        if(remove_empty_token){
            if(it != i->begin())
                Result.push_back(basic_string<CH>(it,i->begin()));
        }
        else
            Result.push_back(basic_string<CH>(it,i->begin()));
        it = i->end();
    }
    if(it != it_end)
        Result.push_back(basic_string<CH>(it,it_end));

    return Result;
}

1

ต่อไปนี้คือ Swiss-Army Knife-tokenizers ของฉันสำหรับการแยกสตริงด้วยช่องว่างการบัญชีสำหรับสตริงเดี่ยวและคู่ที่มีเครื่องหมายคำพูดรวมถึงการลอกอักขระจากผลลัพธ์ ผมใช้ 4.x RegexBuddy ในการสร้างมากที่สุดของรหัสข้อมูลโค้ด แต่ฉันเพิ่มที่กำหนดเองจัดการลอกคำพูดและสิ่งอื่น ๆ น้อย

#include <string>
#include <locale>
#include <regex>

std::vector<std::wstring> tokenize_string(std::wstring string_to_tokenize) {
    std::vector<std::wstring> tokens;

    std::wregex re(LR"(("[^"]*"|'[^']*'|[^"' ]+))", std::regex_constants::collate);

    std::wsregex_iterator next( string_to_tokenize.begin(),
                                string_to_tokenize.end(),
                                re,
                                std::regex_constants::match_not_null );

    std::wsregex_iterator end;
    const wchar_t single_quote = L'\'';
    const wchar_t double_quote = L'\"';
    while ( next != end ) {
        std::wsmatch match = *next;
        const std::wstring token = match.str( 0 );
        next++;

        if (token.length() > 2 && (token.front() == double_quote || token.front() == single_quote))
            tokens.emplace_back( std::wstring(token.begin()+1, token.begin()+token.length()-1) );
        else
            tokens.emplace_back(token);
    }
    return tokens;
}

1
(ลง) การลงคะแนนอาจเป็นสิ่งที่สร้างสรรค์เช่นเดียวกับ upvotes แต่ไม่ใช่เมื่อคุณไม่แสดงความคิดเห็นว่าทำไม ...
kayleeFrye_onDeck

1
ฉันทำให้คุณดีขึ้น แต่อาจเป็นเพราะรหัสนั้นดูน่ากลัวสำหรับโปรแกรมเมอร์ googling 'วิธีแยกสตริง' โดยเฉพาะอย่างยิ่งโดยไม่มีเอกสารประกอบ
mattshu

ขอบคุณ @mattshu! เป็นเซ็กเมนต์ regex ที่ทำให้มันน่ากลัวหรืออย่างอื่น?
kayleeFrye_onDeck

0

หากทราบความยาวสูงสุดของสตริงป้อนข้อมูลที่จะโทเค็นหนึ่งสามารถใช้ประโยชน์จากนี้และใช้รุ่นที่รวดเร็วมาก ฉันกำลังร่างแนวคิดพื้นฐานด้านล่างซึ่งได้รับแรงบันดาลใจจากทั้ง strtok () และ "ส่วนต่อท้าย" โครงสร้างข้อมูลอธิบายของ Jon Bentley "Programming Perls" รุ่นที่ 2 บทที่ 15 คลาส C ++ ในกรณีนี้ให้องค์กรและความสะดวกสบายบางอย่างเท่านั้น การใช้งาน การใช้งานที่แสดงสามารถขยายได้อย่างง่ายดายสำหรับการลบอักขระช่องว่างนำหน้าและต่อท้ายในโทเค็น

โดยทั่วไปจะสามารถแทนที่อักขระตัวคั่นด้วยการสิ้นสุดสตริง '\ 0'-characters และตั้งค่าพอยน์เตอร์เป็นโทเค็นที่มีสตริงที่ปรับเปลี่ยน ในกรณีที่รุนแรงเมื่อสตริงประกอบด้วยตัวคั่นเท่านั้นหนึ่งตัวจะได้รับความยาวของสตริงบวก 1 ทำให้โทเค็นว่างเปล่า เป็นจริงในการทำซ้ำสตริงที่จะแก้ไข

ไฟล์ส่วนหัว:

class TextLineSplitter
{
public:

    TextLineSplitter( const size_t max_line_len );

    ~TextLineSplitter();

    void            SplitLine( const char *line,
                               const char sep_char = ',',
                             );

    inline size_t   NumTokens( void ) const
    {
        return mNumTokens;
    }

    const char *    GetToken( const size_t token_idx ) const
    {
        assert( token_idx < mNumTokens );
        return mTokens[ token_idx ];
    }

private:
    const size_t    mStorageSize;

    char           *mBuff;
    char          **mTokens;
    size_t          mNumTokens;

    inline void     ResetContent( void )
    {
        memset( mBuff, 0, mStorageSize );
        // mark all items as empty:
        memset( mTokens, 0, mStorageSize * sizeof( char* ) );
        // reset counter for found items:
        mNumTokens = 0L;
    }
};

ไฟล์การนำไปใช้งาน:

TextLineSplitter::TextLineSplitter( const size_t max_line_len ):
    mStorageSize ( max_line_len + 1L )
{
    // allocate memory
    mBuff   = new char  [ mStorageSize ];
    mTokens = new char* [ mStorageSize ];

    ResetContent();
}

TextLineSplitter::~TextLineSplitter()
{
    delete [] mBuff;
    delete [] mTokens;
}


void TextLineSplitter::SplitLine( const char *line,
                                  const char sep_char   /* = ',' */,
                                )
{
    assert( sep_char != '\0' );

    ResetContent();
    strncpy( mBuff, line, mMaxLineLen );

    size_t idx       = 0L; // running index for characters

    do
    {
        assert( idx < mStorageSize );

        const char chr = line[ idx ]; // retrieve current character

        if( mTokens[ mNumTokens ] == NULL )
        {
            mTokens[ mNumTokens ] = &mBuff[ idx ];
        } // if

        if( chr == sep_char || chr == '\0' )
        { // item or line finished
            // overwrite separator with a 0-terminating character:
            mBuff[ idx ] = '\0';
            // count-up items:
            mNumTokens ++;
        } // if

    } while( line[ idx++ ] );
}

สถานการณ์การใช้งานจะเป็น:

// create an instance capable of splitting strings up to 1000 chars long:
TextLineSplitter spl( 1000 );
spl.SplitLine( "Item1,,Item2,Item3" );
for( size_t i = 0; i < spl.NumTokens(); i++ )
{
    printf( "%s\n", spl.GetToken( i ) );
}

เอาท์พุท:

Item1

Item2
Item3

0

boost::tokenizerเป็นเพื่อนของคุณ แต่ให้พิจารณาทำให้โค้ดของคุณพกพาได้โดยอ้างอิงกับปัญหาสากล (i18n) โดยใช้wstring/ wchar_tแทนที่จะเป็นแบบดั้งเดิมstring/ charประเภท

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

using namespace std;
using namespace boost;

typedef tokenizer<char_separator<wchar_t>,
                  wstring::const_iterator, wstring> Tok;

int main()
{
  wstring s;
  while (getline(wcin, s)) {
    char_separator<wchar_t> sep(L" "); // list of separator characters
    Tok tok(s, sep);
    for (Tok::iterator beg = tok.begin(); beg != tok.end(); ++beg) {
      wcout << *beg << L"\t"; // output (or store in vector)
    }
    wcout << L"\n";
  }
  return 0;
}

"มรดก" ไม่ถูกต้องและwchar_tเป็นการนำไปปฏิบัติที่น่ากลัวซึ่งไม่มีใครควรใช้เว้นแต่จำเป็นจริงๆ
CoffeeandCode

การใช้ wchar_t ไม่ได้ช่วยแก้ปัญหา i18n โดยอัตโนมัติ คุณใช้การเข้ารหัสเพื่อแก้ไขปัญหานั้น หากคุณกำลังแยกสตริงด้วยตัวคั่นมันก็หมายความว่าตัวคั่นไม่ชนกับเนื้อหาที่เข้ารหัสของโทเค็นใด ๆ ภายในสตริง อาจจำเป็นต้องหลบหนี ฯลฯ wchar_t ไม่ใช่คำตอบที่น่าอัศจรรย์สำหรับเรื่องนี้
yonil

0

รหัส C ++ แบบง่าย (มาตรฐาน C ++ 98) ยอมรับตัวคั่นหลายตัว (ระบุใน std :: string) ใช้เวกเตอร์สตริงและตัววนซ้ำเท่านั้น

#include <iostream>
#include <vector>
#include <string>
#include <stdexcept> 

std::vector<std::string> 
split(const std::string& str, const std::string& delim){
    std::vector<std::string> result;
    if (str.empty())
        throw std::runtime_error("Can not tokenize an empty string!");
    std::string::const_iterator begin, str_it;
    begin = str_it = str.begin(); 
    do {
        while (delim.find(*str_it) == std::string::npos && str_it != str.end())
            str_it++; // find the position of the first delimiter in str
        std::string token = std::string(begin, str_it); // grab the token
        if (!token.empty()) // empty token only when str starts with a delimiter
            result.push_back(token); // push the token into a vector<string>
        while (delim.find(*str_it) != std::string::npos && str_it != str.end())
            str_it++; // ignore the additional consecutive delimiters
        begin = str_it; // process the remaining tokens
        } while (str_it != str.end());
    return result;
}

int main() {
    std::string test_string = ".this is.a.../.simple;;test;;;END";
    std::string delim = "; ./"; // string containing the delimiters
    std::vector<std::string> tokens = split(test_string, delim);           
    for (std::vector<std::string>::const_iterator it = tokens.begin(); 
        it != tokens.end(); it++)
            std::cout << *it << std::endl;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.