C ++で文字列をトークン化するにはどうすればよいですか?


414

Javaには便利な分割メソッドがあります。

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

C ++でこれを行う簡単な方法はありますか?


172
このルーチンのタスクがc ++で頭痛の種になるとは信じられません
wfbarksdale

6
C ++では頭痛の種ではありません。それを実現するにはさまざまな方法があります。プログラマーはc#よりもc ++にあまり気づいていません-マーケティングと投資について...同じことを達成するためのさまざまなc ++オプションについては、これを参照してください:cplusplus.com/faq/sequences/strings/split
hB0

9
@ hB0が多くの質問の回答を調べても、手段を決定できないのは頭痛の種です。ライブラリは、他のはただのスペースのために、他にスペースを処理しないされていることを1つのニーズ...
Paschalis


2
なぜC ++のすべてが苦労しなければならないのですか?
Wael Assaf

回答:


145

C ++標準ライブラリアルゴリズムは、具体的には、具体的なコンテナーではなく、イテレーターをベースにしています。残念ながら、これはsplitC ++標準ライブラリでJavaのような関数を提供することを困難にします。しかし、その戻り値の型はどうなるでしょうか?std::vector<std::basic_string<…>>?たぶん、それでも私たちは(潜在的に冗長でコストのかかる)割り当てを実行せざるを得ません。

代わりに、C ++は、任意の複雑な区切り文字に基づいて文字列を分割するための多くの方法を提供しますが、他の言語ほどうまくカプセル化されていません。ブログ投稿全体を埋めるさまざまな方法があります。

最も単純な場合、を押すstd::string::findまでを使用して繰り返しstd::string::npos、を使用してコンテンツを抽出できますstd::string::substr

空白で分割するための複数の流体(および慣用的な、基本的な)バージョンが使用しますstd::istringstream

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

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

std::istream_iteratorsを使用すると、文字列ストリームの内容を、そのイテレーター範囲コンストラクターを使用してベクターにコピーすることもできます。

複数のライブラリー(Boost.Tokenizerなど)は、特定のトークナイザーを提供します。

より高度な分割には正規表現が必要です。C ++は、std::regex_token_iterator特にこの目的のためにを提供します。

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

53
悲しいことに、ブーストはすべてのプロジェクトで常に利用できるわけではありません。私は非ブーストの答えを探す必要があります。
FuzzyBunnySlippers 2013

36
すべてのプロジェクトが「オープンソース」に開かれているわけではありません。規制の厳しい業界で働いています。それは本当に問題ではありません。それは人生の事実です。ブーストはどこでも利用できるわけではありません。
FuzzyBunnySlippers 2013

5
@NonlinearIdeas他の質問/回答は、オープンソースプロジェクトに関するものではありませんでした。同じことがどのプロジェクトに当てはまります。そうは言っても、もちろんMISRA Cなどの制限された標準については理解していますが、とにかくすべてをゼロから構築していることがわかります(準拠ライブラリを見つけることはまれです–希少)。とにかく、「ブーストが利用できない」ということはほとんどありません。つまり、ほとんどすべての汎用的な回答が不適切になるような特別な要件があるということです。
Konrad Rudolph

1
@NonlinearIdeas適例の場合、他のブースト以外の回答もMISRAに準拠していません。
Konrad Rudolph

3
@Dmitry「STL barf」って何?そして、コミュニティ全体がCプリプロセッサの置き換えに非常に賛成です—実際、それを行うための提案があります。しかし、代わりにPHPまたは他の言語を使用するというあなたの提案は、大きな後退となるでしょう。
Konrad Rudolph

188

トークナイザブーストクラスは、この種のものは、非常にシンプルにすることができます。

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

using namespace std;
using namespace boost;

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

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

C ++ 11用に更新:

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

using namespace std;
using namespace boost;

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

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

1
最近、これを利用しました。トークン(テキスト、sep)ビットの前に空白文字を使用して2つの ">"文字を区切るまで、私のVisual Studioコンパイラーは奇妙な問題を抱えています。 > ')
AndyUK

@AndyUKはい、スペースがないと、コンパイラは2つの終了テンプレートではなく抽出演算子として解析します。
EnabrenTane 2011年

理論的には、これはC ++ 0xで修正されています
David Souther

3
char_separatorコンストラクタの3番目のパラメータに注意してください(これdrop_empty_tokensがデフォルトですが、代わりはですkeep_empty_tokens)。
Benoit

5
@puk-C ++ヘッダーファイルで一般的に使用されるサフィックスです。(.hCヘッダーの場合と同様)
Ferruccio

167

これが本当にシンプルなものです:

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

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

    do
    {
        const char *begin = str;

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

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

    return result;
}

このメソッドのプロトタイプを.hファイルに追加する必要がありますか?
Suhrob Samiev

5
これは、プレーンなC定数文字配列である文字列リテラルを使用するため、「最良の」答えとは言えません。質問者は、後者によって導入された「文字列」型のC ++文字列をトークン化できるかどうか尋ねていたと思います。
Vijay Kumar Kanta 2017

C ++ 11に正規表現を含めることで最良の答えが変わると私は強く疑っているので、これには新しい答えが必要です。
方位

113

strtokを使用します。私の意見では、strtokが必要なものを提供しない限り、トークン化に関するクラスを構築する必要はありません。そうではないかもしれませんが、CおよびC ++でさまざまな解析コードを作成してから15年以上の間、私は常にstrtokを使用してきました。ここに例があります

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

いくつかの注意事項(ニーズに合わない場合があります)。文字列はプロセスで「破棄」されます。つまり、EOS文字がデリミタスポットにインラインで配置されます。正しい使用法では、文字列を非constバージョンにする必要があります。解析中に区切り文字のリストを変更することもできます。

私の意見では、上記のコードは、別のクラスを記述するよりもはるかに単純で使いやすいです。私にとって、これは言語が提供する機能の1つであり、それがうまく、きれいに行われます。それは単に「Cベースの」ソリューションです。それは適切で、簡単で、多くの追加コードを書く必要はありません:-)


42
Cが嫌いなわけではありませんが、strtokはスレッドセーフではないため、バッファーオーバーフローの可能性を回避するために、送信する文字列にnull文字が含まれていることを確認する必要があります。
トローチ

11
strtok_rがありますが、これはC ++の質問でした。
ファルケン教授の契約が

3
@tloach:MS C ++コンパイラでは、内部静的変数がTLS(スレッドローカルストレージ)で作成されるため、strtokはスレッドセーフです(実際にはコンパイラに依存します)
Ahmed Said

3
@ahmed:スレッドセーフとは、異なるスレッドで関数を2回実行できることだけではありません。この場合、strtokの実行中にスレッドが変更されると、strtokの実行全体を通じて文字列が有効になる可能性がありますが、文字列が変更されたためstrtokはめちゃくちゃになり、すでにnull文字を超えているため、セキュリティ違反が発生するか、null文字が見つかるまで、メモリを読み取り続けます。問題が発生した場所で長さを指定しない場合、これは元のC文字列関数の問題です。
2010年

4
strtokには、非const nullで終了するchar配列へのポインターが必要です。これは、c ++コードで見つける一般的な生き物ではありません... std :: stringからこれに変換するお気に入りの方法は何ですか?
fuzzyTew 2013

105

別の簡単な方法は、を使用することgetlineです。何かのようなもの:

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

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

必要に応じて、をsplit()返す簡単なメソッドを作成できますvector<string>。これは非常に便利です。


2
文字列に0x0A文字を使用してこの手法を使用すると、whileループが途中で終了してしまう問題がありました。それ以外の場合は、シンプルで迅速な解決策です。
Ryan H.

4
これは良いことですが、これを行うとデフォルトの区切り文字「\ n」は考慮されないことに注意してください。この例では動作しますが、あなたは次のようなものを使用している場合:しばらく(getlineの(INFILE、単語、」「))INFILEを使用すると、funnny結果を取得する複数の行を含むはifstreamオブジェクトです。..
hackrock

getlineは文字列ではなくストリームを返すため、一時的なストレージがないと初期化リストで使用できなくなります
fuzzyTew

1
涼しい!ブーストなしとC ++ 11、これらのレガシープロジェクトに対する優れたソリューション!
2014

1
それが答えです。関数の名前は少し厄介です。
Nils

82

ストリーム、イテレータ、コピーアルゴリズムを使用して、これをかなり直接行うことができます。

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

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

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

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

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

17
私はそれらのstd ::を読むのがイライラするのを見つけます。
user35978 2008年

80
@Vadi:他の誰かの投稿を編集するのはかなり煩わしいからです。@pheze:私stdは、オブジェクトがどこから来たのかをこのように知ることを好みます。それは単にスタイルの問題です。
Matthieu M.

7
私はあなたの理由を理解し、それがあなたのために働くならそれは実際には良い選択だと思いますが、教育学的観点から私は実際にフェーズに同意します。次の行を解釈するのに必要な労力が少なくて済むため、上部に「using namespace std」があるこのような完全に外部の例を読んで理解するのは簡単です。特にこの場合、すべてが標準ライブラリからのものであるためです。一連の「using std :: string;」によって、オブジェクトがどこから来ているのかを簡単に読み取って明確にすることができます。など特に機能が短いので。
チェシャーコウ

61
"std ::"プレフィックスは苛立たしいか醜いですが、これらの関数がどこから来ているかを完全に明確にするために、それらをサンプルコードに含めるのが最善です。彼らがあなたに迷惑をかけるなら、あなたが例を盗んでそれをあなた自身のものとして主張した後、それらを "using"に置き換えることは簡単です。
dlchambers

20
うん!彼が言ったこと!ベストプラクティスは、stdプレフィックスを使用することです。大規模なコードベースには、独自のライブラリと名前空間が含まれることは間違いありません。「名前空間stdを使用」を使用すると、名前空間の競合が発生し始めると頭痛の種になります。
Miek

48

悪気の人々は、しかし、このような単純な問題のために、あなたは物事を作るされていない方法が複雑すぎます。Boostを使用する理由はたくさんあります。しかし、これほど単純なものについては、20#のそりでフライを打つようなものです。

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

    size_t  start = 0, end = 0;

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

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

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

たとえば(ダグの場合)、

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

int
main()
{
    vector<string> v;

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

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

そして、はい、split()でベクトルを渡すのではなく、新しいベクトルを返すようにすることもできます。ラップしてオーバーロードするのは簡単です。しかし、私が何をしているのかによっては、常に新しいオブジェクトを作成するよりも、既存のオブジェクトを再利用する方がよいことがよくあります。(その間にベクターを空にするのを忘れない限り!)

リファレンス:http : //www.cplusplus.com/reference/string/string/

(もともとはDougの質問に対する回答を書いていました:セパレーターに基づくC ++文字列の変更と抽出(終了)。しかし、Martin Yorkはここにポインターを置いて質問を終了したので、コードを一般化します。)


12
1つの場所でのみ使用するマクロを定義する理由。そして、UASSERTは標準のアサートよりも優れています。比較を3つのトークンに分割すると、他の方法で必要になるよりも多くのコンマが必要になるだけです。
crelbor、

1
たぶん、UASSERTマクロは(エラーメッセージで)2つの比較された値(およびその値)の間の実際の関係を示していますか?それは実際にはかなり良い考えです、IMHO。
GhassanPL 2012年

10
なぜ、std::stringクラスにsplit()関数が含まれていないのですか?
シカダンス氏2012

私は、whileループの最後の行を、whileループをにすべきだと思いstart = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());ますwhile (start != string::npos)。また、ベクターに挿入する前に、部分文字列が空でないことを確認します。
John K

@JohnK入力に2つの連続する区切り文字がある場合、それらの間の文字列は明らかに空であり、ベクトルに挿入する必要があります。空の値が特定の目的で受け入れられない場合、それは別のことですが、IMHOのような制約は、この種の非常に汎用的な関数の外部で適用する必要があります。
Lauri Nurmi 2013年

46

regex_token_iterators を使用したソリューション:

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

using namespace std;

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

    regex reg("\\s+");

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

    vector<string> vec(iter, end);

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

5
これがトップランクの答えになるはずです。これは、C ++> = 11でこれを行うには正しい方法です
Omnifarious

1
私がこの答えまで下までスクロールできて良かったです(現在、9つの賛成票しかありませんでした)。これは、まさにこのタスクでC ++ 11コードがどのように見えるかです。
YePhIcK 2018年

外部ライブラリに依存せず、すでに利用可能なライブラリを使用する優れた回答
Andrew

1
デリミタに最も柔軟性を与える素晴らしい答え。いくつかの注意点:\ s + regexを使用すると、テキストの途中で空のトークンが回避されますが、テキストが空白で始まる場合は、最初のトークンが空になります。また、正規表現は遅いようです:私のラップトップでは、20 MBのランダムテキストの場合、strtok、strsep、またはstr.find_first_ofを使用したParhamの応答の0.014秒、またはPerlの0.027秒、またはPythonの0.021秒と比較して、0.6秒かかります。 。短いテキストの場合、速度は問題にならない場合があります。
マークゲイツ

2
見た目はかっこいいかもしれませんが、これは明らかに正規表現の使いすぎです。パフォーマンスを気にしない場合のみ合理的です。
Marek R

35

Boostには強力なスプリット関数があります:boost :: algorithm :: split

サンプルプログラム:

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

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

出力:

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

26

私はあなたがC ++ソリューションを求めたのを知っています、しかしあなたはこれが役に立つと思うかもしれません:

Qt

#include <QString>

...

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

この例のBoostに勝る利点は、投稿のコードへの1対1の直接マッピングであることです。

Qtドキュメントで詳細を確認する


22

これはあなたが望むことをするかもしれないサンプルトークナイザークラスです

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

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

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

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

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

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

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

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

例:

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

19

これはシンプルなSTLのみのソリューション(〜5行!)を使用std::findstd::find_first_not_of、区切り文字の繰り返し(スペースやピリオドなど)と先頭と末尾の区切り文字を処理します。

#include <string>
#include <vector>

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

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

ライブでお試しください!


3
これは良いものですが、複数の区切り文字で正しく機能するには、find()ではなくfind_first_of()を使用する必要があると思います。

2
@ user755921 find_first_not_ofで開始位置を見つけると、複数の区切り文字がスキップされます。
初心者

16

pystringは、splitメソッドを含む、Pythonの文字列関数の束を実装する小さなライブラリです。

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

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

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

3
わあ、あなたは私の差し迫った質問と多くの将来の質問に答えました c ++は強力だと思います。しかし、文字列を分割すると、上記のようなソースコードが生成されるので、明らかに落胆します。より高いレベルの言語の利便性を低下させるこのような他のライブラリについて知りたいです。
ロス

うわー、あなたは真剣に私の日を作りました!pystringについては知りませんでした。これは多くの時間を節約してくれます!
2015

11

同様の質問にこの回答を投稿しました。
車輪を再発明しないでください。私はいくつかのライブラリを使用してきましたが、出会った中で最も速くて最も柔軟なのは C ++ String Toolkit Libraryです。

これは、stackoverflowの他の場所に投稿した使用方法の例です。

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

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

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

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

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

    {  // parsing a string into specific variables

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

    return 0;
}

8

この例を確認してください。それはあなたを助けるかもしれません。

#include <iostream>
#include <sstream>

using namespace std;

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

1
私はそうするだろうwhile ( is >> tmps ) { std::cout << tmps << "\n"; }
-jordix

6

MFC / ATLには非常に優れたトークナイザーがあります。MSDNから:

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

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

Output

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

1
このTokenize()関数は空のトークンをスキップします。たとえば、メイン文字列に部分文字列 "%%"がある場合、空のトークンは返されません。スキップされます。
シーン

4

Cを使用する場合は、strtok関数を使用できます。使用する場合は、マルチスレッドの問題に注意する必要があります。


3
strtokはチェックしている文字列を変更するため、コピーを作成しないとconst char *文字列で使用できないことに注意してください。
Graeme Perrow

9
マルチスレッドの問題は、strtokがグローバル変数を使用してそれがどこにあるかを追跡することです。そのため、それぞれがstrtokを使用する2つのスレッドがある場合、未定義の動作が発生します。
JohnMcG 2008

@JohnMcGまたは、strtok_s基本的strtokに明示的な状態の受け渡しを伴う使用のみ。
Matthias

4

単純なものについては、私は以下を使用します:

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

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

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

    return number_of_tokens;
}

臆病な免責事項:私は、バイナリファイル、ソケット、またはいくつかのAPI呼び出し(I / Oカード、カメラ)を介してデータを受信するリアルタイムデータ処理ソフトウェアを作成しています。起動時に外部構成ファイルを読み取るよりも複雑な、またはタイムクリティカルなものにこの関数を使用することはありません。


4

単純に正規表現ライブラリを使用できますをを使用してそれを解決ます。

式(\ w +)と\ 1(または正規表現のライブラリ実装に応じて$ 1)の変数を使用します。


正規表現を提案するための+1。ワープ速度が必要ない場合は、最も柔軟なソリューションであり、まだどこでもサポートされていませんが、時間が経つにつれて重要性は低くなります。
odinthenerd 2014

私からの+ 1、c ++ 11で<regex>を試しました。とてもシンプルでエレガント
StahlRat 2014年

4

ここには過度に複雑な提案がたくさんあります。この単純なstd :: stringソリューションを試してください:

using namespace std;

string someText = ...

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

4

それが>>文字列ストリームの演算子の目的であると私は思いました:

string word; sin >> word;

1
悪い(単純すぎる)例を挙げたことに対する私の責任。私の知る限り、区切り文字が空白である場合にのみ機能します。
トカゲに請求する

4

Adam Pierceの回答は、手で紡ぐトークナイザーを提供していconst char*ます。の終了反復子のインクリメントstringは定義されていないため、反復子を使用するのは少し問題があります。とstring str{ "The quick brown fox" }はいえ、これを確実に達成できるとすれば、

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

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

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

Live Example


On Freundが示唆 しているように、標準機能を使用して複雑さを抽象化したい場合strtokは、簡単なオプションです。

vector<string> tokens;

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

C ++ 17にアクセスできない場合はdata(str)、次の例のように置き換える必要があります。。http //ideone.com/8kAGoa

例では示していませんが、strtok各トークンに同じ区切り文字を使用する必要はありません。ただし、この利点に加えて、いくつかの欠点があります。

  1. strtokstrings同時に複数で使用することはできません。nullptr現在のトークン化を続行するにはaを渡す必要があります。stringまたは、char*tokenizeに新しいものを渡す必要があります(ただし、これをサポートする非標準の実装には次のようなものがあります:strtok_s)。
  2. 同じ理由でstrtok、複数のスレッドで同時に使用することはできません(ただし、これは実装によって定義される場合があります。たとえば、Visual Studioの実装はスレッドセーフです。)。
  3. 呼び出しstrtok修正をstring上で使用することはできませんので、それは上で動作している、const stringS、const char*とこれらのいずれかをトークン化するために、S、またはリテラル文字列strtokまたは上で動作するstringコンテンツが保存される必要があります誰が、str可能性がコピーされなければならないだろう、そのコピー手術する

split_view非破壊的な方法で文字列をトークン化するために私たちに提供します:https : //topanswers.xyz/cplusplus?q =749#a874


以前のメソッドはトークン化さvectorれたインプレースを生成できません。つまり、初期化できないヘルパー関数にそれらを抽象化する必要がありますconst vector<string> tokens。この機能任意の空白区切り文字を受け入れる機能はを使用して利用できますistream_iterator。たとえば、次のconst string str{ "The quick \tbrown \nfox" }ことが可能です。

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

Live Example

istringstreamこのオプションに必要な構築には、前の2つのオプションよりもはるかに大きなコストがかかりますが、このコストは通常​​、string割り当ての費用に隠されています。


上記のオプションのいずれもトークン化のニーズに十分に柔軟ではない場合、最も柔軟なオプションはregex_token_iteratorもちろん、この柔軟性を備えたを使用するstringことです。たとえば、エスケープされていないカンマに基づいてトークン化したいとします。次の入力が与えられた場合、空白も食べます。const string str{ "The ,qu\\,ick ,\tbrown, fox" }これを行うことができます:

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

Live Example


strtok_sちなみにC11規格です。strtok_rPOSIX2001標準です。これらの両方の間にstrtok、ほとんどのプラットフォーム用の標準の再入可能バージョンがあります。
Andon M. Coleman

@ AndonM.Colemanしかし、これはc ++の質問であり、C ++では#include <cstring>c99バージョンのみが含まれstrtokます。したがって、私の仮定は、このコメントをサポート資料として提供するだけであり、strtok拡張機能の実装固有の可用性を実証しているということですか?
Jonathan Mee

1
単にそれが他の人が信じているかもしれないほど標準的ではないということだけです。strtok_sC11とMicrosoftのCランタイムのスタンドアロン拡張の両方で提供されます。ここには、Microsoftの_s関数がC標準になったという興味深い奇妙な歴史があります。
Andon M. Coleman

@ AndonM.Colemanそうです、私はあなたと一緒です。明らかに、それがC11標準にある場合、インターフェースと実装には制約が課されており、プラットフォームに依存しない同一の動作が必要です。ここでの唯一の問題は、C11関数がすべてのプラットフォームで使用できるようにすることです。うまくいけば、C11標準は、C ++ 17またはC ++ 20がピックアップすることを選択するものになるでしょう。
ジョナサンミー

3

この質問はすでに回答済みですが、貢献したいと思います。多分私の解決策は少しシンプルですが、これは私が思いついたものです:

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

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

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

    result.push_back(tmp);

    return result;
}

私のコードの何かにより良いアプローチがあるか、何かが間違っている場合はコメントしてください。

更新:汎用セパレーターを追加


群集からあなたのソリューションを使用しました:)コードを変更してセパレータを追加できますか?
Zac、

1
@Zacあなたはそれを気に入って、そしてあなたはそれを変更することができて嬉しいです...私の答えに太字の更新セクションを追加してください...
NutCracker

2

空のトークンを含める(strsepなど)か、除外する(strtokなど)かを制御できる方法を次に示します。

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

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

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

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

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

  return tokens;
}

2

ここで私たち全員がスピードを意識したオタクで、区切り文字用のコンパイル時生成ルックアップテーブルを使用するバージョンを提示した人はいません(実装例はさらに下)。ルックアップテーブルとイテレータを使用すると、効率的にstd :: regexに勝るはずです。正規表現に勝る必要がない場合は、それを使用してください。C++ 11の標準であり、非常に柔軟です。

一部の人はすでに正規表現を提案していますが、ここでの初心者のために、OPが期待することを正確に実行するパッケージ化された例を示します。

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

すべての文字が8ビットでなければならないという制約を受け入れる必要がある場合は、コンパイル時にメタプログラミングを使用してルックアップテーブルを作成できます。

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

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

それができれば、getNextToken関数の作成は簡単です。

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

使い方も簡単です:

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

これが実際の例です:http : //ideone.com/GKtkLQ


1
文字列区切り文字でトークン化することは可能ですか?
Galigator 14

このバージョンは、単一文字の区切り文字に対してのみ最適化されています。ルックアップテーブルの使用は、複数文字(文字列)の区切り文字には適していないため、効率的に正規表現に勝るものはありません。
odinthenerd 2014

1

boost :: make_find_iteratorを利用できます。これに似たもの:

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

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

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

    return Result;
}

1

これは、スペースで文字列を分割し、単一引用符と二重引用符で囲まれた文字列を考慮し、結果からこれらの文字を取り除くための文字列トークン化ツールのSwiss®Army Knifeです。RegexBuddy 4.xを使用してほとんどのコードスニペットを生成しましたが、引用符を取り除くためのカスタム処理やその他のいくつかを追加しました。

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

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

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

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

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

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

1
(下)投票は、賛成投票と同じくらい建設的ですが、理由についてコメントを残さない場合はそうではありません...
kayleeFrye_onDeck

1
私はあなたを平準化しましたが、特にドキュメントなしで「文字列を分割する方法」を
ググる

@mattshuに感謝!それを困難にするのは正規表現セグメントですか、それとも何か他のものですか?
kayleeFrye_onDeck

0

トークン化する入力文字列の最大長がわかっている場合は、これを利用して非常に高速なバージョンを実装できます。以下の基本的なアイデアをスケッチしています。これは、strtok()と、Jon Bentleyの「Programming Perls」第2版の第15章で説明されている「suffix array」データ構造の両方に触発されたものです。使用の。示された実装は、トークンの先頭と末尾の空白文字を削除するために簡単に拡張できます。

基本的に、区切り文字を文字列終端の「\ 0」文字に置き換え、変更された文字列を含むトークンへのポインタを設定できます。文字列がセパレータのみで構成される極端なケースでは、文字列長に1を加えた結果の空のトークンを取得します。変更する文字列を複製するのが実際的です。

ヘッダーファイル:

class TextLineSplitter
{
public:

    TextLineSplitter( const size_t max_line_len );

    ~TextLineSplitter();

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

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

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

private:
    const size_t    mStorageSize;

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

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

実装ファイル:

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

    ResetContent();
}

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


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

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

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

    do
    {
        assert( idx < mStorageSize );

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

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

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

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

使用シナリオは次のとおりです。

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

出力:

Item1

Item2
Item3

0

boost::tokenizerあなたの友達ですが、国際化(i18n)の問題を参照して、レガシー/ タイプの代わりにwstring/ を使用してコードを移植可能にすることを検討してください。wchar_tstringchar

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

using namespace std;
using namespace boost;

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

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

「レガシー」は間違いなく正しくなくwchar_t、絶対に必要でない限り誰も使用してはならない恐ろしい実装依存型です。
CoffeeandCode 2014年

wchar_tを使用しても、何らかの形でi18nの問題が自動的に解決されるわけではありません。エンコーディングを使用してその問題を解決します。文字列を区切り文字で分割している場合、区切り文字が文字列内のトークンのエンコードされたコンテンツと衝突しないことを意味します。エスケープが必要な場合などがあります。wchar_tはこれに対する魔法の解決策ではありません。
yonil

0

単純なC ++コード(標準C ++ 98)、複数の区切り文字(std :: stringで指定)を受け入れ、ベクトル、文字列、および反復子のみを使用します。

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

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

int main() {
    std::string test_string = ".this is.a.../.simple;;test;;;END";
    std::string delim = "; ./"; // string containing the delimiters
    std::vector<std::string> tokens = split(test_string, delim);           
    for (std::vector<std::string>::const_iterator it = tokens.begin(); 
        it != tokens.end(); it++)
            std::cout << *it << std::endl;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.