开发者

How to deal with last comma, when making comma separated string? [duplicate]

This question already has answers here: Closed 11 years ago.

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);
   VecInt开发者_运维知识库s.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.


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 :)


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


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


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 = ",";
}


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).


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


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


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.


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.


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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜