34

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 << '}';
lord.didger
  • 1,357
  • 1
  • 17
  • 31

9 Answers9

62

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 << '}';
bcmpinc
  • 3,202
  • 29
  • 36
46

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 << '}';
Sylvain Defresne
  • 42,429
  • 12
  • 75
  • 85
  • 9
    "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. – Bogatyr Jul 18 '19 at 12:19
  • 2
    I can't see why this quite clunky solution is accepted, especially when the question eventually ("better") was evaded. – Jan Jul 03 '20 at 13:47
  • 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. – Mat Gomes May 24 '22 at 13:01
  • 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! – c z Oct 20 '22 at 14:09
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.

jdehesa
  • 58,456
  • 7
  • 77
  • 121
6
stringstream douCoh;
for(unsigned int i=0;i<dataSize;i++)
  if(v[i].test)
    douCoh << ( douCoh.tellp()==0 ? '{' : ',' ) << i+1;
douCoh << '}';
Drum Inc
  • 61
  • 1
  • 2
  • 1
    Welcome to StackOverflow! It would be useful if you explained your code for those that don't understand it. – ajacian81 Nov 05 '12 at 00:07
5

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 << '}';
DarioP
  • 5,377
  • 1
  • 33
  • 52
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';
}
Benoît
  • 3,355
  • 2
  • 29
  • 34
1

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

Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
  • 1
    How can I get the underlying string? str() returns a copy of string not a reference, doesn't it? – lord.didger Dec 28 '10 at 13:39
  • Yes, you get a copy of the contents using str(). – RedX Dec 28 '10 at 13:58
  • 1
    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. – Sylvain Defresne Dec 28 '10 at 14:10
1

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 << '}';
RedX
  • 14,749
  • 1
  • 53
  • 76
  • 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. – Sylvain Defresne Dec 28 '10 at 14:08
  • It is impossible to predict value of v[j].test for j>i before the coditional. – lord.didger Dec 28 '10 at 23:58
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;   
}
Daniel Hornik
  • 1,957
  • 1
  • 14
  • 33