37

In my code there is a loop that adds sth like that "number," to stringstream. When it ends, I need to extract ',' add '}' and add '{' if the loop is to repeated.

I thought i can use ignore() to remove ',' but it didn't work. Do you know how I can do what I describe?

example:

douCoh << '{';
for(unsigned int i=0;i<dataSize;i++)
  if(v[i].test) douCoh << i+1 << ',';
douCoh.get(); douCoh << '}';
1
  • 1
    Please add some example to illustrate your point. Commented Dec 28, 2010 at 12:29

9 Answers 9

66

You can seek the stringstream and go back 1 character, using stringstream::seekp. Note that it does not remove the last character, but only moves the write head. This is sufficient in this case, as we overwrite the last character with an }.

douCoh << '{';
for(unsigned int i=0;i<dataSize;i++)
  if(v[i].test) douCoh << i+1 << ',';
douCoh.seekp(-1,douCoh.cur); douCoh << '}';
Sign up to request clarification or add additional context in comments.

Comments

50

You can extract the string (with the str() member), remove the last char with std::string::erase and then reset the new string as buffer to the std::ostringstream.

However, a better solution would be to not insert the superfluous ',' in the first place, by doing something like that :

std::ostringstream douCoh;
const char* separator = "";

douCoh << '{';
for (size_t i = 0; i < dataSize; ++ i)
{
  if (v[i].test)
  {
    douCoh << separator << i + 1;
    separator = ",";
  }
}
douCoh << '}';

4 Comments

"Better" depends on the context and includes subjective considerations. Avoiding a single superfluous comma at the end of a sequence usually requires adding additional state and additional comparisons to the loop, resulting in slower execution over multiple iterations. In many cases just removing/replacing one superfluous trailing character after the loop finishes results in cleaner, smaller, and faster code.
I can't see why this quite clunky solution is accepted, especially when the question eventually ("better") was evaded.
Agree with @Bogatyr, this solution adds unnecessary logic in the loop which turns the program slower over many iterations. It's much better to just remove the comma after the loop.
This solution is more robust, it will work even if someone changes the delimiter, for instance to U+3001 or even \r\n. I'd argue any microoptimisations are almost always unnecessary, fragile and confusing, if checking a boolean is slowing things down I'd be far more concerned of how long the string being shown to the user is!
36

I have had this very problem and I found out that you can simply do:

douCoh.seekp(-1, std::ios_base::end);

And the keep inserting data. As others stated, avoiding inserting the bad data in the first place is probably the ideal solution, but in my case was the result of a 3rd party library function, and also I wanted to avoid copying the data to strings.

1 Comment

Remember that it doesn't remove data from buffer so you need to override that data.
6
stringstream douCoh;
for(unsigned int i=0;i<dataSize;i++)
  if(v[i].test)
    douCoh << ( douCoh.tellp()==0 ? '{' : ',' ) << i+1;
douCoh << '}';

1 Comment

Welcome to StackOverflow! It would be useful if you explained your code for those that don't understand it.
6

I've found this way using pop_back() string's method since c++11. Probably not so good as smarter ones above, but useful in much more complicated cases and/or for lazy people.

douCoh << '{';
for(unsigned int i=0;i<dataSize;i++)
  if(v[i].test) douCoh << i+1 << ',';

string foo(douCoh.str());
foo.pop_back();
douCoh.str(foo);
douCoh.seekp (0, douCoh.end);  

douCoh << '}';

Comments

2

Have fun with std::copy, iterators and traits. You either have to assume that your data is reverse iterable (end - 1) or that your output can be rewinded. I choose it was easier to rewind.

#include <ostream>
#include <algorithm>
#include <iterator>

namespace My
{
  template<typename Iterator>
  void print(std::ostream &out, Iterator begin, Iterator end)
  {
    out << '{';
    if (begin != end) {
      Iterator last = end - 1;
      if (begin != last) {
        std::copy(begin, last, std::ostream_iterator< typename std::iterator_traits<Iterator>::value_type  >(out, ", "));
      }
      out << *last;
    }
    out << '}';
  }
}

#include <iostream>

int main(int argc, char** argv)
{
  My::print(std::cout, &argv[0], &argv[argc]);
  std::cout << '\n';
}

1 Comment

Or else, you have to write the full loop instead of relying on std::copy.
1

You could use std::string::erase to remove the last character directly from the underlying string.

3 Comments

How can I get the underlying string? str() returns a copy of string not a reference, doesn't it?
Yes, you get a copy of the contents using str().
There is another override of the std::ostringstream::str function that allow to change the buffer. However, this solution cost two copy of the buffer content, which can be coslty.
1
#include <sstream>
#include <vector>
#include <iterator>
#include <algorithm>

template<typename T>
std::string implode(std::vector<T> vec, std::string&& delim) 
{
    std::stringstream ss;
    std::copy(vec.begin(), vec.end(), std::ostream_iterator<std::string>(ss, delim.c_str()));

    if (!vec.empty()) {
        ss.seekp(-1*delim.size(), std::ios_base::end);
        ss<<'\0';
    }

    return ss.str();
}

int main()
{
    std::cout<<implode(std::vector<std::string>{"1", "2", "3"}, ", ");

    return 0;   
}

1 Comment

Have you actually tested this? Because this does not work: implode(/*...*/) != "1, 2, 3".
0

Why not just check the counter? And not insert the ','

douCoh << '{';
for(unsigned int i=0;i<dataSize;i++){
  if(v[i].test){
    douCoh << i+1;
    if(i != dataSize - 1) douCoh << ',';
  }
}
/*douCoh.get();*/ douCoh << '}';

2 Comments

I think it is not possible to check the counter as there can be some objects that does not pass the test. Another counter is required (that can be a bool), or my trick of changing the separator variable can be used.
It is impossible to predict value of v[j].test for j>i before the coditional.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.