std::vector to string with custom delimiter

前端 未结 9 1112
独厮守ぢ
独厮守ぢ 2020-12-03 16:42

I would like to copy the contents of a vector to one long string with a custom delimiter. So far, I\'ve tried:

// .h
string getLabe         


        
相关标签:
9条回答
  • 2020-12-03 17:32
    string join(const vector<string> & v, const string & delimiter = ",") {
        string out;
        if (auto i = v.begin(), e = v.end(); i != e) {
            out += *i++;
            for (; i != e; ++i) out.append(delimiter).append(*i);
        }
        return out;
    }
    

    A few points:

    • you don't need an extra conditional to avoid an extra trailing delimiter
    • make sure you don't crash when the vector is empty
    • don't make a bunch of temporaries (e.g. don't do this: x = x + d + y)
    0 讨论(0)
  • 2020-12-03 17:35

    This is an extension to the two answers already provided above as run-time performance seemed to be a theme in the comments. I would have added it as comments, but I do not have that privilege yet.

    I tested 2 implementations for run-time performance using Visual Studio 2015:

    Using stringstream:

    std::stringstream result;
    auto it = vec.begin();
    result << (unsigned short)*it++;
    for (; it != vec.end(); it++) {
        result << delimiter;
        result << (unsigned short)*it;
    }
    return result.str();
    

    Using accumulate:

    std::string result = std::accumulate(std::next(vec.begin()), vec.end(),
        std::to_string(vec[0]),
        [&delimiter](std::string& a, uint8_t b) {
        return a + delimiter+ std::to_string(b);
    });
    return result;
    

    Release build run-time performance was close with a couple subtleties.

    The accumulate implementation was slightly faster (20-50ms, ~10-30% of the overall run-time (~180ms) on 1000 iterations over a 256 element vector). However, the accumulate implementation was only faster when the a parameter to the lambda function was passed by reference. Passing the a parameter by value resulted in a similar run-time difference favoring the stringstream implementation. The accumulate implementation also improved some when the result string was returned directly rather than assigned to a local variable that was immediately returned. YMMV with other C++ compilers.

    The Debug build was 5-10 times slower using accumulate so I think the extra string creation noted in several comments above is resolved by the optimizer.

    I was looking at a specific implementation using a vector of uint8_t values. The full test code follows:

    #include <vector>
    #include <iostream>
    #include <sstream>
    #include <numeric>
    #include <chrono>
    
    using namespace std;
    typedef vector<uint8_t> uint8_vec_t;
    
    string concat_stream(const uint8_vec_t& vec, string& delim = string(" "));
    string concat_accumulate(const uint8_vec_t& vec, string& delim = string(" "));
    
    string concat_stream(const uint8_vec_t& vec, string& delimiter)
    {
        stringstream result;
    
        auto it = vec.begin();
        result << (unsigned short)*it++;
        for (; it != vec.end(); it++) {
            result << delimiter;
            result << (unsigned short)*it;
        }
        return result.str();
    }
    
    string concat_accumulate(const uint8_vec_t& vec, string& delimiter)
    {
        return accumulate(next(vec.begin()), vec.end(),
            to_string(vec[0]),
            [&delimiter](string& a, uint8_t b) {
            return a + delimiter + to_string(b);
        });
    }
    
    int main()
    {
        const int elements(256);
        const int iterations(1000);
    
        uint8_vec_t test(elements);
        iota(test.begin(), test.end(), 0);
    
        int i;
        auto stream_start = chrono::steady_clock::now();
        string join_with_stream;
        for (i = 0; i < iterations; ++i) {
            join_with_stream = concat_stream(test);
        }
        auto stream_end = chrono::steady_clock::now();
    
        auto acc_start = chrono::steady_clock::now();
        string join_with_acc;
        for (i = 0; i < iterations; ++i) {
            join_with_acc = concat_accumulate(test);
        }
        auto acc_end = chrono::steady_clock::now();
    
        cout << "Stream Results:" << endl;
        cout << "    elements: " << elements << endl;
        cout << "    iterations: " << iterations << endl;
        cout << "    runtime: " << chrono::duration<double, milli>(stream_end - stream_start).count() << " ms" << endl;
        cout << "    result: " << join_with_stream << endl;
    
        cout << "Accumulate Results:" << endl;
        cout << "    elements: " << elements << endl;
        cout << "    iterations: " << iterations << endl;
        cout << "    runtime: " << chrono::duration<double, milli>(acc_end - acc_start).count() << " ms" << endl;
        cout << "    result:" << join_with_acc << endl;
    
        return 0;
    }
    
    0 讨论(0)
  • 2020-12-03 17:35

    faster variant:

    vector<string> x = {"1", "2", "3"};
    string res;
    res.reserve(16);
    
    std::accumulate(std::begin(x), std::end(x), 0,
                    [&res](int &, string &s)
                    {
                        if (!res.empty())
                        {
                            res.append(",");
                        }
                        res.append(s);
                        return 0;
                   });
    

    it doesn't create interim strings, but just allocate memory once for the whole string result and appends each elem to the end of &res

    0 讨论(0)
提交回复
热议问题