web-dev-qa-db-ja.com

文字列を文字で分割する

これは非常に簡単な問題であることは知っていますが、自分自身で一度だけ解決したいです

文字を分割区切り文字として使用して、文字列を配列に分割したいだけです。 (C#の有名なように 。スプリット() 関数。もちろん、ブルートフォースアプローチを適用することはできますが、それ以上のものはないでしょうか。

これまでに検索したことがあり、おそらく 最も近い ソリューションアプローチは strtok()、しかし、それは不便であるため(文字列をchar配列に変換するなど)私はそれを使うのは好きではありません。これを実装する簡単な方法はありますか?

注意: 私はこれを強調したかったのは、人々が「どうしてブルートフォースが機能しないのか」と尋ねるからです。私のブルートフォースソリューションは、ループを作成し、 substr() 内部の機能。ただし、 出発点 長さ、日付を分割したいときに失敗します。ユーザーは7/12/2012または07/3/2011として入力する可能性があるため、「/」区切り文字の次の位置を計算する前に長さを実際に知ることができます。

37
Ali

ベクトル、文字列、および文字列ストリームを使用します。少し面倒ですが、それはトリックを行います。

std::stringstream test("this_is_a_test_string");
std::string segment;
std::vector<std::string> seglist;

while(std::getline(test, segment, '_'))
{
   seglist.Push_back(segment);
}

結果は、同じ内容のベクトルになります

std::vector<std::string> seglist{ "this", "is", "a", "test", "string" };
76

RegExが好きな人のための別の方法(C++ 11/boost)。個人的に、私はこの種のデータのRegExの大ファンです。 IMOは、区切り文字を使用して単純に文字列を分割するよりもはるかに強力です。これは、必要に応じて「有効な」データを構成する要素をより賢く選択できるためです。

#include <string>
#include <algorithm>    // copy
#include <iterator>     // back_inserter
#include <regex>        // regex, sregex_token_iterator
#include <vector>

int main()
{
    std::string str = "08/04/2012";
    std::vector<std::string> tokens;
    std::regex re("\\d+");

    //start/end points of tokens in str
    std::sregex_token_iterator
        begin(str.begin(), str.end(), re),
        end;

    std::copy(begin, end, std::back_inserter(tokens));
}
13
Ben Cottrell

Boostにはsplit()を探していますalgorithm/string.hpp

std::string sample = "07/3/2011";
std::vector<string> strs;
boost::split(strs, sample, boost::is_any_of("/"));
10
chrisaycock

別の可能性は、特別なctypeファセットを使用するロケールでストリームを埋め込むことです。ストリームはctypeファセットを使用して、何が「空白」であるかを判別します。これはセパレーターとして扱われます。区切り文字を空白として分類するctypeファセットを使用すると、読み取りは非常に簡単になります。ファセットを実装する1つの方法を次に示します。

struct field_reader: std::ctype<char> {

    field_reader(): std::ctype<char>(get_table()) {}

    static std::ctype_base::mask const* get_table() {
        static std::vector<std::ctype_base::mask> 
            rc(table_size, std::ctype_base::mask());

        // we'll assume dates are either a/b/c or a-b-c:
        rc['/'] = std::ctype_base::space;
        rc['-'] = std::ctype_base::space;
        return &rc[0];
    }
};

imbueを使用してそれを使用し、それを含むロケールを使用するようにストリームに指示し、そのストリームからデータを読み取ります。

std::istringstream in("07/3/2011");
in.imbue(std::locale(std::locale(), new field_reader);

これを配置すると、分割はほとんど簡単になります-いくつかのistream_iteratorsを使用してベクトルを初期化し、文字列(istringstreamに埋め込まれている)からピースを読み取ります。

std::vector<std::string>((std::istream_iterator<std::string>(in),
                          std::istream_iterator<std::string>());

1か所でしか使用しない場合、明らかにこれは過剰になりがちです。ただし、多く使用すると、コードの残りの部分を非常にきれいに保つために長い道のりを行くことができます。

4
Jerry Coffin

boost :: tokenizer をご覧ください

独自のメソッドをロールアップする場合は、 std::string::find() を使用して分割ポイントを決定できます。

2
Rafał Rawicki

私はstringstreamが本質的に嫌いですが、理由はわかりません。今日、私はstd::string任意の文字または文字列によるベクトルへ。この質問は古いことは知っていますが、別の分割方法std::string

このコードは、結果から分割した文字列の部分を完全に省略しますが、それらを含めるように簡単に変更できます。

#include <string>
#include <vector>

void split(std::string str, std::string splitBy, std::vector<std::string>& tokens)
{
    /* Store the original string in the array, so we can loop the rest
     * of the algorithm. */
    tokens.Push_back(str);

    // Store the split index in a 'size_t' (unsigned integer) type.
    size_t splitAt;
    // Store the size of what we're splicing out.
    size_t splitLen = splitBy.size();
    // Create a string for temporarily storing the fragment we're processing.
    std::string frag;
    // Loop infinitely - break is internal.
    while(true)
    {
        /* Store the last string in the vector, which is the only logical
         * candidate for processing. */
        frag = tokens.back();
        /* The index where the split is. */
        splitAt = frag.find(splitBy);
        // If we didn't find a new split point...
        if(splitAt == string::npos)
        {
            // Break the loop and (implicitly) return.
            break;
        }
        /* Put everything from the left side of the split where the string
         * being processed used to be. */
        tokens.back() = frag.substr(0, splitAt);
        /* Push everything from the right side of the split to the next empty
         * index in the vector. */
        tokens.Push_back(frag.substr(splitAt+splitLen, frag.size()-(splitAt+splitLen)));
    }
}

使用するには、次のように呼び出します...

std::string foo = "This is some string I want to split by spaces.";
std::vector<std::string> results;
split(foo, " ", results);

これで、ベクターのすべての結果に自由にアクセスできます。そのように単純です-stringstream、サードパーティのライブラリ、Cへのドロップバックはありません!

2
CodeMouse92

erase()関数はどうですか?分割する文字列内の位置がわかっている場合は、erase()を使用して文字列内のフィールドを「抽出」できます。

std::string date("01/02/2019");
std::string day(date);
std::string month(date);
std::string year(date);

day.erase(2, string::npos); // "01"
month.erase(0, 3).erase(2); // "02"
year.erase(0,6); // "2019"
0
Mubin Icyer

stringを文字配列(_char*_)に変換したくない理由はありますか? .c_str()を呼び出すのはかなり簡単です。ループと.find()関数を使用することもできます。

文字列クラス
string .find()
string .c_str()

0
collinjsimpson