문자열의 단어를 어떻게 반복합니까?


2986

문자열의 단어를 반복하려고합니다.

문자열은 공백으로 구분 된 단어로 구성되어 있다고 가정 할 수 있습니다.

C 문자열 함수 나 그런 종류의 문자 조작 / 액세스에는 관심이 없습니다. 또한 답변의 효율성보다 우아함을 우선시하십시오.

내가 지금 가지고있는 가장 좋은 해결책은 다음과 같습니다.

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

더 우아한 방법이 있습니까?


617
Dude ... 우아함은 저의 책에서 "효율적인 것 같습니다"라고 말하는 멋진 방법입니다. 템플릿에 포함되어 있지 않기 때문에 C 함수와 빠른 방법을 사용하여 무엇이든 달성하는 데 주저하지 말고;)

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

21
@Eduardo : 너무 잘못 ... 당신은 즉, 다른 값을 스트리밍을 시도하고 그 값을 사용하여 사이에 테스트 ISS에 필요string sub; while (iss >> sub) cout << "Substring: " << sub << '\n';
토니 델로이을

9
기본적으로이를 수행하기위한 C ++의 다양한 옵션 : cplusplus.com/faq/sequences/strings/split
hB0

14
단순한 효율성 이상의 우아함이 있습니다. 우아한 특성에는 줄 수 및 가독성이 포함됩니다. IMHO Elegance는 효율성을 대변하는 것이 아니라 유지 관리 성을 제공합니다.
Matt

답변:


1369

가치있는 것을 위해 표준 라이브러리 기능에만 의존하여 입력 문자열에서 토큰을 추출하는 또 다른 방법이 있습니다. 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"));
}

추출 된 토큰을 출력 스트림에 복사하는 대신 동일한 일반 copy알고리즘을 사용하여 컨테이너에 삽입 할 수 있습니다 .

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

... 또는 vector직접 작성하십시오 .

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

164
이것에 대한 구분자를 지정할 수 있습니까? 예를 들어 쉼표로 나누는 것과 같이?
l3dx

15
@Jonathan : \ n이 경우 구분 기호가 아니며 cout으로 출력하기위한 분리 자입니다.
huy February

772
이것은 다른 구분 기호를 사용하지 않으므로 확장 가능하지 않고 메인 테이블이 아니기 때문에 좋지 않은 솔루션입니다.
HelloWorld

37
실제로 이것은 다른 구분 기호와 함께 잘 작동 할 수 있습니다 (일부는 약간 추악하지만). 원하는 분리 문자를 공백으로 분류하는 ctype 패싯을 작성하고, 해당 패싯을 포함하는 로케일을 작성한 다음, 문자열을 추출하기 전에 해당 로케일로 문자열 스트림을 삽입하십시오.
Jerry Coffin

53
@Kinderchocolate "문자열은 공백으로 분리 된 단어로 구성되어 있다고 가정 할 수 있습니다" -흠, 질문의 문제에 대한 나쁜 해결책처럼 들리지 않습니다. "확장 가능하지 않고 메인 테이블이 아닙니다" -아, 좋습니다.
Christian Rau

2426

이것을 사용하여 문자열을 구분 기호로 나눕니다. 첫 번째는 결과를 미리 구성된 벡터에 넣고 두 번째는 새 벡터를 반환합니다.

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

template <typename Out>
void split(const std::string &s, char delim, Out result) {
    std::istringstream iss(s);
    std::string item;
    while (std::getline(iss, 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;
}

이 솔루션은 빈 토큰을 건너 뛰지 않으므로 다음은 4 개의 항목을 찾게되며 그 중 하나는 비어 있습니다.

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

86
빈 토큰을 건너 뛰지 않으려면 다음을 empty()확인하십시오.if (!item.empty()) elems.push_back(item)
0x499602D2

11
delim은 두 개의 문자를 포함 ->합니까?
herohuyongtao

7
@ herohuyongtao 에서이 솔루션은 단일 문자 구분 기호에만 사용할 수 있습니다.
Evan Teran

4
@JeshwanthKumarNK는 필요하지 않지만 원하는 경우 f(split(s, d, v))사전 할당 된 이점을 유지하면서 결과를 다음과 같은 함수에 직접 전달하는 등의 작업을 수행 할 수 있습니다 vector.
Evan Teran

8
경고 : split ( "one : two :: three", ':') 및 split ( "one : two :: three :", ':')은 동일한 값을 반환합니다.
dshin

834

Boost를 사용하여 가능한 해결책은 다음과 같습니다.

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

이 방법은 방법보다 훨씬 빠를 수 있습니다 stringstream. 그리고 이것은 일반적인 템플릿 함수이기 때문에 모든 종류의 구분자를 사용하여 다른 유형의 문자열 (wchar 등 또는 UTF-8)을 분할하는 데 사용할 수 있습니다.

자세한 내용은 설명서 를 참조하십시오.


35
두 경우 모두 strtok와 유사한 기능보다 속도가 느리므로 속도는 관련이 없습니다.
Tom

45
그리고 이미이 : 1,000 파일을 통해 부스트 ... BCP 사본이없는 사람들을 위해
로마 Starkov

12
빈 문자열 ( "")이 주어지면이 메서드는 ""문자열이 포함 된 벡터를 반환합니다. 따라서 분할하기 전에 "if (! string_to_split.empty ())"를 추가하십시오.
Offirmo

29
@Ian Embedded 개발자가 모두 부스트를 사용하는 것은 아닙니다.
ACK_stoverflow

31
부록으로 : 부스트를 사용해야 할 때만 부스트를 사용합니다. 일반적으로 독립적이고 이식 가능한 코드 라이브러리에 추가하여 작은 특정 코드를 얻을 수 있으므로 주어진 목표를 달성합니다. 그렇게하면 코드가 공개적이지 않고 성능이 우수하며 사소하고 이식성이 있습니다. Boost는 그 자리를 가지고 있지만 토큰 화 줄에 약간의 과잉을 제안 할 것입니다. 당신은 온 집을 엔지니어링 회사로 옮겨서 벽에 새 손톱을 꽂아 그림을 걸지 않을 것입니다 .... 그들은 그것을 할 수 있습니다 아주 잘하지만 단점은 단점보다 훨씬 뛰어납니다.
GMasucci

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

12
예를 들어 쉼표로 분할 getline하는 while조건 에서 사용하는 경우 다른 구분 기호로 분할 할 수도 있습니다 while(getline(ss, buff, ',')).
Ali

181

코드 크기의 모든 효율성을 희생하고 우아함의 일종으로 "효율적인"것을 보는 것이 좋지 않은 사람들에게는 다음과 같은 장점이 있습니다 (템플릿 컨테이너 클래스는 놀랍도록 우아하다고 생각합니다).

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

나는 일반적으로 std::vector<std::string>유형을 두 번째 매개 변수 ( ContainerT) 로 사용하도록 선택 하지만 직접 액세스가 필요하지 않을 list<>때보 다 훨씬 빠르며 vector<>자체 문자열 클래스를 만들고 놀라운 속도로 복사를 수행하지 않는 std::list<subString>곳 과 같은 것을 사용할 수도 있습니다 subString증가합니다.

이 페이지에서 가장 빠른 토큰 화보다 두 배 이상 빠르며 다른 것보다 거의 5 배 빠릅니다. 또한 완벽한 매개 변수 유형을 사용하면 모든 문자열 및 목록 사본을 제거하여 추가 속도를 높일 수 있습니다.

또한 결과의 (매우 비효율적 인) 반환을 수행하지는 않지만 대신 토큰을 참조로 전달하므로 원하는 경우 여러 호출을 사용하여 토큰을 작성할 수도 있습니다.

마지막으로 마지막 선택적 매개 변수를 통해 결과에서 빈 토큰을 트리밍할지 여부를 지정할 수 있습니다.

필요한 것은 std::string... 나머지는 선택 사항입니다. 스트림이나 부스트 라이브러리를 사용하지 않지만 이러한 외부 유형 중 일부를 자연스럽게 수용 할 수있을 정도로 유연합니다.


5
나는 이것의 팬이지만 g ++ (그리고 아마도 좋은 습관)의 경우 이것을 사용하는 사람은 typedef와 typename을 원할 것입니다 : typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType; 그런 다음 value_type과 size_types를 적절히 대체하십시오.
aws

11
템플릿 내용과 첫 번째 주석이 완전히 외국인 사람들에게는 필수 포함이 포함 된 사용 예가 아름답습니다.
Wes Miller

3
잘 알았습니다. aws '주석의 C ++ 행을 tokenize ()의 함수 본문에 넣은 다음 tokens.push_back () 행을 편집하여 ContainerT :: value_type을 ValueType으로 변경하고 (ContainerT :: value_type :: size_type)을 ( 크기 유형). g ++가 고발 한 비트를 수정했습니다. tokenize (some_string, some_vector);로 호출하십시오.
Wes Miller

2
샘플 데이터에 대해 몇 가지 성능 테스트를 실행하는 것 외에도 주로 다른 문자열의 오프셋 / 길이 만 참조하는 하위 문자열 클래스를 사용하여 가능한 적은 명령으로 줄이고 가능한 적은 메모리 사본으로 줄였습니다. (내 자신을 굴 렸지만 다른 구현이 있습니다). 불행히도이를 개선하기 위해 할 수있는 일이 너무 많지는 않지만 점진적으로 증가 할 수있었습니다.
마리우스

3
그것은 when에 대한 올바른 출력입니다 trimEmpty = true. 점을 명심 "abo"이 답변에 구분하지만 구분 문자 목록이 없습니다. 단일 구분 문자 문자열을 사용하도록 수정하는 것이 간단합니다 (로 변경 str.find_first_of해야 한다고 생각 str.find_first하지만 잘못 될 수 있습니다 ... 테스트 할 수 없음)
Marius

158

다른 해결책이 있습니다. 컴팩트하고 합리적으로 효율적입니다.

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

문자열 구분 기호, 넓은 문자열 등을 쉽게 처리 할 수 ​​있습니다.

분할 ""하면 단일 빈 문자열이 생성되고 분할 ","(즉, sep)은 두 개의 빈 문자열이됩니다.

빈 토큰을 건너 뛰도록 쉽게 확장 할 수도 있습니다.

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

빈 토큰을 건너 뛰면서 여러 구분 기호로 문자열을 분할하려는 경우이 버전을 사용할 수 있습니다.

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

10
첫 번째 버전은 간단하며 작업을 완벽하게 수행합니다. 내가 한 유일한 변경은 결과를 매개 변수로 전달하는 대신 결과를 직접 반환하는 것입니다.
gregschlom

2
출력은 효율성을위한 매개 변수로 전달됩니다. 결과가 반환되면 벡터의 사본 또는 힙 할당이 필요하며이 경우 힙 할당을 해제해야합니다.
Alec Thomas

2
위의 주석에 대한 약간의 부록 : C ++ 11 이동 의미를 사용하는 경우이 함수는 페널티없이 벡터를 반환 할 수 있습니다.
Alec Thomas

7
@AlecThomas : C ++ 11 이전에도 대부분의 컴파일러는 NRVO를 통해 리턴 카피를 최적화하지 않습니까? (어쨌든 +1; 매우 간결)
Marcelo Cantos

11
모든 답변 중 가장 매력적이고 융통성있는 것으로 보입니다. 덜 명확한 해결책이지만 구분 기호가있는 getline과 함께. c ++ 11 표준에는 이것에 대한 내용이 없습니까? 요즘 C ++ 11은 펀치 카드를 지원합니까?
Spacen Jasset

123

이것은 문자열을 반복하는 가장 좋아하는 방법입니다. 단어마다 원하는 것을 할 수 있습니다.

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

istringstream iss(line, istringstream::in);

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

이 선언 가능 wordA와 char?
abatishchev

죄송합니다 abatishchev, C ++은 제 장점이 아닙니다. 그러나 각 단어의 모든 문자를 반복하기 위해 내부 루프를 추가하는 것이 어렵지 않을 것이라고 생각합니다. 그러나 현재 전류 루프는 단어 분리를위한 공간에 달려 있다고 생각합니다. 당신은 단지 문자에 "단어"를 캐스팅 할 수있는 경우에는 모든 공간 사이에 단 하나의 문자가 있다는 것을 알고하지 않으면 ... 미안 내가 살아야 내 C ++에 브러시을 의미하고, 더 도움이 될 캔트
gnomed

11
단어를 문자로 선언하면 공백이 아닌 모든 문자를 반복합니다. 시도하기에 충분히 간단합니다.stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c;
Wayne Werner

79

이것은 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;
    }
}

이것이 모든 토큰의 사본을 구체화합니까, 아니면 현재 토큰의 시작 및 종료 위치 만 유지합니까?
einpoklum

66

결과를 벡터에 넣고 문자열을 delim로 지원하고 빈 값을 유지하는 것을 제어하기 때문에 다음을 좋아합니다. 그러나 그때는 좋지 않습니다.

#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"));
}

물론 Boost는 split()부분적으로 그런 식으로 작동합니다. 그리고 '공백'으로 부스트의 분할을 사용하면 실제로 모든 유형의 공백을 의미합니다 is_any_of().


마지막으로 문자열의 양쪽에서 빈 토큰을 올바르게 처리하는 솔루션
fmuecke

53

STL에는 이러한 방법이 아직 없습니다.

그러나 멤버 strtok()를 사용하여 C의 함수를 사용 std::string::c_str()하거나 직접 작성할 수 있습니다. 빠른 Google 검색 후 찾은 코드 샘플은 다음과 같습니다 ( "STL string split" ).

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

에서 가져온 : http://oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html

코드 샘플에 대해 궁금한 점이 있으면 의견을 남겨 주시면 설명하겠습니다.

그리고 typedef호출 된 반복자를 구현하지 않거나 과부하로 인해 <<연산자가 잘못된 코드임을 의미하지는 않습니다. 나는 C 함수를 아주 자주 사용합니다. 예를 들어, printf그리고 scanf모두가 빨라지보다 std::cinstd::cout(크게)의fopen 구문은 바이너리 형식에 대한 더 많은 친절, 또한 작은 EXE 파일을 생성하는 경향이있다.

"성능 에 대한 우아함" 거래 에서 판매하지 마십시오 .


C 문자열 함수를 알고 있으며 성능 문제도 알고 있습니다 (둘 다 내 질문에 언급했습니다). 그러나이 특정 질문에 대해 우아한 C ++ 솔루션을 찾고 있습니다.
Ashwin Nanjappa

11
@Nelson LaQuet : 추측하자 : strtok이 재진입이 아니기 때문에?
paercebal

40
하지 않는 @Nelson strtok를에 string.c_str ()를 통과! strtok은 입력 문자열을 휴지통에 버리고 (각 foudn 구분자를 대체하기 위해 '\ 0'문자를 삽입) c_str ()은 수정할 수없는 문자열을 반환합니다.
Evan Teran

3
@ 넬슨 : 마지막 배열에서 배열의 크기는 str.size () + 1이어야합니다. 그러나 나는 "미학적"이유로 C 함수를 피하는 것이 어리 석다는 이론에 동의합니다.
j_random_hacker

2
@ paulm : 아니요, C ++ 스트림의 속도 저하는 패싯으로 인해 발생합니다. 동기화가 비활성화되어 있고 동기화 할 수없는 문자열 스트림에서도 stdio.h 기능보다 여전히 느립니다.
벤 Voigt

42

다음은 스플릿 기능입니다.

  • 일반적이다
  • 표준 C ++ 사용 (부스트 없음)
  • 여러 구분 기호를 허용합니다
  • 빈 토큰을 무시합니다 (쉽게 변경할 수 있음)

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

사용법 예 :

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

당신은 사용 목록에 추가하는 것을 잊었다 "매우 비효율적"
젠더 튤립에게

1
@ XanderTulip, 더 건설적이고 어떻게 또는 왜 설명 할 수 있습니까?
Marco M.

3
@ XanderTulip : 값으로 벡터를 반환한다고 가정합니다. 반품 가치 최적화 (RVO, google it)가이를 처리해야합니다. 또한 C ++ 11에서는 이동 참조로 돌아갈 수 있습니다.
Joseph Garvin

3
이것은 실제로 더 최적화 될 수 있습니다 : .push_back (str.substr (...)) 대신 .emplace_back (str, start, pos-start)를 사용할 수 있습니다. 이렇게하면 문자열 객체가 컨테이너에 구성되므로 .substr 함수로 수행되는 이동 작업 + 다른 shenanigan을 피할 수 있습니다.
Mihai Bişog

@zoopp 예. 좋은 생각. VS10은 이것을 쓸 때 emplace_back을 지원하지 않았습니다. 답변을 업데이트하겠습니다. 감사합니다
Marco M.

36

이 문제에 대한 2 줄 해결책이 있습니다.

char sep = ' ';
std::string s="1 This is an example";

for(size_t p=0, q=0; p!=s.npos; p=q)
  std::cout << s.substr(p+(p!=0), (q=s.find(sep, p+1))-p-(p!=0)) << std::endl;

그런 다음 인쇄하는 대신 벡터에 넣을 수 있습니다.


35

또 다른 유연하고 빠른 방법

template<typename Operator>
void tokenize(Operator& op, const char* input, const char* delimiters) {
  const char* s = input;
  const char* e = s;
  while (*e != 0) {
    e = s;
    while (*e != 0 && strchr(delimiters, *e) == 0) ++e;
    if (e - s > 0) {
      op(s, e - s);
    }
    s = e + 1;
  }
}

문자열 벡터와 함께 사용하려면 (편집 : 누군가가 STL 클래스를 상속하지 않기로 지적했기 때문에 ... hrmf;)) :

template<class ContainerType>
class Appender {
public:
  Appender(ContainerType& container) : container_(container) {;}
  void operator() (const char* s, unsigned length) { 
    container_.push_back(std::string(s,length));
  }
private:
  ContainerType& container_;
};

std::vector<std::string> strVector;
Appender v(strVector);
tokenize(v, "A number of words to be tokenized", " \t");

그게 다야! 그리고 그것은 단어를 세는 방법과 같이 토크 나이저를 사용하는 한 가지 방법입니다.

class WordCounter {
public:
  WordCounter() : noOfWords(0) {}
  void operator() (const char*, unsigned) {
    ++noOfWords;
  }
  unsigned noOfWords;
};

WordCounter wc;
tokenize(wc, "A number of words to be counted", " \t"); 
ASSERT( wc.noOfWords == 7 );

상상력에 의해 제한;)



32

표준 정규식 라이브러리 만 사용하는 간단한 솔루션이 있습니다.

#include <regex>
#include <string>
#include <vector>

std::vector<string> Tokenize( const string str, const std::regex regex )
{
    using namespace std;

    std::vector<string> result;

    sregex_token_iterator it( str.begin(), str.end(), regex, -1 );
    sregex_token_iterator reg_end;

    for ( ; it != reg_end; ++it ) {
        if ( !it->str().empty() ) //token could be empty:check
            result.emplace_back( it->str() );
    }

    return result;
}

정규식 인수를 사용하면 여러 인수 (공백, 쉼표 등)를 확인할 수 있습니다.

나는 보통 공백과 쉼표로 나누기를 확인 하므로이 기본 기능도 있습니다.

std::vector<string> TokenizeDefault( const string str )
{
    using namespace std;

    regex re( "[\\s,]+" );

    return Tokenize( str, re );
}

"[\\s,]+"공간 (검사 용 \\s) 및 쉼표 (, ).

참고 분할하려는 경우 wstring대신 string,

  • 모두 std::regex로 변경std::wregex
  • 모두 sregex_token_iterator로 변경wsregex_token_iterator

컴파일러에 따라 문자열 인수를 참조로 사용할 수도 있습니다.


이것은 내가 가장 좋아하는 대답이지만 GCC 4.8에서는 std :: regex가 깨졌습니다. 그들은 GCC 4.9에서 올바르게 구현했다고 말했다. 나는 아직도 당신이 내 +1주고있다
mchiasson

1
이것은 당신이 말한대로 참조로 반환 된 벡터와 참조로 전달 된 인수 "str"및 "regex"도 약간 변경하여 가장 좋아합니다. 고마워.
QuantumKarl

1
원시 문자열은 정규식 패턴을 처리하는 동안 매우 유용합니다. 그렇게하면 이스케이프 시퀀스를 사용할 필요가 없습니다 ... 그냥 사용할 수 있습니다 R"([\s,]+)".
Sam

26

사용 std::stringstream하면서 완벽하게 작동하고 원하는 것을 정확하게 수행하십시오. 그냥하지만 일을하는 다른 방법을 찾고 있다면, 당신은 사용할 수 있습니다 std::find()/ std::find_first_of()std::string::substr().

예를 들면 다음과 같습니다.

#include <iostream>
#include <string>

int main()
{
    std::string s("Somewhere down the road");
    std::string::size_type prev_pos = 0, pos = 0;

    while( (pos = s.find(' ', pos)) != std::string::npos )
    {
        std::string substring( s.substr(prev_pos, pos-prev_pos) );

        std::cout << substring << '\n';

        prev_pos = ++pos;
    }

    std::string substring( s.substr(prev_pos, pos-prev_pos) ); // Last word
    std::cout << substring << '\n';

    return 0;
}

이것은 단일 문자 분리 문자에서만 작동합니다. 간단한 변경으로 여러 문자로 작업 할 수 있습니다.prev_pos = pos += delimiter.length();
David Doria

25

boost를 사용하고 싶지만 이전에 제안 된 대부분의 솔루션에서와 같이 단일 문자 대신 전체 문자열을 구분 기호로 사용하려는 경우을 사용할 수 있습니다 boost_split_iterator.

편리한 템플릿을 포함한 예제 코드 :

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

template<typename _OutputIterator>
inline void split(
    const std::string& str, 
    const std::string& delim, 
    _OutputIterator result)
{
    using namespace boost::algorithm;
    typedef split_iterator<std::string::const_iterator> It;

    for(It iter=make_split_iterator(str, first_finder(delim, is_equal()));
            iter!=It();
            ++iter)
    {
        *(result++) = boost::copy_range<std::string>(*iter);
    }
}

int main(int argc, char* argv[])
{
    using namespace std;

    vector<string> splitted;
    split("HelloFOOworldFOO!", "FOO", back_inserter(splitted));

    // or directly to console, for example
    split("HelloFOOworldFOO!", "FOO", ostream_iterator<string>(cout, "\n"));
    return 0;
}

20

표준 정규식 라이브러리 만 사용하는 정규식 솔루션입니다. (나는 약간 녹슨, 그래서 몇 가지 구문 오류가있을 수 있지만 이것은 적어도 일반적인 아이디어입니다)

#include <regex.h>
#include <string.h>
#include <vector.h>

using namespace std;

vector<string> split(string s){
    regex r ("\\w+"); //regex matches whole words, (greedy, so no fragment words)
    regex_iterator<string::iterator> rit ( s.begin(), s.end(), r );
    regex_iterator<string::iterator> rend; //iterators to iterate thru words
    vector<string> result<regex_iterator>(rit, rend);
    return result;  //iterates through the matches to fill the vector
}

아마도 더 나은 정규식 접근 방식을 사용한 비슷한 반응 : herehere .
nobar

20

라는 함수가 strtok있습니다.

#include<string>
using namespace std;

vector<string> split(char* str,const char* delim)
{
    char* saveptr;
    char* token = strtok_r(str,delim,&saveptr);

    vector<string> result;

    while(token != NULL)
    {
        result.push_back(token);
        token = strtok_r(NULL,delim,&saveptr);
    }
    return result;
}

3
strtokC ++이 아닌 C 표준 라이브러리에서 가져온 것입니다. 멀티 스레드 프로그램에서 사용하는 것은 안전하지 않습니다. 입력 문자열을 수정합니다.
케빈 판코

13
첫 번째 호출의 char 포인터를 정적 변수에 저장하므로 NULL이 전달 될 때 후속 호출에서 어떤 포인터를 사용해야하는지 기억합니다. strtok다른 스레드가 여전히 처리 중일 때 두 번째 스레드가 호출 되면이 문자 포인터를 덮어 쓰고 두 스레드 모두 잘못된 결과를 갖습니다. mkssoftware.com/docs/man3/strtok.3.asp
Kevin Panko

1
strtok이 안전하지 않고 C에서도 strtok_r을 사용하는 것이 좋습니다
systemsfault

4
액세스 가능한 코드 섹션에있는 경우 strtok_r을 사용할 수 있습니다. 이것은 "라인 노이즈"가 아닌 위의 모든 것의 유일한 솔루션이며 c ++에서 정확히 무엇이 잘못되었는지에 대한 증거입니다.
Erik Aronesty

C ++ ks 크의 스레드 안전성에 대한 반대 의견이 없도록 업데이트되었습니다.
Erik Aronesty

17

이제 stringstream은 당신이 공백이 아닌 문자로 문자열을 구문 분석해야하는 경우 편리 할 수 있습니다 :

string s = "Name:JAck; Spouse:Susan; ...";
string dummy, name, spouse;

istringstream iss(s);
getline(iss, dummy, ':');
getline(iss, name, ';');
getline(iss, dummy, ':');
getline(iss, spouse, ';')

14

지금까지 Boost 에서 하나를 사용 했지만 그것에 의존하지 않는 것이 필요했기 때문에 다음과 같이했습니다.

static void Split(std::vector<std::string>& lst, const std::string& input, const std::string& separators, bool remove_empty = true)
{
    std::ostringstream word;
    for (size_t n = 0; n < input.size(); ++n)
    {
        if (std::string::npos == separators.find(input[n]))
            word << input[n];
        else
        {
            if (!word.str().empty() || !remove_empty)
                lst.push_back(word.str());
            word.str("");
        }
    }
    if (!word.str().empty() || !remove_empty)
        lst.push_back(word.str());
}

좋은 점은 separators둘 이상의 문자를 전달할 수 있다는 것입니다.


13

strtok을 사용하여 내 자신을 굴려서 boost를 사용하여 문자열을 나눕니다. 내가 찾은 가장 좋은 방법은 C ++ String Toolkit Library 입니다. 매우 유연하고 빠릅니다.

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

툴킷은이 간단한 예제보다 훨씬 융통성이 있지만 문자열을 유용한 요소로 파싱하는 유틸리티는 놀랍습니다.


13

짧고 우아한

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

vector<string> split(string data, string token)
{
    vector<string> output;
    size_t pos = string::npos; // size_t to avoid improbable overflow
    do
    {
        pos = data.find(token);
        output.push_back(data.substr(0, pos));
        if (string::npos != pos)
            data = data.substr(pos + token.size());
    } while (string::npos != pos);
    return output;
}

모든 문자열을 구분 기호로 사용할 수 있으며 이진 데이터와 함께 사용할 수도 있습니다 (std :: string은 null을 포함한 이진 데이터를 지원함)

사용하여 :

auto a = split("this!!is!!!example!string", "!!");

산출:

this
is
!example!string

1
구분 기호가 문자가 아닌 문자열이 될 수 있기 때문에이 솔루션이 마음에 들지만 문자열을 수정하여 원래 문자열의 사본을 작성해야합니다.
Alessandro Teruzzi

11

문자열과 c 기반 문자열을 쉽게 분리 할 수있는 방법이 필요했기 때문에 이것을 만들었습니다. 다른 사람도 유용하게 사용할 수 있기를 바랍니다. 또한 토큰에 의존하지 않으며 필드를 구분 기호로 사용할 수 있습니다. 이는 필자가 필요한 또 다른 키입니다.

우아함을 더욱 향상시킬 수있는 개선 사항이 있다고 확신합니다.

StringSplitter.hpp :

#include <vector>
#include <iostream>
#include <string.h>

using namespace std;

class StringSplit
{
private:
    void copy_fragment(char*, char*, char*);
    void copy_fragment(char*, char*, char);
    bool match_fragment(char*, char*, int);
    int untilnextdelim(char*, char);
    int untilnextdelim(char*, char*);
    void assimilate(char*, char);
    void assimilate(char*, char*);
    bool string_contains(char*, char*);
    long calc_string_size(char*);
    void copy_string(char*, char*);

public:
    vector<char*> split_cstr(char);
    vector<char*> split_cstr(char*);
    vector<string> split_string(char);
    vector<string> split_string(char*);
    char* String;
    bool do_string;
    bool keep_empty;
    vector<char*> Container;
    vector<string> ContainerS;

    StringSplit(char * in)
    {
        String = in;
    }

    StringSplit(string in)
    {
        size_t len = calc_string_size((char*)in.c_str());
        String = new char[len + 1];
        memset(String, 0, len + 1);
        copy_string(String, (char*)in.c_str());
        do_string = true;
    }

    ~StringSplit()
    {
        for (int i = 0; i < Container.size(); i++)
        {
            if (Container[i] != NULL)
            {
                delete[] Container[i];
            }
        }
        if (do_string)
        {
            delete[] String;
        }
    }
};

StringSplitter.cpp :

#include <string.h>
#include <iostream>
#include <vector>
#include "StringSplit.hpp"

using namespace std;

void StringSplit::assimilate(char*src, char delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }

        }
        else
        {
            delete[] temp;
        }
    }
}

void StringSplit::assimilate(char*src, char* delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }
        }
        else
        {
            delete[] temp;
        }
    }
}

long StringSplit::calc_string_size(char* _in)
{
    long i = 0;
    while (*_in++)
    {
        i++;
    }
    return i;
}

bool StringSplit::string_contains(char* haystack, char* needle)
{
    size_t len = calc_string_size(needle);
    size_t lenh = calc_string_size(haystack);
    while (lenh--)
    {
        if (match_fragment(haystack + lenh, needle, len))
        {
            return true;
        }
    }
    return false;
}

bool StringSplit::match_fragment(char* _src, char* cmp, int len)
{
    while (len--)
    {
        if (*(_src + len) != *(cmp + len))
        {
            return false;
        }
    }
    return true;
}

int StringSplit::untilnextdelim(char* _in, char delim)
{
    size_t len = calc_string_size(_in);
    if (*_in == delim)
    {
        _in += 1;
        return len - 1;
    }

    int c = 0;
    while (*(_in + c) != delim && c < len)
    {
        c++;
    }

    return c;
}

int StringSplit::untilnextdelim(char* _in, char* delim)
{
    int s = calc_string_size(delim);
    int c = 1 + s;

    if (!string_contains(_in, delim))
    {
        return calc_string_size(_in);
    }
    else if (match_fragment(_in, delim, s))
    {
        _in += s;
        return calc_string_size(_in);
    }

    while (!match_fragment(_in + c, delim, s))
    {
        c++;
    }

    return c;
}

void StringSplit::copy_fragment(char* dest, char* src, char delim)
{
    if (*src == delim)
    {
        src++;
    }

    int c = 0;
    while (*(src + c) != delim && *(src + c))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

void StringSplit::copy_string(char* dest, char* src)
{
    int i = 0;
    while (*(src + i))
    {
        *(dest + i) = *(src + i);
        i++;
    }
}

void StringSplit::copy_fragment(char* dest, char* src, char* delim)
{
    size_t len = calc_string_size(delim);
    size_t lens = calc_string_size(src);

    if (match_fragment(src, delim, len))
    {
        src += len;
        lens -= len;
    }

    int c = 0;
    while (!match_fragment(src + c, delim, len) && (c < lens))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

vector<char*> StringSplit::split_cstr(char Delimiter)
{
    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char Delimiter)
{
    do_string = true;

    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

vector<char*> StringSplit::split_cstr(char* Delimiter)
{
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while(*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String,Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char* Delimiter)
{
    do_string = true;
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while (*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

예 :

int main(int argc, char*argv[])
{
    StringSplit ss = "This:CUT:is:CUT:an:CUT:example:CUT:cstring";
    vector<char*> Split = ss.split_cstr(":CUT:");

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

출력합니다 :

이것은
이다 예를 들어, CString을



int main(int argc, char*argv[])
{
    StringSplit ss = "This:is:an:example:cstring";
    vector<char*> Split = ss.split_cstr(':');

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This[SPLIT]is[SPLIT]an[SPLIT]example[SPLIT]string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string("[SPLIT]");

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This|is|an|example|string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string('|');

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

빈 항목을 유지하려면 (기본적으로 비어 있음) 제외됩니다.

StringSplit ss = mystring;
ss.keep_empty = true;
vector<string> Split = ss.split_string(":DELIM:");

목표는 문자열을 쉽게 분할하는 C #의 Split () 메서드와 비슷하게 만드는 것입니다.

String[] Split = 
    "Hey:cut:what's:cut:your:cut:name?".Split(new[]{":cut:"}, StringSplitOptions.None);

foreach(String X in Split)
{
    Console.Write(X);
}

다른 사람이 내가하는 것처럼 유용한 것을 찾을 수 있기를 바랍니다.


10

이건 어때?

#include <string>
#include <vector>

using namespace std;

vector<string> split(string str, const char delim) {
    vector<string> v;
    string tmp;

    for(string::const_iterator i; i = str.begin(); i <= str.end(); ++i) {
        if(*i != delim && i != str.end()) {
            tmp += *i; 
        } else {
            v.push_back(tmp);
            tmp = ""; 
        }   
    }   

    return v;
}

단일 구분 문자로만 분할하려는 경우이 방법이 가장 적합합니다. 원래의 질문은 공백으로 분할되기를 원했는데 이는 하나 이상의 연속 공백 또는 탭의 조합을 의미합니다. 당신은 실제로 대답했다 stackoverflow.com/questions/53849
Oktalist

10

이 대답은 문자열을 가져 와서 문자열 벡터로 만듭니다. 부스트 라이브러리를 사용합니다.

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

9

다른 방법이 있습니다 ..

void split_string(string text,vector<string>& words)
{
  int i=0;
  char ch;
  string word;

  while(ch=text[i++])
  {
    if (isspace(ch))
    {
      if (!word.empty())
      {
        words.push_back(word);
      }
      word = "";
    }
    else
    {
      word += ch;
    }
  }
  if (!word.empty())
  {
    words.push_back(word);
  }
}

9

나는이 기준에 부스트 / 정규식 방법을 사용하고 싶습니다. 분할 기준을 지정할 때 최대한의 유연성을 제공하기 때문입니다.

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

int main() {
    std::string line("A:::line::to:split");
    const boost::regex re(":+"); // one or more colons

    // -1 means find inverse matches aka split
    boost::sregex_token_iterator tokens(line.begin(),line.end(),re,-1);
    boost::sregex_token_iterator end;

    for (; tokens != end; ++tokens)
        std::cout << *tokens << std::endl;
}

9

최근에 나는 낙타로 된 단어를 하위 단어로 나누었습니다. 구분 기호가 없으며 대문자 만 있습니다.

#include <string>
#include <list>
#include <locale> // std::isupper

template<class String>
const std::list<String> split_camel_case_string(const String &s)
{
    std::list<String> R;
    String w;

    for (String::const_iterator i = s.begin(); i < s.end(); ++i) {  {
        if (std::isupper(*i)) {
            if (w.length()) {
                R.push_back(w);
                w.clear();
            }
        }
        w += *i;
    }

    if (w.length())
        R.push_back(w);
    return R;
}

예를 들어 "AQueryTrades"를 "A", "Query"및 "Trades"로 분할합니다. 이 기능은 좁고 넓은 문자열에서 작동합니다. 현재 로캘을 존중하기 때문에 "RaumfahrtÜberwachungsVerordnung"을 "Raumfahrt", "Überwachungs"및 "Verordnung"으로 나눕니다.

참고 std::upper정말 기능 템플릿 인수로 전달해야한다. 다음에 분리가 더욱 나눌 수 등에서이 함수로부터 일반화 ",", ";"또는 " "도.


2
2 개정이있었습니다. 좋습니다. 내 영어가 "독일어"에 많은 것처럼 보입니다. 그러나 개정 주의자는 두 가지 사소한 버그가 수정 되었기 때문에 어쨌든 분명했기 때문에 std::isupper인수가 아닌 인수로 전달 될 수 있습니다 std::upper. 두 번째 typename앞에 String::const_iterator.
Andreas Spindler

9
#include<iostream>
#include<string>
#include<sstream>
#include<vector>
using namespace std;

    vector<string> split(const string &s, char delim) {
        vector<string> elems;
        stringstream ss(s);
        string item;
        while (getline(ss, item, delim)) {
            elems.push_back(item);
        }
        return elems;
    }

int main() {

        vector<string> x = split("thi is an sample test",' ');
        unsigned int i;
        for(i=0;i<x.size();i++)
            cout<<i<<":"<<x[i]<<endl;
        return 0;
}

9

std::string_viewEric Niebler의 range-v3라이브러리 사용 및 :

https://wandbox.org/permlink/kW5lwRCL1pxjp2pW

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"
#include "range/v3/algorithm.hpp"

int main() {
    std::string s = "Somewhere down the range v3 library";
    ranges::for_each(s  
        |   ranges::view::split(' ')
        |   ranges::view::transform([](auto &&sub) {
                return std::string_view(&*sub.begin(), ranges::distance(sub));
            }),
        [](auto s) {std::cout << "Substring: " << s << "\n";}
    );
}

알고리즘 for대신 범위 루프 를 사용하여 ranges::for_each:

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"

int main()
{
    std::string str = "Somewhere down the range v3 library";
    for (auto s : str | ranges::view::split(' ')
                      | ranges::view::transform([](auto&& sub) { return std::string_view(&*sub.begin(), ranges::distance(sub)); }
                      ))
    {
        std::cout << "Substring: " << s << "\n";
    }
}

Yepp, 기반 범위가 더 좋아 보입니다-동의합니다
Porsche9II
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.