34

Possible Duplicates:
Don't print space after last number
Printing lists with commas C++

#include <vector>
#include <iostream>
#include <sstream>
#include <boost/foreach.hpp>
using namespace std;

int main()
{
   vector<int> VecInts;

   VecInts.push_back(1);
   VecInts.push_back(2);
   VecInts.push_back(3);
   VecInts.push_back(4);
   VecInts.push_back(5);

   stringstream ss;
   BOOST_FOREACH(int i, VecInts)
   {
      ss << i << ",";
   }

   cout << ss.str();

   return 0;
}

This prints out: 1,2,3,4,5, However I want: 1,2,3,4,5

How can I achieve that in an elegant way?

I see there is some confusion about what I mean with "elegant": E.g. no slowing down "if-clause" in my loop. Imagine 100.000 entries in the vector! If that is all you have to offer, I'd rather remove the last comma after I have gone through the loop.

Community
  • 1
  • 1
AudioDroid
  • 2,292
  • 2
  • 20
  • 31
  • I've always wondered about this as well. – Viktor Sehr Jul 14 '11 at 12:11
  • @Matthieu that's not a dupe, I think. When using `BOOST_FOREACH` there is no obvious way for detecting the start or end (you can't simply check `index == 0` or `it == c.begin()` or something). So I wouldn't close this one as a dupe. – Johannes Schaub - litb Jul 14 '11 at 12:22
  • @Johannes: I find the answer to be "BOOST_FOREACH" agnostic. The simplest way seems to be keeping an alternative variable on the side whatever the iteration method and... turn the problem on its head and detect the *first* iteration instead of the last. – Matthieu M. Jul 14 '11 at 12:26
  • You could try our [pretty printer](http://louisdx.github.com/cxx-prettyprint/) for all containers :-) – Kerrek SB Jul 14 '11 at 12:27
  • @Matt but that would be the wrong way for a `for(int i = ..; i < ...; i++)` loop, and the wrong way for a `for(iterator i = ..` loop too. Both loops can just compare `i` to `..`, instead of introducing an alternative variable. The difficulty in this question is that there is no iterator and no index variable to use. Voted for reopen. These questions are related, but I don't think they are "exact duplicates" – Johannes Schaub - litb Jul 14 '11 at 16:48

10 Answers10

34

How about this:

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <string>
#include <sstream>

int main()
{
   std::vector<int> v;

   v.push_back(1);
   v.push_back(2);
   v.push_back(3);
   v.push_back(4);
   v.push_back(5);

   std::ostringstream ss;
   if(!v.empty()) {
      std::copy(v.begin(), std::prev(v.end()), std::ostream_iterator<int>(ss, ", "));
      ss << v.back();
   }
   std::cout << ss.str() << "\n";
}

No need to add extra variables and doesn't even depend on boost! Actually, in addition to the "no additional variable in the loop" requirement, one could say that there is not even a loop :)

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
Juho
  • 976
  • 1
  • 13
  • 27
  • 1
    This is the best answer I have seen so far, and I have not seen it in any of the suggested duplicates! – AudioDroid Jul 14 '11 at 12:54
  • 1
    I added a `if(v.empty()) return;` line, changed the whole thing into a template function, wrote some tests, and I'm really happy with it. It will go straight to my `StrUtils.hpp` header. ;-) If I don't see any unexpected objections coming up, this will soon be marked as the correct answer. – AudioDroid Jul 14 '11 at 13:32
16

Detecting the one before last is always tricky, detecting the first is very easy.

bool first = true;
stringstream ss;
BOOST_FOREACH(int i, VecInts)
{
  if (!first) { ss << ","; }
  first = false;
  ss << i;
}
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
10

Using Karma from Boost Spirit - has a reputation for being fast.

#include <iostream>
#include <vector>
#include <boost/spirit/include/karma.hpp>

int main()
{
  std::vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);

  using namespace boost::spirit::karma;
  std::cout << format(int_ % ',', v) << std::endl;
}
Brian O'Kennedy
  • 1,721
  • 1
  • 13
  • 18
  • In fact it has reputation of actually being faster than iostreams (which are quite slow in some standard library implementations). – Jan Hudec Jul 14 '11 at 13:23
  • This is also a very nice solution. But I had to choose, and I feel for the "pure stl" version. Shame on me. ;-) – AudioDroid Jul 14 '11 at 13:48
  • I don't blame you - Karma is a bit heavyweight if all you want to do is print some ints! – Brian O'Kennedy Jul 14 '11 at 13:50
  • I benchmarked it, comparing it to the "pure stl" version using 10000 `int`'s. windows, gcc, release version: PureStl: 126, Boost:2658. (I'm not sure if I made a reliable test though.) – AudioDroid Jul 14 '11 at 13:58
  • 2
    @AudioDroid: did you make sure to compile with optimizations on ? Karma relies a lot on heavy template machinery, so it relies a lot on optimization to get decent speed. – Matthieu M. Jul 14 '11 at 18:11
  • For reference: As much as I admire Boost.Spirit, a simple addition like this added 5 seconds to the compilation time (from 0.4s to 4.5s with gcc and from 0.6 to 5.6 with clang). – alfC Feb 13 '18 at 20:20
8

Try:

if (ss.tellp ())
{
   ss << ",";
}
ss << i;

Alternatively, if the "if" is making you worried:

char *comma = "";
BOOST_FOREACH(int i, VecInts)
{
   ss << comma << i;
   comma = ",";
}
Skizz
  • 69,698
  • 10
  • 71
  • 108
  • If you made that correct C++, you'd have seen it's nowhere near elegant. – Jan Hudec Jul 14 '11 at 12:11
  • @Jan: OK, it's now valid C++ rather than simple pseudo-code. – Skizz Jul 14 '11 at 12:16
  • @PoweRoy: It's not deleting anything -- it's adding before and not adding the initial one. – Jan Hudec Jul 14 '11 at 12:19
  • Wow. I really like the second idea. Brilliant. :-D. Hm, than again I assign comma every time. :-/ – AudioDroid Jul 14 '11 at 12:27
  • @AudioDroid: The assignment should just be a single 'mov' instruction when processed by the optimiser. – Skizz Jul 14 '11 at 12:32
  • @AudioDroid: Don't worry about optimization say 97% of the time... You worry about assigning a `char const*` when right next to it you're performing two function calls in a row... you'll not notice in the noise. – Matthieu M. Jul 14 '11 at 13:05
  • @Jan, indeed it is just adding it. Well I gues i missed it :P – RvdK Jul 14 '11 at 13:08
  • This second solution here is one of the few general purpose solutions in this entire thread. The rest break down if you deviate even slightly from "print a vector of number to a string stream". I really like it. I suppose Matthieu M.'s solution is also general purpose (maybe even more general), but this one is one line shorter and in my opinion twice as pretty. – Mark VY Feb 16 '17 at 07:29
5

Personally, I like a solution that does not cause potential memory allocations (because the string grows larger than needed). An extra-if within the loop body should be tractable thanks to branch target buffering, but I would do so:

#include <vector>
#include <iostream>

int main () {
    using std::cout;
    typedef std::vector<int>::iterator iterator;

    std::vector<int> ints;    
    ints.push_back(5);
    ints.push_back(1);
    ints.push_back(4);
    ints.push_back(2);
    ints.push_back(3);


    if (!ints.empty()) {
        iterator        it = ints.begin();
        const iterator end = ints.end();

        cout << *it;
        for (++it; it!=end; ++it) {
            cout << ", " << *it;
        }
        cout << std::endl;
    }
}

Alternatively, BYORA (bring your own re-usable algorithm):

// Follow the signature of std::getline. Allows us to stay completely
// type agnostic.
template <typename Stream, typename Iter, typename Infix>
inline Stream& infix (Stream &os, Iter from, Iter to, Infix infix_) {
    if (from == to) return os;
    os << *from;
    for (++from; from!=to; ++from) {
        os << infix_ << *from;
    }
    return os;
}

template <typename Stream, typename Iter>
inline Stream& comma_seperated (Stream &os, Iter from, Iter to) {
    return infix (os, from, to, ", ");
}

so that

...
comma_seperated(cout, ints.begin(), ints.end()) << std::endl;

infix(cout, ints.begin(), ints.end(), "-") << std::endl;
infix(cout, ints.begin(), ints.end(), "> <") << std::endl;
...

output:

5, 1, 4, 2, 3
5-1-4-2-3
5> <1> <4> <2> <3

The neat thing is it works for every output stream, any container that has forward iterators, with any infix, and with any infix type (interesting e.g. when you use wide strings).

Sebastian Mach
  • 38,570
  • 8
  • 95
  • 130
  • The infix method lacks initial check for `from == to` (I was not the one to downvote and don't think such trivial omission is reason to downvote). – Jan Hudec Jul 14 '11 at 13:05
  • You're right. Will fix this :) – Sebastian Mach Jul 14 '11 at 13:09
  • @phresnel: Actually I think it would even work with an `InputIterator`, like `std::cin` for example. The tricky part with those being not eating up a supplementary argument :/ – Matthieu M. Jul 14 '11 at 18:19
3

I like moving the test outside the loop.
It only needs to be done once. So do it first.

Like this:

if (!VecInts.empty())
{
    ss << VecInts[0]

    for(any loop = ++(VecInts.begin()); loop != VecInts.end(); ++loop)
    {
        ss << "," << *loop;
    }
}
Martin York
  • 257,169
  • 86
  • 333
  • 562
1

You can either trim the string at the end, or using single for loop instead of foreach and dont concatenate at the last iteration

Ahmed
  • 7,148
  • 12
  • 57
  • 96
  • 1
    going back is not always possible (think printing to logs). Stopping before the end means you can detect the end (not possible on forward iterators ranges). – Matthieu M. Jul 14 '11 at 12:18
1

Well, if you format into a stringstream anyway, you can just trim the resulting string by one character:

cout << ss.str().substr(0, ss.str().size() - 1);

If the string is empty, than the second argument says -1, which means everything and does not crash and if the string is non-empty, it always ends with a comma.

But if you write to an output stream directly, I never found anything better than the first flag.

That is unless you want to use join from boost.string algo.

Jan Hudec
  • 73,652
  • 13
  • 125
  • 172
  • `boost::join(...)` sounds very promising. Would you mind providing an example with the given context? – AudioDroid Jul 14 '11 at 12:39
  • @AudioDroid: `boost::join(boost::make_transform_iterator(v, boost::lexical_cast), ",")` should do the trick (using more bits from boost), but I am not too sure this is sufficient to use the lexical cast. – Jan Hudec Jul 14 '11 at 12:58
  • (at)Jan Hudec: Unfortunately my compiler doesn't like code. Maybe I would need the compiled version of boost. I only have the features that work without compiling the library. :-/ – AudioDroid Jul 14 '11 at 13:38
0

This would work

stringstream ss;
BOOST_FOREACH(int const& i, VecInts)
{
   if(&i != &VecInts[0])
     ss << ", ";
   ss << i;
}

I suspect with "elegant" you mean "without introducing a new variable". But I think I would just do it "non-elegant" if I couldn't find anything else. It's still clear

stringstream ss;
bool comma = false;
BOOST_FOREACH(int i, VecInts)
{
   if(comma)
     ss << ", ";
   ss << i;
   comma = true;
}

Imagine 100.000 entries in the vector! If that is all you have to offer, I'd rather remove the last comma after I have gone thorough the loop.

You are saying that as if printing ss << i is one machine instruction. Come on, executing that expression will execute lots of if's and loops inside. Your if will be nothing compared to that.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
-2

cout << ss.str()<<"\b" <<" ";

You can add the "\b" backspace

This will overwrite the extra "," .

for Example :

int main()
{
    cout<<"Hi";
    cout<<'\b';  //Cursor moves 1 position backwards
    cout<<" ";   //Overwrites letter 'i' with space
}

So the output would be

H
xsari3x
  • 442
  • 2
  • 12
  • 36