Câu hỏi Cách thanh lịch nhất để lặp lại các từ của một chuỗi [đóng]


Cách thanh lịch nhất để lặp lại các từ của một chuỗi là gì? Chuỗi có thể được giả định là các từ được phân tách bằng khoảng trắng.

Lưu ý rằng tôi không quan tâm đến các hàm chuỗi C hoặc loại thao tác / truy cập ký tự đó. Ngoài ra, hãy ưu tiên cho sự sang trọng hơn hiệu quả trong câu trả lời của bạn.

Giải pháp tốt nhất tôi có ngay bây giờ là:

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

using namespace std;

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

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

2638


gốc


Dude ... Elegance chỉ là một cách ưa thích để nói "hiệu quả-trông-đẹp" trong cuốn sách của tôi. Đừng ngại sử dụng các hàm C và các phương thức nhanh để thực hiện bất cứ điều gì chỉ vì nó không được chứa trong một khuôn mẫu;) - nlaq
while (iss) { string subs; iss >> subs; cout << "Substring: " << sub << endl; } - pyon
@Eduardo: đó cũng là sai lầm ... bạn cần phải kiểm tra iss giữa cố gắng để dòng giá trị khác và sử dụng giá trị đó, tức là string sub; while (iss >> sub) cout << "Substring: " << sub << '\n'; - Tony Delroy
Các tùy chọn khác nhau trong C ++ để làm điều này theo mặc định: cplusplus.com/faq/sequences/strings/split - hB0
Có nhiều thứ sang trọng hơn là hiệu quả khá. Thuộc tính thanh lịch bao gồm số lượng dòng thấp và mức độ dễ đọc cao. IMHO Elegance không phải là một proxy cho hiệu quả nhưng bảo trì. - Matt


Các câu trả lời:


Đối với những gì nó có giá trị, đây là một cách khác để trích xuất mã thông báo từ một chuỗi đầu vào, chỉ dựa vào các cơ sở thư viện chuẩn. Đó là một ví dụ về sức mạnh và sự thanh lịch đằng sau thiết kế của STL.

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

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

Thay vì sao chép mã thông báo được trích xuất sang luồng đầu ra, người dùng có thể chèn chúng vào một vùng chứa, sử dụng cùng một loại chung copy thuật toán.

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

... hoặc tạo vector trực tiếp:

vector<string> tokens{istream_iterator<string>{iss},
                      istream_iterator<string>{}};

1189



Có thể chỉ định một dấu phân tách cho điều này không? Giống như ví dụ tách trên dấu phẩy? - l3dx
@ Jonathan: \ n không phải là dấu phân cách trong trường hợp này, đó là dấu phân cách để xuất ra cout. - huy
Đây là một giải pháp kém vì nó không có bất kỳ dấu phân cách nào khác, do đó không thể mở rộng và không thể duy trì được. - SmallChess
Trên thực tế, điều này có thể làm việc tốt với các delimiters khác (mặc dù làm một số có phần xấu xí). Bạn tạo một khía cạnh ctype để phân loại các dấu phân tách mong muốn thành khoảng trắng, tạo một miền địa phương chứa phần tử đó, sau đó truyền chuỗi với miền địa phương đó trước khi giải nén chuỗi. - Jerry Coffin
@Kinderchocolate "Chuỗi có thể được giả định là bao gồm các từ được phân tách bằng khoảng trắng" - Hmm, không có vẻ như là một giải pháp tồi cho vấn đề của câu hỏi. "không thể mở rộng và không thể duy trì" - Hah, đẹp quá. - Christian Rau


Tôi sử dụng điều này để tách chuỗi bằng dấu phân tách. Việc đầu tiên đặt kết quả trong một vector được xây dựng trước, thứ hai trả về một vectơ mới.

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

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

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

Lưu ý rằng giải pháp này không bỏ qua các thẻ trống, vì vậy, sau đây sẽ tìm thấy 4 mục, một trong số đó trống:

std::vector<std::string> x = split("one:two::three", ':');

2308



Để tránh bỏ qua các thẻ trống, hãy thực hiện empty() kiểm tra: if (!item.empty()) elems.push_back(item) - 0x499602D2
Làm thế nào về delim chứa hai ký tự như ->? - herohuyongtao
@herohuyongtao, giải pháp này chỉ hoạt động đối với các dấu phân cách đơn char. - Evan Teran
@ JeshwanthKumarNK, nó không cần thiết, nhưng nó cho phép bạn làm những việc như chuyển kết quả trực tiếp đến một hàm như thế này: f(split(s, d, v)) trong khi vẫn có lợi ích của việc phân bổ trước vector nếu bạn thích. - Evan Teran
Caveat: split ("một: hai :: ba", ':') và chia nhỏ ("một: hai :: ba:", ':') trả lại cùng một giá trị. - dshin


Một giải pháp có thể sử dụng Boost có thể là:

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

Cách tiếp cận này thậm chí còn nhanh hơn stringstream tiếp cận. Và vì đây là một hàm mẫu chung, nó có thể được sử dụng để phân tách các loại chuỗi khác (wchar, vv hoặc UTF-8) bằng cách sử dụng tất cả các loại dấu phân tách.

Xem tài liệu để biết chi tiết.


794



Tốc độ không liên quan ở đây, vì cả hai trường hợp này đều chậm hơn nhiều so với chức năng giống như strtok. - Tom
Và đối với những người không có tăng cường ... bcp bản sao hơn 1.000 tập tin cho điều này :) - Roman Starkov
strtok là một cái bẫy. chủ đề của nó không an toàn. - tuxSlayer
Các nhà phát triển Embedded @Ian không phải tất cả đều sử dụng boost. - ACK_stoverflow
như một phụ lục: Tôi sử dụng tăng chỉ khi tôi phải, thông thường tôi muốn thêm vào thư viện mã riêng của mình, là độc lập và di động để tôi có thể đạt được mã cụ thể nhỏ, hoàn thành một mục đích nhất định. Bằng cách đó, mã không công khai, hiệu suất, tầm thường và di động. Boost có vị trí của nó nhưng tôi sẽ đề nghị rằng một chút overkill cho chuỗi tokenising: bạn sẽ không có toàn bộ ngôi nhà của bạn vận chuyển đến một công ty kỹ thuật để có được một móng tay mới rèn vào tường để treo một bức tranh .... họ có thể làm điều đó cực kỳ tốt, nhưng sự thịnh vượng của xa hơn nhiều so với khuyết điểm. - GMasucci


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

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

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

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

    return 0;
}

321



quá tệ nó chỉ chia nhỏ trên không gian ' '... - Offirmo


Đối với những người không ngồi tốt để hy sinh tất cả hiệu quả cho kích thước mã và thấy "hiệu quả" như một loại sang trọng, sau đây sẽ đạt điểm ngọt (và tôi nghĩ rằng lớp chứa mẫu là một bổ sung tuyệt vời.):

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

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

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

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

      lastPos = pos + 1;
   }
}

Tôi thường chọn sử dụng std::vector<std::string> loại như tham số thứ hai của tôi (ContainerT)... nhưng list<> là cách nhanh hơn vector<> khi truy cập trực tiếp là không cần thiết, và bạn thậm chí có thể tạo ra lớp chuỗi của riêng bạn và sử dụng một cái gì đó như std::list<subString> Ở đâu subString không làm bất kỳ bản sao nào để tăng tốc độ đáng kinh ngạc.

Nó nhanh hơn gấp đôi so với tốc độ nhanh nhất trên trang này và nhanh gấp gần 5 lần so với một số trang khác. Ngoài ra với các loại tham số hoàn hảo, bạn có thể loại bỏ tất cả các chuỗi và danh sách các bản sao để tăng tốc độ bổ sung.

Ngoài ra nó không làm việc trả lại kết quả (cực kỳ không hiệu quả), mà đúng hơn là chuyển các thẻ thành một tham chiếu, do đó cũng cho phép bạn xây dựng các mã thông báo bằng nhiều cuộc gọi nếu bạn muốn.

Cuối cùng, nó cho phép bạn chỉ định có nên cắt các thẻ trống từ các kết quả thông qua tham số tùy chọn cuối cùng hay không.

Tất cả những gì nó cần là std::string... phần còn lại là tùy chọn. Nó không sử dụng các luồng hoặc thư viện tăng cường, nhưng đủ linh hoạt để có thể chấp nhận một số kiểu ngoại lai này một cách tự nhiên.


168



Tôi là một fan hâm mộ của điều này, nhưng đối với g + + (và có lẽ thực hành tốt) bất cứ ai sử dụng này sẽ muốn typedefs và typenames: typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType;  Sau đó, để thay thế value_type và size_types cho phù hợp. - aws
Đối với những người trong chúng ta cho những người mẫu các công cụ và bình luận đầu tiên là hoàn toàn nước ngoài, một ví dụ sử dụng cmplete với yêu cầu bao gồm sẽ là đáng yêu. - Wes Miller
Ahh tốt, tôi đã tìm ra. Tôi đặt dòng C ++ từ bình luận của aws bên trong phần thân của tokenize (), sau đó chỉnh sửa các dòng tokens.push_back () để thay đổi ContainerT :: value_type thành ValueType và thay đổi (ContainerT :: value_type :: size_type) thành ( SizeType). Cố định các bit g ++ đã được rên rỉ về. Chỉ cần gọi nó là tokenize (some_string, some_vector); - Wes Miller
Ngoài việc chạy một vài thử nghiệm hiệu suất trên dữ liệu mẫu, chủ yếu tôi đã giảm nó xuống càng ít càng tốt các chỉ dẫn có thể và cũng ít nhất có thể các bản sao bộ nhớ được kích hoạt bằng cách sử dụng một lớp chuỗi con chỉ tham chiếu các offset / độ dài trong các chuỗi khác. (Tôi đã cuộn của riêng mình, nhưng có một số triển khai khác). Thật không may là không có quá nhiều người khác có thể làm để cải thiện về điều này, nhưng tăng gia tăng là có thể. - Marius
Đó là đầu ra chính xác khi trimEmpty = true. Hãy nhớ rằng "abo" không phải là dấu phân cách trong câu trả lời này, mà là danh sách các ký tự dấu tách. Nó sẽ đơn giản để sửa đổi nó để lấy một chuỗi ký tự phân cách duy nhất (tôi nghĩ str.find_first_ofnên thay đổi thành str.find_firstnhưng tôi có thể sai ... không thể kiểm tra) - Marius


Đây là một giải pháp khác. Đó là nhỏ gọn và hợp lý hiệu quả:

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

Nó có thể dễ dàng được templatised để xử lý tách chuỗi, chuỗi rộng, vv

Lưu ý rằng chia tách "" kết quả trong một chuỗi rỗng và tách "," (ví dụ. sep) dẫn đến hai chuỗi rỗng.

Nó cũng có thể dễ dàng mở rộng để bỏ qua các thẻ trống:

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

Nếu bạn muốn tách một chuỗi tại nhiều dấu phân cách trong khi bỏ qua các mã thông báo trống, phiên bản này có thể được sử dụng:

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

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

    return tokens;
}

150



Phiên bản đầu tiên rất đơn giản và hoàn thành công việc một cách hoàn hảo. Thay đổi duy nhất tôi thực hiện sẽ là trả về kết quả trực tiếp, thay vì chuyển nó thành một tham số. - gregschlom
Đầu ra được truyền như một tham số cho hiệu quả. Nếu kết quả được trả về, nó sẽ yêu cầu hoặc là một bản sao của vectơ, hoặc phân bổ đống mà sau đó sẽ phải được giải phóng. - Alec Thomas
Một phụ lục nhỏ để bình luận của tôi ở trên: chức năng này có thể trả lại véc tơ mà không bị phạt nếu sử dụng C ++ 11 di chuyển ngữ nghĩa. - Alec Thomas
@AlecThomas: Ngay cả trước khi C ++ 11, hầu hết các trình biên dịch sẽ không tối ưu hóa bản sao trả lại thông qua NRVO? (+1 anyway; rất ngắn gọn) - Marcelo Cantos
Trong số tất cả các câu trả lời, điều này dường như là một trong những câu trả lời hấp dẫn và linh hoạt nhất. Cùng với đường giới hạn với một dấu phân cách, mặc dù nó là một giải pháp ít rõ ràng hơn. Liệu tiêu chuẩn c ++ 11 không có bất cứ điều gì cho điều này? Có c ++ 11 hỗ trợ thẻ đục lỗ những ngày này? - Spacen Jasset


Đây là cách yêu thích của tôi để lặp qua một chuỗi. Bạn có thể làm bất cứ điều gì bạn muốn cho mỗi từ.

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

istringstream iss(line, istringstream::in);

while( iss >> word )     
{
    // Do something on `word` here...
}

106



Có thể khai báo không word như một char? - abatishchev
Xin lỗi abatishchev, C ++ không phải là điểm mạnh của tôi. Nhưng tôi tưởng tượng nó sẽ không khó để thêm một vòng lặp bên trong để lặp qua mỗi ký tự trong mỗi từ. Nhưng ngay bây giờ tôi tin rằng vòng lặp hiện tại phụ thuộc vào không gian để tách từ. Trừ khi bạn biết rằng chỉ có một nhân vật duy nhất giữa mọi không gian, trong trường hợp đó bạn có thể chỉ cần "từ" vào một char ... xin lỗi tôi không thể được giúp đỡ nhiều hơn, tôi có ý nghĩa để chải lên trên C ++ của tôi - gnomed
nếu bạn khai báo từ như một char nó sẽ lặp lại trên mỗi ký tự không khoảng trắng. Nó đủ đơn giản để thử: stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c; - Wayne Werner


Điều này tương tự với câu hỏi Stack Overflow Làm thế nào để tokenize một chuỗi trong C + +?.

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

using namespace std;
using namespace boost;

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

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

76



Điều này có thực hiện một bản sao của tất cả các mã thông báo hay nó chỉ giữ vị trí bắt đầu và kết thúc của mã thông báo hiện tại? - einpoklum


Tôi thích những điều sau đây vì nó đặt các kết quả vào một vectơ, hỗ trợ một chuỗi như một delim và cho phép kiểm soát việc giữ các giá trị rỗng. Nhưng, nó không có vẻ tốt như vậy.

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

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

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

Tất nhiên, Boost có một split() hoạt động một phần như thế. Và, nếu bằng 'không gian trắng', bạn thực sự có nghĩa là bất kỳ loại khoảng trắng nào, sử dụng tính năng chia tách của Boost với is_any_of() hoạt động tốt.


65



Cuối cùng, một giải pháp xử lý các thẻ trống ở cả hai bên của chuỗi - fmuecke


STL không có sẵn phương thức như vậy.

Tuy nhiên, bạn có thể sử dụng C strtok() chức năng bằng cách sử dụng std::string::c_str() thành viên, hoặc bạn có thể viết của riêng bạn. Đây là mẫu mã tôi tìm thấy sau khi tìm kiếm nhanh trên Google ("Phân tách chuỗi STL"):

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

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

Được lấy từ: http://oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html

Nếu bạn có câu hỏi về mẫu mã, hãy để lại nhận xét và tôi sẽ giải thích.

Và chỉ vì nó không thực hiện typedef được gọi là iterator hoặc quá tải << nhà điều hành không có nghĩa là nó là mã xấu. Tôi sử dụng các hàm C khá thường xuyên. Ví dụ, printf và scanf cả hai đều nhanh hơn std::cin và std::cout (đáng kể), fopen cú pháp là rất thân thiện hơn cho các loại nhị phân, và họ cũng có xu hướng sản xuất EXE nhỏ hơn.

Không được bán trên này "Elegance over performance" thỏa thuận.


48



Tôi nhận thức được các hàm chuỗi C và tôi cũng biết về các vấn đề về hiệu năng (cả hai đều đã được lưu ý trong câu hỏi của tôi). Tuy nhiên, đối với câu hỏi cụ thể này, tôi đang tìm kiếm một giải pháp C ++ thanh lịch. - Ashwin Nanjappa
@Nelson LaQuet: Hãy để tôi đoán: Bởi vì strtok không phải là reentrant? - paercebal
@Nelson không không bao giờ vượt qua string.c_str () để strtok! strtok trashes chuỗi đầu vào (chèn '\ 0' ký tự để thay thế mỗi dấu phân cách foudn) và c_str () trả về một chuỗi không thể sửa đổi được. - Evan Teran
@Nelson: Mảng đó cần phải có kích thước str.size () + 1 trong bình luận cuối cùng của bạn. Nhưng tôi đồng ý với luận án của bạn rằng thật ngớ ngẩn để tránh các hàm C vì lý do "thẩm mỹ". - j_random_hacker
@paulm: Không, sự chậm chạp của dòng C ++ được gây ra bởi các khía cạnh. Chúng vẫn còn chậm hơn so với các chức năng stdio.h ngay cả khi đồng bộ hóa bị vô hiệu hóa (và trên các chuỗi, không thể đồng bộ hóa). - Ben Voigt


Đây là một hàm chia tách:

  • là chung
  • sử dụng tiêu chuẩn C ++ (không tăng)
  • chấp nhận nhiều dấu phân cách
  • bỏ qua các thẻ trống (có thể dễ dàng thay đổi)

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

Sử dụng ví dụ:

    vector<string> v = split<string>("Hello, there; World", ";,");
    vector<wstring> v = split<wstring>(L"Hello, there; World", L";,");

38



Bạn quên thêm vào danh sách sử dụng: "cực kỳ kém hiệu quả" - Xander Tulip
@ XanderTulip, bạn có thể mang tính xây dựng hơn và giải thích như thế nào hay tại sao? - Marco M.
@ XanderTulip: Tôi giả sử bạn đang đề cập đến nó trả về vector theo giá trị. Trả lại giá trị-Tối ưu hóa (RVO, google nó) nên chăm sóc này. Cũng trong C ++ 11 bạn có thể quay trở lại bằng cách di chuyển tham chiếu. - Joseph Garvin
Điều này thực sự có thể được tối ưu hóa hơn nữa: thay vì .push_back (str.substr (...)) người ta có thể sử dụng .emplace_back (str, start, pos - start). Bằng cách này, đối tượng chuỗi được xây dựng trong vùng chứa và do đó chúng ta tránh thao tác di chuyển + các hành vi khác được thực hiện bởi hàm .substr. - Mihai Bişog
@zoopp vâng. Ý tưởng tốt. VS10 không có hỗ trợ emplace_back khi tôi viết bài này. Tôi sẽ cập nhật câu trả lời của mình. Cảm ơn - Marco M.