views:

303

answers:

12

I know how to do this in other languages, but not C++, which I am forced to use here.

I have a Set of Strings that I'm printing to out in a list, and they need a comma between each one, but not a trailing comma. In java for instance, I would use a stringbuilder and just delete the comma off the end after I've built my string. How do I do it in C++?

auto iter = keywords.begin();
for (iter; iter != keywords.end( ); iter++ )
{

    out << *iter << ", ";
}
out << endl;

I initially tried inserting this block to do it (moving the comma printing here)

if (iter++ != keywords.end())
    out << ", ";
iter--;

I hate when the small things trip me up.

EDIT: Thanks everyone. This is why I post stuff like this here. So many good answers, and tackled in different ways. After a semester of Java and assembly (different classes), having to do a C++ project in 4 days threw me for a loop. Not only did I get my answer, I got a chance to think about the different ways to approach a problem like this. Awesome.

+9  A: 

One common approach is to print the first item prior to the loop, and loop only over the remaining items, PRE-printing a comma before each remaining item.

Alternately you should be able to create your own stream that maintains a current state of the line (before endl) and puts commas in the appropriate place.

EDIT: You can also use a middle-tested loop as suggested by T.E.D. It would be something like:

if(!keywords.empty())
{
    auto iter = keywords.begin();
    while(true)
    {
        out << *iter;
        ++iter;
        if(iter == keywords.end())
        {
            break;
        }
        else
        {
            out << ", ";
        }
    }
}

I mentioned the "print first item before loop" method first because it keeps the loop body really simple, but any of the approaches work fine.

Mark B
You really should mention the (IMHO better) option of using a middle-tested loop.
T.E.D.
+1  A: 

I think this should work

while (iter != keywords.end( ))
{

    out << *iter;
    iter++ ;
    if (iter != keywords.end( )) out << ", ";
}
aaa
What's going on with this line: `;iter++`? Also, this is wrong - you're double appending commas. This would produce `word1,,word2,,word3,`
Jamie Wong
@Jam dyslexia !!
aaa
That's better - downvote removed.
Jamie Wong
+2  A: 

There is a little problem with the ++ operator you are using.

You can try:

if (++iter != keywords.end())
    out << ", ";
iter--;

This way, ++ will be evaluated before compare the iterator with keywords.end().

Pablo Santa Cruz
+6  A: 

Something like this?

while (iter != keywords.end())
{
 out << *iter;
 iter++;
 if (iter != keywords.end()) cout << ", ";
}
Klark
Not downvoated, but my problem with this solution is that it performs a check on the exact same condition **twice** every iteration.
T.E.D.
+8  A: 

Use an infix_iterator:

// infix_iterator.h 
// 
// Lifted from Jerry Coffin's 's prefix_ostream_iterator 
#if !defined(INFIX_ITERATOR_H_) 
#define  INFIX_ITERATOR_H_ 
#include <ostream> 
#include <iterator> 
template <class T, 
          class charT=char, 
          class traits=std::char_traits<charT> > 
class infix_ostream_iterator : 
    public std::iterator<std::output_iterator_tag,void,void,void,void> 
{ 
    std::basic_ostream<charT,traits> *os; 
    charT const* delimiter; 
    bool first_elem; 
public: 
    typedef charT char_type; 
    typedef traits traits_type; 
    typedef std::basic_ostream<charT,traits> ostream_type; 
    infix_ostream_iterator(ostream_type& s) 
        : os(&s),delimiter(0), first_elem(true) 
    {} 
    infix_ostream_iterator(ostream_type& s, charT const *d) 
        : os(&s),delimiter(d), first_elem(true) 
    {} 
    infix_ostream_iterator<T,charT,traits>& operator=(T const &item) 
    { 
        // Here's the only real change from ostream_iterator: 
        // Normally, the '*os << item;' would come before the 'if'. 
        if (!first_elem && delimiter != 0) 
            *os << delimiter; 
        *os << item; 
        first_elem = false; 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator*() { 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator++() { 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator++(int) { 
        return *this; 
    } 
};     
#endif 

Usage would be something like:

#include "infix_iterator.h"

// ...
std::copy(keywords.begin(), keywords.end(), infix_iterator(out, ","));
Jerry Coffin
Cool. Like that. Why is there not something like this in boost?
Martin York
@Martin: because I've never bothered to submit it? I probably should, come to think of it...
Jerry Coffin
Do submit it. :) You should post to the mailing list and ask if there are any similar iterators someone might want.
GMan
@GMan: as you might guess from the comment, I also have a `prefix_iterator` that (obviously enough) puts the delimiter before each item -- but it's true that somebody might easily want something else as well...
Jerry Coffin
+1  A: 

Try this:

typedef  std::vector<std::string>   Container;
typedef Container::const_iterator   CIter;
Container   data;

// Now fill the container.


// Now print the container.
// The advantage of this technique is that ther is no extra test during the loop.
// There is only one additional test !test.empty() done at the beginning.
if (!data.empty())
{
    std::cout << data[0];
    for(CIter loop = data.begin() + 1; loop != data.end(); ++loop)
    {
        std::cout << "," << *loop;
    }
}
Martin York
`const_iterator` is a distinct, incompatible type from the plain `iterator` that non-const `begin` returns.
Potatoswatter
Although this will compile on platforms where `vector::iterator` is a simple pointer, confusing breakage may result in debugging mode or changing compilers.
Potatoswatter
@Potatoswatter: What are you talking about. This will work on all compilers (assuming they are C++ compilers). If you are not modifying the content of a container you should always prefer to use the const_iterator over the iterator.
Martin York
@Martin: that's what I thought. And then I thought, "where in the standard does it say that `std::vector<std::string>::iterator` is convertible to `std::vector<std::string>::const_iterator`? Now I'm worried that you do actually have to cast `data` to `const Container` before calling `begin()` and `end()`.
Steve Jessop
@Steve (and Martin): Sorry, that was totally wrong. Table 65 in §23.1 requires that `iterator` be convertible to `const_iterator`. I'm just not used to seeing it written like that. (It doesn't have anything to do with overloads, though. `const_iterator` simply provides a conversion constructor. Usually I try to implement `iterator` and `const_iterator` with a single template and disable the undesirable constructor with SFINAE.)
Potatoswatter
@Steve Jessop: Went down the wrong path before. I will check the standard when I get home. But on the SGI page: http://www.sgi.com/tech/stl/Container.html <quote>A conversion from the iterator type to the const iterator type must exist.</quote>
Martin York
+4  A: 

Because everyone has decided to do this with while loops, I'll give an example with for loops.

for (iter = keywords.begin(); iter != keywords.end(); iter++) {
  if (iter != iter.begin()) cout << ", ";
  cout << *iter;
}
Jamie Wong
+3  A: 

My typical method for doing separators (in any language) is to use a mid-tested loop. The C++ code would be:

for (;;iter++) {
   std::cout << *iter;
   if (iter == keywords.end()) break;
   std::cout << ",";
}

(note: An extra if check is needed prior to the loop if keywords may be empty)

Most of the other solutions shown end up doing an entire extra test every loop iteration. You are doing I/O, so the time taken by that isn't a huge problem, but it offends my sensibilities.

T.E.D.
The condition doesn't get tested the first time through. That's really a `do` loop.
Potatoswatter
@Potatoswatter - I suppose that depends on how you chose to define your terms. For me, loops are either top-tested, bottom-tested, or middle tested. This loop is middle-tested. As for implementing it, in C-syntax languages I generally prefer to use `for()` loops unless it happens to be a special case where one of the other forms (`while` or `do`) matches exactly. That's just a matter of taste though.
T.E.D.
Speaking of offended sensibilities, you've omitted the one-off tested needed at the start to ensure `keywords.size() > 0` or equivalent. This makes your code look simpler than it really is. Sneaky ;-)
Steve Jessop
BTW: To get an idea of how rare the need for `do` is, see closed question http://stackoverflow.com/questions/3347001/do-while-vs-while/3347063#3347063 .
T.E.D.
@Steve Jessop - I said right in the answer that I was doing that. Perhaps it is a little sneaky. However, it is often the case that you know ahead of time that the list won't be empty. Failing that, I generally prefer to test for it with an `if (keywords.size() == 0) return;`, but of course that only works if used in a specialized print routine that does no other work. Of course, it is my style to write such things. Rather than go into that kind of detail, I just left it out with a note that it might be needed.
T.E.D.
Yes, sorry, I must have been skimming and missed that note.
Steve Jessop
+1  A: 

Could be like so..

bool bFirst = true;
for (auto curr = keywords.begin();  curr != keywords.end(); ++it)
{
   std::cout << bFirst ? "" : ", " << *curr;
   bFirst = false;
}
JohnMcG
Why conditional and not `if`?
Potatoswatter
I like the brevity. I could be convinced otherwise.
JohnMcG
A: 

You can use a do loop, rewrite the loop condition for the first iteration, and use the short-circuit && operator and the fact that a valid stream is true.

auto iter = keywords.begin();
if ( ! keywords.empty() ) do {
    out << * iter;
} while ( ++ iter != keywords.end() && out << ", " );
out << endl;
Potatoswatter
Seems like this would write two commas.
Michael Mathews
@Michael: woops, copy-paste left in from original code. Fixed.
Potatoswatter
A: 

Assuming a vaguely normal output stream, so that writing an empty string to it does indeed do nothing:

const char *padding = "";
for (auto iter = keywords.begin(); iter != keywords.end(); ++iter) {
    out << padding << *iter;
    padding = ", "
}
Steve Jessop
A: 

I would go with something like this, an easy solution and should work for all iterators.

int maxele = maxele = v.size() - 1;
for ( cur = v.begin() , i = 0; i < maxele ; ++i)
{
    std::cout << *cur++ << " , ";
}
if ( maxele >= 0 )
{
  std::cout << *cur << std::endl;
}