59245


関数のシグネチャは下記のような感じを想定しています。

[code]

    // 必要な関数
    std::string func(
      const std::vector<unsigned char>& data,
      const std::vector<int>& separate);
    
    // 利用イメージ
    int main()
    {
      const std::vector<unsigned char> data = {0x31,0x32,0x33,0x34,0x35,0x36};
      const set::vector<int> separate = {1,2,2,1};
    
      std::string str = func(data,separate);
      std::cout << str; // 出力 1,23,45,6
      retrun 0;
    }
    
[/code]

前提：  
・バイト列はASCII範囲とします。  
・C++03ですが、C++11で回答いただいてもこちらで書き直します。  
・単純にforやifばかりの複雑度の高い処理でなくalgorithmなどのSTLを利用してください。

for文とif文でネストすれば実装できるのですが、algorithmを利用した実装方法を教えてください。

## 追記

下のような関数を考えてみました。結局algorithmはつかっていません。

下記の点で問題があるかと思います。  
・insertの度に移動の計算が走る  
・indexの加算をoperator++ でなく数値で加算しているので領域外参照する恐れがある  
・774RR 様のご指摘の通りif文で細かな条件チェックをしてしまっているのでそこで複雑度が増加してしまう。

これらをalgorithmを利用してスマートに解決できればとおもい質問させていただきました。

[code]

    std::string StringToCsv(std::string &data, std::vector<int>& seperater)
    {
    
        // 元のデータ長＋カンマの数で領域を確保
        std::string str = data;
        str.reserve(data.size() + seperater.size());
    
        // カンマを挿入する
        typedef std::vector<int>::iterator VecIt;
        VecIt comma_index = seperater.begin();
        const VecIt end = seperater.end();
        std::string::iterator itr = str.begin();
    
        while (comma_index != end)
        {
            itr += *comma_index;
            // はみ出たらその場で終了する。
            if (itr >= str.end()) { break; }
            itr = str.insert(itr, ',');
            ++comma_index;
            ++itr;
        }
    
        // 最後のカンマを消す
        if (*(itr - 1) == ',')
        {
            str.replace(itr - 1, itr, "");
        }
        std::cout << str;
        return str;
    
    }
    
    int main()
    {
        //test_csvParser();
    
        std::vector<int> sep = { 1,2,3,1,1,5}; // 0,1,3,6,7,8
        std::string data = { 0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38 };
    
        StringToCsv(data, sep);
    
    }
    
[/code]


CC BY-SA 4.0
2019-09-24T05:49:27.323
59235
2019-09-26T22:13:00.697
2019-09-25T01:54:56.400
19110
35923
question
c++ stl
バイナリ文字列（std::vector<unsigned char>）と数字配列（std::vector<int>）を引数にとり、CSV形式の文字列で返す関数及びクラスの実装を教えてください。


処理速度・効率やSTLらしさとかは気にせずVisualStudioCommunity2019で書いてみました。  
仕様の曖昧な部分は`//****`でコメントにしています。

csvにする処理は以下からコピーしています。  
[【C++】std::vectorの要素を結合する方法【join 区切り文字】](https://marycore.jp/prog/cpp/vector-
join/)

[code]

    #include <iostream>
    #include <sstream>
    #include <vector>
    #include <Algorithm>
    std::string func(
        const std::vector<unsigned char>& data,
        const std::vector<int>& separate)
    {
        // 文字配列を文字列ストリームに変換
        std::stringstream ss(std::string(data.begin(), data.end()));
        std::vector<std::string> v;     // 作業用の切り出し文字列格納配列
    
        std::for_each(separate.begin(), // algorithm から for_eachを選択
            separate.end(),
            [&ss, &v](const int n) {
                if (n <= 0) return;     //**** separate に 0以下が入っていた時は無視
                std::string se(n, '\0');
                ss.read(&se[0], n);
                if (strlen(se.c_str()) <= 0) return;//**** data長 が 0以下なら追加しない
                v.push_back(se);        //**** separate値より短くても文字があれば追加
            }
        );
        //**** data が余っている場合、残りは無視
    
        //---- いったんここで切って上と下を分けて2つの関数にするのもアリでしょう
        const char* delim = ",";// 区切り記号はカンマ
        std::string sv;         // csv文字列作成作業用変数
    
        if (!v.empty()) {
            sv += v[0];
            for (decltype(v.size()) i = 1, c = v.size(); i < c; ++i) {
                if (delim) sv += delim;
                //**** カンマや " を判定して特別に扱う場合はここに処理を追加
                sv += v[i];
            }
        }
        return sv;
    }
    
[/code]


CC BY-SA 4.0
2019-09-24T15:01:59.877
59245
2019-09-24T15:01:59.877
26370
59235
answer


カラムや値の桁数が不足しているようではCSVとしては不完全であり、であればこの関数はエラーになるべきだとは思います。というわけでエラー処理を省くとこのくらいで記述できると思いますが、複雑度の高い処理でしょうか…？

[code]

    std::string func(const std::vector<unsigned char>& data, const std::vector<int>& separate) {
        assert(std::accumulate(begin(separate), end(separate), 0) <= size(data));
        std::string result;
        auto it = begin(data);
        for (auto len : separate) {
            result += ',';          // 先頭に関わらずカンマを入れる
            while (0 < len--)
                result += static_cast<char>(*it++);
        }
        return result.substr(1);    // 先頭のカンマをスキップする
    }
    
[/code]

Range-based for
loopの代わりに`std::for_each`を使うことぐらいはできそうですが、ループ内容をラムダ式などで記述すると余計に煩雑ですので、あまりおすすめできない気がします。

* * *

C++20にはRangeライブラリが追加されます。その元となっている[Range-v3ライブラリ](https://github.com/ericniebler/range-v3)で表現してみました。まだRange力が足りないのであまりキレイではありませんが、[こんな感じ](https://wandbox.org/permlink/FIrDxjBNWZW3E2xf)に書けるようになります。

[code]

    auto func(const std::vector<unsigned char>& data, const std::vector<int>& separate) {
        assert((size_t)ranges::accumulate(separate, 0) <= size(data));
        auto takeData = [it = begin(data)](auto len) mutable {
            auto const value = ranges::views::counted(it, len);
            it += len;
            return ranges::views::concat(ranges::views::single(','), value);    // 先頭に関わらずカンマを入れる
        };
        return (separate | ranges::views::all >>= takeData)
            | ranges::views::drop(1)                                            // 先頭のカンマをスキップする
            | ranges::to<std::string>();
    }
    
[/code]


CC BY-SA 4.0
2019-09-24T19:31:44.610
59246
2019-09-26T22:13:00.697
2019-09-26T22:13:00.697
4236
4236
59235
answer