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