1

Let's say I have a generic list of ints. It can be an std::set:

std::set<int> myInts;
myInts.insert(1);
myInts.insert(2);
myInts.insert(3);

I want to convert them to a std::string with commas between values (not after the last value!).

I searched here and there are many different solutions.

One for example is an easy one-liner:

copy(v.begin(), v.end(), ostream_iterator<string>(cout, ","));

This works, but

  1. it puts an extra "," at the end
  2. as I need string as output I'd have to use an intermediate extra stringstream which causes unnecessary performance-overhead.

I could use a basic foreach too, where I can correct the last comma problem, and can also convert those ints to string, and concatenate them to the output string, but it feels not the best (fastest) way of doing this.

Boost is not available, and compiler is CLANG with C++14 support.

What is the best way (performance-wise) to convert that set<int> to CSV string? (I can measure execution time difference between ostream_iterator approach and foreach approach, but the question here if there is anything else which fulfills all my wishes?

Daniel
  • 2,318
  • 2
  • 22
  • 53
  • Personally I would use `copy(v.begin(), v.end() - 1, ostream_iterator(cout, ","));` and than manually add the last element to the output. – NathanOliver Jun 04 '21 at 13:11
  • That's nice, but I need `std::string`. – Daniel Jun 04 '21 at 13:15
  • If you're worried about performance consider replacing `std::set` with something else. For most sizes/operation sequences it's outperformed by different data structures, especially for traversal... As for the "I need string" comment. You should be able to replace `cout` with a `std::ostringstream`... – fabian Jun 04 '21 at 13:15
  • I can replace `set` to anything, but that won't solve my question. Using `ostringstream` might work but then there is an extra copy, no? First when items are copied by `std::copy`, then when I request `std::string` by calling `sstream.str()`. – Daniel Jun 04 '21 at 13:16
  • Use a `stringstream` in place of `cout` so you can get the string out of it. Manually building the string or letting a stringstream do it should have about the same perofmance. – NathanOliver Jun 04 '21 at 13:18
  • @NathanOliver: `stringstream.str()` will do an extra copy: "returns a string object with a copy of the current contents of the stream." from https://www.cplusplus.com/reference/sstream/ostringstream/str/ – Daniel Jun 04 '21 at 13:19
  • 1
    Does this answer your question? [How can I print a list of elements separated by commas?](https://stackoverflow.com/questions/3496982/how-can-i-print-a-list-of-elements-separated-by-commas) – acraig5075 Jun 04 '21 at 13:35
  • 1
    Plenty of options. If this is really something you need to optimize you should probably write your own function for it. There's an example for exactly this with [std::accumulate](https://en.cppreference.com/w/cpp/algorithm/accumulate) on cppreference. – super Jun 04 '21 at 13:48
  • Since performance is such a concern that the cost of copying the final string is too much, pre-calculate how many characters the string will be, set the capacity of the resulting string (including the separators), then use `auto sep=""; for (auto&& e : v) { s+= sep; s += to_string(e); sep = ","; }` – Eljay Jun 04 '21 at 14:48

1 Answers1

1

The easiest method is probably something like:

std::set<int> set = {1, 2, 3, 4};
std::string s = std::to_string(*set.begin());

std::for_each(std::next(set.begin()), set.end(), [&s] (int val) {
    s.append(", ").append(std::to_string(val));
});

If you are concerned about performance you need to preallocate a large enough buffer and use to_chars or sprintf instead. I would not consider this case important for optimization in that way as converting to csv is a rare operation.

std::set<int> set = {1, 2, 3, 4};

//remember to check for errors when using sprintf, I am not.
char buf[1024];
int idx = std::sprintf(buf, "%d", *set.begin());

std::for_each(std::next(set.begin()), set.end(), [&buf, &idx] (int val) {
    idx += std::sprintf(buf + idx, ", %d", val);
});

std::string s(buf);
Mestkon
  • 3,532
  • 7
  • 18
  • For the easy one, how about `std::transform(std::begin(set), std::end(set), std::back_inserter(s), [](int const &i) { return i + '0'; });`? I mean is this `tansform` better or worse than `for_each`? – Daniel Jun 04 '21 at 13:26
  • I don't think that does what you want it to do, that only works if the integers are between 0 and 9, and it doesn't insert ", " between them. – Mestkon Jun 04 '21 at 13:32