文字列の単語を反復するにはどうすればよいですか?


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
おい...エレガンスは、私の本の中で、「効率、見た目がきれい」を表現するための派手な方法にすぎません。テンプレートに含まれていないという理由だけで、C関数とクイックメソッドを使用して何かを実行することを

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

21
@Eduardo:あまりにも間違っている...あなたはすなわち、別の値をストリーミングしようとし、その値を使用しての間でテストISSに必要string sub; while (iss >> sub) cout << "Substring: " << sub << '\n';
トニー・デルロイを

9
デフォルトでこれを行うためのC ++のさまざまなオプション:cplusplus.com/faq/sequences/strings/split
hB0

14
非常に効率的であるだけでなく、エレガンス以上のものがあります。エレガントな属性には、少ない行数と高い読みやすさが含まれます。IMHOエレガンスは、効率性の代用ではなく、保守性の代用です。
マット

回答:


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 2009

15
@ジョナサン:\ nこの場合の区切り文字ではありません、それはcoutに出力するための区切り文字です。
フイ

772
これは他の区切り文字を使用しないため、適切なソリューションではありません。したがって、スケーラブルでなく、保守もできません。
HelloWorld

37
実際、これ他の区切り文字でも問題なく機能します(ただし、一部の操作はやや醜いですが)。目的の区切り文字を空白として分類するctypeファセットを作成し、そのファセットを含むロケールを作成し、文字列を抽出する前にそのロケールを文字列ストリームに組み込みます。
Jerry Coffin、

53
@Kinderchocolate 「文字列は空白で区切られた単語で構成されていると見なすことができます」 -うーん、質問の問題に対する不十分な解決策のようには聞こえません。「スケーラブルではなく、メインテーブルでもない」 - ああ、いいですね。
Christian Rau

2426

これを使用して、区切り文字で文字列を分割します。1つ目は結果を事前に作成したベクトルに入れ、2つ目は新しいベクトルを返します。

#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つのアイテムが見つかり、そのうちの1つは空です。

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

86
空のトークンをスキップし、それを避けるために、行うempty():チェックif (!item.empty()) elems.push_back(item)
0x499602D2

11
delimには2つの文字が含まれてい->ますか?
herohuyongtao 2013

7
@herohuyongtao、このソリューションは単一の文字区切り文字に対してのみ機能します。
Evan Teran

4
@JeshwanthKumarNK、これは必須ではありませんが、結果を次のような関数に直接渡すなどのことができます。必要に応じて、f(split(s, d, v))事前に割り当てられた利点をvector利用できます。
Evan Teran 2014年

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のような関数よりもはるかに遅いため、ここでは速度は関係ありません。
トム・

45
そして、まだブーストしていない人のために... bcpはこれのために1,000以上のファイルをコピーします:)
Roman Starkov

12
警告、空の文字列( "")を指定すると、このメソッドは ""文字列を含むベクトルを返します。したがって、分割の前に「if(!string_to_split.empty())」を追加します。
Offirmo、2011年

29
@Ian Embedded開発者はすべて、ブーストを使用しているわけではありません。
ACK_stoverflow 2012年

31
補遺として:私は必要なときにだけブーストを使用します。通常、スタンドアロンで移植可能な独自のコードライブラリに追加して、特定の目的を達成する小さな正確な特定のコードを達成できるようにします。このようにして、コードは非公開で、パフォーマンスが高く、ささいな移植性があります。Boostはその場所にありますが、文字列をトークン化するには少々やり過ぎだとお勧めします。家全体をエンジニアリング会社に運んで、新しい釘を壁に打ち込んで写真を掛けてもらえないでしょう。非常に良いですが、短所ははるかに勝っています。
GMasucci 2013年

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
カンマで区切るなどgetlinewhile条件で使用する場合は、他の区切り文字で分割することもできます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>、2番目のパラメーターとして型を使用することを選択します(ContainerT)...が、直接アクセスが必要ない場合list<>よりもはるかvector<>に高速です。独自の文字列クラスを作成し、信じられないほどの速度でコピーを行わないstd::list<subString>場所などを使用することもできます。subString増加します。

これは、このページで最速のトークン化の2倍以上の速さで、他のいくつかの5倍近く高速です。また、完璧なパラメータタイプを使用すると、すべての文字列とリストのコピーを削除して、速度をさらに上げることができます。

さらに、(非常に非効率的な)結果の返却を行わず、参照としてトークンを渡すため、必要に応じて複数の呼び出しを使用してトークンを構築することもできます。

最後に、最後のオプションパラメータを使用して、結果から空のトークンを削除するかどうかを指定できます。

必要なのはstd::string...残りはオプションです。ストリームやブーストライブラリは使用しませんが、これらの外部型の一部を自然に受け入れることができるほど柔軟です。


5
私はこれのかなりのファンですが、g ++(およびおそらく良い習慣)の場合、これを使用する誰もtypedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType; がtypedefsとtypenamesを必要とします: 次に、value_typeとsize_typesを適宜置き換えます。
aws

11
テンプレートと最初のコメントが完全に外部のものである私たちにとって、required includeを使用したcmpleteの使用例は素敵です。
Wes Miller

3
ああ、よくわかりました。awsのコメントのC ++行をtokenize()の関数本体内に置き、tokens.push_back()行を編集してContainerT :: value_typeをValueTypeだけに変更し、(ContainerT :: value_type :: size_type)を( SizeType)。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;
}

文字列セパレータ、ワイド文字列などを処理するために簡単にテンプレート化できます。

分割""すると1つの空の文字列になり、分割","(つまりsep)すると2つの空の文字列になります。

空のトークンをスキップするように簡単に拡張することもできます:

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 2012年

2
出力は、効率のためにパラメーターとして渡されます。結果が返された場合は、ベクターのコピー、または解放する必要のあるヒープ割り当てが必要です。
アレックトーマス

2
上記の私のコメントのわずかな補足:この関数は、C ++ 11移動セマンティクスを使用している場合、ペナルティなしでベクトルを返す可能性があります。
アレックトーマス

7
@AlecThomas:C ++ 11以前でも、ほとんどのコンパイラーはNRVOを介して戻りコピーを最適化しませんか?(とにかく+1、非常に簡潔)
Marcelo Cantos 2013

11
すべての回答のうち、これは最も魅力的で柔軟なものの1つであるようです。デリミタ付きのgetlineと一緒に使用しますが、それほど明確な解決策ではありません。c ++ 11標準にはこれについて何もありませんか?最近C ++ 11はパンチカードをサポートしていますか?
Spacen Jasset 2015

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

wordとして宣言することは可能charですか?
abatishchev 2010年

申し訳ありませんが、C ++は私の強みではありません。しかし、各単語のすべての文字をループする内部ループを追加することは難しくないと思います。しかし、現在のところ、現在のループは単語の分離のためのスペースに依存していると思います。あなたはあなただけのchar型に「言葉」をキャストすることができ、その場合には、すべてのスペースとの間の唯一の文字があることを知っている場合を除き...申し訳ありませんが、私は、より多くの助けになるカント私のC ++をブラッシュアップすることを意味してアイブ
gnomed

11
wordをcharとして宣言すると、空白文字以外のすべての文字が繰り返されます。試すのに十分簡単です:stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c;
ウェインヴェルナー

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()そのように部分的に機能するがあります。そして、もし '空白'なら、あなたは本当にBoostのsplit with withを使うことでどんな種類の空白も意味するでしょう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関数をかなり頻繁に使用しています。たとえば、printfscanfの両方がより速いですstd::cinし、std::cout(大幅に)、fopen構文がより多くの友好バイナリタイプのためである、と彼らはまた、より小さなEXEファイルを生成する傾向があります。

この「パフォーマンスよりも優雅」な取引で売られないでください。


私はC文字列関数を認識しています。また、パフォーマンスの問題も認識しています(どちらも質問で指摘しました)。ただし、この特定の質問については、エレガントなC ++ソリューションを探しています。
アシュウィンナンジャッパ

11
@Nelson LaQuet:推測させてください:strtokはリエントラントではないからですか?
paercebal 2008年

40
@Nelsonは、string.c_str()をstrtokに渡さないください!strtokは入力文字列を破棄し(各foudn区切り文字を置き換えるために '\ 0'文字を挿入します)、c_str()は変更不可能な文字列を返します。
エヴァンテラン

3
@ネルソン:その配列は、最後のコメントでstr.size()+ 1のサイズである必要があります。しかし、「美的」理由でC関数を回避するのはばかげているというあなたの論文には同意します。
j_random_hacker

2
@paulm:いいえ、C ++ストリームの速度低下はファセットが原因です。同期が無効になっている場合(および同期できない文字列ストリーム上)でも、stdio.h関数よりも低速です。
Ben Voigt 2015

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";,");

使用リストに追加するのを忘れました:「非常に非効率的」
Xander Tulip

1
@XanderTulip、あなたはより建設的になり、どのように、またはなぜ説明することができますか?
Marco M.

3
@XanderTulip:値でベクトルを返すことを参照していると思います。Return-Value-Optimization(RVO、google it)がこれを処理します。また、C ++ 11では、移動参照によって戻ることができました。
ジョセフガービン

3
これは実際にはさらに最適化できます。.push_back(str.substr(...))の代わりに.emplace_back(str、start、pos-start)を使用できます。このようにして、文字列オブジェクトはコンテナ内で構築されるため、移動操作+ .substr関数によって実行される他の不正操作を回避できます。
MihaiBişog2012

@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");

それでおしまい!これは、単語を数える方法など、トークナイザーを使用する1つの方法にすぎません。

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

regex引数を使用すると、複数の引数(スペース、コンマなど)をチェックできます。

私は通常、スペースとコンマで分割することだけをチェックするので、このデフォルトの関数も持っています:

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

コンパイラによっては、文字列引数を参照で取得することもできます。


これは私のお気に入りの答えでしたが、std :: regexはGCC 4.8で壊れています。彼らはGCC 4.9でそれを正しく実装したと言いました。私はまだあなたに私の+1を与えています
mchiasson 2014

1
これはマイナーな変更点で私のお気に入りです。あなたが言ったように参照として返されるベクトルと、参照によって渡される引数「str」と「regex」もです。THX。
QuantumKarl 2015年

1
生の文字列は、正規表現パターンを処理する際に非常に便利です。そうすれば、エスケープシーケンスを使用する必要はありません...だけを使用できますR"([\s,]+)"
サム

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

これは、1文字の区切り文字に対してのみ機能します。簡単な変更により、マルチキャラクターで動作します:prev_pos = pos += delimiter.length();
デヴィッド・ドリア

25

ブーストを使用したいが、区切り文字として(以前に提案されたソリューションのほとんどのように単一の文字の代わりに)文字列全体を使用したい場合は、を使用できます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
}

おそらくより良い正規表現アプローチによる同様の応答:ここここ
nobar 2014

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標準ライブラリからのものです。マルチスレッドプログラムで使用するのは安全ではありません。入力文字列を変更します。
Kevin Panko

13
最初の呼び出しからのcharポインターを静的変数に格納するため、後続の呼び出しでNULLが渡されたときに、どのポインターを使用するかが記憶されます。strtok別のスレッドがまだ処理しているときに2番目のスレッドが呼び出されると、このcharポインターが上書きされ、両方のスレッドが誤った結果になります。mkssoftware.com/docs/man3/strtok.3.asp
Kevin Panko

1
strtokは前に述べたように安全ではないとさえCのはstrtok_rで使用するために推奨されます
systemsfault

4
strtok_rは、アクセス可能なコードのセクションにいる場合に使用できます。これは、「ラインノイズ」ではない上記すべての唯一の解決策であり、c ++の正確な誤り
Erik Aronesty

更新されたため、C ++ワンクによるスレッドセーフの理由で異論はありません。
Erik Aronesty、2014年

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を使用して自分自身をロールバックし、ブーストを使用して文字列を分割しました。私が見つけた最良の方法は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
区切り文字を文字ではなく文字列にすることができるので、このソリューションが好きですが、文字列の位置を変更するため、元の文字列のコピーを強制的に作成します。
アレッサンドロテルッツィ

11

文字列とCベースの文字列を分割する簡単な方法が必要だったので、これを作成しました... また、トークンに依存せず、フィールドを区切り文字として使用できます。これは、私が必要としたもう1つのキーです。

そのエレガンスをさらに高めるためにできる改善点があると思いますので、是非どうぞ

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

単一の区切り文字で分割したい場合は、これが最良の答えです。元の質問は空白で分割したかったのですが、これは1つ以上の連続したスペースまたはタブの任意の組み合わせを意味します。あなたが実際に答えている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

分割基準を指定するための最大の柔軟性を提供するので、このタスクにはboost / regexメソッドを使用するのが好きです。

#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本当に関数テンプレートの引数として渡す必要があります。次に、この関数のより一般化されたfromは","";"などの区切り文字で分割でき" "ます。


2
2回転がありました。それはすばらしい。まるで私の英語は「ドイツ語」の大部分を占めていたようです。ただし、修正主義者は2つのマイナーなバグを修正しませんstd::isupperでしたstd::upper。いずれにせよ、それらが明らかであったためと思われます。第二は、typename前に置くString::const_iterator
Andreas Spindler、2015

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

イェップ、ベースのレンジが良く見える-同意する
ポルシェ9II
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.